mirror of
https://github.com/rwinkhart/go-winio.git
synced 2026-08-28 04:46:50 -04:00
* Add lint and go generate stages to CI
Add CI step to verify `go generate` was run on repo.
Add linter stage to CI along with linter config file,
`.golangci.yml`.
Will likely prefer revive over static-check.
Updated README Contributing section on linting requirements.
Added sequence ordering to make sure lint and go generate stages run
before tests and build.
This way, build and tests are not run on code that could potentially:
1. not build due to `gofmt` issues;
2. contain bugs;
3. have to be re-submitted after issues are fixed; or
4. contain outdated Win32 syscall or other auto-generated files.
Signed-off-by: Hamza El-Saawy <hamzaelsaawy@microsoft.com>
* Fixed linter issues
Code changes to satisfy linters:
- Ran `gofmt -s -w` on repo.
- Broke up long lines.
- When possible, changed names with incorrect initialism formatting
- Added exceptions for exported variables.
- Added exceptions for ALL_CAPS_WITH_UNDERSCORES code.
- Switched to using `windows` or `syscall` definitions if possible;
especially if some constants were unused.
- Added `_ =` to satisfy error linter, and acknowledge that errors are
being ignored.
- Switched to using `errors.Is` and `As` in places, elsewhere added
exceptions if error value was known to be `syscall.Errno`.
- Removed bare returns.
- Prevented variables from being overshadowed in certain places
(ignoring cases of overshadowing `err`).
- Renamed variables and functions (eg, `len`, `eventMetadata.bytes`) to
prevent shadowing pre-built functions and imported pacakges.
- Removed unused method receivers.
- Added exceptions to certain unused (unexported) constants and
functions.
- Deleted unused `once` from `pkg/etw.providerMap`.
- Renamed `noop.go` files to `main_other.go` or `doc.go`, to better fit
style recommendations.
- Added exceptions for non-secure use of SHA1 and weak crypto
libraries.
- Replaced `ioutil` with `io` and `os` (and `t.TempDir` in tests).
- Added fully exhaustive checks for `switch` statements in `pkg/etw`.
- Defined constant strings for `tools/mkwinsyscall`.
- Removed unnecessary conversions.
- Made sure `context.Cancel` was called.
Additionally, added `//go:build windows" constraints on files with
unexported code, since linter will complain about unused code on
non-Windows platforms.
Added a stub `main() {}` for `mkwinsyscall` for non-Windows builds, just in
case `//go:generate` directives are added to OS-agnostic files.
Signed-off-by: Hamza El-Saawy <hamzaelsaawy@microsoft.com>
* PR: spelling, constants, fuzzing
Moved HVSocket fuzzing tests to separate file with go 1.18 build
constraint.
Signed-off-by: Hamza El-Saawy <hamzaelsaawy@microsoft.com>
Signed-off-by: Hamza El-Saawy <hamzaelsaawy@microsoft.com>
661 lines
15 KiB
Go
661 lines
15 KiB
Go
//go:build windows
|
|
|
|
package winio
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/sys/windows"
|
|
|
|
"github.com/Microsoft/go-winio/internal/socket"
|
|
"github.com/Microsoft/go-winio/pkg/guid"
|
|
)
|
|
|
|
const testStr = "test"
|
|
|
|
func randHvsockAddr() *HvsockAddr {
|
|
p := rand.Uint32() //nolint:gosec // used for testing
|
|
return &HvsockAddr{
|
|
VMID: HvsockGUIDLoopback(),
|
|
ServiceID: VsockServiceID(p),
|
|
}
|
|
}
|
|
|
|
func serverListen(u testUtil) (l *HvsockListener, a *HvsockAddr) {
|
|
var err error
|
|
for i := 0; i < 3; i++ {
|
|
a = randHvsockAddr()
|
|
l, err = ListenHvsock(a)
|
|
if errors.Is(err, windows.WSAEADDRINUSE) {
|
|
u.T.Logf("address collision %v", a)
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
u.Must(err, "could not listen")
|
|
u.T.Cleanup(func() {
|
|
if l != nil {
|
|
u.Must(l.Close(), "Hyper-V socket listener close")
|
|
}
|
|
})
|
|
|
|
return l, a
|
|
}
|
|
|
|
func clientServer(u testUtil) (cl, sv *HvsockConn, _ *HvsockAddr) {
|
|
l, addr := serverListen(u)
|
|
ch := u.Go(func() error {
|
|
conn, err := l.Accept()
|
|
if err != nil {
|
|
return fmt.Errorf("listener accept: %w", err)
|
|
}
|
|
sv = conn.(*HvsockConn)
|
|
if err := l.Close(); err != nil {
|
|
return err
|
|
}
|
|
l = nil
|
|
return nil
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
|
defer cancel()
|
|
cl, err := Dial(ctx, addr)
|
|
u.Must(err, "could not dial")
|
|
u.T.Cleanup(func() {
|
|
if cl != nil {
|
|
u.Must(cl.Close(), "client close")
|
|
}
|
|
})
|
|
|
|
u.WaitErr(ch, time.Second)
|
|
u.T.Cleanup(func() {
|
|
if sv != nil {
|
|
u.Must(sv.Close(), "server close")
|
|
}
|
|
})
|
|
return cl, sv, addr
|
|
}
|
|
|
|
func TestHvSockConstants(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
want string
|
|
give guid.GUID
|
|
}{
|
|
{"wildcard", "00000000-0000-0000-0000-000000000000", HvsockGUIDWildcard()},
|
|
{"broadcast", "ffffffff-ffff-ffff-ffff-ffffffffffff", HvsockGUIDBroadcast()},
|
|
{"loopback", "e0e16197-dd56-4a10-9195-5ee7a155a838", HvsockGUIDLoopback()},
|
|
{"children", "90db8b89-0d35-4f79-8ce9-49ea0ac8b7cd", HvsockGUIDChildren()},
|
|
{"parent", "a42e7cda-d03f-480c-9cc2-a4de20abb878", HvsockGUIDParent()},
|
|
{"silohost", "36bd0c5c-7276-4223-88ba-7d03b654c568", HvsockGUIDSiloHost()},
|
|
{"vsock template", "00000000-facb-11e6-bd58-64006a7986d3", hvsockVsockServiceTemplate()},
|
|
}
|
|
for _, tt := range tests {
|
|
if tt.give.String() != tt.want {
|
|
t.Errorf("%s give: %v; want: %s", tt.name, tt.give, tt.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestHvSockListenerAddresses(t *testing.T) {
|
|
u := newUtil(t)
|
|
l, addr := serverListen(u)
|
|
|
|
la := (l.Addr()).(*HvsockAddr)
|
|
u.Assert(*la == *addr, fmt.Sprintf("give: %v; want: %v", la, addr))
|
|
|
|
ra := rawHvsockAddr{}
|
|
sa := HvsockAddr{}
|
|
u.Must(socket.GetSockName(windows.Handle(l.sock.handle), &ra))
|
|
sa.fromRaw(&ra)
|
|
u.Assert(sa == *addr, fmt.Sprintf("listener local addr give: %v; want: %v", sa, addr))
|
|
}
|
|
|
|
func TestHvSockAddresses(t *testing.T) {
|
|
u := newUtil(t)
|
|
cl, sv, addr := clientServer(u)
|
|
|
|
sra := (sv.RemoteAddr()).(*HvsockAddr)
|
|
sla := (sv.LocalAddr()).(*HvsockAddr)
|
|
cra := (cl.RemoteAddr()).(*HvsockAddr)
|
|
cla := (cl.LocalAddr()).(*HvsockAddr)
|
|
|
|
t.Run("Info", func(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
give *HvsockAddr
|
|
want HvsockAddr
|
|
}{
|
|
{"client local", cla, HvsockAddr{HvsockGUIDChildren(), sra.ServiceID}},
|
|
{"client remote", cra, *addr},
|
|
{"server local", sla, HvsockAddr{HvsockGUIDChildren(), addr.ServiceID}},
|
|
{"server remote", sra, HvsockAddr{HvsockGUIDLoopback(), cla.ServiceID}},
|
|
}
|
|
for _, tt := range tests {
|
|
if *tt.give != tt.want {
|
|
t.Errorf("%s address give: %v; want: %v", tt.name, tt.give, tt.want)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("OSinfo", func(t *testing.T) {
|
|
u := newUtil(t)
|
|
ra := rawHvsockAddr{}
|
|
sa := HvsockAddr{}
|
|
|
|
localTests := []struct {
|
|
name string
|
|
giveSock *win32File
|
|
wantAddr HvsockAddr
|
|
}{
|
|
{"client", cl.sock, HvsockAddr{HvsockGUIDChildren(), cla.ServiceID}},
|
|
// The server sockets local address seems arbitrary, so skip this test
|
|
// see comment in `(*HvsockListener) Accept()` for more info
|
|
// {"server", sv.sock, _sla},
|
|
}
|
|
for _, tt := range localTests {
|
|
u.Must(socket.GetSockName(windows.Handle(tt.giveSock.handle), &ra))
|
|
sa.fromRaw(&ra)
|
|
if sa != tt.wantAddr {
|
|
t.Errorf("%s local addr give: %v; want: %v", tt.name, sa, tt.wantAddr)
|
|
}
|
|
}
|
|
|
|
remoteTests := []struct {
|
|
name string
|
|
giveConn *HvsockConn
|
|
}{
|
|
{"client", cl},
|
|
{"server", sv},
|
|
}
|
|
for _, tt := range remoteTests {
|
|
u.Must(socket.GetPeerName(windows.Handle(tt.giveConn.sock.handle), &ra))
|
|
sa.fromRaw(&ra)
|
|
if sa != tt.giveConn.remote {
|
|
t.Errorf("%s remote addr give: %v; want: %v", tt.name, sa, tt.giveConn.remote)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestHvSockReadWrite(t *testing.T) {
|
|
u := newUtil(t)
|
|
l, addr := serverListen(u)
|
|
tests := []struct {
|
|
req, rsp string
|
|
}{
|
|
{"hello ", "world!"},
|
|
{"ping", "pong"},
|
|
}
|
|
|
|
// a sync.WaitGroup doesnt offer a channel to use in a select with a timeout
|
|
// could use an errgroup.Group, but for now dual channels work fine
|
|
svCh := u.Go(func() error {
|
|
c, err := l.Accept()
|
|
if err != nil {
|
|
return fmt.Errorf("listener accept: %w", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
b := make([]byte, 64)
|
|
for _, tt := range tests {
|
|
n, err := c.Read(b)
|
|
if err != nil {
|
|
return fmt.Errorf("server rx: %w", err)
|
|
}
|
|
|
|
r := string(b[:n])
|
|
if r != tt.req {
|
|
return fmt.Errorf("server rx error: got %q; wanted %q", r, tt.req)
|
|
}
|
|
if _, err = c.Write([]byte(tt.rsp)); err != nil {
|
|
return fmt.Errorf("server tx error, could not send %q: %w", tt.rsp, err)
|
|
}
|
|
}
|
|
n, err := c.Read(b)
|
|
if n != 0 {
|
|
return errors.New("server did not get EOF")
|
|
}
|
|
if !errors.Is(err, io.EOF) {
|
|
return fmt.Errorf("server did not get EOF: %w", err)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
clCh := u.Go(func() error {
|
|
cl, err := Dial(context.Background(), addr)
|
|
if err != nil {
|
|
return fmt.Errorf("client dial: %w", err)
|
|
}
|
|
defer cl.Close()
|
|
|
|
b := make([]byte, 64)
|
|
for _, tt := range tests {
|
|
_, err := cl.Write([]byte(tt.req))
|
|
if err != nil {
|
|
return fmt.Errorf("client tx error, could not send %q: %w", tt.req, err)
|
|
}
|
|
|
|
n, err := cl.Read(b)
|
|
if err != nil {
|
|
return fmt.Errorf("client tx: %w", err)
|
|
}
|
|
|
|
r := string(b[:n])
|
|
if r != tt.rsp {
|
|
return fmt.Errorf("client rx error: got %q; wanted %q", b[:n], tt.rsp)
|
|
}
|
|
}
|
|
return cl.CloseWrite()
|
|
})
|
|
|
|
u.WaitErr(svCh, 15*time.Second, "server")
|
|
u.WaitErr(clCh, 15*time.Second, "client")
|
|
}
|
|
|
|
func TestHvSockReadTooSmall(t *testing.T) {
|
|
u := newUtil(t)
|
|
s := "this is a really long string that hopefully takes up more than 16 bytes ..."
|
|
l, addr := serverListen(u)
|
|
|
|
svCh := u.Go(func() error {
|
|
c, err := l.Accept()
|
|
if err != nil {
|
|
return fmt.Errorf("listener accept: %w", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
b := make([]byte, 16)
|
|
ss := ""
|
|
for {
|
|
n, err := c.Read(b)
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("server rx: %w", err)
|
|
}
|
|
ss += string(b[:n])
|
|
}
|
|
|
|
if ss != s {
|
|
return fmt.Errorf("got %q, wanted: %q", ss, s)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
clCh := u.Go(func() error {
|
|
cl, err := Dial(context.Background(), addr)
|
|
if err != nil {
|
|
return fmt.Errorf("client dial: %w", err)
|
|
}
|
|
defer cl.Close()
|
|
|
|
if _, err = cl.Write([]byte(s)); err != nil {
|
|
return fmt.Errorf("client tx error, could not send: %w", err)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
u.WaitErr(svCh, 15*time.Second, "server")
|
|
u.WaitErr(clCh, 15*time.Second, "client")
|
|
}
|
|
|
|
func TestHvSockCloseReadWriteListener(t *testing.T) {
|
|
u := newUtil(t)
|
|
l, addr := serverListen(u)
|
|
|
|
ch := make(chan struct{})
|
|
svCh := u.Go(func() error {
|
|
defer close(ch)
|
|
c, err := l.Accept()
|
|
if err != nil {
|
|
return fmt.Errorf("listener accept: %w", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
hv := c.(*HvsockConn)
|
|
//
|
|
// test CloseWrite()
|
|
//
|
|
n, err := c.Write([]byte(testStr))
|
|
if err != nil {
|
|
return fmt.Errorf("server tx: %w", err)
|
|
}
|
|
if n != len(testStr) {
|
|
return fmt.Errorf("server wrote %d bytes, wanted %d", n, len(testStr))
|
|
}
|
|
|
|
if err := hv.CloseWrite(); err != nil {
|
|
return fmt.Errorf("server close write: %w", err)
|
|
}
|
|
|
|
if _, err = c.Write([]byte(testStr)); !errors.Is(err, windows.WSAESHUTDOWN) {
|
|
return fmt.Errorf("server did not shutdown writes: %w", err)
|
|
}
|
|
// safe to call multiple times
|
|
if err := hv.CloseWrite(); err != nil {
|
|
return fmt.Errorf("server second close write: %w", err)
|
|
}
|
|
|
|
//
|
|
// test CloseRead()
|
|
//
|
|
b := make([]byte, 256)
|
|
n, err = c.Read(b)
|
|
if err != nil {
|
|
return fmt.Errorf("server read: %w", err)
|
|
}
|
|
if n != len(testStr) {
|
|
return fmt.Errorf("server read %d bytes, wanted %d", n, len(testStr))
|
|
}
|
|
if string(b[:n]) != testStr {
|
|
return fmt.Errorf("server got %q; wanted %q", b[:n], testStr)
|
|
}
|
|
if err := hv.CloseRead(); err != nil {
|
|
return fmt.Errorf("server close read: %w", err)
|
|
}
|
|
|
|
ch <- struct{}{}
|
|
|
|
// signal the client to send more info
|
|
// if it was sent before, the read would succeed if the data was buffered prior
|
|
_, err = c.Read(b)
|
|
if !errors.Is(err, windows.WSAESHUTDOWN) {
|
|
return fmt.Errorf("server did not shutdown reads: %w", err)
|
|
}
|
|
// safe to call multiple times
|
|
if err := hv.CloseRead(); err != nil {
|
|
return fmt.Errorf("server second close read: %w", err)
|
|
}
|
|
|
|
c.Close()
|
|
if err := hv.CloseWrite(); !errors.Is(err, socket.ErrSocketClosed) {
|
|
return fmt.Errorf("server close write: %w", err)
|
|
}
|
|
if err := hv.CloseRead(); !errors.Is(err, socket.ErrSocketClosed) {
|
|
return fmt.Errorf("server close read: %w", err)
|
|
}
|
|
return nil
|
|
})
|
|
|
|
cl, err := Dial(context.Background(), addr)
|
|
u.Must(err, "could not dial")
|
|
defer cl.Close()
|
|
|
|
b := make([]byte, 256)
|
|
n, err := cl.Read(b)
|
|
u.Must(err, "client read")
|
|
u.Assert(n == len(testStr), fmt.Sprintf("client read %d bytes, wanted %d", n, len(testStr)))
|
|
u.Assert(string(b[:n]) == testStr, fmt.Sprintf("client got %q; wanted %q", b[:n], testStr))
|
|
|
|
n, err = cl.Read(b)
|
|
u.Assert(n == 0, "client did not get EOF")
|
|
u.Is(err, io.EOF, "client did not get EOF")
|
|
|
|
n, err = cl.Write([]byte(testStr))
|
|
u.Must(err, "client write")
|
|
u.Assert(n == len(testStr), fmt.Sprintf("client wrote %d bytes, wanted %d", n, len(testStr)))
|
|
|
|
u.Wait(ch, time.Second)
|
|
|
|
// this should succeed
|
|
_, err = cl.Write([]byte("test2"))
|
|
u.Must(err, "client write")
|
|
u.WaitErr(svCh, time.Second, "server")
|
|
}
|
|
|
|
func TestHvSockCloseReadWriteDial(t *testing.T) {
|
|
u := newUtil(t)
|
|
l, addr := serverListen(u)
|
|
|
|
ch := make(chan struct{})
|
|
clCh := u.Go(func() error {
|
|
defer close(ch)
|
|
c, err := l.Accept()
|
|
if err != nil {
|
|
return fmt.Errorf("listener accept: %w", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
b := make([]byte, 256)
|
|
n, err := c.Read(b)
|
|
if err != nil {
|
|
return fmt.Errorf("server read: %w", err)
|
|
}
|
|
if string(b[:n]) != testStr {
|
|
return fmt.Errorf("server got %q; wanted %q", b[:n], testStr)
|
|
}
|
|
|
|
n, err = c.Read(b)
|
|
if n != 0 {
|
|
return fmt.Errorf("server did not get EOF")
|
|
}
|
|
if !errors.Is(err, io.EOF) {
|
|
return errors.New("server did not get EOF")
|
|
}
|
|
|
|
_, err = c.Write([]byte(testStr))
|
|
if err != nil {
|
|
return fmt.Errorf("server tx: %w", err)
|
|
}
|
|
|
|
ch <- struct{}{}
|
|
|
|
_, err = c.Write([]byte(testStr))
|
|
if err != nil {
|
|
return fmt.Errorf("server tx: %w", err)
|
|
}
|
|
return c.Close()
|
|
})
|
|
|
|
cl, err := Dial(context.Background(), addr)
|
|
u.Must(err, "could not dial")
|
|
defer cl.Close()
|
|
|
|
//
|
|
// test CloseWrite()
|
|
//
|
|
_, err = cl.Write([]byte(testStr))
|
|
u.Must(err, "client write")
|
|
u.Must(cl.CloseWrite(), "client close write")
|
|
|
|
_, err = cl.Write([]byte(testStr))
|
|
u.Is(err, windows.WSAESHUTDOWN, "client did not shutdown writes")
|
|
|
|
// safe to call multiple times
|
|
u.Must(cl.CloseWrite(), "client second close write")
|
|
|
|
//
|
|
// test CloseRead()
|
|
//
|
|
b := make([]byte, 256)
|
|
n, err := cl.Read(b)
|
|
u.Must(err, "client read")
|
|
u.Assert(string(b[:n]) == testStr, fmt.Sprintf("client got %q; wanted %q", b[:n], testStr))
|
|
u.Must(cl.CloseRead(), "client close read")
|
|
|
|
u.Wait(ch, time.Millisecond)
|
|
|
|
// signal the client to send more info
|
|
// if it was sent before, the read would succeed if the data was buffered prior
|
|
_, err = cl.Read(b)
|
|
u.Is(err, windows.WSAESHUTDOWN, "client did not shutdown reads")
|
|
|
|
// safe to call multiple times
|
|
u.Must(cl.CloseRead(), "client second close write")
|
|
|
|
l.Close()
|
|
cl.Close()
|
|
|
|
wantErr := socket.ErrSocketClosed
|
|
u.Is(cl.CloseWrite(), wantErr, "client close write")
|
|
u.Is(cl.CloseRead(), wantErr, "client close read")
|
|
u.WaitErr(clCh, time.Second, "client")
|
|
}
|
|
|
|
func TestHvSockDialNoTimeout(t *testing.T) {
|
|
u := newUtil(t)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
ch := u.Go(func() error {
|
|
addr := randHvsockAddr()
|
|
cl, err := Dial(ctx, addr)
|
|
if err == nil {
|
|
cl.Close()
|
|
}
|
|
if !errors.Is(err, windows.WSAECONNREFUSED) {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
|
|
// connections usually take about ~500µs
|
|
u.WaitErr(ch, 2*time.Millisecond, "dial did not time out")
|
|
}
|
|
|
|
func TestHvSockDialDeadline(t *testing.T) {
|
|
u := newUtil(t)
|
|
d := &HvsockDialer{}
|
|
d.Deadline = time.Now().Add(50 * time.Microsecond)
|
|
d.Retries = 1
|
|
// we need the wait time to be long enough for the deadline goroutine to run first and signal
|
|
// timeout
|
|
d.RetryWait = 100 * time.Millisecond
|
|
addr := randHvsockAddr()
|
|
cl, err := d.Dial(context.Background(), addr)
|
|
if err == nil {
|
|
cl.Close()
|
|
t.Fatalf("dial should not have finished")
|
|
}
|
|
u.Is(err, context.DeadlineExceeded, "dial did not exceed deadline")
|
|
}
|
|
|
|
func TestHvSockDialContext(t *testing.T) {
|
|
u := newUtil(t)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
time.AfterFunc(50*time.Microsecond, cancel)
|
|
|
|
d := &HvsockDialer{}
|
|
d.Retries = 1
|
|
d.RetryWait = 100 * time.Millisecond
|
|
addr := randHvsockAddr()
|
|
cl, err := d.Dial(ctx, addr)
|
|
if err == nil {
|
|
cl.Close()
|
|
t.Fatalf("dial should not have finished")
|
|
}
|
|
u.Is(err, context.Canceled, "dial was not canceled")
|
|
}
|
|
|
|
func TestHvSockAcceptClose(t *testing.T) {
|
|
u := newUtil(t)
|
|
l, _ := serverListen(u)
|
|
go func() {
|
|
time.Sleep(50 * time.Millisecond)
|
|
l.Close()
|
|
}()
|
|
|
|
c, err := l.Accept()
|
|
if err == nil {
|
|
c.Close()
|
|
t.Fatal("listener should not have accepted anything")
|
|
}
|
|
u.Is(err, ErrFileClosed)
|
|
}
|
|
|
|
//
|
|
// helpers
|
|
//
|
|
|
|
type testUtil struct {
|
|
T testing.TB
|
|
}
|
|
|
|
func newUtil(t testing.TB) testUtil {
|
|
return testUtil{
|
|
T: t,
|
|
}
|
|
}
|
|
|
|
// Go launches f in a go routine and returns a channel that can be monitored for the result.
|
|
// ch is closed after f completes.
|
|
//
|
|
// Intended for use with [testUtil.WaitErr].
|
|
func (*testUtil) Go(f func() error) chan error {
|
|
ch := make(chan error)
|
|
go func() {
|
|
defer close(ch)
|
|
ch <- f()
|
|
}()
|
|
return ch
|
|
}
|
|
|
|
func (u testUtil) Wait(ch <-chan struct{}, d time.Duration, msgs ...string) {
|
|
t := time.NewTimer(d)
|
|
defer t.Stop()
|
|
select {
|
|
case <-ch:
|
|
case <-t.C:
|
|
u.T.Helper()
|
|
u.T.Fatalf(msgJoin(msgs, "timed out after %v"), d)
|
|
}
|
|
}
|
|
|
|
func (u testUtil) WaitErr(ch <-chan error, d time.Duration, msgs ...string) {
|
|
t := time.NewTimer(d)
|
|
defer t.Stop()
|
|
select {
|
|
case err := <-ch:
|
|
if err != nil {
|
|
u.T.Helper()
|
|
u.T.Fatalf(msgJoin(msgs, "%v"), err)
|
|
}
|
|
case <-t.C:
|
|
u.T.Helper()
|
|
u.T.Fatalf(msgJoin(msgs, "timed out after %v"), d)
|
|
}
|
|
}
|
|
|
|
func (u testUtil) Assert(b bool, msgs ...string) {
|
|
if b {
|
|
return
|
|
}
|
|
u.T.Helper()
|
|
u.T.Fatalf(msgJoin(msgs, "failed assertion"))
|
|
}
|
|
|
|
func (u testUtil) Is(err, target error, msgs ...string) {
|
|
if errors.Is(err, target) {
|
|
return
|
|
}
|
|
u.T.Helper()
|
|
u.T.Fatalf(msgJoin(msgs, "got error %q; wanted %q"), err, target)
|
|
}
|
|
|
|
func (u testUtil) Must(err error, msgs ...string) {
|
|
if err == nil {
|
|
return
|
|
}
|
|
u.T.Helper()
|
|
u.T.Fatalf(msgJoin(msgs, "%v"), err)
|
|
}
|
|
|
|
// Check stops execution if testing failed in another go-routine.
|
|
func (u testUtil) Check() {
|
|
if u.T.Failed() {
|
|
u.T.FailNow()
|
|
}
|
|
}
|
|
|
|
func msgJoin(pre []string, s string) string {
|
|
return strings.Join(append(pre, s), ": ")
|
|
}
|