Signed-off-by: Anuraag Agrawal <anuraaga@gmail.com> Co-authored-by: Adrian Cole <adrian@tetrate.io>
465 lines
11 KiB
Go
465 lines
11 KiB
Go
package assemblyscript
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
_ "embed"
|
|
"errors"
|
|
"io"
|
|
"testing"
|
|
"testing/iotest"
|
|
"unicode/utf16"
|
|
|
|
"github.com/tetratelabs/wazero"
|
|
"github.com/tetratelabs/wazero/api"
|
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
)
|
|
|
|
var abortWasm = []byte(`(module
|
|
(import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32)))
|
|
(memory 1 1)
|
|
(export "abort" (func 0))
|
|
)`)
|
|
|
|
var seedWasm = []byte(`(module
|
|
(import "env" "seed" (func $~lib/builtins/seed (result f64)))
|
|
(memory 1 1)
|
|
(export "seed" (func 0))
|
|
)`)
|
|
|
|
var traceWasm = []byte(`(module
|
|
(import "env" "trace" (func $~lib/builtins/trace (param i32 i32 f64 f64 f64 f64 f64)))
|
|
(memory 1 1)
|
|
(export "trace" (func 0))
|
|
)`)
|
|
|
|
// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
|
|
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
|
|
|
|
func TestAbort(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
enabled bool
|
|
expected string
|
|
}{
|
|
{
|
|
name: "enabled",
|
|
enabled: true,
|
|
expected: "message at filename:1:2\n",
|
|
},
|
|
{
|
|
name: "disabled",
|
|
enabled: false,
|
|
expected: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
r := wazero.NewRuntime()
|
|
defer r.Close(testCtx)
|
|
|
|
out := &bytes.Buffer{}
|
|
|
|
if tc.enabled {
|
|
_, err := Instantiate(testCtx, r)
|
|
require.NoError(t, err)
|
|
} else {
|
|
_, err := NewModuleBuilder(r).WithAbortMessageDisabled().Instantiate(testCtx)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
code, err := r.CompileModule(testCtx, abortWasm, wazero.NewCompileConfig())
|
|
require.NoError(t, err)
|
|
|
|
mod, err := r.InstantiateModule(testCtx, code, wazero.NewModuleConfig().WithStderr(out))
|
|
require.NoError(t, err)
|
|
|
|
messageOff, filenameOff := writeAbortMessageAndFileName(t, mod.Memory(), encodeUTF16("message"), encodeUTF16("filename"))
|
|
|
|
_, err = mod.ExportedFunction("abort").Call(testCtx, uint64(messageOff), uint64(filenameOff), 1, 2)
|
|
require.Error(t, err)
|
|
|
|
require.Equal(t, tc.expected, out.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSeed(t *testing.T) {
|
|
r := wazero.NewRuntime()
|
|
defer r.Close(testCtx)
|
|
|
|
seed := []byte{0, 1, 2, 3, 4, 5, 6, 7}
|
|
|
|
_, err := Instantiate(testCtx, r)
|
|
require.NoError(t, err)
|
|
|
|
code, err := r.CompileModule(testCtx, seedWasm, wazero.NewCompileConfig())
|
|
require.NoError(t, err)
|
|
|
|
mod, err := r.InstantiateModule(testCtx, code, wazero.NewModuleConfig().WithRandSource(bytes.NewReader(seed)))
|
|
require.NoError(t, err)
|
|
|
|
seedFn := mod.ExportedFunction("seed")
|
|
|
|
res, err := seedFn.Call(testCtx)
|
|
require.NoError(t, err)
|
|
|
|
// If this test doesn't break, the seed is deterministic.
|
|
require.Equal(t, uint64(506097522914230528), res[0])
|
|
}
|
|
|
|
func TestTrace(t *testing.T) {
|
|
noArgs := []uint64{4, 0, 0, 0, 0, 0, 0}
|
|
tests := []struct {
|
|
name string
|
|
mode traceMode
|
|
params []uint64
|
|
expected string
|
|
}{
|
|
{
|
|
name: "stderr",
|
|
mode: traceStderr,
|
|
params: noArgs,
|
|
expected: "trace: hello\n",
|
|
},
|
|
{
|
|
name: "stdout",
|
|
mode: traceStdout,
|
|
params: noArgs,
|
|
expected: "trace: hello\n",
|
|
},
|
|
{
|
|
name: "disabled",
|
|
mode: traceDisabled,
|
|
params: noArgs,
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "one",
|
|
mode: traceStdout,
|
|
params: []uint64{4, 1, api.EncodeF64(1), 0, 0, 0, 0},
|
|
expected: "trace: hello 1\n",
|
|
},
|
|
{
|
|
name: "two",
|
|
mode: traceStdout,
|
|
params: []uint64{4, 2, api.EncodeF64(1), api.EncodeF64(2), 0, 0, 0},
|
|
expected: "trace: hello 1,2\n",
|
|
},
|
|
{
|
|
name: "five",
|
|
mode: traceStdout,
|
|
params: []uint64{
|
|
4,
|
|
5,
|
|
api.EncodeF64(1),
|
|
api.EncodeF64(2),
|
|
api.EncodeF64(3.3),
|
|
api.EncodeF64(4.4),
|
|
api.EncodeF64(5),
|
|
},
|
|
expected: "trace: hello 1,2,3.3,4.4,5\n",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
r := wazero.NewRuntime()
|
|
defer r.Close(testCtx)
|
|
|
|
out := &bytes.Buffer{}
|
|
|
|
as := NewModuleBuilder(r)
|
|
modConfig := wazero.NewModuleConfig()
|
|
switch tc.mode {
|
|
case traceStderr:
|
|
as = as.WithTraceToStderr()
|
|
modConfig = modConfig.WithStderr(out)
|
|
case traceStdout:
|
|
as = as.WithTraceToStdout()
|
|
modConfig = modConfig.WithStdout(out)
|
|
case traceDisabled:
|
|
// Set but not used
|
|
modConfig = modConfig.WithStderr(out)
|
|
modConfig = modConfig.WithStdout(out)
|
|
}
|
|
|
|
_, err := as.Instantiate(testCtx)
|
|
require.NoError(t, err)
|
|
|
|
code, err := r.CompileModule(testCtx, traceWasm, wazero.NewCompileConfig())
|
|
require.NoError(t, err)
|
|
|
|
mod, err := r.InstantiateModule(testCtx, code, modConfig)
|
|
require.NoError(t, err)
|
|
|
|
message := encodeUTF16("hello")
|
|
ok := mod.Memory().WriteUint32Le(testCtx, 0, uint32(len(message)))
|
|
require.True(t, ok)
|
|
ok = mod.Memory().Write(testCtx, uint32(4), message)
|
|
require.True(t, ok)
|
|
|
|
_, err = mod.ExportedFunction("trace").Call(testCtx, tc.params...)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expected, out.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReadAssemblyScriptString(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
memory func(api.Memory)
|
|
offset int
|
|
expected, expectedErr string
|
|
}{
|
|
{
|
|
name: "success",
|
|
memory: func(memory api.Memory) {
|
|
memory.WriteUint32Le(testCtx, 0, 10)
|
|
b := encodeUTF16("hello")
|
|
memory.Write(testCtx, 4, b)
|
|
},
|
|
offset: 4,
|
|
expected: "hello",
|
|
},
|
|
{
|
|
name: "can't read size",
|
|
memory: func(memory api.Memory) {
|
|
b := encodeUTF16("hello")
|
|
memory.Write(testCtx, 0, b)
|
|
},
|
|
offset: 0, // will attempt to read size from offset -4
|
|
expectedErr: "Memory.ReadUint32Le(4294967292) out of range",
|
|
},
|
|
{
|
|
name: "odd size",
|
|
memory: func(memory api.Memory) {
|
|
memory.WriteUint32Le(testCtx, 0, 9)
|
|
b := encodeUTF16("hello")
|
|
memory.Write(testCtx, 4, b)
|
|
},
|
|
offset: 4,
|
|
expectedErr: "read an odd number of bytes for utf-16 string: 9",
|
|
},
|
|
{
|
|
name: "can't read string",
|
|
memory: func(memory api.Memory) {
|
|
memory.WriteUint32Le(testCtx, 0, 10_000_000) // set size to too large value
|
|
b := encodeUTF16("hello")
|
|
memory.Write(testCtx, 4, b)
|
|
},
|
|
offset: 4,
|
|
expectedErr: "Memory.Read(4, 10000000) out of range",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
r := wazero.NewRuntime()
|
|
defer r.Close(testCtx)
|
|
|
|
mod, err := r.NewModuleBuilder("mod").
|
|
ExportMemory("memory", 1).
|
|
Instantiate(testCtx)
|
|
require.NoError(t, err)
|
|
|
|
tc.memory(mod.Memory())
|
|
|
|
s, err := readAssemblyScriptString(testCtx, mod, uint32(tc.offset))
|
|
if tc.expectedErr != "" {
|
|
require.EqualError(t, err, tc.expectedErr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expected, s)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAbort_error(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
messageUTF16 []byte
|
|
fileNameUTF16 []byte
|
|
}{
|
|
{
|
|
name: "bad message",
|
|
messageUTF16: encodeUTF16("message")[:5],
|
|
fileNameUTF16: encodeUTF16("filename"),
|
|
},
|
|
{
|
|
name: "bad filename",
|
|
messageUTF16: encodeUTF16("message"),
|
|
fileNameUTF16: encodeUTF16("filename")[:5],
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
r := wazero.NewRuntime()
|
|
defer r.Close(testCtx)
|
|
|
|
_, err := Instantiate(testCtx, r)
|
|
require.NoError(t, err)
|
|
|
|
compiled, err := r.CompileModule(testCtx, abortWasm, wazero.NewCompileConfig())
|
|
require.NoError(t, err)
|
|
|
|
out := &bytes.Buffer{}
|
|
config := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(out)
|
|
mod, err := r.InstantiateModule(testCtx, compiled, config)
|
|
require.NoError(t, err)
|
|
|
|
messageOff, filenameOff := writeAbortMessageAndFileName(t, mod.Memory(), tc.messageUTF16, tc.fileNameUTF16)
|
|
|
|
_, err = mod.ExportedFunction("abort").Call(testCtx, uint64(messageOff), uint64(filenameOff), 1, 2)
|
|
require.NoError(t, err)
|
|
require.Equal(t, "", out.String()) // nothing output if strings fail to read.
|
|
})
|
|
}
|
|
}
|
|
|
|
func writeAbortMessageAndFileName(t *testing.T, mem api.Memory, messageUTF16, fileNameUTF16 []byte) (int, int) {
|
|
off := 0
|
|
ok := mem.WriteUint32Le(testCtx, uint32(off), uint32(len(messageUTF16)))
|
|
require.True(t, ok)
|
|
off += 4
|
|
messageOff := off
|
|
ok = mem.Write(testCtx, uint32(off), messageUTF16)
|
|
require.True(t, ok)
|
|
off += len(messageUTF16)
|
|
ok = mem.WriteUint32Le(testCtx, uint32(off), uint32(len(fileNameUTF16)))
|
|
require.True(t, ok)
|
|
off += 4
|
|
filenameOff := off
|
|
ok = mem.Write(testCtx, uint32(off), fileNameUTF16)
|
|
require.True(t, ok)
|
|
return messageOff, filenameOff
|
|
}
|
|
|
|
func TestSeed_error(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
source io.Reader
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "not 8 bytes",
|
|
source: bytes.NewReader([]byte{0, 1}),
|
|
expectedErr: `error reading Module.RandSource: unexpected EOF (recovered by wazero)
|
|
wasm stack trace:
|
|
env.seed() f64`,
|
|
},
|
|
{
|
|
name: "error reading",
|
|
source: iotest.ErrReader(errors.New("ice cream")),
|
|
expectedErr: `error reading Module.RandSource: ice cream (recovered by wazero)
|
|
wasm stack trace:
|
|
env.seed() f64`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
r := wazero.NewRuntime()
|
|
defer r.Close(testCtx)
|
|
|
|
_, err := Instantiate(testCtx, r)
|
|
require.NoError(t, err)
|
|
|
|
compiled, err := r.CompileModule(testCtx, seedWasm, wazero.NewCompileConfig())
|
|
require.NoError(t, err)
|
|
|
|
config := wazero.NewModuleConfig().WithName(t.Name()).WithRandSource(tc.source)
|
|
mod, err := r.InstantiateModule(testCtx, compiled, config)
|
|
require.NoError(t, err)
|
|
|
|
_, err = mod.ExportedFunction("seed").Call(testCtx)
|
|
require.EqualError(t, err, tc.expectedErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTrace_error(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
message []byte
|
|
out io.Writer
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "not 8 bytes",
|
|
message: encodeUTF16("hello")[:5],
|
|
out: &bytes.Buffer{},
|
|
expectedErr: `read an odd number of bytes for utf-16 string: 5 (recovered by wazero)
|
|
wasm stack trace:
|
|
env.trace(i32,i32,f64,f64,f64,f64,f64)`,
|
|
},
|
|
{
|
|
name: "error writing",
|
|
message: encodeUTF16("hello"),
|
|
out: &errWriter{err: errors.New("ice cream")},
|
|
expectedErr: `ice cream (recovered by wazero)
|
|
wasm stack trace:
|
|
env.trace(i32,i32,f64,f64,f64,f64,f64)`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
r := wazero.NewRuntime()
|
|
defer r.Close(testCtx)
|
|
|
|
_, err := NewModuleBuilder(r).WithTraceToStdout().Instantiate(testCtx)
|
|
require.NoError(t, err)
|
|
|
|
compiled, err := r.CompileModule(testCtx, traceWasm, wazero.NewCompileConfig())
|
|
require.NoError(t, err)
|
|
|
|
config := wazero.NewModuleConfig().WithName(t.Name()).WithStdout(tc.out)
|
|
mod, err := r.InstantiateModule(testCtx, compiled, config)
|
|
require.NoError(t, err)
|
|
|
|
ok := mod.Memory().WriteUint32Le(testCtx, 0, uint32(len(tc.message)))
|
|
require.True(t, ok)
|
|
ok = mod.Memory().Write(testCtx, uint32(4), tc.message)
|
|
require.True(t, ok)
|
|
|
|
_, err = mod.ExportedFunction("trace").Call(testCtx, 4, 0, 0, 0, 0, 0, 0)
|
|
require.EqualError(t, err, tc.expectedErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func encodeUTF16(s string) []byte {
|
|
runes := utf16.Encode([]rune(s))
|
|
b := make([]byte, len(runes)*2)
|
|
for i, r := range runes {
|
|
b[i*2] = byte(r)
|
|
b[i*2+1] = byte(r >> 8)
|
|
}
|
|
return b
|
|
}
|
|
|
|
type errWriter struct {
|
|
err error
|
|
}
|
|
|
|
func (w *errWriter) Write([]byte) (int, error) {
|
|
return 0, w.err
|
|
}
|