blob: e4e88c4fac0354296d1607b6b45915478df5d4ea [file] [log] [blame]
// 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")
}
}