wasi: adds Go readdir integration tests for GOOS=wasip1 and TinyGo (#1562)
Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
2
Makefile
2
Makefile
@@ -53,7 +53,7 @@ build.examples.as:
|
||||
build.examples.zig: examples/allocation/zig/testdata/greet.wasm imports/wasi_snapshot_preview1/example/testdata/zig/cat.wasm imports/wasi_snapshot_preview1/testdata/zig/wasi.wasm
|
||||
@cd internal/testing/dwarftestdata/testdata/zig; zig build; mv zig-out/*/main.wasm ./ # Need DWARF custom sections.
|
||||
|
||||
tinygo_sources := examples/basic/testdata/add.go examples/allocation/tinygo/testdata/greet.go examples/cli/testdata/cli.go imports/wasi_snapshot_preview1/example/testdata/tinygo/cat.go
|
||||
tinygo_sources := examples/basic/testdata/add.go examples/allocation/tinygo/testdata/greet.go examples/cli/testdata/cli.go imports/wasi_snapshot_preview1/example/testdata/tinygo/cat.go imports/wasi_snapshot_preview1/testdata/tinygo/wasi.go
|
||||
.PHONY: build.examples.tinygo
|
||||
build.examples.tinygo: $(tinygo_sources)
|
||||
@for f in $^; do \
|
||||
|
||||
@@ -747,7 +747,7 @@ type preader struct {
|
||||
offset int64
|
||||
}
|
||||
|
||||
// Read implements the same function as documented on internalapi.File.
|
||||
// Read implements the same function as documented on fsapi.File.
|
||||
func (w *preader) Read(buf []byte) (n int, errno syscall.Errno) {
|
||||
if len(buf) == 0 {
|
||||
return 0, 0 // less overhead on zero-length reads.
|
||||
@@ -1235,7 +1235,7 @@ type pwriter struct {
|
||||
offset int64
|
||||
}
|
||||
|
||||
// Write implements the same function as documented on internalapi.File.
|
||||
// Write implements the same function as documented on fsapi.File.
|
||||
func (w *pwriter) Write(buf []byte) (n int, errno syscall.Errno) {
|
||||
if len(buf) == 0 {
|
||||
return 0, 0 // less overhead on zero-length writes.
|
||||
|
||||
@@ -241,7 +241,7 @@ func Test_fdFdstatGet(t *testing.T) {
|
||||
preopen := fsc.RootFS()
|
||||
|
||||
// replace stdin with a fake TTY file.
|
||||
// TODO: Make this easier once we have in-memory internalapi.File
|
||||
// TODO: Make this easier once we have in-memory fsapi.File
|
||||
stdin, _ := fsc.LookupFile(sys.FdStdin)
|
||||
stdinFile, errno := sysfs.Adapt(&gofstest.MapFS{"stdin": &gofstest.MapFile{
|
||||
Mode: fs.ModeDevice | fs.ModeCharDevice | 0o600,
|
||||
|
||||
@@ -528,12 +528,14 @@ func fdReadSubFd(fd byte) []byte {
|
||||
// subscription for an EventTypeFdRead on stdin
|
||||
var fdReadSub = fdReadSubFd(byte(sys.FdStdin))
|
||||
|
||||
// ttyStat returns fs.ModeCharDevice as an approximation for isatty.
|
||||
// ttyStat returns fs.ModeCharDevice | fs.ModeCharDevice as an approximation
|
||||
// for isatty.
|
||||
//
|
||||
// See go-isatty for a more specific approach:
|
||||
// https://github.com/mattn/go-isatty/blob/v0.0.18/isatty_tcgets.go#LL11C1-L12C1
|
||||
type ttyStat struct{}
|
||||
|
||||
// Stat implements the same method as documented on internalapi.File
|
||||
// Stat implements the same method as documented on fsapi.File
|
||||
func (ttyStat) Stat() (fsapi.Stat_t, syscall.Errno) {
|
||||
return fsapi.Stat_t{
|
||||
Mode: fs.ModeDevice | fs.ModeCharDevice,
|
||||
@@ -551,7 +553,7 @@ type neverReadyTtyStdinFile struct {
|
||||
ttyStat
|
||||
}
|
||||
|
||||
// PollRead implements the same method as documented on internalapi.File
|
||||
// PollRead implements the same method as documented on fsapi.File
|
||||
func (neverReadyTtyStdinFile) PollRead(timeout *time.Duration) (ready bool, errno syscall.Errno) {
|
||||
time.Sleep(*timeout)
|
||||
return false, 0
|
||||
@@ -563,7 +565,7 @@ type pollStdinFile struct {
|
||||
ready bool
|
||||
}
|
||||
|
||||
// PollRead implements the same method as documented on internalapi.File
|
||||
// PollRead implements the same method as documented on fsapi.File
|
||||
func (p *pollStdinFile) PollRead(*time.Duration) (ready bool, errno syscall.Errno) {
|
||||
return p.ready, 0
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -13,18 +15,38 @@ import (
|
||||
|
||||
func main() {
|
||||
switch os.Args[1] {
|
||||
case "ls":
|
||||
var repeat bool
|
||||
if len(os.Args) == 4 {
|
||||
repeat = os.Args[3] == "repeat"
|
||||
}
|
||||
// Go doesn't open with O_DIRECTORY, so we don't end up with ENOTDIR,
|
||||
// rather EBADF trying to read the directory later.
|
||||
if err := mainLs(os.Args[2], repeat); errors.Is(err, syscall.EBADF) {
|
||||
fmt.Println("ENOTDIR")
|
||||
} else if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "stat":
|
||||
if err := mainStat(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "sock":
|
||||
if err := mainSock(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "http":
|
||||
if err := mainHTTP(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "nonblock":
|
||||
if err := mainNonblock(os.Args[2], os.Args[3:]); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Handle go-specific additions
|
||||
switch os.Args[1] {
|
||||
case "http":
|
||||
if err := mainHTTP(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "stdin":
|
||||
if err := mainStdin(); err != nil {
|
||||
panic(err)
|
||||
@@ -32,7 +54,57 @@ func main() {
|
||||
case "stdout":
|
||||
mainStdout()
|
||||
}
|
||||
}
|
||||
|
||||
func mainLs(path string, repeat bool) error {
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
if err = printFileNames(d); err != nil {
|
||||
return err
|
||||
} else if repeat {
|
||||
// rewind
|
||||
if _, err = d.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
return printFileNames(d)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printFileNames(d *os.File) error {
|
||||
if names, err := d.Readdirnames(-1); err != nil {
|
||||
return err
|
||||
} else {
|
||||
for _, n := range names {
|
||||
fmt.Println("./" + n)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mainStat() error {
|
||||
var isatty = func(name string, fd uintptr) error {
|
||||
f := os.NewFile(fd, "")
|
||||
if st, err := f.Stat(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
ttyMode := fs.ModeDevice | fs.ModeCharDevice
|
||||
isatty := st.Mode()&ttyMode == ttyMode
|
||||
fmt.Println(name, "isatty:", isatty)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
for fd, name := range []string{"stdin", "stdout", "stderr", "/"} {
|
||||
if err := isatty(name, uintptr(fd)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// mainSock is an explicit test of a blocking socket.
|
||||
@@ -65,55 +137,6 @@ func mainSock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// mainHTTP implicitly tests non-blocking sockets, as they are needed for
|
||||
// middleware.
|
||||
func mainHTTP() error {
|
||||
// Get the file representing a pre-opened TCP socket.
|
||||
// The socket (listener) is the first pre-open, with a file-descriptor of
|
||||
// 3 because the host didn't add any pre-opened files.
|
||||
listenerFD := 3
|
||||
f := os.NewFile(uintptr(listenerFD), "")
|
||||
|
||||
// Wasm runs similarly to GOMAXPROCS=1, so multiple goroutines cannot work
|
||||
// in parallel. non-blocking allows the poller to park the go-routine
|
||||
// accepting connections while work is done on one.
|
||||
if err := syscall.SetNonblock(listenerFD, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert the file representing the pre-opened socket to a listener, so
|
||||
// that we can integrate it with HTTP middleware.
|
||||
ln, err := net.FileListener(f)
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
// Serve middleware that echos the request body to the response once, then quits.
|
||||
h := &echoOnce{ch: make(chan struct{}, 1)}
|
||||
go http.Serve(ln, h)
|
||||
<-h.ch
|
||||
return nil
|
||||
}
|
||||
|
||||
type echoOnce struct {
|
||||
ch chan struct{}
|
||||
}
|
||||
|
||||
func (e echoOnce) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Copy up to 32 bytes from the request to the response, appending a newline.
|
||||
// Note: the test should write: "wazero", so that's all we should read.
|
||||
var buf [32]byte
|
||||
if n, err := r.Body.Read(buf[:]); err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
} else if n, err = w.Write(append(buf[:n], '\n')); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Once one request was served, close the channel.
|
||||
close(e.ch)
|
||||
}
|
||||
|
||||
// Adapted from nonblock.go
|
||||
// https://github.com/golang/go/blob/0fcc70ecd56e3b5c214ddaee4065ea1139ae16b5/src/runtime/internal/wasitest/testdata/nonblock.go
|
||||
func mainNonblock(mode string, files []string) error {
|
||||
@@ -169,6 +192,55 @@ func mainNonblock(mode string, files []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// mainHTTP implicitly tests non-blocking sockets, as they are needed for
|
||||
// middleware.
|
||||
func mainHTTP() error {
|
||||
// Get the file representing a pre-opened TCP socket.
|
||||
// The socket (listener) is the first pre-open, with a file-descriptor of
|
||||
// 3 because the host didn't add any pre-opened files.
|
||||
listenerFD := 3
|
||||
f := os.NewFile(uintptr(listenerFD), "")
|
||||
|
||||
// Wasm runs similarly to GOMAXPROCS=1, so multiple goroutines cannot work
|
||||
// in parallel. non-blocking allows the poller to park the go-routine
|
||||
// accepting connections while work is done on one.
|
||||
if err := syscall.SetNonblock(listenerFD, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert the file representing the pre-opened socket to a listener, so
|
||||
// that we can integrate it with HTTP middleware.
|
||||
ln, err := net.FileListener(f)
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ln.Close()
|
||||
|
||||
// Serve middleware that echos the request body to the response once, then quits.
|
||||
h := &echoOnce{ch: make(chan struct{}, 1)}
|
||||
go http.Serve(ln, h)
|
||||
<-h.ch
|
||||
return nil
|
||||
}
|
||||
|
||||
type echoOnce struct {
|
||||
ch chan struct{}
|
||||
}
|
||||
|
||||
func (e echoOnce) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Copy up to 32 bytes from the request to the response, appending a newline.
|
||||
// Note: the test should write: "wazero", so that's all we should read.
|
||||
var buf [32]byte
|
||||
if n, err := r.Body.Read(buf[:]); err != nil && err != io.EOF {
|
||||
panic(err)
|
||||
} else if n, err = w.Write(append(buf[:n], '\n')); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Once one request was served, close the channel.
|
||||
close(e.ch)
|
||||
}
|
||||
|
||||
// Reproducer for https://github.com/tetratelabs/wazero/issues/1538
|
||||
func mainStdin() error {
|
||||
go func() {
|
||||
|
||||
86
imports/wasi_snapshot_preview1/testdata/tinygo/wasi.go
vendored
Normal file
86
imports/wasi_snapshot_preview1/testdata/tinygo/wasi.go
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
switch os.Args[1] {
|
||||
case "ls":
|
||||
var repeat bool
|
||||
if len(os.Args) == 4 {
|
||||
repeat = os.Args[3] == "repeat"
|
||||
}
|
||||
// Go doesn't open with O_DIRECTORY, so we don't end up with ENOTDIR,
|
||||
// rather EBADF trying to read the directory later.
|
||||
if err := mainLs(os.Args[2], repeat); errors.Is(err, syscall.EBADF) {
|
||||
fmt.Println("ENOTDIR")
|
||||
} else if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "stat":
|
||||
if err := mainStat(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case "sock":
|
||||
// TODO: undefined: net.FileListener
|
||||
case "nonblock":
|
||||
// TODO: undefined: syscall.SetNonblock
|
||||
}
|
||||
}
|
||||
|
||||
func mainLs(path string, repeat bool) error {
|
||||
d, err := os.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer d.Close()
|
||||
|
||||
if err = printFileNames(d); err != nil {
|
||||
return err
|
||||
} else if repeat {
|
||||
// rewind
|
||||
if _, err = d.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
return printFileNames(d)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func printFileNames(d *os.File) error {
|
||||
if names, err := d.Readdirnames(-1); err != nil {
|
||||
return err
|
||||
} else {
|
||||
for _, n := range names {
|
||||
fmt.Println("./" + n)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mainStat() error {
|
||||
var isatty = func(name string, fd uintptr) error {
|
||||
f := os.NewFile(fd, "")
|
||||
if st, err := f.Stat(); err != nil {
|
||||
return err
|
||||
} else {
|
||||
ttyMode := fs.ModeDevice | fs.ModeCharDevice
|
||||
isatty := st.Mode()&ttyMode == ttyMode
|
||||
fmt.Println(name, "isatty:", isatty)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
for fd, name := range []string{"stdin", "stdout", "stderr", "/"} {
|
||||
if err := isatty(name, uintptr(fd)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
BIN
imports/wasi_snapshot_preview1/testdata/tinygo/wasi.wasm
vendored
Executable file
BIN
imports/wasi_snapshot_preview1/testdata/tinygo/wasi.wasm
vendored
Executable file
Binary file not shown.
@@ -5,15 +5,16 @@ import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
gofstest "testing/fstest"
|
||||
"time"
|
||||
|
||||
"github.com/tetratelabs/wazero"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
experimentalsock "github.com/tetratelabs/wazero/experimental/sock"
|
||||
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
"github.com/tetratelabs/wazero/internal/fstest"
|
||||
internalsys "github.com/tetratelabs/wazero/internal/sys"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
@@ -38,6 +40,14 @@ var sleepALittle = func() { time.Sleep(500 * time.Millisecond) }
|
||||
//go:embed testdata/cargo-wasi/wasi.wasm
|
||||
var wasmCargoWasi []byte
|
||||
|
||||
// wasmGotip is conditionally compiled from testdata/gotip/wasi.go
|
||||
var wasmGotip []byte
|
||||
|
||||
// wasmTinyGo was compiled from testdata/tinygo/wasi.go
|
||||
//
|
||||
//go:embed testdata/tinygo/wasi.wasm
|
||||
var wasmTinyGo []byte
|
||||
|
||||
// wasmZigCc was compiled from testdata/zig-cc/wasi.c
|
||||
//
|
||||
//go:embed testdata/zig-cc/wasi.wasm
|
||||
@@ -48,37 +58,52 @@ var wasmZigCc []byte
|
||||
//go:embed testdata/zig/wasi.wasm
|
||||
var wasmZig []byte
|
||||
|
||||
// wasmGotip is conditionally compiled from testdata/gotip/wasi.go
|
||||
var wasmGotip []byte
|
||||
|
||||
func Test_fdReaddir_ls(t *testing.T) {
|
||||
for toolchain, bin := range map[string][]byte{
|
||||
toolchains := map[string][]byte{
|
||||
"cargo-wasi": wasmCargoWasi,
|
||||
"tinygo": wasmTinyGo,
|
||||
"zig-cc": wasmZigCc,
|
||||
"zig": wasmZig,
|
||||
} {
|
||||
}
|
||||
if wasmGotip != nil {
|
||||
toolchains["gotip"] = wasmGotip
|
||||
}
|
||||
|
||||
tmpDir := t.TempDir()
|
||||
require.NoError(t, fstest.WriteTestFiles(tmpDir))
|
||||
|
||||
tons := path.Join(tmpDir, "tons")
|
||||
require.NoError(t, os.Mkdir(tons, 0o0777))
|
||||
for i := 0; i < direntCountTons; i++ {
|
||||
require.NoError(t, os.WriteFile(path.Join(tons, strconv.Itoa(i)), nil, 0o0666))
|
||||
}
|
||||
|
||||
for toolchain, bin := range toolchains {
|
||||
toolchain := toolchain
|
||||
bin := bin
|
||||
t.Run(toolchain, func(t *testing.T) {
|
||||
expectDots := toolchain == "zig-cc"
|
||||
testFdReaddirLs(t, bin, expectDots)
|
||||
var expectDots int
|
||||
if toolchain == "zig-cc" {
|
||||
expectDots = 1
|
||||
}
|
||||
testFdReaddirLs(t, bin, toolchain, tmpDir, expectDots)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testFdReaddirLs(t *testing.T, bin []byte, expectDots bool) {
|
||||
// TODO: make a subfs
|
||||
const direntCountTons = 8096
|
||||
|
||||
func testFdReaddirLs(t *testing.T, bin []byte, toolchain, rootDir string, expectDots int) {
|
||||
t.Helper()
|
||||
|
||||
moduleConfig := wazero.NewModuleConfig().
|
||||
WithFS(fstest.MapFS{
|
||||
"-": {},
|
||||
"a-": {Mode: fs.ModeDir},
|
||||
"ab-": {},
|
||||
})
|
||||
WithFSConfig(wazero.NewFSConfig().
|
||||
WithReadOnlyDirMount(path.Join(rootDir, "dir"), "/"))
|
||||
|
||||
t.Run("empty directory", func(t *testing.T) {
|
||||
console := compileAndRun(t, testCtx, moduleConfig.WithArgs("wasi", "ls", "./a-"), bin)
|
||||
|
||||
requireLsOut(t, "\n", expectDots, console)
|
||||
requireLsOut(t, nil, expectDots, console)
|
||||
})
|
||||
|
||||
t.Run("not a directory", func(t *testing.T) {
|
||||
@@ -91,75 +116,73 @@ ENOTDIR
|
||||
|
||||
t.Run("directory with entries", func(t *testing.T) {
|
||||
console := compileAndRun(t, testCtx, moduleConfig.WithArgs("wasi", "ls", "."), bin)
|
||||
requireLsOut(t, `
|
||||
./-
|
||||
./a-
|
||||
./ab-
|
||||
`, expectDots, console)
|
||||
requireLsOut(t, []string{
|
||||
"./-",
|
||||
"./a-",
|
||||
"./ab-",
|
||||
}, expectDots, console)
|
||||
})
|
||||
|
||||
t.Run("directory with entries - read twice", func(t *testing.T) {
|
||||
console := compileAndRun(t, testCtx, moduleConfig.WithArgs("wasi", "ls", ".", "repeat"), bin)
|
||||
if expectDots {
|
||||
require.Equal(t, `
|
||||
./.
|
||||
./..
|
||||
./-
|
||||
./a-
|
||||
./ab-
|
||||
./.
|
||||
./..
|
||||
./-
|
||||
./a-
|
||||
./ab-
|
||||
`, "\n"+console)
|
||||
} else {
|
||||
require.Equal(t, `
|
||||
./-
|
||||
./a-
|
||||
./ab-
|
||||
./-
|
||||
./a-
|
||||
./ab-
|
||||
`, "\n"+console)
|
||||
if toolchain == "tinygo" {
|
||||
t.Skip("https://github.com/tinygo-org/tinygo/issues/3823")
|
||||
}
|
||||
|
||||
console := compileAndRun(t, testCtx, moduleConfig.WithArgs("wasi", "ls", ".", "repeat"), bin)
|
||||
requireLsOut(t, []string{
|
||||
"./-",
|
||||
"./a-",
|
||||
"./ab-",
|
||||
"./-",
|
||||
"./a-",
|
||||
"./ab-",
|
||||
}, expectDots*2, console)
|
||||
})
|
||||
|
||||
t.Run("directory with tons of entries", func(t *testing.T) {
|
||||
testFS := fstest.MapFS{}
|
||||
count := 8096
|
||||
for i := 0; i < count; i++ {
|
||||
testFS[strconv.Itoa(i)] = &fstest.MapFile{}
|
||||
}
|
||||
config := wazero.NewModuleConfig().WithFS(testFS).WithArgs("wasi", "ls", ".")
|
||||
console := compileAndRun(t, testCtx, config, bin)
|
||||
moduleConfig = wazero.NewModuleConfig().
|
||||
WithFSConfig(wazero.NewFSConfig().
|
||||
WithReadOnlyDirMount(path.Join(rootDir, "tons"), "/")).
|
||||
WithArgs("wasi", "ls", ".")
|
||||
|
||||
console := compileAndRun(t, testCtx, moduleConfig, bin)
|
||||
|
||||
lines := strings.Split(console, "\n")
|
||||
expected := count + 1 /* trailing newline */
|
||||
if expectDots {
|
||||
expected += 2
|
||||
}
|
||||
expected := direntCountTons + 1 /* trailing newline */
|
||||
expected += expectDots * 2
|
||||
require.Equal(t, expected, len(lines))
|
||||
})
|
||||
}
|
||||
|
||||
func requireLsOut(t *testing.T, expected string, expectDots bool, console string) {
|
||||
dots := `
|
||||
./.
|
||||
./..
|
||||
`
|
||||
if expectDots {
|
||||
expected = dots + expected[1:]
|
||||
func requireLsOut(t *testing.T, expected []string, expectDots int, console string) {
|
||||
for i := 0; i < expectDots; i++ {
|
||||
expected = append(expected, "./.", "./..")
|
||||
}
|
||||
|
||||
actual := strings.Split(console, "\n")
|
||||
sort.Strings(actual) // os directories are not lexicographic order
|
||||
actual = actual[1:] // trailing newline
|
||||
|
||||
sort.Strings(expected)
|
||||
if len(actual) == 0 {
|
||||
require.Nil(t, expected)
|
||||
} else {
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
require.Equal(t, expected, "\n"+console)
|
||||
}
|
||||
|
||||
func Test_fdReaddir_stat(t *testing.T) {
|
||||
for toolchain, bin := range map[string][]byte{
|
||||
toolchains := map[string][]byte{
|
||||
"cargo-wasi": wasmCargoWasi,
|
||||
"tinygo": wasmTinyGo,
|
||||
"zig-cc": wasmZigCc,
|
||||
"zig": wasmZig,
|
||||
} {
|
||||
}
|
||||
if wasmGotip != nil {
|
||||
toolchains["gotip"] = wasmGotip
|
||||
}
|
||||
|
||||
for toolchain, bin := range toolchains {
|
||||
toolchain := toolchain
|
||||
bin := bin
|
||||
t.Run(toolchain, func(t *testing.T) {
|
||||
@@ -171,7 +194,7 @@ func Test_fdReaddir_stat(t *testing.T) {
|
||||
func testFdReaddirStat(t *testing.T, bin []byte) {
|
||||
moduleConfig := wazero.NewModuleConfig().WithArgs("wasi", "stat")
|
||||
|
||||
console := compileAndRun(t, testCtx, moduleConfig.WithFS(fstest.MapFS{}), bin)
|
||||
console := compileAndRun(t, testCtx, moduleConfig.WithFS(gofstest.MapFS{}), bin)
|
||||
|
||||
// TODO: switch this to a real stat test
|
||||
require.Equal(t, `
|
||||
@@ -200,7 +223,7 @@ func testPreopen(t *testing.T, bin []byte) {
|
||||
console := compileAndRun(t, testCtx, moduleConfig.
|
||||
WithFSConfig(wazero.NewFSConfig().
|
||||
WithDirMount(".", "/").
|
||||
WithFSMount(fstest.MapFS{}, "/tmp")), bin)
|
||||
WithFSMount(gofstest.MapFS{}, "/tmp")), bin)
|
||||
|
||||
require.Equal(t, `
|
||||
0: stdin
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"github.com/tetratelabs/wazero/internal/fsapi"
|
||||
)
|
||||
|
||||
// compile-time check to ensure lazyDir implements internalapi.File.
|
||||
// compile-time check to ensure lazyDir implements fsapi.File.
|
||||
var _ fsapi.File = (*lazyDir)(nil)
|
||||
|
||||
type lazyDir struct {
|
||||
@@ -18,7 +18,7 @@ type lazyDir struct {
|
||||
f fsapi.File
|
||||
}
|
||||
|
||||
// Ino implements the same method as documented on internalapi.File
|
||||
// Ino implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Ino() (uint64, syscall.Errno) {
|
||||
if f, ok := r.file(); !ok {
|
||||
return 0, syscall.EBADF
|
||||
@@ -27,17 +27,17 @@ func (r *lazyDir) Ino() (uint64, syscall.Errno) {
|
||||
}
|
||||
}
|
||||
|
||||
// IsAppend implements the same method as documented on internalapi.File
|
||||
// IsAppend implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) IsAppend() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// SetAppend implements the same method as documented on internalapi.File
|
||||
// SetAppend implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) SetAppend(bool) syscall.Errno {
|
||||
return syscall.EISDIR
|
||||
}
|
||||
|
||||
// Seek implements the same method as documented on internalapi.File
|
||||
// Seek implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Seek(offset int64, whence int) (newOffset int64, errno syscall.Errno) {
|
||||
if f, ok := r.file(); !ok {
|
||||
return 0, syscall.EBADF
|
||||
@@ -46,7 +46,7 @@ func (r *lazyDir) Seek(offset int64, whence int) (newOffset int64, errno syscall
|
||||
}
|
||||
}
|
||||
|
||||
// Stat implements the same method as documented on internalapi.File
|
||||
// Stat implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Stat() (fsapi.Stat_t, syscall.Errno) {
|
||||
if f, ok := r.file(); !ok {
|
||||
return fsapi.Stat_t{}, syscall.EBADF
|
||||
@@ -55,7 +55,7 @@ func (r *lazyDir) Stat() (fsapi.Stat_t, syscall.Errno) {
|
||||
}
|
||||
}
|
||||
|
||||
// Readdir implements the same method as documented on internalapi.File
|
||||
// Readdir implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Readdir(n int) (dirents []fsapi.Dirent, errno syscall.Errno) {
|
||||
if f, ok := r.file(); !ok {
|
||||
return nil, syscall.EBADF
|
||||
@@ -64,7 +64,7 @@ func (r *lazyDir) Readdir(n int) (dirents []fsapi.Dirent, errno syscall.Errno) {
|
||||
}
|
||||
}
|
||||
|
||||
// Sync implements the same method as documented on internalapi.File
|
||||
// Sync implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Sync() syscall.Errno {
|
||||
if f, ok := r.file(); !ok {
|
||||
return syscall.EBADF
|
||||
@@ -73,7 +73,7 @@ func (r *lazyDir) Sync() syscall.Errno {
|
||||
}
|
||||
}
|
||||
|
||||
// Datasync implements the same method as documented on internalapi.File
|
||||
// Datasync implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Datasync() syscall.Errno {
|
||||
if f, ok := r.file(); !ok {
|
||||
return syscall.EBADF
|
||||
@@ -82,7 +82,7 @@ func (r *lazyDir) Datasync() syscall.Errno {
|
||||
}
|
||||
}
|
||||
|
||||
// Chmod implements the same method as documented on internalapi.File
|
||||
// Chmod implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Chmod(mode fs.FileMode) syscall.Errno {
|
||||
if f, ok := r.file(); !ok {
|
||||
return syscall.EBADF
|
||||
@@ -91,7 +91,7 @@ func (r *lazyDir) Chmod(mode fs.FileMode) syscall.Errno {
|
||||
}
|
||||
}
|
||||
|
||||
// Chown implements the same method as documented on internalapi.File
|
||||
// Chown implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Chown(uid, gid int) syscall.Errno {
|
||||
if f, ok := r.file(); !ok {
|
||||
return syscall.EBADF
|
||||
@@ -100,7 +100,7 @@ func (r *lazyDir) Chown(uid, gid int) syscall.Errno {
|
||||
}
|
||||
}
|
||||
|
||||
// Utimens implements the same method as documented on internalapi.File
|
||||
// Utimens implements the same method as documented on fsapi.File
|
||||
func (r *lazyDir) Utimens(times *[2]syscall.Timespec) syscall.Errno {
|
||||
if f, ok := r.file(); !ok {
|
||||
return syscall.EBADF
|
||||
|
||||
@@ -19,7 +19,7 @@ type StdinFile struct {
|
||||
io.Reader
|
||||
}
|
||||
|
||||
// Read implements the same method as documented on internalapi.File
|
||||
// Read implements the same method as documented on fsapi.File
|
||||
func (f *StdinFile) Read(buf []byte) (int, syscall.Errno) {
|
||||
n, err := f.Reader.Read(buf)
|
||||
return n, platform.UnwrapOSError(err)
|
||||
@@ -31,7 +31,7 @@ type writerFile struct {
|
||||
w io.Writer
|
||||
}
|
||||
|
||||
// Write implements the same method as documented on internalapi.File
|
||||
// Write implements the same method as documented on fsapi.File
|
||||
func (f *writerFile) Write(buf []byte) (int, syscall.Errno) {
|
||||
n, err := f.w.Write(buf)
|
||||
return n, platform.UnwrapOSError(err)
|
||||
@@ -44,17 +44,17 @@ type noopStdinFile struct {
|
||||
noopStdioFile
|
||||
}
|
||||
|
||||
// AccessMode implements the same method as documented on internalapi.File
|
||||
// AccessMode implements the same method as documented on fsapi.File
|
||||
func (noopStdinFile) AccessMode() int {
|
||||
return syscall.O_RDONLY
|
||||
}
|
||||
|
||||
// Read implements the same method as documented on internalapi.File
|
||||
// Read implements the same method as documented on fsapi.File
|
||||
func (noopStdinFile) Read([]byte) (int, syscall.Errno) {
|
||||
return 0, 0 // Always EOF
|
||||
}
|
||||
|
||||
// PollRead implements the same method as documented on internalapi.File
|
||||
// PollRead implements the same method as documented on fsapi.File
|
||||
func (noopStdinFile) PollRead(*time.Duration) (ready bool, errno syscall.Errno) {
|
||||
return true, 0 // always ready to read nothing
|
||||
}
|
||||
@@ -65,12 +65,12 @@ type noopStdoutFile struct {
|
||||
noopStdioFile
|
||||
}
|
||||
|
||||
// AccessMode implements the same method as documented on internalapi.File
|
||||
// AccessMode implements the same method as documented on fsapi.File
|
||||
func (noopStdoutFile) AccessMode() int {
|
||||
return syscall.O_WRONLY
|
||||
}
|
||||
|
||||
// Write implements the same method as documented on internalapi.File
|
||||
// Write implements the same method as documented on fsapi.File
|
||||
func (noopStdoutFile) Write(buf []byte) (int, syscall.Errno) {
|
||||
return len(buf), 0 // same as io.Discard
|
||||
}
|
||||
@@ -79,17 +79,17 @@ type noopStdioFile struct {
|
||||
fsapi.UnimplementedFile
|
||||
}
|
||||
|
||||
// Stat implements the same method as documented on internalapi.File
|
||||
// Stat implements the same method as documented on fsapi.File
|
||||
func (noopStdioFile) Stat() (fsapi.Stat_t, syscall.Errno) {
|
||||
return fsapi.Stat_t{Mode: modeDevice, Nlink: 1}, 0
|
||||
}
|
||||
|
||||
// IsDir implements the same method as documented on internalapi.File
|
||||
// IsDir implements the same method as documented on fsapi.File
|
||||
func (noopStdioFile) IsDir() (bool, syscall.Errno) {
|
||||
return false, 0
|
||||
}
|
||||
|
||||
// Close implements the same method as documented on internalapi.File
|
||||
// Close implements the same method as documented on fsapi.File
|
||||
func (noopStdioFile) Close() (errno syscall.Errno) { return }
|
||||
|
||||
func stdinFileEntry(r io.Reader) (*FileEntry, error) {
|
||||
|
||||
@@ -155,7 +155,7 @@ func TestModuleInstance_Close(t *testing.T) {
|
||||
m, err := s.Instantiate(testCtx, &Module{}, t.Name(), sysCtx, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// In internalapi.FS, non syscall errors map to syscall.EIO.
|
||||
// In fsapi.FS, non syscall errors map to syscall.EIO.
|
||||
require.EqualErrno(t, syscall.EIO, m.Close(testCtx))
|
||||
|
||||
// Verify our intended side-effect
|
||||
@@ -254,7 +254,7 @@ func TestModuleInstance_CallDynamic(t *testing.T) {
|
||||
m, err := s.Instantiate(testCtx, &Module{}, t.Name(), sysCtx, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// In internalapi.FS, non syscall errors map to syscall.EIO.
|
||||
// In fsapi.FS, non syscall errors map to syscall.EIO.
|
||||
require.EqualErrno(t, syscall.EIO, m.Close(testCtx))
|
||||
|
||||
// Verify our intended side-effect
|
||||
|
||||
Reference in New Issue
Block a user