diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:19:13 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:19:13 +0000 |
commit | ccd992355df7192993c666236047820244914598 (patch) | |
tree | f00fea65147227b7743083c6148396f74cd66935 /src/runtime/internal/wasitest | |
parent | Initial commit. (diff) | |
download | golang-1.21-ccd992355df7192993c666236047820244914598.tar.xz golang-1.21-ccd992355df7192993c666236047820244914598.zip |
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/runtime/internal/wasitest')
-rw-r--r-- | src/runtime/internal/wasitest/host_test.go | 14 | ||||
-rw-r--r-- | src/runtime/internal/wasitest/nonblock_test.go | 101 | ||||
-rw-r--r-- | src/runtime/internal/wasitest/tcpecho_test.go | 99 | ||||
-rw-r--r-- | src/runtime/internal/wasitest/testdata/nonblock.go | 65 | ||||
-rw-r--r-- | src/runtime/internal/wasitest/testdata/tcpecho.go | 74 |
5 files changed, 353 insertions, 0 deletions
diff --git a/src/runtime/internal/wasitest/host_test.go b/src/runtime/internal/wasitest/host_test.go new file mode 100644 index 0000000..ca4ef8f --- /dev/null +++ b/src/runtime/internal/wasitest/host_test.go @@ -0,0 +1,14 @@ +// Copyright 2023 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. + +package wasi_test + +import "flag" + +var target string + +func init() { + // The dist test runner passes -target when running this as a host test. + flag.StringVar(&target, "target", "", "") +} diff --git a/src/runtime/internal/wasitest/nonblock_test.go b/src/runtime/internal/wasitest/nonblock_test.go new file mode 100644 index 0000000..3072b96 --- /dev/null +++ b/src/runtime/internal/wasitest/nonblock_test.go @@ -0,0 +1,101 @@ +// Copyright 2023 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. + +// Not all systems have syscall.Mkfifo. +//go:build !aix && !plan9 && !solaris && !wasm && !windows + +package wasi_test + +import ( + "bufio" + "fmt" + "io" + "math/rand" + "os" + "os/exec" + "path/filepath" + "syscall" + "testing" +) + +// This test creates a set of FIFOs and writes to them in reverse order. It +// checks that the output order matches the write order. The test binary opens +// the FIFOs in their original order and spawns a goroutine for each that reads +// from the FIFO and writes the result to stderr. If I/O was blocking, all +// goroutines would be blocked waiting for one read call to return, and the +// output order wouldn't match. + +type fifo struct { + file *os.File + path string +} + +func TestNonblock(t *testing.T) { + if target != "wasip1/wasm" { + t.Skip() + } + + switch os.Getenv("GOWASIRUNTIME") { + case "wasmer": + t.Skip("wasmer does not support non-blocking I/O") + } + + for _, mode := range []string{"os.OpenFile", "os.NewFile"} { + t.Run(mode, func(t *testing.T) { + args := []string{"run", "./testdata/nonblock.go", mode} + + fifos := make([]*fifo, 8) + for i := range fifos { + path := filepath.Join(t.TempDir(), fmt.Sprintf("wasip1-nonblock-fifo-%d-%d", rand.Uint32(), i)) + if err := syscall.Mkfifo(path, 0666); err != nil { + t.Fatal(err) + } + + file, err := os.OpenFile(path, os.O_RDWR, 0) + if err != nil { + t.Fatal(err) + } + defer file.Close() + + args = append(args, path) + fifos[len(fifos)-i-1] = &fifo{file, path} + } + + subProcess := exec.Command("go", args...) + + subProcess.Env = append(os.Environ(), "GOOS=wasip1", "GOARCH=wasm") + + pr, pw := io.Pipe() + defer pw.Close() + + subProcess.Stderr = pw + + if err := subProcess.Start(); err != nil { + t.Fatal(err) + } + + scanner := bufio.NewScanner(pr) + if !scanner.Scan() { + t.Fatal("expected line:", scanner.Err()) + } else if scanner.Text() != "waiting" { + t.Fatal("unexpected output:", scanner.Text()) + } + + for _, fifo := range fifos { + if _, err := fifo.file.WriteString(fifo.path + "\n"); err != nil { + t.Fatal(err) + } + if !scanner.Scan() { + t.Fatal("expected line:", scanner.Err()) + } else if scanner.Text() != fifo.path { + t.Fatal("unexpected line:", scanner.Text()) + } + } + + if err := subProcess.Wait(); err != nil { + t.Fatal(err) + } + }) + } +} diff --git a/src/runtime/internal/wasitest/tcpecho_test.go b/src/runtime/internal/wasitest/tcpecho_test.go new file mode 100644 index 0000000..1137395 --- /dev/null +++ b/src/runtime/internal/wasitest/tcpecho_test.go @@ -0,0 +1,99 @@ +// Copyright 2023 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. + +package wasi_test + +import ( + "bytes" + "fmt" + "math/rand" + "net" + "os" + "os/exec" + "testing" + "time" +) + +func TestTCPEcho(t *testing.T) { + if target != "wasip1/wasm" { + t.Skip() + } + + // We're unable to use port 0 here (let the OS choose a spare port). + // Although the WASM runtime accepts port 0, and the WASM module listens + // successfully, there's no way for this test to query the selected port + // so that it can connect to the WASM module. The WASM module itself + // cannot access any information about the socket due to limitations + // with WASI preview 1 networking, and the WASM runtimes do not log the + // port when you pre-open a socket. So, we probe for a free port here. + // Given there's an unavoidable race condition, the test is disabled by + // default. + if os.Getenv("GOWASIENABLERACYTEST") != "1" { + t.Skip("skipping WASI test with unavoidable race condition") + } + var host string + port := rand.Intn(10000) + 40000 + for attempts := 0; attempts < 10; attempts++ { + host = fmt.Sprintf("127.0.0.1:%d", port) + l, err := net.Listen("tcp", host) + if err == nil { + l.Close() + break + } + port++ + } + + subProcess := exec.Command("go", "run", "./testdata/tcpecho.go") + + subProcess.Env = append(os.Environ(), "GOOS=wasip1", "GOARCH=wasm") + + switch os.Getenv("GOWASIRUNTIME") { + case "wazero": + subProcess.Env = append(subProcess.Env, "GOWASIRUNTIMEARGS=--listen="+host) + case "wasmtime", "": + subProcess.Env = append(subProcess.Env, "GOWASIRUNTIMEARGS=--tcplisten="+host) + default: + t.Skip("WASI runtime does not support sockets") + } + + var b bytes.Buffer + subProcess.Stdout = &b + subProcess.Stderr = &b + + if err := subProcess.Start(); err != nil { + t.Log(b.String()) + t.Fatal(err) + } + defer subProcess.Process.Kill() + + var conn net.Conn + var err error + for { + conn, err = net.Dial("tcp", host) + if err == nil { + break + } + time.Sleep(500 * time.Millisecond) + } + if err != nil { + t.Log(b.String()) + t.Fatal(err) + } + defer conn.Close() + + payload := []byte("foobar") + if _, err := conn.Write(payload); err != nil { + t.Fatal(err) + } + var buf [256]byte + n, err := conn.Read(buf[:]) + if err != nil { + t.Fatal(err) + } + if string(buf[:n]) != string(payload) { + t.Error("unexpected payload") + t.Logf("expect: %d bytes (%v)", len(payload), payload) + t.Logf("actual: %d bytes (%v)", n, buf[:n]) + } +} diff --git a/src/runtime/internal/wasitest/testdata/nonblock.go b/src/runtime/internal/wasitest/testdata/nonblock.go new file mode 100644 index 0000000..8cbf21b --- /dev/null +++ b/src/runtime/internal/wasitest/testdata/nonblock.go @@ -0,0 +1,65 @@ +// Copyright 2023 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. + +package main + +import ( + "os" + "sync" + "syscall" +) + +func main() { + if len(os.Args) < 2 { + panic("usage: nonblock <MODE> [PATH...]") + } + mode := os.Args[1] + + ready := make(chan struct{}) + + var wg sync.WaitGroup + for _, path := range os.Args[2:] { + f, err := os.Open(path) + if err != nil { + panic(err) + } + switch mode { + case "os.OpenFile": + case "os.NewFile": + fd := f.Fd() + if err := syscall.SetNonblock(int(fd), true); err != nil { + panic(err) + } + f = os.NewFile(fd, path) + default: + panic("invalid test mode") + } + + spawnWait := make(chan struct{}) + + wg.Add(1) + go func(f *os.File) { + defer f.Close() + defer wg.Done() + + close(spawnWait) + + <-ready + + var buf [256]byte + n, err := f.Read(buf[:]) + if err != nil { + panic(err) + } + os.Stderr.Write(buf[:n]) + }(f) + + // Spawn one goroutine at a time. + <-spawnWait + } + + println("waiting") + close(ready) + wg.Wait() +} diff --git a/src/runtime/internal/wasitest/testdata/tcpecho.go b/src/runtime/internal/wasitest/testdata/tcpecho.go new file mode 100644 index 0000000..819e352 --- /dev/null +++ b/src/runtime/internal/wasitest/testdata/tcpecho.go @@ -0,0 +1,74 @@ +// Copyright 2023 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. + +package main + +import ( + "errors" + "net" + "os" + "syscall" +) + +func main() { + if err := run(); err != nil { + println(err) + os.Exit(1) + } +} + +func run() error { + l, err := findListener() + if err != nil { + return err + } + if l == nil { + return errors.New("no pre-opened sockets available") + } + defer l.Close() + + c, err := l.Accept() + if err != nil { + return err + } + return handleConn(c) +} + +func handleConn(c net.Conn) error { + defer c.Close() + + var buf [128]byte + n, err := c.Read(buf[:]) + if err != nil { + return err + } + if _, err := c.Write(buf[:n]); err != nil { + return err + } + if err := c.(*net.TCPConn).CloseWrite(); err != nil { + return err + } + return c.Close() +} + +func findListener() (net.Listener, error) { + // We start looking for pre-opened sockets at fd=3 because 0, 1, and 2 + // are reserved for stdio. Pre-opened directors also start at fd=3, so + // we skip fds that aren't sockets. Once we reach EBADF we know there + // are no more pre-opens. + for preopenFd := uintptr(3); ; preopenFd++ { + f := os.NewFile(preopenFd, "") + l, err := net.FileListener(f) + f.Close() + + var se syscall.Errno + switch errors.As(err, &se); se { + case syscall.ENOTSOCK: + continue + case syscall.EBADF: + err = nil + } + return l, err + } +} |