123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- // 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.
- //go:build !js
- 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 "android", "darwin", "ios", "dragonfly", "freebsd", "illumos", "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 := newLocalListener(t, "tcp")
- 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")
- }
- }
|