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:
Crypt Keeper
2023-07-06 16:19:24 +08:00
committed by GitHub
parent d7193952e1
commit 2c21f3aa8f
11 changed files with 336 additions and 153 deletions

View File

@@ -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 \

View File

@@ -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.

View File

@@ -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,

View File

@@ -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
}

View File

@@ -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() {

View 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
}

Binary file not shown.

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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