|  | // Copyright 2016 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | // +build !js | 
|  |  | 
|  | package net | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "fmt" | 
|  | "internal/poll" | 
|  | "io" | 
|  | "io/ioutil" | 
|  | "reflect" | 
|  | "runtime" | 
|  | "sync" | 
|  | "testing" | 
|  | ) | 
|  |  | 
|  | func TestBuffers_read(t *testing.T) { | 
|  | const story = "once upon a time in Gopherland ... " | 
|  | buffers := Buffers{ | 
|  | []byte("once "), | 
|  | []byte("upon "), | 
|  | []byte("a "), | 
|  | []byte("time "), | 
|  | []byte("in "), | 
|  | []byte("Gopherland ... "), | 
|  | } | 
|  | got, err := ioutil.ReadAll(&buffers) | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  | if string(got) != story { | 
|  | t.Errorf("read %q; want %q", got, story) | 
|  | } | 
|  | if len(buffers) != 0 { | 
|  | t.Errorf("len(buffers) = %d; want 0", len(buffers)) | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestBuffers_consume(t *testing.T) { | 
|  | tests := []struct { | 
|  | in      Buffers | 
|  | consume int64 | 
|  | want    Buffers | 
|  | }{ | 
|  | { | 
|  | in:      Buffers{[]byte("foo"), []byte("bar")}, | 
|  | consume: 0, | 
|  | want:    Buffers{[]byte("foo"), []byte("bar")}, | 
|  | }, | 
|  | { | 
|  | in:      Buffers{[]byte("foo"), []byte("bar")}, | 
|  | consume: 2, | 
|  | want:    Buffers{[]byte("o"), []byte("bar")}, | 
|  | }, | 
|  | { | 
|  | in:      Buffers{[]byte("foo"), []byte("bar")}, | 
|  | consume: 3, | 
|  | want:    Buffers{[]byte("bar")}, | 
|  | }, | 
|  | { | 
|  | in:      Buffers{[]byte("foo"), []byte("bar")}, | 
|  | consume: 4, | 
|  | want:    Buffers{[]byte("ar")}, | 
|  | }, | 
|  | { | 
|  | in:      Buffers{nil, nil, nil, []byte("bar")}, | 
|  | consume: 1, | 
|  | want:    Buffers{[]byte("ar")}, | 
|  | }, | 
|  | { | 
|  | in:      Buffers{nil, nil, nil, []byte("foo")}, | 
|  | consume: 0, | 
|  | want:    Buffers{[]byte("foo")}, | 
|  | }, | 
|  | { | 
|  | in:      Buffers{nil, nil, nil}, | 
|  | consume: 0, | 
|  | want:    Buffers{}, | 
|  | }, | 
|  | } | 
|  | for i, tt := range tests { | 
|  | in := tt.in | 
|  | in.consume(tt.consume) | 
|  | if !reflect.DeepEqual(in, tt.want) { | 
|  | t.Errorf("%d. after consume(%d) = %+v, want %+v", i, tt.consume, in, tt.want) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func TestBuffers_WriteTo(t *testing.T) { | 
|  | for _, name := range []string{"WriteTo", "Copy"} { | 
|  | for _, size := range []int{0, 10, 1023, 1024, 1025} { | 
|  | t.Run(fmt.Sprintf("%s/%d", name, size), func(t *testing.T) { | 
|  | testBuffer_writeTo(t, size, name == "Copy") | 
|  | }) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | func testBuffer_writeTo(t *testing.T, chunks int, useCopy bool) { | 
|  | oldHook := poll.TestHookDidWritev | 
|  | defer func() { poll.TestHookDidWritev = oldHook }() | 
|  | var writeLog struct { | 
|  | sync.Mutex | 
|  | log []int | 
|  | } | 
|  | poll.TestHookDidWritev = func(size int) { | 
|  | writeLog.Lock() | 
|  | writeLog.log = append(writeLog.log, size) | 
|  | writeLog.Unlock() | 
|  | } | 
|  | var want bytes.Buffer | 
|  | for i := 0; i < chunks; i++ { | 
|  | want.WriteByte(byte(i)) | 
|  | } | 
|  |  | 
|  | withTCPConnPair(t, func(c *TCPConn) error { | 
|  | buffers := make(Buffers, chunks) | 
|  | for i := range buffers { | 
|  | buffers[i] = want.Bytes()[i : i+1] | 
|  | } | 
|  | var n int64 | 
|  | var err error | 
|  | if useCopy { | 
|  | n, err = io.Copy(c, &buffers) | 
|  | } else { | 
|  | n, err = buffers.WriteTo(c) | 
|  | } | 
|  | if err != nil { | 
|  | return err | 
|  | } | 
|  | if len(buffers) != 0 { | 
|  | return fmt.Errorf("len(buffers) = %d; want 0", len(buffers)) | 
|  | } | 
|  | if n != int64(want.Len()) { | 
|  | return fmt.Errorf("Buffers.WriteTo returned %d; want %d", n, want.Len()) | 
|  | } | 
|  | return nil | 
|  | }, func(c *TCPConn) error { | 
|  | all, err := ioutil.ReadAll(c) | 
|  | if !bytes.Equal(all, want.Bytes()) || err != nil { | 
|  | return fmt.Errorf("client read %q, %v; want %q, nil", all, err, want.Bytes()) | 
|  | } | 
|  |  | 
|  | writeLog.Lock() // no need to unlock | 
|  | var gotSum int | 
|  | for _, v := range writeLog.log { | 
|  | gotSum += v | 
|  | } | 
|  |  | 
|  | var wantSum int | 
|  | switch runtime.GOOS { | 
|  | case "android", "darwin", "dragonfly", "freebsd", "linux", "netbsd", "openbsd": | 
|  | var wantMinCalls int | 
|  | wantSum = want.Len() | 
|  | v := chunks | 
|  | for v > 0 { | 
|  | wantMinCalls++ | 
|  | v -= 1024 | 
|  | } | 
|  | if len(writeLog.log) < wantMinCalls { | 
|  | t.Errorf("write calls = %v < wanted min %v", len(writeLog.log), wantMinCalls) | 
|  | } | 
|  | case "windows": | 
|  | var wantCalls int | 
|  | wantSum = want.Len() | 
|  | if wantSum > 0 { | 
|  | wantCalls = 1 // windows will always do 1 syscall, unless sending empty buffer | 
|  | } | 
|  | if len(writeLog.log) != wantCalls { | 
|  | t.Errorf("write calls = %v; want %v", len(writeLog.log), wantCalls) | 
|  | } | 
|  | } | 
|  | if gotSum != wantSum { | 
|  | t.Errorf("writev call sum  = %v; want %v", gotSum, wantSum) | 
|  | } | 
|  | return nil | 
|  | }) | 
|  | } | 
|  |  | 
|  | func TestWritevError(t *testing.T) { | 
|  | if runtime.GOOS == "windows" { | 
|  | t.Skipf("skipping the test: windows does not have problem sending large chunks of data") | 
|  | } | 
|  |  | 
|  | ln, err := newLocalListener("tcp") | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  | defer ln.Close() | 
|  |  | 
|  | ch := make(chan Conn, 1) | 
|  | go func() { | 
|  | defer close(ch) | 
|  | c, err := ln.Accept() | 
|  | if err != nil { | 
|  | t.Error(err) | 
|  | return | 
|  | } | 
|  | ch <- c | 
|  | }() | 
|  | c1, err := Dial("tcp", ln.Addr().String()) | 
|  | if err != nil { | 
|  | t.Fatal(err) | 
|  | } | 
|  | defer c1.Close() | 
|  | c2 := <-ch | 
|  | if c2 == nil { | 
|  | t.Fatal("no server side connection") | 
|  | } | 
|  | c2.Close() | 
|  |  | 
|  | // 1 GB of data should be enough to notice the connection is gone. | 
|  | // Just a few bytes is not enough. | 
|  | // Arrange to reuse the same 1 MB buffer so that we don't allocate much. | 
|  | buf := make([]byte, 1<<20) | 
|  | buffers := make(Buffers, 1<<10) | 
|  | for i := range buffers { | 
|  | buffers[i] = buf | 
|  | } | 
|  | if _, err := buffers.WriteTo(c1); err == nil { | 
|  | t.Fatal("Buffers.WriteTo(closed conn) succeeded, want error") | 
|  | } | 
|  | } |