| // 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, 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") |
| } |
| } |