Files
wazero/internal/wasm/host_test.go
Crypt Keeper 5180d2d9c3 Refactors out public API from internals (#238)
This moves to a new end-user API under the root package `wazero`. This
simplifies call sites while hardening function calls to their known
return value. Most importantly, this moves most logic internal, as
noted in the RATIONALE.md.

Ex.

```go
	// Read WebAssembly binary containing an exported "fac" function.
	source, _ := os.ReadFile("./tests/engine/testdata/fac.wasm")

	// Decode the binary as WebAssembly module.
	mod, _ := wazero.DecodeModuleBinary(source)

	// Initialize the execution environment called "store" with Interpreter-based engine.
	store := wazero.NewStore()

	// Instantiate the module, which returns its exported functions
	functions, _ := store.Instantiate(mod)

	// Get the factorial function
	fac, _ := functions.GetFunctionI64Return("fac")

	// Discover 7! is 5040
	fmt.Println(fac(context.Background(), 7))

```

PS I changed the README to factorial because the wat version of
fibonacci is not consistent with the TinyGo one!

Signed-off-by: Adrian Cole <adrian@tetrate.io>
Co-authored-by: Takaya Saeki <takaya@tetrate.io>
Co-authored-by: Takeshi Yoneda <takeshi@tetrate.io>
2022-02-17 17:39:28 +08:00

451 lines
12 KiB
Go

package internalwasm
import (
"math"
"testing"
"github.com/stretchr/testify/require"
)
func TestMemoryInstance_HasLen(t *testing.T) {
memory := &MemoryInstance{Buffer: make([]byte, 100)}
tests := []struct {
name string
offset uint32
sizeInBytes uint64
expected bool
}{
{
name: "simple valid arguments",
offset: 0, // arbitrary valid offset
sizeInBytes: 8, // arbitrary valid size
expected: true,
},
{
name: "maximum valid sizeInBytes",
offset: memory.Len() - 8,
sizeInBytes: 8,
expected: true,
},
{
name: "sizeInBytes exceeds the valid size by 1",
offset: 100, // arbitrary valid offset
sizeInBytes: uint64(memory.Len() - 99),
expected: false,
},
{
name: "offset exceeds the memory size",
offset: memory.Len(),
sizeInBytes: 1, // arbitrary size
expected: false,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.expected, memory.hasLen(tc.offset, uint32(tc.sizeInBytes)))
})
}
}
func TestMemoryInstance_ReadUint32Le(t *testing.T) {
tests := []struct {
name string
memory []byte
offset uint32
expected uint32
expectedOk bool
}{
{
name: "valid offset with an endian-insensitive v",
memory: []byte{0xff, 0xff, 0xff, 0xff},
offset: 0, // arbitrary valid offset.
expected: math.MaxUint32,
expectedOk: true,
},
{
name: "valid offset with an endian-sensitive v",
memory: []byte{0xfe, 0xff, 0xff, 0xff},
offset: 0, // arbitrary valid offset.
expected: math.MaxUint32 - 1,
expectedOk: true,
},
{
name: "maximum boundary valid offset",
offset: 1,
memory: []byte{0x00, 0x1, 0x00, 0x00, 0x00},
expected: 1, // arbitrary valid v
expectedOk: true,
},
{
name: "offset exceeds the maximum valid offset by 1",
memory: []byte{0xff, 0xff, 0xff, 0xff},
offset: 1,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
memory := &MemoryInstance{Buffer: tc.memory}
v, ok := memory.ReadUint32Le(tc.offset)
require.Equal(t, tc.expectedOk, ok)
require.Equal(t, tc.expected, v)
})
}
}
func TestMemoryInstance_ReadUint64Le(t *testing.T) {
tests := []struct {
name string
memory []byte
offset uint32
expected uint64
expectedOk bool
}{
{
name: "valid offset with an endian-insensitive v",
memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
offset: 0, // arbitrary valid offset.
expected: math.MaxUint64,
expectedOk: true,
},
{
name: "valid offset with an endian-sensitive v",
memory: []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
offset: 0, // arbitrary valid offset.
expected: math.MaxUint64 - 1,
expectedOk: true,
},
{
name: "maximum boundary valid offset",
offset: 1,
memory: []byte{0x00, 0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
expected: 1, // arbitrary valid v
expectedOk: true,
},
{
name: "offset exceeds the maximum valid offset by 1",
memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
offset: 1,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
memory := &MemoryInstance{Buffer: tc.memory}
v, ok := memory.ReadUint64Le(tc.offset)
require.Equal(t, tc.expectedOk, ok)
require.Equal(t, tc.expected, v)
})
}
}
func TestMemoryInstance_ReadFloat32Le(t *testing.T) {
tests := []struct {
name string
memory []byte
offset uint32
expected float32
expectedOk bool
}{
{
name: "valid offset with an endian-insensitive v",
memory: []byte{0xff, 0x00, 0x00, 0xff},
offset: 0, // arbitrary valid offset.
expected: math.Float32frombits(uint32(0xff0000ff)),
expectedOk: true,
},
{
name: "valid offset with an endian-sensitive v",
memory: []byte{0xfe, 0x00, 0x00, 0xff},
offset: 0, // arbitrary valid offset.
expected: math.Float32frombits(uint32(0xff0000fe)),
expectedOk: true,
},
{
name: "maximum boundary valid offset",
offset: 1,
memory: []byte{0x00, 0xcd, 0xcc, 0xcc, 0x3d},
expected: 0.1, // arbitrary valid v
expectedOk: true,
},
{
name: "offset exceeds the maximum valid offset by 1",
memory: []byte{0xff, 0xff, 0xff, 0xff},
offset: 1,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
memory := &MemoryInstance{Buffer: tc.memory}
v, ok := memory.ReadFloat32Le(tc.offset)
require.Equal(t, tc.expectedOk, ok)
require.Equal(t, tc.expected, v)
})
}
}
func TestMemoryInstance_ReadFloat64Le(t *testing.T) {
tests := []struct {
name string
memory []byte
offset uint32
expected float64
expectedOk bool
}{
{
name: "valid offset with an endian-insensitive v",
memory: []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff},
offset: 0, // arbitrary valid offset.
expected: math.Float64frombits(uint64(0xff000000000000ff)),
expectedOk: true,
},
{
name: "valid offset with an endian-sensitive v",
memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
offset: 0, // arbitrary valid offset.
expected: math.MaxFloat64, // arbitrary valid v
expectedOk: true,
},
{
name: "maximum boundary valid offset",
offset: 1,
memory: []byte{0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
expected: math.MaxFloat64, // arbitrary valid v
expectedOk: true,
},
{
name: "offset exceeds the maximum valid offset by 1",
memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
offset: 1,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
memory := &MemoryInstance{Buffer: tc.memory}
v, ok := memory.ReadFloat64Le(tc.offset)
require.Equal(t, tc.expectedOk, ok)
require.Equal(t, tc.expected, v)
})
}
}
func TestMemoryInstance_WriteUint32Le(t *testing.T) {
memory := &MemoryInstance{Buffer: make([]byte, 100)}
tests := []struct {
name string
offset uint32
v uint32
expectedOk bool
expectedBytes []byte
}{
{
name: "valid offset with an endian-insensitive v",
offset: 0, // arbitrary valid offset.
v: math.MaxUint32,
expectedOk: true,
expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
},
{
name: "valid offset with an endian-sensitive v",
offset: 0, // arbitrary valid offset.
v: math.MaxUint32 - 1,
expectedOk: true,
expectedBytes: []byte{0xfe, 0xff, 0xff, 0xff},
},
{
name: "maximum boundary valid offset",
offset: memory.Len() - 4, // 4 is the size of uint32
v: 1, // arbitrary valid v
expectedOk: true,
expectedBytes: []byte{0x1, 0x00, 0x00, 0x00},
},
{
name: "offset exceeds the maximum valid offset by 1",
offset: memory.Len() - 4 + 1, // 4 is the size of uint32
v: 1, // arbitrary valid v
expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.expectedOk, memory.WriteUint32Le(tc.offset, tc.v))
if tc.expectedOk {
require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of uint32
}
})
}
}
func TestMemoryInstance_WriteUint64Le(t *testing.T) {
memory := &MemoryInstance{Buffer: make([]byte, 100)}
tests := []struct {
name string
offset uint32
v uint64
expectedOk bool
expectedBytes []byte
}{
{
name: "valid offset with an endian-insensitive v",
offset: 0, // arbitrary valid offset.
v: math.MaxUint64,
expectedOk: true,
expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
},
{
name: "valid offset with an endian-sensitive v",
offset: 0, // arbitrary valid offset.
v: math.MaxUint64 - 1,
expectedOk: true,
expectedBytes: []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
},
{
name: "maximum boundary valid offset",
offset: memory.Len() - 8, // 8 is the size of uint64
v: 1, // arbitrary valid v
expectedOk: true,
expectedBytes: []byte{0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
},
{
name: "offset exceeds the maximum valid offset by 1",
offset: memory.Len() - 8 + 1, // 8 is the size of uint64
v: 1, // arbitrary valid v
expectedOk: false,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.expectedOk, memory.WriteUint64Le(tc.offset, tc.v))
if tc.expectedOk {
require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of uint64
}
})
}
}
func TestMemoryInstance_WriteFloat32Le(t *testing.T) {
memory := &MemoryInstance{Buffer: make([]byte, 100)}
tests := []struct {
name string
offset uint32
v float32
expectedOk bool
expectedBytes []byte
}{
{
name: "valid offset with an endian-insensitive v",
offset: 0, // arbitrary valid offset.
v: math.Float32frombits(uint32(0xff0000ff)),
expectedOk: true,
expectedBytes: []byte{0xff, 0x00, 0x00, 0xff},
},
{
name: "valid offset with an endian-sensitive v",
offset: 0, // arbitrary valid offset.
v: math.Float32frombits(uint32(0xff0000fe)), // arbitrary valid v
expectedOk: true,
expectedBytes: []byte{0xfe, 0x00, 0x00, 0xff},
},
{
name: "maximum boundary valid offset",
offset: memory.Len() - 4, // 4 is the size of float32
v: 0.1, // arbitrary valid v
expectedOk: true,
expectedBytes: []byte{0xcd, 0xcc, 0xcc, 0x3d},
},
{
name: "offset exceeds the maximum valid offset by 1",
offset: memory.Len() - 4 + 1, // 4 is the size of float32
v: math.MaxFloat32, // arbitrary valid v
expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.expectedOk, memory.WriteFloat32Le(tc.offset, tc.v))
if tc.expectedOk {
require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of float32
}
})
}
}
func TestMemoryInstance_WriteFloat64Le(t *testing.T) {
memory := &MemoryInstance{Buffer: make([]byte, 100)}
tests := []struct {
name string
offset uint32
v float64
expectedOk bool
expectedBytes []byte
}{
{
name: "valid offset with an endian-insensitive v",
offset: 0, // arbitrary valid offset.
v: math.Float64frombits(uint64(0xff000000000000ff)),
expectedOk: true,
expectedBytes: []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff},
},
{
name: "valid offset with an endian-sensitive v",
offset: 0, // arbitrary valid offset.
v: math.MaxFloat64, // arbitrary valid v
expectedOk: true,
expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
},
{
name: "maximum boundary valid offset",
offset: memory.Len() - 8, // 8 is the size of float64
v: math.MaxFloat64, // arbitrary valid v
expectedOk: true,
expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
},
{
name: "offset exceeds the maximum valid offset by 1",
offset: memory.Len() - 8 + 1, // 8 is the size of float64
v: math.MaxFloat64, // arbitrary valid v
expectedOk: false,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
require.Equal(t, tc.expectedOk, memory.WriteFloat64Le(tc.offset, tc.v))
if tc.expectedOk {
require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of float64
}
})
}
}