mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-05 19:21:46 +00:00
355 lines
8.3 KiB
Go
355 lines
8.3 KiB
Go
package ionet
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// timedWaitGroup extends sync.WaitGroup with an option
|
|
// to wait for a limited time.
|
|
type timedWaitGroup struct{ sync.WaitGroup }
|
|
|
|
// WaitFor blocks until the WaitGroup counter is zero or duration d
|
|
// has elapsed. It returns true iff it was unblocked due to the counter
|
|
// dropping to zero.
|
|
func (t *timedWaitGroup) WaitFor(d time.Duration) (completed bool) {
|
|
timeout := time.After(d)
|
|
success := make(chan struct{})
|
|
go func() {
|
|
t.WaitGroup.Wait()
|
|
close(success)
|
|
}()
|
|
select {
|
|
case <-success:
|
|
return true
|
|
case <-timeout:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func TestConnBasics(t *testing.T) {
|
|
in := []byte("you can't read from a closed connection (but you can be happy, if you've a mind to)")
|
|
r := bytes.NewBuffer(in)
|
|
w := new(bytes.Buffer)
|
|
c := &Conn{R: r, W: w}
|
|
b := make([]byte, len(in))
|
|
|
|
if c.LocalAddr() == nil || c.RemoteAddr() == nil {
|
|
t.Errorf("conn should have a local and remote addr")
|
|
}
|
|
|
|
n, err := c.Read(b)
|
|
if n == 0 || err != nil {
|
|
t.Errorf("conn read failed with err %v", err)
|
|
}
|
|
if !bytes.Equal(b, in) {
|
|
t.Errorf("read incorrect bytes, want %q got %q", in, b)
|
|
}
|
|
|
|
n, err = c.Write(b)
|
|
if n == 0 || err != nil {
|
|
t.Errorf("conn write failed with err %v", err)
|
|
}
|
|
if !bytes.Equal(w.Bytes(), in) {
|
|
t.Errorf("wrote incorrect bytes, want %q got %q", w.Bytes(), in)
|
|
}
|
|
|
|
err = c.Close()
|
|
if n == 0 || err != nil {
|
|
t.Errorf("close err %v", err)
|
|
}
|
|
n, err = c.Read(b)
|
|
if n != 0 || err == nil {
|
|
t.Errorf("conn read should fail after close, instead read %d bytes", n)
|
|
}
|
|
n, err = c.Write(b)
|
|
if n != 0 || err == nil {
|
|
t.Errorf("conn write should fail after close, instead read %d bytes", n)
|
|
}
|
|
}
|
|
|
|
func TestConnDoubleClose(t *testing.T) {
|
|
c := &Conn{}
|
|
err := c.Close()
|
|
if err != nil {
|
|
t.Errorf("conn close should succeed the first time, got error: %s", err)
|
|
}
|
|
err = c.Close()
|
|
if err == nil {
|
|
t.Errorf("conn close should fail with an error the second time")
|
|
} else if err := err.(*net.OpError); err.Temporary() {
|
|
t.Errorf("conn close should fail with a permanent error the second time")
|
|
}
|
|
}
|
|
|
|
func TestConnZero(t *testing.T) {
|
|
c := new(Conn)
|
|
b := []byte{0x00}
|
|
n, err := c.Read(b)
|
|
if n != 0 || err == nil {
|
|
t.Errorf("zero conn reads should return 0, wrapped EOF, got %d, %v", n, err)
|
|
}
|
|
n, err = c.Write(b)
|
|
if n != len(b) || err != nil {
|
|
t.Errorf("zero conn writes should succeed after writing %d bytes, wrote %d with err %v", len(b), n, err)
|
|
}
|
|
err = c.Close()
|
|
if err != nil {
|
|
t.Errorf("zero conn should close without error, got %v", err)
|
|
}
|
|
}
|
|
|
|
// blockrw is an io.ReadWriter that blocks forever on reads and writes.
|
|
type blockrw struct{}
|
|
|
|
func (*blockrw) Read(b []byte) (int, error) { select {} }
|
|
func (*blockrw) Write(b []byte) (int, error) { select {} }
|
|
|
|
func TestConnCloseUnblocksReadWrites(t *testing.T) {
|
|
c := &Conn{R: &blockrw{}, W: &blockrw{}}
|
|
b := []byte{}
|
|
var w timedWaitGroup
|
|
w.Add(2)
|
|
go func() {
|
|
c.Read(b)
|
|
w.Done()
|
|
}()
|
|
go func() {
|
|
c.Write(b)
|
|
w.Done()
|
|
}()
|
|
time.Sleep(time.Millisecond * 5) // ensure that the read/write has started
|
|
c.Close()
|
|
c.Wait()
|
|
if !w.WaitFor(time.Millisecond * 10) {
|
|
t.Errorf("conn close did not unblock read/write")
|
|
}
|
|
}
|
|
|
|
func TestConnTimeouts(t *testing.T) {
|
|
c := &Conn{R: &blockrw{}, W: &blockrw{}}
|
|
c.SetDeadline(time.Now().Add(time.Millisecond * 10))
|
|
b := []byte{}
|
|
var w timedWaitGroup
|
|
w.Add(2)
|
|
go func() {
|
|
c.Read(b)
|
|
w.Done()
|
|
}()
|
|
go func() {
|
|
c.Write(b)
|
|
w.Done()
|
|
}()
|
|
if !w.WaitFor(time.Millisecond * 50) {
|
|
t.Errorf("conn timeout did not unblock read/write (deadline after 10ms, waited 50ms)")
|
|
}
|
|
}
|
|
|
|
// errorrw is an io.ReadWriter that fails tests when read from or written to.
|
|
type errorrw struct {
|
|
t *testing.T
|
|
msg string
|
|
}
|
|
|
|
func (e *errorrw) Read(b []byte) (int, error) {
|
|
e.t.Errorf("unexpected read: %v", e.msg)
|
|
return 0, fmt.Errorf("errorrw does not read")
|
|
}
|
|
|
|
func (e *errorrw) Write(b []byte) (int, error) {
|
|
e.t.Errorf("unexpected write: %v", e.msg)
|
|
return 0, fmt.Errorf("errorrw does not write")
|
|
}
|
|
|
|
func TestConnNoReadWriteAfterClose(t *testing.T) {
|
|
erw := &errorrw{t: t, msg: "no reads or writes after connection close"}
|
|
b := []byte{}
|
|
c := &Conn{R: erw, W: erw}
|
|
c.Close()
|
|
c.Read(b)
|
|
c.Write(b)
|
|
}
|
|
|
|
func TestConnNoReadWriteAfterTimeout(t *testing.T) {
|
|
erw := &errorrw{t: t, msg: "no reads or writes after timeout"}
|
|
b := []byte{}
|
|
c := &Conn{R: erw, W: erw}
|
|
c.SetDeadline(time.Now().Add(-time.Second))
|
|
_, err := c.Read(b)
|
|
if err := err.(*net.OpError); !err.Timeout() {
|
|
t.Errorf("read timeout errors should be marked as timeouts")
|
|
}
|
|
_, err = c.Write(b)
|
|
if err := err.(*net.OpError); !err.Timeout() {
|
|
t.Errorf("write timeout errors should be marked as timeouts")
|
|
}
|
|
}
|
|
|
|
// seqrw is an io.ReadWriter that does not handle concurrency.
|
|
// It panics when multiple concurrent reads/writes occur.
|
|
// And it reads and writes slowly, to provide ample opportunity
|
|
// for concurrent access.
|
|
type seqrw struct {
|
|
reading bool
|
|
writing bool
|
|
t *testing.T
|
|
d time.Duration
|
|
}
|
|
|
|
func (s *seqrw) Read(b []byte) (int, error) {
|
|
if s.reading {
|
|
s.t.Errorf("non-sequential read")
|
|
}
|
|
s.reading = true
|
|
time.Sleep(s.d)
|
|
s.reading = false
|
|
return 0, fmt.Errorf("seqrw does not read")
|
|
}
|
|
|
|
func (s *seqrw) Write(b []byte) (int, error) {
|
|
if s.writing {
|
|
s.t.Errorf("non-sequential write")
|
|
}
|
|
s.writing = true
|
|
time.Sleep(s.d)
|
|
s.writing = false
|
|
return 0, fmt.Errorf("seqrw does not write")
|
|
}
|
|
|
|
func TestConnEnforcesSequentialReadWrites(t *testing.T) {
|
|
s := &seqrw{t: t, d: time.Millisecond * 10}
|
|
b := []byte{0x00}
|
|
c := &Conn{R: s, W: s}
|
|
var w sync.WaitGroup
|
|
for i := 0; i < 2; i++ {
|
|
w.Add(1)
|
|
go func() {
|
|
c.Read(b)
|
|
w.Done()
|
|
}()
|
|
w.Add(1)
|
|
go func() {
|
|
c.Write(b)
|
|
w.Done()
|
|
}()
|
|
}
|
|
w.Wait()
|
|
}
|
|
|
|
func TestListenerAcceptsWhenDialed(t *testing.T) {
|
|
l := Listener{}
|
|
timeout := time.After(time.Millisecond * 10)
|
|
successc := make(chan struct{}, 1)
|
|
go func() {
|
|
conn, err := l.Accept()
|
|
if conn == nil || err != nil {
|
|
t.Errorf("accept error: %v", err)
|
|
}
|
|
successc <- struct{}{}
|
|
}()
|
|
go func() {
|
|
l.Dial(nil, nil)
|
|
}()
|
|
select {
|
|
case <-successc:
|
|
case <-timeout:
|
|
t.Errorf("listener was dialed but failed to accept")
|
|
}
|
|
}
|
|
|
|
func TestListenerAcceptManyConcurrently(t *testing.T) {
|
|
l := Listener{}
|
|
n := 10
|
|
wg := sync.WaitGroup{}
|
|
timeout := time.After(time.Millisecond * 25) // enough time for 10 concurrent 10ms reads/writes, but not enough to 10 serial ones
|
|
successc := make(chan struct{}, 1)
|
|
// Fire off a bunch of accepts
|
|
for i := 0; i < n; i++ {
|
|
wg.Add(1)
|
|
go func() {
|
|
conn, err := l.Accept()
|
|
if conn == nil || err != nil {
|
|
t.Errorf("accept error: %v", err)
|
|
}
|
|
b := []byte{0x00}
|
|
conn.Read(b)
|
|
conn.Write(b)
|
|
wg.Done()
|
|
}()
|
|
}
|
|
// Fire off a bunch of dials with slow read/writers
|
|
for i := 0; i < n; i++ {
|
|
s := &seqrw{t: t, d: time.Millisecond * 5}
|
|
go func() {
|
|
l.Dial(s, s)
|
|
}()
|
|
}
|
|
// Wait for all reads and writes to complete;
|
|
// this will happen in time iff they are concurrent.
|
|
go func() {
|
|
wg.Wait()
|
|
successc <- struct{}{}
|
|
}()
|
|
select {
|
|
case <-successc:
|
|
case <-timeout:
|
|
t.Errorf("listener did not accept concurrently (too slow)")
|
|
}
|
|
}
|
|
|
|
func TestListenerDialFailsWhenClosed(t *testing.T) {
|
|
l := Listener{}
|
|
l.Close()
|
|
conn, err := l.Dial(nil, nil)
|
|
if conn != nil || err == nil {
|
|
t.Errorf("dialing a listener should fail with an error when the listener is closed")
|
|
}
|
|
}
|
|
|
|
func TestListenerCloseUnblocksAccept(t *testing.T) {
|
|
l := Listener{}
|
|
var w timedWaitGroup
|
|
w.Add(1)
|
|
go func() {
|
|
l.Accept()
|
|
w.Done()
|
|
}()
|
|
l.Close()
|
|
if !w.WaitFor(time.Millisecond * 10) {
|
|
t.Errorf("listener close did not unblock accept")
|
|
}
|
|
}
|
|
|
|
func TestListenerDoubleClose(t *testing.T) {
|
|
l := &Listener{}
|
|
err := l.Close()
|
|
if err != nil {
|
|
t.Errorf("listener close should succeed the first time, got error: %s", err)
|
|
}
|
|
err = l.Close()
|
|
if err == nil {
|
|
t.Errorf("listener close should fail with an error the second time")
|
|
} else if err := err.(*net.OpError); err.Temporary() {
|
|
t.Errorf("listener close should fail with a permanent error the second time")
|
|
}
|
|
}
|
|
|
|
// When you're at 9x% test coverage, and the remaining lines are trivial, it's so
|
|
// very, very hard to resist the temptation to push to 100%. :)
|
|
func TestNothingAtAllImportant(t *testing.T) {
|
|
a := addr("addr")
|
|
if a.Network() == "" || a.String() == "" {
|
|
t.Errorf("addrs should have a non-empty network and string description")
|
|
}
|
|
|
|
s := "dummy err"
|
|
e := neterr{err: fmt.Errorf(s)}
|
|
if e.Error() != s {
|
|
t.Errorf("err did not return the wrapped error")
|
|
}
|
|
}
|