Files
wazero/wasi/wasi_test.go
Teppei Fukuda 7794530d01 Allow passing fs.FS when calling functions (#571)
Fixes #563 

Signed-off-by: knqyf263 <knqyf263@gmail.com>
Co-authored-by: Adrian Cole <adrian@tetrate.io>
2022-05-20 10:51:17 +09:00

2153 lines
70 KiB
Go

package wasi
import (
"bytes"
"context"
_ "embed"
"errors"
"fmt"
"io"
"io/fs"
"math"
"math/rand"
"os"
"path"
"testing"
"testing/fstest"
"testing/iotest"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/experimental"
fs2 "github.com/tetratelabs/wazero/internal/fs"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/sys"
)
// compile-time check to ensure fakeSys implements experimental.Sys.
var _ experimental.Sys = fakeSys{}
type fakeSys struct{}
const (
epochNanos = uint64(1640995200000000000) // midnight UTC 2022-01-01
seed = int64(42) // fixed seed value
)
func (d fakeSys) TimeNowUnixNano() uint64 {
return epochNanos
}
// testCtx ensures fakeSys is used for WASI functions.
var testCtx = context.WithValue(context.Background(), experimental.SysKey{}, fakeSys{})
func TestSnapshotPreview1_ArgsGet(t *testing.T) {
sysCtx, err := newSysContext([]string{"a", "bc"}, nil, nil)
require.NoError(t, err)
argv := uint32(7) // arbitrary offset
argvBuf := uint32(1) // arbitrary offset
expectedMemory := []byte{
'?', // argvBuf is after this
'a', 0, 'b', 'c', 0, // null terminated "a", "bc"
'?', // argv is after this
1, 0, 0, 0, // little endian-encoded offset of "a"
3, 0, 0, 0, // little endian-encoded offset of "bc"
'?', // stopped after encoding
}
a, mod, fn := instantiateModule(testCtx, t, functionArgsGet, importArgsGet, sysCtx)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.ArgsGet", func(t *testing.T) {
maskMemory(t, testCtx, mod, len(expectedMemory))
// Invoke ArgsGet directly and check the memory side effects.
errno := a.ArgsGet(testCtx, mod, argv, argvBuf)
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
t.Run(functionArgsGet, func(t *testing.T) {
maskMemory(t, testCtx, mod, len(expectedMemory))
results, err := fn.Call(testCtx, uint64(argv), uint64(argvBuf))
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
}
func TestSnapshotPreview1_ArgsGet_Errors(t *testing.T) {
sysCtx, err := newSysContext([]string{"a", "bc"}, nil, nil)
require.NoError(t, err)
a, mod, _ := instantiateModule(testCtx, t, functionArgsGet, importArgsGet, sysCtx)
defer mod.Close(testCtx)
memorySize := mod.Memory().Size(testCtx)
validAddress := uint32(0) // arbitrary valid address as arguments to args_get. We chose 0 here.
tests := []struct {
name string
argv uint32
argvBuf uint32
}{
{
name: "out-of-memory argv",
argv: memorySize,
argvBuf: validAddress,
},
{
name: "out-of-memory argvBuf",
argv: validAddress,
argvBuf: memorySize,
},
{
name: "argv exceeds the maximum valid address by 1",
// 4*argCount is the size of the result of the pointers to args, 4 is the size of uint32
argv: memorySize - 4*2 + 1,
argvBuf: validAddress,
},
{
name: "argvBuf exceeds the maximum valid address by 1",
argv: validAddress,
// "a", "bc" size = size of "a0bc0" = 5
argvBuf: memorySize - 5 + 1,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
errno := a.ArgsGet(testCtx, mod, tc.argv, tc.argvBuf)
require.NoError(t, err)
require.Equal(t, ErrnoFault, errno, ErrnoName(errno))
})
}
}
func TestSnapshotPreview1_ArgsSizesGet(t *testing.T) {
sysCtx, err := newSysContext([]string{"a", "bc"}, nil, nil)
require.NoError(t, err)
resultArgc := uint32(1) // arbitrary offset
resultArgvBufSize := uint32(6) // arbitrary offset
expectedMemory := []byte{
'?', // resultArgc is after this
0x2, 0x0, 0x0, 0x0, // little endian-encoded arg count
'?', // resultArgvBufSize is after this
0x5, 0x0, 0x0, 0x0, // little endian-encoded size of null terminated strings
'?', // stopped after encoding
}
a, mod, fn := instantiateModule(testCtx, t, functionArgsSizesGet, importArgsSizesGet, sysCtx)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.ArgsSizesGet", func(t *testing.T) {
maskMemory(t, testCtx, mod, len(expectedMemory))
// Invoke ArgsSizesGet directly and check the memory side effects.
errno := a.ArgsSizesGet(testCtx, mod, resultArgc, resultArgvBufSize)
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
t.Run(functionArgsSizesGet, func(t *testing.T) {
maskMemory(t, testCtx, mod, len(expectedMemory))
results, err := fn.Call(testCtx, uint64(resultArgc), uint64(resultArgvBufSize))
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
}
func TestSnapshotPreview1_ArgsSizesGet_Errors(t *testing.T) {
sysCtx, err := newSysContext([]string{"a", "bc"}, nil, nil)
require.NoError(t, err)
a, mod, _ := instantiateModule(testCtx, t, functionArgsSizesGet, importArgsSizesGet, sysCtx)
defer mod.Close(testCtx)
memorySize := mod.Memory().Size(testCtx)
validAddress := uint32(0) // arbitrary valid address as arguments to args_sizes_get. We chose 0 here.
tests := []struct {
name string
argc uint32
argvBufSize uint32
}{
{
name: "out-of-memory argc",
argc: memorySize,
argvBufSize: validAddress,
},
{
name: "out-of-memory argvBufSize",
argc: validAddress,
argvBufSize: memorySize,
},
{
name: "argc exceeds the maximum valid address by 1",
argc: memorySize - 4 + 1, // 4 is the size of uint32, the type of the count of args
argvBufSize: validAddress,
},
{
name: "argvBufSize exceeds the maximum valid size by 1",
argc: validAddress,
argvBufSize: memorySize - 4 + 1, // 4 is count of bytes to encode uint32le
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
errno := a.ArgsSizesGet(testCtx, mod, tc.argc, tc.argvBufSize)
require.Equal(t, ErrnoFault, errno, ErrnoName(errno))
})
}
}
func TestSnapshotPreview1_EnvironGet(t *testing.T) {
sysCtx, err := newSysContext(nil, []string{"a=b", "b=cd"}, nil)
require.NoError(t, err)
resultEnviron := uint32(11) // arbitrary offset
resultEnvironBuf := uint32(1) // arbitrary offset
expectedMemory := []byte{
'?', // environBuf is after this
'a', '=', 'b', 0, // null terminated "a=b",
'b', '=', 'c', 'd', 0, // null terminated "b=cd"
'?', // environ is after this
1, 0, 0, 0, // little endian-encoded offset of "a=b"
5, 0, 0, 0, // little endian-encoded offset of "b=cd"
'?', // stopped after encoding
}
a, mod, fn := instantiateModule(testCtx, t, functionEnvironGet, importEnvironGet, sysCtx)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.EnvironGet", func(t *testing.T) {
maskMemory(t, testCtx, mod, len(expectedMemory))
// Invoke EnvironGet directly and check the memory side effects.
errno := a.EnvironGet(testCtx, mod, resultEnviron, resultEnvironBuf)
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
t.Run(functionEnvironGet, func(t *testing.T) {
maskMemory(t, testCtx, mod, len(expectedMemory))
results, err := fn.Call(testCtx, uint64(resultEnviron), uint64(resultEnvironBuf))
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
}
func TestSnapshotPreview1_EnvironGet_Errors(t *testing.T) {
sysCtx, err := newSysContext(nil, []string{"a=bc", "b=cd"}, nil)
require.NoError(t, err)
a, mod, _ := instantiateModule(testCtx, t, functionEnvironGet, importEnvironGet, sysCtx)
defer mod.Close(testCtx)
memorySize := mod.Memory().Size(testCtx)
validAddress := uint32(0) // arbitrary valid address as arguments to environ_get. We chose 0 here.
tests := []struct {
name string
environ uint32
environBuf uint32
}{
{
name: "out-of-memory environPtr",
environ: memorySize,
environBuf: validAddress,
},
{
name: "out-of-memory environBufPtr",
environ: validAddress,
environBuf: memorySize,
},
{
name: "environPtr exceeds the maximum valid address by 1",
// 4*envCount is the expected length for environPtr, 4 is the size of uint32
environ: memorySize - 4*2 + 1,
environBuf: validAddress,
},
{
name: "environBufPtr exceeds the maximum valid address by 1",
environ: validAddress,
// "a=bc", "b=cd" size = size of "a=bc0b=cd0" = 10
environBuf: memorySize - 10 + 1,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
errno := a.EnvironGet(testCtx, mod, tc.environ, tc.environBuf)
require.Equal(t, ErrnoFault, errno, ErrnoName(errno))
})
}
}
func TestSnapshotPreview1_EnvironSizesGet(t *testing.T) {
sysCtx, err := newSysContext(nil, []string{"a=b", "b=cd"}, nil)
require.NoError(t, err)
resultEnvironc := uint32(1) // arbitrary offset
resultEnvironBufSize := uint32(6) // arbitrary offset
expectedMemory := []byte{
'?', // resultEnvironc is after this
0x2, 0x0, 0x0, 0x0, // little endian-encoded environment variable count
'?', // resultEnvironBufSize is after this
0x9, 0x0, 0x0, 0x0, // little endian-encoded size of null terminated strings
'?', // stopped after encoding
}
a, mod, fn := instantiateModule(testCtx, t, functionEnvironSizesGet, importEnvironSizesGet, sysCtx)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.EnvironSizesGet", func(t *testing.T) {
maskMemory(t, testCtx, mod, len(expectedMemory))
// Invoke EnvironSizesGet directly and check the memory side effects.
errno := a.EnvironSizesGet(testCtx, mod, resultEnvironc, resultEnvironBufSize)
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
t.Run(functionEnvironSizesGet, func(t *testing.T) {
maskMemory(t, testCtx, mod, len(expectedMemory))
results, err := fn.Call(testCtx, uint64(resultEnvironc), uint64(resultEnvironBufSize))
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
}
func TestSnapshotPreview1_EnvironSizesGet_Errors(t *testing.T) {
sysCtx, err := newSysContext(nil, []string{"a=b", "b=cd"}, nil)
require.NoError(t, err)
a, mod, _ := instantiateModule(testCtx, t, functionEnvironSizesGet, importEnvironSizesGet, sysCtx)
defer mod.Close(testCtx)
memorySize := mod.Memory().Size(testCtx)
validAddress := uint32(0) // arbitrary valid address as arguments to environ_sizes_get. We chose 0 here.
tests := []struct {
name string
environc uint32
environBufSize uint32
}{
{
name: "out-of-memory environCountPtr",
environc: memorySize,
environBufSize: validAddress,
},
{
name: "out-of-memory environBufSizePtr",
environc: validAddress,
environBufSize: memorySize,
},
{
name: "environCountPtr exceeds the maximum valid address by 1",
environc: memorySize - 4 + 1, // 4 is the size of uint32, the type of the count of environ
environBufSize: validAddress,
},
{
name: "environBufSizePtr exceeds the maximum valid size by 1",
environc: validAddress,
environBufSize: memorySize - 4 + 1, // 4 is count of bytes to encode uint32le
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
errno := a.EnvironSizesGet(testCtx, mod, tc.environc, tc.environBufSize)
require.Equal(t, ErrnoFault, errno, ErrnoName(errno))
})
}
}
// TestSnapshotPreview1_ClockResGet only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_ClockResGet(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionClockResGet, importClockResGet, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.ClockResGet", func(t *testing.T) {
require.Equal(t, ErrnoNosys, a.ClockResGet(testCtx, mod, 0, 0))
})
t.Run(functionClockResGet, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
func TestSnapshotPreview1_ClockTimeGet(t *testing.T) {
resultTimestamp := uint32(1) // arbitrary offset
expectedMemory := []byte{
'?', // resultTimestamp is after this
0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, // little endian-encoded epochNanos
'?', // stopped after encoding
}
a, mod, fn := instantiateModule(testCtx, t, functionClockTimeGet, importClockTimeGet, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.ClockTimeGet", func(t *testing.T) {
maskMemory(t, testCtx, mod, len(expectedMemory))
// invoke ClockTimeGet directly and check the memory side effects!
errno := a.ClockTimeGet(testCtx, mod, 0 /* TODO: id */, 0 /* TODO: precision */, resultTimestamp)
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
t.Run(functionClockTimeGet, func(t *testing.T) {
maskMemory(t, testCtx, mod, len(expectedMemory))
results, err := fn.Call(testCtx, 0 /* TODO: id */, 0 /* TODO: precision */, uint64(resultTimestamp))
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
}
func TestSnapshotPreview1_ClockTimeGet_Errors(t *testing.T) {
_, mod, fn := instantiateModule(testCtx, t, functionClockTimeGet, importClockTimeGet, nil)
defer mod.Close(testCtx)
memorySize := mod.Memory().Size(testCtx)
tests := []struct {
name string
resultTimestamp uint32
argvBufSize uint32
}{
{
name: "resultTimestamp out-of-memory",
resultTimestamp: memorySize,
},
{
name: "resultTimestamp exceeds the maximum valid address by 1",
resultTimestamp: memorySize - 4 + 1, // 4 is the size of uint32, the type of the count of args
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
results, err := fn.Call(testCtx, 0 /* TODO: id */, 0 /* TODO: precision */, uint64(tc.resultTimestamp))
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoFault, errno, ErrnoName(errno))
})
}
}
// TestSnapshotPreview1_FdAdvise only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_FdAdvise(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionFdAdvise, importFdAdvise, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.FdAdvise", func(t *testing.T) {
errno := a.FdAdvise(testCtx, mod, 0, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionFdAdvise, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_FdAllocate only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_FdAllocate(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionFdAllocate, importFdAllocate, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.FdAllocate", func(t *testing.T) {
errno := a.FdAllocate(testCtx, mod, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionFdAllocate, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
func TestSnapshotPreview1_FdClose(t *testing.T) {
fdToClose := uint32(3) // arbitrary fd
fdToKeep := uint32(4) // another arbitrary fd
setupFD := func() (api.Module, api.Function, *snapshotPreview1) {
// fd_close needs to close an open file descriptor. Open two files so that we can tell which is closed.
path1, path2 := "a", "b"
testFs := fstest.MapFS{path1: {Data: make([]byte, 0)}, path2: {Data: make([]byte, 0)}}
entry1, errno := openFileEntry(testFs, path1)
require.Zero(t, errno, ErrnoName(errno))
entry2, errno := openFileEntry(testFs, path2)
require.Zero(t, errno, ErrnoName(errno))
sysCtx, err := newSysContext(nil, nil, map[uint32]*fs2.FileEntry{
fdToClose: entry1,
fdToKeep: entry2,
})
require.NoError(t, err)
a, mod, fn := instantiateModule(testCtx, t, functionFdClose, importFdClose, sysCtx)
return mod, fn, a
}
verify := func(mod api.Module) {
// Verify fdToClose is closed and removed from the opened FDs.
_, fsc := sysFSCtx(testCtx, mod)
_, ok := fsc.OpenedFile(fdToClose)
require.False(t, ok)
// Verify fdToKeep is not closed
_, ok = fsc.OpenedFile(fdToKeep)
require.True(t, ok)
}
t.Run("snapshotPreview1.FdClose", func(t *testing.T) {
mod, _, api := setupFD()
defer mod.Close(testCtx)
errno := api.FdClose(testCtx, mod, fdToClose)
require.Zero(t, errno, ErrnoName(errno))
verify(mod)
})
t.Run(functionFdClose, func(t *testing.T) {
mod, fn, _ := setupFD()
defer mod.Close(testCtx)
results, err := fn.Call(testCtx, uint64(fdToClose))
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Zero(t, errno, ErrnoName(errno))
verify(mod)
})
t.Run("ErrnoBadF for an invalid FD", func(t *testing.T) {
mod, _, api := setupFD()
defer mod.Close(testCtx)
errno := api.FdClose(testCtx, mod, 42) // 42 is an arbitrary invalid FD
require.Equal(t, ErrnoBadf, errno)
})
}
// TestSnapshotPreview1_FdDatasync only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_FdDatasync(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionFdDatasync, importFdDatasync, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.FdDatasync", func(t *testing.T) {
errno := a.FdDatasync(testCtx, mod, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionFdDatasync, func(t *testing.T) {
results, err := fn.Call(testCtx, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TODO: TestSnapshotPreview1_FdFdstatGet TestSnapshotPreview1_FdFdstatGet_Errors
func TestSnapshotPreview1_FdFdstatGet(t *testing.T) {
t.Skip("TODO")
_ = importFdFdstatGet // stop linter complaint until we implement this
}
// TestSnapshotPreview1_FdFdstatSetFlags only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_FdFdstatSetFlags(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionFdFdstatSetFlags, importFdFdstatSetFlags, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.FdFdstatSetFlags", func(t *testing.T) {
errno := a.FdFdstatSetFlags(testCtx, mod, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionFdFdstatSetFlags, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_FdFdstatSetRights only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_FdFdstatSetRights(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionFdFdstatSetRights, importFdFdstatSetRights, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.FdFdstatSetRights", func(t *testing.T) {
errno := a.FdFdstatSetRights(testCtx, mod, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionFdFdstatSetRights, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_FdFilestatGet only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_FdFilestatGet(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionFdFilestatGet, importFdFilestatGet, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.FdFilestatGet", func(t *testing.T) {
errno := a.FdFilestatGet(testCtx, mod, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionFdFilestatGet, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_FdFilestatSetSize only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_FdFilestatSetSize(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionFdFilestatSetSize, importFdFilestatSetSize, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.FdFilestatSetSize", func(t *testing.T) {
errno := a.FdFilestatSetSize(testCtx, mod, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionFdFilestatSetSize, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_FdFilestatSetTimes only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_FdFilestatSetTimes(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionFdFilestatSetTimes, importFdFilestatSetTimes, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.FdFilestatSetTimes", func(t *testing.T) {
errno := a.FdFilestatSetTimes(testCtx, mod, 0, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionFdFilestatSetTimes, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_FdPread only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_FdPread(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionFdPread, importFdPread, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.FdPread", func(t *testing.T) {
errno := a.FdPread(testCtx, mod, 0, 0, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionFdPread, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
func TestSnapshotPreview1_FdPrestatGet(t *testing.T) {
fd := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err
pathName := "/tmp"
sysCtx, err := newSysContext(nil, nil, map[uint32]*fs2.FileEntry{fd: {Path: pathName}})
require.NoError(t, err)
a, mod, fn := instantiateModule(testCtx, t, functionFdPrestatGet, importFdPrestatGet, sysCtx)
defer mod.Close(testCtx)
resultPrestat := uint32(1) // arbitrary offset
expectedMemory := []byte{
'?', // resultPrestat after this
0, // 8-bit tag indicating `prestat_dir`, the only available tag
0, 0, 0, // 3-byte padding
// the result path length field after this
byte(len(pathName)), 0, 0, 0, // = in little endian encoding
'?',
}
t.Run("snapshotPreview1.FdPrestatGet", func(t *testing.T) {
maskMemory(t, testCtx, mod, len(expectedMemory))
errno := a.FdPrestatGet(testCtx, mod, fd, resultPrestat)
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
t.Run(functionFdPrestatDirName, func(t *testing.T) {
maskMemory(t, testCtx, mod, len(expectedMemory))
results, err := fn.Call(testCtx, uint64(fd), uint64(resultPrestat))
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
}
func TestSnapshotPreview1_FdPrestatGet_Errors(t *testing.T) {
fd := uint32(3) // fd 3 will be opened for the "/tmp" directory after 0, 1, and 2, that are stdin/out/err
validAddress := uint32(0) // Arbitrary valid address as arguments to fd_prestat_get. We chose 0 here.
sysCtx, err := newSysContext(nil, nil, map[uint32]*fs2.FileEntry{fd: {Path: "/tmp"}})
require.NoError(t, err)
a, mod, _ := instantiateModule(testCtx, t, functionFdPrestatGet, importFdPrestatGet, sysCtx)
defer mod.Close(testCtx)
memorySize := mod.Memory().Size(testCtx)
tests := []struct {
name string
fd uint32
resultPrestat uint32
expectedErrno Errno
}{
{
name: "invalid FD",
fd: 42, // arbitrary invalid FD
resultPrestat: validAddress,
expectedErrno: ErrnoBadf,
},
{
name: "out-of-memory resultPrestat",
fd: fd,
resultPrestat: memorySize,
expectedErrno: ErrnoFault,
},
// TODO: non pre-opened file == api.ErrnoBadf
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
errno := a.FdPrestatGet(testCtx, mod, tc.fd, tc.resultPrestat)
require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno))
})
}
}
func TestSnapshotPreview1_FdPrestatDirName(t *testing.T) {
fd := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err
sysCtx, err := newSysContext(nil, nil, map[uint32]*fs2.FileEntry{fd: {Path: "/tmp"}})
require.NoError(t, err)
a, mod, fn := instantiateModule(testCtx, t, functionFdPrestatDirName, importFdPrestatDirName, sysCtx)
defer mod.Close(testCtx)
path := uint32(1) // arbitrary offset
pathLen := uint32(3) // shorter than len("/tmp") to test the path is written for the length of pathLen
expectedMemory := []byte{
'?',
'/', 't', 'm',
'?', '?', '?',
}
t.Run("snapshotPreview1.FdPrestatDirName", func(t *testing.T) {
maskMemory(t, testCtx, mod, len(expectedMemory))
errno := a.FdPrestatDirName(testCtx, mod, fd, path, pathLen)
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
t.Run(functionFdPrestatDirName, func(t *testing.T) {
maskMemory(t, testCtx, mod, len(expectedMemory))
results, err := fn.Call(testCtx, uint64(fd), uint64(path), uint64(pathLen))
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
}
func TestSnapshotPreview1_FdPrestatDirName_Errors(t *testing.T) {
fd := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err
sysCtx, err := newSysContext(nil, nil, map[uint32]*fs2.FileEntry{fd: {Path: "/tmp"}})
require.NoError(t, err)
a, mod, _ := instantiateModule(testCtx, t, functionFdPrestatDirName, importFdPrestatDirName, sysCtx)
defer mod.Close(testCtx)
memorySize := mod.Memory().Size(testCtx)
validAddress := uint32(0) // Arbitrary valid address as arguments to fd_prestat_dir_name. We chose 0 here.
pathLen := uint32(len("/tmp"))
tests := []struct {
name string
fd uint32
path uint32
pathLen uint32
expectedErrno Errno
}{
{
name: "out-of-memory path",
fd: fd,
path: memorySize,
pathLen: pathLen,
expectedErrno: ErrnoFault,
},
{
name: "path exceeds the maximum valid address by 1",
fd: fd,
path: memorySize - pathLen + 1,
pathLen: pathLen,
expectedErrno: ErrnoFault,
},
{
name: "pathLen exceeds the length of the dir name",
fd: fd,
path: validAddress,
pathLen: pathLen + 1,
expectedErrno: ErrnoNametoolong,
},
{
name: "invalid fd",
fd: 42, // arbitrary invalid fd
path: validAddress,
pathLen: pathLen,
expectedErrno: ErrnoBadf,
},
// TODO: non pre-opened file == wasi.ErrnoBadf
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
errno := a.FdPrestatDirName(testCtx, mod, tc.fd, tc.path, tc.pathLen)
require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno))
})
}
}
// TestSnapshotPreview1_FdPwrite only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_FdPwrite(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionFdPwrite, importFdPwrite, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.FdPwrite", func(t *testing.T) {
errno := a.FdPwrite(testCtx, mod, 0, 0, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionFdPwrite, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
func TestSnapshotPreview1_FdRead(t *testing.T) {
fd := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err
iovs := uint32(1) // arbitrary offset
initialMemory := []byte{
'?', // `iovs` is after this
18, 0, 0, 0, // = iovs[0].offset
4, 0, 0, 0, // = iovs[0].length
23, 0, 0, 0, // = iovs[1].offset
2, 0, 0, 0, // = iovs[1].length
'?',
}
iovsCount := uint32(2) // The count of iovs
resultSize := uint32(26) // arbitrary offset
expectedMemory := append(
initialMemory,
'w', 'a', 'z', 'e', // iovs[0].length bytes
'?', // iovs[1].offset is after this
'r', 'o', // iovs[1].length bytes
'?', // resultSize is after this
6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero"
'?',
)
// TestSnapshotPreview1_FdRead uses a matrix because setting up test files is complicated and has to be clean each time.
type fdReadFn func(ctx context.Context, m api.Module, fd, iovs, iovsCount, resultSize uint32) Errno
tests := []struct {
name string
fdRead func(*snapshotPreview1, api.Module, api.Function) fdReadFn
}{
{"snapshotPreview1.FdRead", func(a *snapshotPreview1, _ api.Module, _ api.Function) fdReadFn {
return a.FdRead
}},
{functionFdRead, func(_ *snapshotPreview1, mod api.Module, fn api.Function) fdReadFn {
return func(ctx context.Context, m api.Module, fd, iovs, iovsCount, resultSize uint32) Errno {
results, err := fn.Call(testCtx, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultSize))
require.NoError(t, err)
return Errno(results[0])
}
}},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
// Create a fresh file to read the contents from
file, testFS := createFile(t, "test_path", []byte("wazero"))
sysCtx, err := newSysContext(nil, nil, map[uint32]*fs2.FileEntry{
fd: {Path: "test_path", FS: testFS, File: file},
})
require.NoError(t, err)
a, mod, fn := instantiateModule(testCtx, t, functionFdRead, importFdRead, sysCtx)
defer mod.Close(testCtx)
maskMemory(t, testCtx, mod, len(expectedMemory))
ok := mod.Memory().Write(testCtx, 0, initialMemory)
require.True(t, ok)
errno := tc.fdRead(a, mod, fn)(testCtx, mod, fd, iovs, iovsCount, resultSize)
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
}
}
func TestSnapshotPreview1_FdRead_Errors(t *testing.T) {
validFD := uint32(3) // arbitrary valid fd after 0, 1, and 2, that are stdin/out/err
file, testFS := createFile(t, "test_path", []byte{}) // file with empty contents
sysCtx, err := newSysContext(nil, nil, map[uint32]*fs2.FileEntry{
validFD: {Path: "test_path", FS: testFS, File: file},
})
require.NoError(t, err)
a, mod, _ := instantiateModule(testCtx, t, functionFdRead, importFdRead, sysCtx)
defer mod.Close(testCtx)
tests := []struct {
name string
fd, iovs, iovsCount, resultSize uint32
memory []byte
expectedErrno Errno
}{
{
name: "invalid fd",
fd: 42, // arbitrary invalid fd
expectedErrno: ErrnoBadf,
},
{
name: "out-of-memory reading iovs[0].offset",
fd: validFD,
iovs: 1,
memory: []byte{'?'},
expectedErrno: ErrnoFault,
},
{
name: "out-of-memory reading iovs[0].length",
fd: validFD,
iovs: 1, iovsCount: 1,
memory: []byte{
'?', // `iovs` is after this
9, 0, 0, 0, // = iovs[0].offset
},
expectedErrno: ErrnoFault,
},
{
name: "iovs[0].offset is outside memory",
fd: validFD,
iovs: 1, iovsCount: 1,
memory: []byte{
'?', // `iovs` is after this
0, 0, 0x1, 0, // = iovs[0].offset on the second page
1, 0, 0, 0, // = iovs[0].length
},
expectedErrno: ErrnoFault,
},
{
name: "length to read exceeds memory by 1",
fd: validFD,
iovs: 1, iovsCount: 1,
memory: []byte{
'?', // `iovs` is after this
9, 0, 0, 0, // = iovs[0].offset
0, 0, 0x1, 0, // = iovs[0].length on the second page
'?',
},
expectedErrno: ErrnoFault,
},
{
name: "resultSize offset is outside memory",
fd: validFD,
iovs: 1, iovsCount: 1,
resultSize: 10, // 1 past memory
memory: []byte{
'?', // `iovs` is after this
9, 0, 0, 0, // = iovs[0].offset
1, 0, 0, 0, // = iovs[0].length
'?',
},
expectedErrno: ErrnoFault,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
offset := uint32(wasm.MemoryPagesToBytesNum(testMemoryPageSize) - uint64(len(tc.memory)))
memoryWriteOK := mod.Memory().Write(testCtx, offset, tc.memory)
require.True(t, memoryWriteOK)
errno := a.FdRead(testCtx, mod, tc.fd, tc.iovs+offset, tc.iovsCount+offset, tc.resultSize+offset)
require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno))
})
}
}
// TestSnapshotPreview1_FdReaddir only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_FdReaddir(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionFdReaddir, importFdReaddir, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.FdReaddir", func(t *testing.T) {
errno := a.FdReaddir(testCtx, mod, 0, 0, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionFdReaddir, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_FdRenumber only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_FdRenumber(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionFdRenumber, importFdRenumber, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.FdRenumber", func(t *testing.T) {
errno := a.FdRenumber(testCtx, mod, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionFdRenumber, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
func TestSnapshotPreview1_FdSeek(t *testing.T) {
fd := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err
resultNewoffset := uint32(1) // arbitrary offset in `ctx.Memory` for the new offset value
file, testFS := createFile(t, "test_path", []byte("wazero")) // arbitrary non-empty contents
sysCtx, err := newSysContext(nil, nil, map[uint32]*fs2.FileEntry{
fd: {Path: "test_path", FS: testFS, File: file},
})
require.NoError(t, err)
fsCtx := sysCtx.FS()
a, mod, fn := instantiateModule(testCtx, t, functionFdSeek, importFdSeek, sysCtx)
defer mod.Close(testCtx)
// TestSnapshotPreview1_FdSeek uses a matrix because setting up test files is complicated and has to be clean each time.
type fdSeekFn func(ctx context.Context, m api.Module, fd uint32, offset uint64, whence, resultNewOffset uint32) Errno
seekFns := []struct {
name string
fdSeek func() fdSeekFn
}{
{"snapshotPreview1.FdSeek", func() fdSeekFn {
return a.FdSeek
}},
{functionFdSeek, func() fdSeekFn {
return func(ctx context.Context, m api.Module, fd uint32, offset uint64, whence, resultNewoffset uint32) Errno {
results, err := fn.Call(ctx, uint64(fd), offset, uint64(whence), uint64(resultNewoffset))
require.NoError(t, err)
return Errno(results[0])
}
}},
}
tests := []struct {
name string
offset int64
whence int
expectedOffset int64
expectedMemory []byte
}{
{
name: "SeekStart",
offset: 4, // arbitrary offset
whence: io.SeekStart,
expectedOffset: 4, // = offset
expectedMemory: []byte{
'?', // resultNewoffset is after this
4, 0, 0, 0, // = expectedOffset
'?',
},
},
{
name: "SeekCurrent",
offset: 1, // arbitrary offset
whence: io.SeekCurrent,
expectedOffset: 2, // = 1 (the initial offset of the test file) + 1 (offset)
expectedMemory: []byte{
'?', // resultNewoffset is after this
2, 0, 0, 0, // = expectedOffset
'?',
},
},
{
name: "SeekEnd",
offset: -1, // arbitrary offset, note that offset can be negative
whence: io.SeekEnd,
expectedOffset: 5, // = 6 (the size of the test file with content "wazero") + -1 (offset)
expectedMemory: []byte{
'?', // resultNewoffset is after this
5, 0, 0, 0, // = expectedOffset
'?',
},
},
}
for _, seekFn := range seekFns {
sf := seekFn
t.Run(sf.name, func(t *testing.T) {
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
maskMemory(t, testCtx, mod, len(tc.expectedMemory))
// Since we initialized this file, we know it is a seeker (because it is a MapFile)
f, ok := fsCtx.OpenedFile(fd)
require.True(t, ok)
seeker := f.File.(io.Seeker)
// set the initial offset of the file to 1
offset, err := seeker.Seek(1, io.SeekStart)
require.NoError(t, err)
require.Equal(t, int64(1), offset)
errno := sf.fdSeek()(testCtx, mod, fd, uint64(tc.offset), uint32(tc.whence), resultNewoffset)
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(tc.expectedMemory)))
require.True(t, ok)
require.Equal(t, tc.expectedMemory, actual)
offset, err = seeker.Seek(0, io.SeekCurrent)
require.NoError(t, err)
require.Equal(t, tc.expectedOffset, offset) // test that the offset of file is actually updated.
})
}
})
}
}
func TestSnapshotPreview1_FdSeek_Errors(t *testing.T) {
validFD := uint32(3) // arbitrary valid fd after 0, 1, and 2, that are stdin/out/err
file, testFS := createFile(t, "test_path", []byte("wazero")) // arbitrary valid file with non-empty contents
sysCtx, err := newSysContext(nil, nil, map[uint32]*fs2.FileEntry{
validFD: {Path: "test_path", FS: testFS, File: file},
})
require.NoError(t, err)
a, mod, _ := instantiateModule(testCtx, t, functionFdSeek, importFdSeek, sysCtx)
defer mod.Close(testCtx)
memorySize := mod.Memory().Size(testCtx)
tests := []struct {
name string
fd uint32
offset uint64
whence, resultNewoffset uint32
expectedErrno Errno
}{
{
name: "invalid fd",
fd: 42, // arbitrary invalid fd
expectedErrno: ErrnoBadf,
},
{
name: "invalid whence",
fd: validFD,
whence: 3, // invalid whence, the largest whence io.SeekEnd(2) + 1
expectedErrno: ErrnoInval,
},
{
name: "out-of-memory writing resultNewoffset",
fd: validFD,
resultNewoffset: memorySize,
expectedErrno: ErrnoFault,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
errno := a.FdSeek(testCtx, mod, tc.fd, tc.offset, tc.whence, tc.resultNewoffset)
require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno))
})
}
}
// TestSnapshotPreview1_FdSync only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_FdSync(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionFdSync, importFdSync, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.FdSync", func(t *testing.T) {
errno := a.FdSync(testCtx, mod, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionFdSync, func(t *testing.T) {
results, err := fn.Call(testCtx, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_FdTell only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_FdTell(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionFdTell, importFdTell, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.FdTell", func(t *testing.T) {
errno := a.FdTell(testCtx, mod, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionFdTell, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
func TestSnapshotPreview1_FdWrite(t *testing.T) {
fd := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err
iovs := uint32(1) // arbitrary offset
initialMemory := []byte{
'?', // `iovs` is after this
18, 0, 0, 0, // = iovs[0].offset
4, 0, 0, 0, // = iovs[0].length
23, 0, 0, 0, // = iovs[1].offset
2, 0, 0, 0, // = iovs[1].length
'?', // iovs[0].offset is after this
'w', 'a', 'z', 'e', // iovs[0].length bytes
'?', // iovs[1].offset is after this
'r', 'o', // iovs[1].length bytes
'?',
}
iovsCount := uint32(2) // The count of iovs
resultSize := uint32(26) // arbitrary offset
expectedMemory := append(
initialMemory,
6, 0, 0, 0, // sum(iovs[...].length) == length of "wazero"
'?',
)
// TestSnapshotPreview1_FdWrite uses a matrix because setting up test files is complicated and has to be clean each time.
type fdWriteFn func(ctx context.Context, m api.Module, fd, iovs, iovsCount, resultSize uint32) Errno
tests := []struct {
name string
fdWrite func(*snapshotPreview1, api.Module, api.Function) fdWriteFn
}{
{"snapshotPreview1.FdWrite", func(a *snapshotPreview1, _ api.Module, _ api.Function) fdWriteFn {
return a.FdWrite
}},
{functionFdWrite, func(_ *snapshotPreview1, mod api.Module, fn api.Function) fdWriteFn {
return func(ctx context.Context, m api.Module, fd, iovs, iovsCount, resultSize uint32) Errno {
results, err := fn.Call(ctx, uint64(fd), uint64(iovs), uint64(iovsCount), uint64(resultSize))
require.NoError(t, err)
return Errno(results[0])
}
}},
}
tmpDir := t.TempDir() // open before loop to ensure no locking problems.
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
// Create a fresh file to write the contents to
pathName := "test_path"
file, testFS := createWriteableFile(t, tmpDir, pathName, []byte{})
sysCtx, err := newSysContext(nil, nil, map[uint32]*fs2.FileEntry{
fd: {Path: pathName, FS: testFS, File: file},
})
require.NoError(t, err)
a, mod, fn := instantiateModule(testCtx, t, functionFdWrite, importFdWrite, sysCtx)
defer mod.Close(testCtx)
maskMemory(t, testCtx, mod, len(expectedMemory))
ok := mod.Memory().Write(testCtx, 0, initialMemory)
require.True(t, ok)
errno := tc.fdWrite(a, mod, fn)(testCtx, mod, fd, iovs, iovsCount, resultSize)
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
// Since we initialized this file, we know we can read it by path
buf, err := os.ReadFile(path.Join(tmpDir, pathName))
require.NoError(t, err)
require.Equal(t, []byte("wazero"), buf) // verify the file was actually written
})
}
}
func TestSnapshotPreview1_FdWrite_Errors(t *testing.T) {
validFD := uint32(3) // arbitrary valid fd after 0, 1, and 2, that are stdin/out/err
tmpDir := t.TempDir() // open before loop to ensure no locking problems.
pathName := "test_path"
file, testFS := createWriteableFile(t, tmpDir, pathName, []byte{})
sysCtx, err := newSysContext(nil, nil, map[uint32]*fs2.FileEntry{
validFD: {Path: pathName, FS: testFS, File: file},
})
require.NoError(t, err)
a, mod, _ := instantiateModule(testCtx, t, functionFdWrite, importFdWrite, sysCtx)
defer mod.Close(testCtx)
// Setup valid test memory
iovs, iovsCount := uint32(0), uint32(1)
memory := []byte{
8, 0, 0, 0, // = iovs[0].offset (where the data "hi" begins)
2, 0, 0, 0, // = iovs[0].length (how many bytes are in "hi")
'h', 'i', // iovs[0].length bytes
}
tests := []struct {
name string
fd, resultSize uint32
memory []byte
expectedErrno Errno
}{
{
name: "invalid fd",
fd: 42, // arbitrary invalid fd
expectedErrno: ErrnoBadf,
},
{
name: "out-of-memory reading iovs[0].offset",
fd: validFD,
memory: []byte{},
expectedErrno: ErrnoFault,
},
{
name: "out-of-memory reading iovs[0].length",
fd: validFD,
memory: memory[0:4], // iovs[0].offset was 4 bytes and iovs[0].length next, but not enough mod.Memory()!
expectedErrno: ErrnoFault,
},
{
name: "iovs[0].offset is outside memory",
fd: validFD,
memory: memory[0:8], // iovs[0].offset (where to read "hi") is outside memory.
expectedErrno: ErrnoFault,
},
{
name: "length to read exceeds memory by 1",
fd: validFD,
memory: memory[0:9], // iovs[0].offset (where to read "hi") is in memory, but truncated.
expectedErrno: ErrnoFault,
},
{
name: "resultSize offset is outside memory",
fd: validFD,
memory: memory,
resultSize: uint32(len(memory)), // read was ok, but there wasn't enough memory to write the result.
expectedErrno: ErrnoFault,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
mod.Memory().(*wasm.MemoryInstance).Buffer = tc.memory
errno := a.FdWrite(testCtx, mod, tc.fd, iovs, iovsCount, tc.resultSize)
require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno))
})
}
}
// TestSnapshotPreview1_PathCreateDirectory only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_PathCreateDirectory(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionPathCreateDirectory, importPathCreateDirectory, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.PathCreateDirectory", func(t *testing.T) {
errno := a.PathCreateDirectory(testCtx, mod, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionPathCreateDirectory, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_PathFilestatGet only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_PathFilestatGet(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionPathFilestatGet, importPathFilestatGet, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.PathFilestatGet", func(t *testing.T) {
errno := a.PathFilestatGet(testCtx, mod, 0, 0, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionPathFilestatGet, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_PathFilestatSetTimes only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_PathFilestatSetTimes(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionPathFilestatSetTimes, importPathFilestatSetTimes, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.PathFilestatSetTimes", func(t *testing.T) {
errno := a.PathFilestatSetTimes(testCtx, mod, 0, 0, 0, 0, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionPathFilestatSetTimes, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0, 0, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_PathLink only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_PathLink(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionPathLink, importPathLink, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.PathLink", func(t *testing.T) {
errno := a.PathLink(testCtx, mod, 0, 0, 0, 0, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionPathLink, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0, 0, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
func TestSnapshotPreview1_PathOpen(t *testing.T) {
type pathOpenArgs struct {
fd uint32
dirflags uint32
pathPtr uint32
pathLen uint32
oflags uint32
fsRightsBase uint64
fsRightsInheriting uint64
fdflags uint32
resultOpenedFd uint32
}
setup := func(workdirFD uint32, pathName string) (*snapshotPreview1, api.Module, api.Function, pathOpenArgs, []byte, uint32) {
// Setup the initial memory to include the path name starting at an offset.
initialMemory := append([]byte{'?'}, pathName...)
expectedFD := workdirFD + 1
expectedMemory := append(
initialMemory,
'?', // `resultOpenedFd` is after this
byte(expectedFD), 0, 0, 0,
'?',
)
args := pathOpenArgs{
fd: workdirFD,
dirflags: 0,
pathPtr: 1,
pathLen: uint32(len(pathName)),
oflags: 0,
fsRightsBase: 1, // rights are ignored per https://github.com/WebAssembly/WASI/issues/469#issuecomment-1045251844
fsRightsInheriting: 2,
fdflags: 0,
resultOpenedFd: uint32(len(initialMemory) + 1),
}
testFS := fstest.MapFS{pathName: &fstest.MapFile{Mode: os.ModeDir}}
sysCtx, err := newSysContext(nil, nil, map[uint32]*fs2.FileEntry{
workdirFD: {Path: ".", FS: testFS},
})
require.NoError(t, err)
a, mod, fn := instantiateModule(testCtx, t, functionPathOpen, importPathOpen, sysCtx)
maskMemory(t, testCtx, mod, len(expectedMemory))
ok := mod.Memory().Write(testCtx, 0, initialMemory)
require.True(t, ok)
return a, mod, fn, args, expectedMemory, expectedFD
}
verify := func(ctx context.Context, errno Errno, mod api.Module, pathName string, expectedMemory []byte, expectedFD uint32) {
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
// verify the file was actually opened
_, fsc := sysFSCtx(ctx, mod)
f, ok := fsc.OpenedFile(expectedFD)
require.True(t, ok)
require.Equal(t, pathName, f.Path)
}
t.Run("snapshotPreview1.PathOpen", func(t *testing.T) {
workdirFD := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err
pathName := "wazero"
a, mod, _, args, expectedMemory, expectedFD := setup(workdirFD, pathName)
errno := a.PathOpen(testCtx, mod, args.fd, args.dirflags, args.pathPtr, args.pathLen, args.oflags,
args.fsRightsBase, args.fsRightsInheriting, args.fdflags, args.resultOpenedFd)
verify(testCtx, errno, mod, pathName, expectedMemory, expectedFD)
})
t.Run(functionPathOpen, func(t *testing.T) {
workdirFD := uint32(3) // arbitrary fd after 0, 1, and 2, that are stdin/out/err
pathName := "wazero"
_, mod, fn, args, expectedMemory, expectedFD := setup(workdirFD, pathName)
results, err := fn.Call(testCtx, uint64(args.fd), uint64(args.dirflags), uint64(args.pathPtr), uint64(args.pathLen),
uint64(args.oflags), args.fsRightsBase, args.fsRightsInheriting, uint64(args.fdflags), uint64(args.resultOpenedFd))
require.NoError(t, err)
errno := Errno(results[0])
verify(testCtx, errno, mod, pathName, expectedMemory, expectedFD)
})
t.Run("snapshotPreview1.PathOpen.WithFS", func(t *testing.T) {
workdirFD := uint32(100) // dummy fd as it is not used
pathName := "wazero"
// The filesystem initialized in setup() is not used as it will be overridden.
a, mod, _, args, expectedMemory, _ := setup(workdirFD, pathName)
// Override fs.FS through context
workdirFD = uint32(4) // 3 is '/' and 4 is '.'
expectedFD := workdirFD + 1
expectedMemory[8] = byte(expectedFD) // replace expected memory with expected fd
testFS := fstest.MapFS{pathName: &fstest.MapFile{Mode: os.ModeDir}}
ctx, closer, err := experimental.WithFS(testCtx, testFS)
require.NoError(t, err)
defer closer.Close(ctx)
errno := a.PathOpen(ctx, mod, workdirFD, args.dirflags, args.pathPtr, args.pathLen, args.oflags,
args.fsRightsBase, args.fsRightsInheriting, args.fdflags, args.resultOpenedFd)
require.Zero(t, errno, ErrnoName(errno))
verify(ctx, errno, mod, pathName, expectedMemory, expectedFD)
})
}
func TestSnapshotPreview1_PathOpen_Errors(t *testing.T) {
validFD := uint32(3) // arbitrary valid fd after 0, 1, and 2, that are stdin/out/err
pathName := "wazero"
testFS := fstest.MapFS{pathName: &fstest.MapFile{Mode: os.ModeDir}}
sysCtx, err := newSysContext(nil, nil, map[uint32]*fs2.FileEntry{
validFD: {Path: ".", FS: testFS},
})
require.NoError(t, err)
a, mod, _ := instantiateModule(testCtx, t, functionPathOpen, importPathOpen, sysCtx)
defer mod.Close(testCtx)
validPath := uint32(0) // arbitrary offset
validPathLen := uint32(6) // the length of "wazero"
mod.Memory().Write(testCtx, validPath, []byte(pathName))
tests := []struct {
name string
fd, path, pathLen, oflags, resultOpenedFd uint32
expectedErrno Errno
}{
{
name: "invalid fd",
fd: 42, // arbitrary invalid fd
expectedErrno: ErrnoBadf,
},
{
name: "out-of-memory reading path",
fd: validFD,
path: mod.Memory().Size(testCtx),
pathLen: validPathLen,
expectedErrno: ErrnoFault,
},
{
name: "out-of-memory reading pathLen",
fd: validFD,
path: validPath,
pathLen: mod.Memory().Size(testCtx) + 1, // path is in the valid memory range, but pathLen is out-of-memory for path
expectedErrno: ErrnoFault,
},
{
name: "no such file exists",
fd: validFD,
path: validPath,
pathLen: validPathLen - 1, // this make the path "wazer", which doesn't exit
expectedErrno: ErrnoNoent,
},
{
name: "out-of-memory writing resultOpenedFd",
fd: validFD,
path: validPath,
pathLen: validPathLen,
resultOpenedFd: mod.Memory().Size(testCtx), // path and pathLen correctly point to the right path, but where to write the opened FD is outside memory.
expectedErrno: ErrnoFault,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
errno := a.PathOpen(testCtx, mod, tc.fd, 0, tc.path, tc.pathLen, tc.oflags, 0, 0, 0, tc.resultOpenedFd)
require.Equal(t, tc.expectedErrno, errno, ErrnoName(errno))
})
}
}
// TestSnapshotPreview1_PathReadlink only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_PathReadlink(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionPathReadlink, importPathReadlink, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.PathLink", func(t *testing.T) {
errno := a.PathReadlink(testCtx, mod, 0, 0, 0, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionPathReadlink, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_PathRemoveDirectory only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_PathRemoveDirectory(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionPathRemoveDirectory, importPathRemoveDirectory, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.PathRemoveDirectory", func(t *testing.T) {
errno := a.PathRemoveDirectory(testCtx, mod, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionPathRemoveDirectory, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_PathRename only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_PathRename(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionPathRename, importPathRename, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.PathRename", func(t *testing.T) {
errno := a.PathRename(testCtx, mod, 0, 0, 0, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionPathRename, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_PathSymlink only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_PathSymlink(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionPathSymlink, importPathSymlink, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.PathSymlink", func(t *testing.T) {
errno := a.PathSymlink(testCtx, mod, 0, 0, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionPathSymlink, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_PathUnlinkFile only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_PathUnlinkFile(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionPathUnlinkFile, importPathUnlinkFile, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.PathUnlinkFile", func(t *testing.T) {
errno := a.PathUnlinkFile(testCtx, mod, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionPathUnlinkFile, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_PollOneoff only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_PollOneoff(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionPollOneoff, importPollOneoff, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.PollOneoff", func(t *testing.T) {
errno := a.PollOneoff(testCtx, mod, 0, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionPollOneoff, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
func TestSnapshotPreview1_ProcExit(t *testing.T) {
tests := []struct {
name string
exitCode uint32
}{
{
name: "success (exitcode 0)",
exitCode: 0,
},
{
name: "arbitrary non-zero exitcode",
exitCode: 42,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
// Note: Unlike most tests, this uses fn, not the 'a' result parameter. This is because currently, this function
// body panics, and we expect Call to unwrap the panic.
_, mod, fn := instantiateModule(testCtx, t, functionProcExit, importProcExit, nil)
defer mod.Close(testCtx)
// When ProcExit is called, store.Callfunction returns immediately, returning the exit code as the error.
_, err := fn.Call(testCtx, uint64(tc.exitCode))
require.Equal(t, tc.exitCode, err.(*sys.ExitError).ExitCode())
})
}
}
// TestSnapshotPreview1_ProcRaise only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_ProcRaise(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionProcRaise, importProcRaise, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.ProcRaise", func(t *testing.T) {
errno := a.ProcRaise(testCtx, mod, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionProcRaise, func(t *testing.T) {
results, err := fn.Call(testCtx, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_SchedYield only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_SchedYield(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionSchedYield, importSchedYield, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.SchedYield", func(t *testing.T) {
errno := a.SchedYield(mod)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionSchedYield, func(t *testing.T) {
results, err := fn.Call(testCtx)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
func TestSnapshotPreview1_RandomGet(t *testing.T) {
expectedMemory := []byte{
'?', // `offset` is after this
0x53, 0x8c, 0x7f, 0x96, 0xb1, // random data from seed value of 42
'?', // stopped after encoding
}
length := uint32(5) // arbitrary length,
offset := uint32(1) // offset,
t.Run("snapshotPreview1.RandomGet", func(t *testing.T) {
source := rand.New(rand.NewSource(seed))
sysCtx, err := wasm.NewSysContext(math.MaxUint32, nil, nil, new(bytes.Buffer), nil, nil, source, nil)
require.NoError(t, err)
a, mod, _ := instantiateModule(testCtx, t, functionRandomGet, importRandomGet, sysCtx)
defer mod.Close(testCtx)
maskMemory(t, testCtx, mod, len(expectedMemory))
// Invoke RandomGet directly and check the memory side effects!
errno := a.RandomGet(testCtx, mod, offset, length)
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, offset+length+1)
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
t.Run(functionRandomGet, func(t *testing.T) {
source := rand.New(rand.NewSource(seed))
sysCtx, err := wasm.NewSysContext(math.MaxUint32, nil, nil, new(bytes.Buffer), nil, nil, source, nil)
require.NoError(t, err)
_, mod, fn := instantiateModule(testCtx, t, functionRandomGet, importRandomGet, sysCtx)
defer mod.Close(testCtx)
maskMemory(t, testCtx, mod, len(expectedMemory))
results, err := fn.Call(testCtx, uint64(offset), uint64(length))
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Zero(t, errno, ErrnoName(errno))
actual, ok := mod.Memory().Read(testCtx, 0, offset+length+1)
require.True(t, ok)
require.Equal(t, expectedMemory, actual)
})
}
func TestSnapshotPreview1_RandomGet_Errors(t *testing.T) {
validAddress := uint32(0) // arbitrary valid address
a, mod, _ := instantiateModule(testCtx, t, functionRandomGet, importRandomGet, nil)
defer mod.Close(testCtx)
memorySize := mod.Memory().Size(testCtx)
tests := []struct {
name string
offset uint32
length uint32
}{
{
name: "out-of-memory",
offset: memorySize,
length: 1,
},
{
name: "random length exceeds maximum valid address by 1",
offset: validAddress,
length: memorySize + 1,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
errno := a.RandomGet(testCtx, mod, tc.offset, tc.length)
require.Equal(t, ErrnoFault, errno, ErrnoName(errno))
})
}
}
// compile-time check to ensure fakeSysErr implements experimental.Sys.
var _ experimental.Sys = &fakeSysErr{}
type fakeSysErr struct{}
func (d *fakeSysErr) TimeNowUnixNano() uint64 {
panic(errors.New("TimeNowUnixNano error"))
}
func TestSnapshotPreview1_RandomGet_SourceError(t *testing.T) {
for _, tc := range []struct {
name string
randSource io.Reader
}{
{
name: "error",
randSource: iotest.ErrReader(errors.New("RandSource error")),
},
{
name: "incomplete",
randSource: bytes.NewReader([]byte{1, 2}),
},
} {
t.Run(tc.name, func(t *testing.T) {
var errCtx = context.WithValue(context.Background(), experimental.SysKey{}, &fakeSysErr{})
sysCtx, err := wasm.NewSysContext(math.MaxUint32, nil, nil, new(bytes.Buffer), nil, nil, tc.randSource, nil)
require.NoError(t, err)
a, mod, _ := instantiateModule(errCtx, t, functionRandomGet, importRandomGet, sysCtx)
defer mod.Close(errCtx)
errno := a.RandomGet(errCtx, mod, uint32(1), uint32(5)) // arbitrary offset and length
require.Equal(t, ErrnoIo, errno, ErrnoName(errno))
})
}
}
// TestSnapshotPreview1_SockRecv only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_SockRecv(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionSockRecv, importSockRecv, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.SockRecv", func(t *testing.T) {
errno := a.SockRecv(testCtx, mod, 0, 0, 0, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionSockRecv, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_SockSend only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_SockSend(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionSockSend, importSockSend, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.SockSend", func(t *testing.T) {
errno := a.SockSend(testCtx, mod, 0, 0, 0, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionSockSend, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0, 0, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
// TestSnapshotPreview1_SockShutdown only tests it is stubbed for GrainLang per #271
func TestSnapshotPreview1_SockShutdown(t *testing.T) {
a, mod, fn := instantiateModule(testCtx, t, functionSockShutdown, importSockShutdown, nil)
defer mod.Close(testCtx)
t.Run("snapshotPreview1.SockShutdown", func(t *testing.T) {
errno := a.SockShutdown(testCtx, mod, 0, 0)
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
t.Run(functionSockShutdown, func(t *testing.T) {
results, err := fn.Call(testCtx, 0, 0)
require.NoError(t, err)
errno := Errno(results[0]) // results[0] is the errno
require.Equal(t, ErrnoNosys, errno, ErrnoName(errno))
})
}
const testMemoryPageSize = 1
// maskMemory sets the first memory in the store to '?' * size, so tests can see what's written.
func maskMemory(t *testing.T, ctx context.Context, mod api.Module, size int) {
for i := uint32(0); i < uint32(size); i++ {
require.True(t, mod.Memory().WriteByte(ctx, i, '?'))
}
}
func instantiateModule(ctx context.Context, t *testing.T, wasifunction, wasiimport string, sysCtx *wasm.SysContext) (*snapshotPreview1, api.Module, api.Function) {
r := wazero.NewRuntimeWithConfig(wazero.NewRuntimeConfigInterpreter())
// The package `wazero` has a simpler interface for adding host modules, but we can't use that as it would create an
// import cycle. Instead, we export wasm.NewHostModule and use it here.
a, fns := snapshotPreview1Functions(ctx)
_, err := r.NewModuleBuilder("wasi_snapshot_preview1").ExportFunctions(fns).Instantiate(testCtx)
require.NoError(t, err)
compiled, err := r.CompileModule(ctx, []byte(fmt.Sprintf(`(module
%[2]s
(memory 1 1) ;; just an arbitrary size big enough for tests
(export "memory" (memory 0))
(export "%[1]s" (func $wasi.%[1]s))
)`, wasifunction, wasiimport)), wazero.NewCompileConfig())
require.NoError(t, err)
defer compiled.Close(ctx)
mod, err := r.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName(t.Name()))
require.NoError(t, err)
if sysCtx != nil {
mod.(*wasm.CallContext).Sys = sysCtx
}
fn := mod.ExportedFunction(wasifunction)
require.NotNil(t, fn)
return a, mod, fn
}
func newSysContext(args, environ []string, openedFiles map[uint32]*fs2.FileEntry) (sysCtx *wasm.SysContext, err error) {
return wasm.NewSysContext(math.MaxUint32, args, environ, new(bytes.Buffer), nil, nil, nil, openedFiles)
}
func createFile(t *testing.T, pathName string, data []byte) (fs.File, fs.FS) {
mapFile := &fstest.MapFile{Data: data}
if data == nil {
mapFile.Mode = os.ModeDir
}
mapFS := fstest.MapFS{pathName: mapFile}
f, err := mapFS.Open(pathName)
require.NoError(t, err)
return f, mapFS
}
// createWriteableFile uses real files when io.Writer tests are needed.
func createWriteableFile(t *testing.T, tmpDir string, pathName string, data []byte) (fs.File, fs.FS) {
require.NotNil(t, data)
absolutePath := path.Join(tmpDir, pathName)
require.NoError(t, os.WriteFile(absolutePath, data, 0o600))
// open the file for writing in a custom way until #390
f, err := os.OpenFile(absolutePath, os.O_RDWR, 0o600)
require.NoError(t, err)
return f, os.DirFS(tmpDir)
}