This renames `InstantiateModuleFromBinary` to `Instantiate` to both make first time use simpler to write and also de-complicate adding a `WithConfig` variant as requested in #1105 End users in simple case need to change their signature like so. ```diff - mod, err := r.InstantiateModuleFromBinary(ctx, addWasm) + mod, err := r.Instantiate(ctx, addWasm) ``` In practice, many will not need to change their signature because they had to use the `InstantiateModule` function in order to assign configuration such as the module name, filesystem or use a real clock. Instead, they had to use the more complicated chain of `CompileModule` and `InstantiateModule` even when only assigning config. Users in this situation can opt into the more simplified syntax below: ```go mod, err := r.InstantiateWithConfig(ctx, addWasm, wazero.NewModuleConfig().WithName("adder")) ``` ```diff - mod, err := r.InstantiateModuleFromBinary(ctx, addWasm) + mod, err := r.Instantiate(ctx, addWasm) ``` Signed-off-by: Adrian Cole <adrian@tetrate.io>
187 lines
5.1 KiB
Go
187 lines
5.1 KiB
Go
package fs
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"testing"
|
|
"testing/iotest"
|
|
|
|
"github.com/tetratelabs/wazero"
|
|
"github.com/tetratelabs/wazero/api"
|
|
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
|
|
"github.com/tetratelabs/wazero/internal/fstest"
|
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
. "github.com/tetratelabs/wazero/internal/wasi_snapshot_preview1"
|
|
)
|
|
|
|
var testCtx = context.Background()
|
|
|
|
//go:embed testdata/animals.txt
|
|
var animals []byte
|
|
|
|
// fsWasm was generated by the following:
|
|
//
|
|
// cd testdata; wat2wasm --debug-names fs.wat
|
|
//
|
|
//go:embed testdata/fs.wasm
|
|
var fsWasm []byte
|
|
|
|
// wasiFs is an implementation of fs.Fs calling into wasiWASI. Not thread-safe because we use
|
|
// fixed Memory offsets for transferring data with wasm.
|
|
type wasiFs struct {
|
|
t *testing.T
|
|
|
|
wasm wazero.Runtime
|
|
memory api.Memory
|
|
|
|
workdirFd uint32
|
|
|
|
pathOpen api.Function
|
|
fdClose api.Function
|
|
fdRead api.Function
|
|
fdSeek api.Function
|
|
}
|
|
|
|
func (fs *wasiFs) Open(name string) (fs.File, error) {
|
|
pathBytes := []byte(name)
|
|
// Pick anywhere in memory to write the path to.
|
|
pathPtr := uint32(0)
|
|
ok := fs.memory.Write(pathPtr, pathBytes)
|
|
require.True(fs.t, ok)
|
|
resultOpenedFd := pathPtr + uint32(len(pathBytes))
|
|
|
|
fd := fs.workdirFd
|
|
dirflags := uint32(0) // arbitrary dirflags
|
|
pathLen := len(pathBytes)
|
|
oflags := uint32(0) // arbitrary oflags
|
|
// rights are ignored per https://github.com/WebAssembly/WASI/issues/469#issuecomment-1045251844
|
|
fsRightsBase, fsRightsInheriting := uint64(1), uint64(2)
|
|
fdflags := uint32(0) // arbitrary fdflags
|
|
res, err := fs.pathOpen.Call(
|
|
testCtx,
|
|
uint64(fd), uint64(dirflags), uint64(pathPtr), uint64(pathLen), uint64(oflags),
|
|
fsRightsBase, fsRightsInheriting, uint64(fdflags), uint64(resultOpenedFd))
|
|
require.NoError(fs.t, err)
|
|
require.Equal(fs.t, uint64(ErrnoSuccess), res[0])
|
|
|
|
resFd, ok := fs.memory.ReadUint32Le(resultOpenedFd)
|
|
require.True(fs.t, ok)
|
|
|
|
return &wasiFile{fd: resFd, fs: fs}, nil
|
|
}
|
|
|
|
// wasiFile implements io.Reader and io.Seeker using wasiWASI functions. It does not
|
|
// implement io.ReaderAt because there is no wasiWASI function for directly reading
|
|
// from an offset.
|
|
type wasiFile struct {
|
|
fd uint32
|
|
fs *wasiFs
|
|
}
|
|
|
|
func (f *wasiFile) Stat() (fs.FileInfo, error) {
|
|
// We currently don't implement wasi's fd_stat but also don't use this method from this test.
|
|
panic("unused")
|
|
}
|
|
|
|
func (f *wasiFile) Read(bytes []byte) (int, error) {
|
|
// Pick anywhere in memory for wasm to write resultSize too. We do this first since it's fixed length
|
|
// while iovs is variable.
|
|
resultSizeOff := uint32(0)
|
|
// next place iovs
|
|
iovsOff := uint32(4)
|
|
// We do not directly write to hardware, there is no need for more than one iovec
|
|
iovsCount := uint32(1)
|
|
// iov starts at iovsOff + 8 because we first write four bytes for the offset itself, and
|
|
// four bytes for the length of the iov.
|
|
iovOff := iovsOff + uint32(8)
|
|
ok := f.fs.memory.WriteUint32Le(iovsOff, iovOff)
|
|
require.True(f.fs.t, ok)
|
|
// next write the length.
|
|
ok = f.fs.memory.WriteUint32Le(iovsOff+uint32(4), uint32(len(bytes)))
|
|
require.True(f.fs.t, ok)
|
|
|
|
res, err := f.fs.fdRead.Call(testCtx, uint64(f.fd), uint64(iovsOff), uint64(iovsCount), uint64(resultSizeOff))
|
|
require.NoError(f.fs.t, err)
|
|
|
|
require.NotEqual(f.fs.t, uint64(ErrnoFault), res[0])
|
|
|
|
numRead, ok := f.fs.memory.ReadUint32Le(resultSizeOff)
|
|
require.True(f.fs.t, ok)
|
|
|
|
if numRead == 0 {
|
|
if len(bytes) == 0 {
|
|
return 0, nil
|
|
}
|
|
if Errno(res[0]) == ErrnoSuccess {
|
|
return 0, io.EOF
|
|
} else {
|
|
return 0, fmt.Errorf("could not read from file")
|
|
}
|
|
}
|
|
|
|
buf, ok := f.fs.memory.Read(iovOff, numRead)
|
|
require.True(f.fs.t, ok)
|
|
copy(bytes, buf)
|
|
return int(numRead), nil
|
|
}
|
|
|
|
func (f *wasiFile) Close() error {
|
|
res, err := f.fs.fdClose.Call(testCtx, uint64(f.fd))
|
|
require.NoError(f.fs.t, err)
|
|
require.NotEqual(f.fs.t, uint64(ErrnoFault), res[0])
|
|
return nil
|
|
}
|
|
|
|
func (f *wasiFile) Seek(offset int64, whence int) (int64, error) {
|
|
// Pick anywhere in memory for wasm to write the result newOffset to
|
|
resultNewoffsetOff := uint32(0)
|
|
|
|
res, err := f.fs.fdSeek.Call(testCtx, uint64(f.fd), uint64(offset), uint64(whence), uint64(resultNewoffsetOff))
|
|
require.NoError(f.fs.t, err)
|
|
require.NotEqual(f.fs.t, uint64(ErrnoFault), res[0])
|
|
|
|
newOffset, ok := f.fs.memory.ReadUint32Le(resultNewoffsetOff)
|
|
require.True(f.fs.t, ok)
|
|
|
|
return int64(newOffset), nil
|
|
}
|
|
|
|
func TestReader(t *testing.T) {
|
|
r := wazero.NewRuntime(testCtx)
|
|
defer r.Close(testCtx)
|
|
|
|
wasi_snapshot_preview1.MustInstantiate(testCtx, r)
|
|
|
|
sys := wazero.NewModuleConfig().WithFS(fstest.FS)
|
|
|
|
// Create a module that just delegates to wasi functions.
|
|
mod, err := r.InstantiateWithConfig(testCtx, fsWasm, sys)
|
|
require.NoError(t, err)
|
|
|
|
pathOpen := mod.ExportedFunction("path_open")
|
|
fdClose := mod.ExportedFunction("fd_close")
|
|
fdRead := mod.ExportedFunction("fd_read")
|
|
fdSeek := mod.ExportedFunction("fd_seek")
|
|
|
|
wasiFs := &wasiFs{
|
|
t: t,
|
|
wasm: r,
|
|
memory: mod.Memory(),
|
|
workdirFd: uint32(3),
|
|
pathOpen: pathOpen,
|
|
fdClose: fdClose,
|
|
fdRead: fdRead,
|
|
fdSeek: fdSeek,
|
|
}
|
|
|
|
f, err := wasiFs.Open("animals.txt")
|
|
require.NoError(t, err)
|
|
defer f.Close()
|
|
|
|
err = iotest.TestReader(f, animals)
|
|
require.NoError(t, err)
|
|
}
|