Files
wazero/internal/wasm/gofunc_test.go
Crypt Keeper 329ccca6b1 Switches from gofmt to gofumpt (#848)
This switches to gofumpt and applies changes, as I've noticed working
in dapr (who uses this) that it finds some things that are annoying,
such as inconsistent block formatting in test tables.

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2022-11-09 05:48:24 +01:00

283 lines
7.7 KiB
Go

package wasm
import (
"context"
"math"
"testing"
"unsafe"
"github.com/tetratelabs/wazero/api"
"github.com/tetratelabs/wazero/internal/testing/require"
)
// testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
func Test_parseGoFunc(t *testing.T) {
tests := []struct {
name string
input interface{}
expectNeedsModule bool
expectedType *FunctionType
}{
{
name: "() -> ()",
input: func() {},
expectedType: &FunctionType{},
},
{
name: "(ctx) -> ()",
input: func(context.Context) {},
expectedType: &FunctionType{},
},
{
name: "(ctx, mod) -> ()",
input: func(context.Context, api.Module) {},
expectNeedsModule: true,
expectedType: &FunctionType{},
},
{
name: "all supported params and i32 result",
input: func(uint32, uint64, float32, float64, uintptr) uint32 { return 0 },
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}},
},
{
name: "all supported params and i32 result - (ctx)",
input: func(context.Context, uint32, uint64, float32, float64, uintptr) uint32 { return 0 },
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}},
},
{
name: "all supported params and i32 result - (ctx, mod)",
input: func(context.Context, api.Module, uint32, uint64, float32, float64, uintptr) uint32 { return 0 },
expectNeedsModule: true,
expectedType: &FunctionType{Params: []ValueType{i32, i64, f32, f64, externref}, Results: []ValueType{i32}},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
paramTypes, resultTypes, code, err := parseGoReflectFunc(tc.input)
require.NoError(t, err)
_, isModuleFunc := code.GoFunc.(api.GoModuleFunction)
require.Equal(t, tc.expectNeedsModule, isModuleFunc)
require.Equal(t, tc.expectedType, &FunctionType{Params: paramTypes, Results: resultTypes})
})
}
}
func Test_parseGoFunc_Errors(t *testing.T) {
tests := []struct {
name string
input interface{}
expectedErr string
}{
{
name: "module no context",
input: func(api.Module) {},
expectedErr: "invalid signature: api.Module parameter must be preceded by context.Context",
},
{
name: "not a func",
input: struct{}{},
expectedErr: "kind != func: struct",
},
{
name: "unsupported param",
input: func(context.Context, uint32, string) {},
expectedErr: "param[2] is unsupported: string",
},
{
name: "unsupported result",
input: func() string { return "" },
expectedErr: "result[0] is unsupported: string",
},
{
name: "error result",
input: func() error { return nil },
expectedErr: "result[0] is an error, which is unsupported",
},
{
name: "incorrect order",
input: func(api.Module, context.Context) error { return nil },
expectedErr: "invalid signature: api.Module parameter must be preceded by context.Context",
},
{
name: "multiple context.Context",
input: func(context.Context, uint64, context.Context) error { return nil },
expectedErr: "param[2] is a context.Context, which may be defined only once as param[0]",
},
{
name: "multiple wasm.Module",
input: func(context.Context, api.Module, uint64, api.Module) error { return nil },
expectedErr: "param[3] is a api.Module, which may be defined only once as param[0]",
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
_, _, _, err := parseGoReflectFunc(tc.input)
require.EqualError(t, err, tc.expectedErr)
})
}
}
// stack simulates the value stack in a way easy to be tested.
type stack struct {
vals []uint64
}
func (s *stack) pop() (result uint64) {
stackTopIndex := len(s.vals) - 1
result = s.vals[stackTopIndex]
s.vals = s.vals[:stackTopIndex]
return
}
func TestPopValues(t *testing.T) {
stackVals := []uint64{1, 2, 3, 4, 5, 6, 7}
tests := []struct {
name string
count int
expected []uint64
}{
{
name: "pop zero doesn't allocate a slice ",
},
{
name: "pop 1",
count: 1,
expected: []uint64{7},
},
{
name: "pop 2",
count: 2,
expected: []uint64{6, 7},
},
{
name: "pop 3",
count: 3,
expected: []uint64{5, 6, 7},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
vals := PopValues(tc.count, (&stack{stackVals}).pop)
require.Equal(t, tc.expected, vals)
})
}
}
func Test_callGoFunc(t *testing.T) {
tPtr := uintptr(unsafe.Pointer(t))
callCtx := &CallContext{}
tests := []struct {
name string
input interface{}
inputParams, expectedResults []uint64
}{
{
name: "() -> ()",
input: func() {},
},
{
name: "(ctx) -> ()",
input: func(ctx context.Context) {
require.Equal(t, testCtx, ctx)
},
},
{
name: "(ctx, mod) -> ()",
input: func(ctx context.Context, m api.Module) {
require.Equal(t, testCtx, ctx)
require.Equal(t, callCtx, m)
},
},
{
name: "all supported params and i32 result",
input: func(v uintptr, w uint32, x uint64, y float32, z float64) uint32 {
require.Equal(t, tPtr, v)
require.Equal(t, uint32(math.MaxUint32), w)
require.Equal(t, uint64(math.MaxUint64), x)
require.Equal(t, float32(math.MaxFloat32), y)
require.Equal(t, math.MaxFloat64, z)
return 100
},
inputParams: []uint64{
api.EncodeExternref(tPtr),
math.MaxUint32,
math.MaxUint64,
api.EncodeF32(math.MaxFloat32),
api.EncodeF64(math.MaxFloat64),
},
expectedResults: []uint64{100},
},
{
name: "all supported params and i32 result - (ctx)",
input: func(ctx context.Context, v uintptr, w uint32, x uint64, y float32, z float64) uint32 {
require.Equal(t, testCtx, ctx)
require.Equal(t, tPtr, v)
require.Equal(t, uint32(math.MaxUint32), w)
require.Equal(t, uint64(math.MaxUint64), x)
require.Equal(t, float32(math.MaxFloat32), y)
require.Equal(t, math.MaxFloat64, z)
return 100
},
inputParams: []uint64{
api.EncodeExternref(tPtr),
math.MaxUint32,
math.MaxUint64,
api.EncodeF32(math.MaxFloat32),
api.EncodeF64(math.MaxFloat64),
},
expectedResults: []uint64{100},
},
{
name: "all supported params and i32 result - (ctx, mod)",
input: func(ctx context.Context, m api.Module, v uintptr, w uint32, x uint64, y float32, z float64) uint32 {
require.Equal(t, testCtx, ctx)
require.Equal(t, callCtx, m)
require.Equal(t, tPtr, v)
require.Equal(t, uint32(math.MaxUint32), w)
require.Equal(t, uint64(math.MaxUint64), x)
require.Equal(t, float32(math.MaxFloat32), y)
require.Equal(t, math.MaxFloat64, z)
return 100
},
inputParams: []uint64{
api.EncodeExternref(tPtr),
math.MaxUint32,
math.MaxUint64,
api.EncodeF32(math.MaxFloat32),
api.EncodeF64(math.MaxFloat64),
},
expectedResults: []uint64{100},
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
_, _, code, err := parseGoReflectFunc(tc.input)
require.NoError(t, err)
var results []uint64
switch code.GoFunc.(type) {
case api.GoFunction:
results = code.GoFunc.(api.GoFunction).Call(testCtx, tc.inputParams)
case api.GoModuleFunction:
results = code.GoFunc.(api.GoModuleFunction).Call(testCtx, callCtx, tc.inputParams)
default:
t.Fatal("unexpected type.")
}
require.Equal(t, tc.expectedResults, results)
})
}
}