net/nettest: new package with net-like testing primitives

This is a lot like wiring up a local UDP socket, read and write
deadlines work. The big difference is the Block feature, which
lets you stop the packet flow without breaking the connection.
This lets you emulate broken sockets and test timeouts actually
work.

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
This commit is contained in:
David Crawshaw
2020-03-12 11:01:58 -04:00
committed by David Crawshaw
parent 52c0cb12fb
commit 41ac4a79d6
3 changed files with 463 additions and 0 deletions

116
net/nettest/pipe_test.go Normal file
View File

@ -0,0 +1,116 @@
package nettest
import (
"errors"
"fmt"
"testing"
"time"
)
func TestPipeHello(t *testing.T) {
p := NewPipe("p1", 1<<16)
msg := "Hello, World!"
if n, err := p.Write([]byte(msg)); err != nil {
t.Fatal(err)
} else if n != len(msg) {
t.Errorf("p.Write(%q) n=%d, want %d", msg, n, len(msg))
}
b := make([]byte, len(msg))
if n, err := p.Read(b); err != nil {
t.Fatal(err)
} else if n != len(b) {
t.Errorf("p.Read(%q) n=%d, want %d", string(b[:n]), n, len(b))
}
if got := string(b); got != msg {
t.Errorf("p.Read: %q, want %q", got, msg)
}
}
func TestPipeTimeout(t *testing.T) {
t.Run("write", func(t *testing.T) {
p := NewPipe("p1", 1<<16)
p.SetWriteDeadline(time.Now().Add(-1 * time.Second))
n, err := p.Write([]byte{'h'})
if err == nil || !errors.Is(err, ErrWriteTimeout) || !errors.Is(err, ErrTimeout) {
t.Errorf("missing write timeout got err: %v", err)
}
if n != 0 {
t.Errorf("n=%d on timeout", n)
}
})
t.Run("read", func(t *testing.T) {
p := NewPipe("p1", 1<<16)
p.Write([]byte{'h'})
p.SetReadDeadline(time.Now().Add(-1 * time.Second))
b := make([]byte, 1)
n, err := p.Read(b)
if err == nil || !errors.Is(err, ErrReadTimeout) || !errors.Is(err, ErrTimeout) {
t.Errorf("missing read timeout got err: %v", err)
}
if n != 0 {
t.Errorf("n=%d on timeout", n)
}
})
t.Run("block-write", func(t *testing.T) {
p := NewPipe("p1", 1<<16)
p.SetWriteDeadline(time.Now().Add(10 * time.Millisecond))
if _, err := p.Write([]byte{'h'}); err != nil {
t.Fatal(err)
}
if err := p.Block(); err != nil {
t.Fatal(err)
}
if _, err := p.Write([]byte{'h'}); err == nil || !errors.Is(err, ErrWriteTimeout) {
t.Fatalf("want write timeout got: %v", err)
}
})
t.Run("block-read", func(t *testing.T) {
p := NewPipe("p1", 1<<16)
p.Write([]byte{'h', 'i'})
p.SetReadDeadline(time.Now().Add(10 * time.Millisecond))
b := make([]byte, 1)
if _, err := p.Read(b); err != nil {
t.Fatal(err)
}
if err := p.Block(); err != nil {
t.Fatal(err)
}
if _, err := p.Read(b); err == nil || !errors.Is(err, ErrReadTimeout) {
t.Fatalf("want read timeout got: %v", err)
}
})
}
func TestLimit(t *testing.T) {
p := NewPipe("p1", 1)
errCh := make(chan error)
go func() {
n, err := p.Write([]byte{'a', 'b', 'c'})
if err != nil {
errCh <- err
} else if n != 3 {
errCh <- fmt.Errorf("p.Write n=%d, want 3", n)
} else {
errCh <- nil
}
}()
b := make([]byte, 3)
if n, err := p.Read(b); err != nil {
t.Fatal(err)
} else if n != 1 {
t.Errorf("Read(%q): n=%d want 1", string(b), n)
}
if n, err := p.Read(b); err != nil {
t.Fatal(err)
} else if n != 1 {
t.Errorf("Read(%q): n=%d want 1", string(b), n)
}
if n, err := p.Read(b); err != nil {
t.Fatal(err)
} else if n != 1 {
t.Errorf("Read(%q): n=%d want 1", string(b), n)
}
}