| // 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. | |
| package net | |
| import ( | |
| "bytes" | |
| "fmt" | |
| "internal/poll" | |
| "io" | |
| "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 := io.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 := io.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 "aix", "android", "darwin", "ios", "dragonfly", "freebsd", "illumos", "linux", "netbsd", "openbsd", "solaris": | |
| 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 := newLocalListener(t, "tcp") | |
| ch := make(chan Conn, 1) | |
| defer func() { | |
| ln.Close() | |
| for c := range ch { | |
| c.Close() | |
| } | |
| }() | |
| 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") | |
| } | |
| } |