This changes the default random source to provide deterministic values similar to how nanotime and walltime do. This also prevents any worries about if wasm can deplete the host's underlying source of entropy. Signed-off-by: Adrian Cole <adrian@tetrate.io>
812 lines
21 KiB
Go
812 lines
21 KiB
Go
package wazero
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"io"
|
|
"io/fs"
|
|
"math"
|
|
"reflect"
|
|
"testing"
|
|
"testing/fstest"
|
|
|
|
internalsys "github.com/tetratelabs/wazero/internal/sys"
|
|
testfs "github.com/tetratelabs/wazero/internal/testing/fs"
|
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
"github.com/tetratelabs/wazero/internal/wasm"
|
|
"github.com/tetratelabs/wazero/sys"
|
|
)
|
|
|
|
func TestRuntimeConfig(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
with func(RuntimeConfig) RuntimeConfig
|
|
expected RuntimeConfig
|
|
}{
|
|
{
|
|
name: "bulk-memory-operations",
|
|
with: func(c RuntimeConfig) RuntimeConfig {
|
|
return c.WithFeatureBulkMemoryOperations(true)
|
|
},
|
|
expected: &runtimeConfig{
|
|
enabledFeatures: wasm.FeatureBulkMemoryOperations | wasm.FeatureReferenceTypes,
|
|
},
|
|
},
|
|
{
|
|
name: "multi-value",
|
|
with: func(c RuntimeConfig) RuntimeConfig {
|
|
return c.WithFeatureMultiValue(true)
|
|
},
|
|
expected: &runtimeConfig{
|
|
enabledFeatures: wasm.FeatureMultiValue,
|
|
},
|
|
},
|
|
{
|
|
name: "mutable-global",
|
|
with: func(c RuntimeConfig) RuntimeConfig {
|
|
return c.WithFeatureMutableGlobal(true)
|
|
},
|
|
expected: &runtimeConfig{
|
|
enabledFeatures: wasm.FeatureMutableGlobal,
|
|
},
|
|
},
|
|
{
|
|
name: "nontrapping-float-to-int-conversion",
|
|
with: func(c RuntimeConfig) RuntimeConfig {
|
|
return c.WithFeatureNonTrappingFloatToIntConversion(true)
|
|
},
|
|
expected: &runtimeConfig{
|
|
enabledFeatures: wasm.FeatureNonTrappingFloatToIntConversion,
|
|
},
|
|
},
|
|
{
|
|
name: "sign-extension-ops",
|
|
with: func(c RuntimeConfig) RuntimeConfig {
|
|
return c.WithFeatureSignExtensionOps(true)
|
|
},
|
|
expected: &runtimeConfig{
|
|
enabledFeatures: wasm.FeatureSignExtensionOps,
|
|
},
|
|
},
|
|
{
|
|
name: "REC-wasm-core-1-20191205",
|
|
with: func(c RuntimeConfig) RuntimeConfig {
|
|
return c.WithFeatureSignExtensionOps(true).WithWasmCore1()
|
|
},
|
|
expected: &runtimeConfig{
|
|
enabledFeatures: wasm.Features20191205,
|
|
},
|
|
},
|
|
{
|
|
name: "WD-wasm-core-2-20220419",
|
|
with: func(c RuntimeConfig) RuntimeConfig {
|
|
return c.WithFeatureMutableGlobal(false).WithWasmCore2()
|
|
},
|
|
expected: &runtimeConfig{
|
|
enabledFeatures: wasm.Features20220419,
|
|
},
|
|
},
|
|
{
|
|
name: "reference-types",
|
|
with: func(c RuntimeConfig) RuntimeConfig {
|
|
return c.WithFeatureReferenceTypes(true)
|
|
},
|
|
expected: &runtimeConfig{
|
|
enabledFeatures: wasm.FeatureBulkMemoryOperations | wasm.FeatureReferenceTypes,
|
|
},
|
|
},
|
|
{
|
|
name: "simd",
|
|
with: func(c RuntimeConfig) RuntimeConfig {
|
|
return c.WithFeatureSIMD(true)
|
|
},
|
|
expected: &runtimeConfig{
|
|
enabledFeatures: wasm.FeatureSIMD,
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
input := &runtimeConfig{}
|
|
rc := tc.with(input)
|
|
require.Equal(t, tc.expected, rc)
|
|
// The source wasn't modified
|
|
require.Equal(t, &runtimeConfig{}, input)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRuntimeConfig_FeatureToggle(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
feature wasm.Features
|
|
expectDefault bool
|
|
setFeature func(RuntimeConfig, bool) RuntimeConfig
|
|
}{
|
|
{
|
|
name: "bulk-memory-operations",
|
|
feature: wasm.FeatureBulkMemoryOperations,
|
|
expectDefault: false,
|
|
setFeature: func(c RuntimeConfig, v bool) RuntimeConfig {
|
|
return c.WithFeatureBulkMemoryOperations(v)
|
|
},
|
|
},
|
|
{
|
|
name: "multi-value",
|
|
feature: wasm.FeatureMultiValue,
|
|
expectDefault: false,
|
|
setFeature: func(c RuntimeConfig, v bool) RuntimeConfig {
|
|
return c.WithFeatureMultiValue(v)
|
|
},
|
|
},
|
|
{
|
|
name: "mutable-global",
|
|
feature: wasm.FeatureMutableGlobal,
|
|
expectDefault: true,
|
|
setFeature: func(c RuntimeConfig, v bool) RuntimeConfig {
|
|
return c.WithFeatureMutableGlobal(v)
|
|
},
|
|
},
|
|
{
|
|
name: "nontrapping-float-to-int-conversion",
|
|
feature: wasm.FeatureNonTrappingFloatToIntConversion,
|
|
expectDefault: false,
|
|
setFeature: func(c RuntimeConfig, v bool) RuntimeConfig {
|
|
return c.WithFeatureNonTrappingFloatToIntConversion(v)
|
|
},
|
|
},
|
|
{
|
|
name: "sign-extension-ops",
|
|
feature: wasm.FeatureSignExtensionOps,
|
|
expectDefault: false,
|
|
setFeature: func(c RuntimeConfig, v bool) RuntimeConfig {
|
|
return c.WithFeatureSignExtensionOps(v)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c := NewRuntimeConfig().(*runtimeConfig)
|
|
require.Equal(t, tc.expectDefault, c.enabledFeatures.Get(tc.feature))
|
|
|
|
// Set to false even if it was initially false.
|
|
c = tc.setFeature(c, false).(*runtimeConfig)
|
|
require.False(t, c.enabledFeatures.Get(tc.feature))
|
|
|
|
// Set true makes it true
|
|
c = tc.setFeature(c, true).(*runtimeConfig)
|
|
require.True(t, c.enabledFeatures.Get(tc.feature))
|
|
|
|
// Set false makes it false again
|
|
c = tc.setFeature(c, false).(*runtimeConfig)
|
|
require.False(t, c.enabledFeatures.Get(tc.feature))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompileConfig(t *testing.T) {
|
|
mp := func(minPages uint32, maxPages *uint32) (min, capacity, max uint32) {
|
|
return 0, 1, 1
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
with func(CompileConfig) CompileConfig
|
|
expected *compileConfig
|
|
}{
|
|
{
|
|
name: "WithMemorySizer",
|
|
with: func(c CompileConfig) CompileConfig {
|
|
return c.WithMemorySizer(mp)
|
|
},
|
|
expected: &compileConfig{memorySizer: mp},
|
|
},
|
|
{
|
|
name: "WithMemorySizer twice",
|
|
with: func(c CompileConfig) CompileConfig {
|
|
return c.WithMemorySizer(wasm.MemorySizer).WithMemorySizer(mp)
|
|
},
|
|
expected: &compileConfig{memorySizer: mp},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
input := &compileConfig{}
|
|
rc := tc.with(input).(*compileConfig)
|
|
|
|
// We cannot compare func, but we can compare reflect.Value
|
|
// See https://go.dev/ref/spec#Comparison_operators
|
|
require.Equal(t, reflect.ValueOf(tc.expected.memorySizer), reflect.ValueOf(rc.memorySizer))
|
|
// The source wasn't modified
|
|
require.Equal(t, &compileConfig{}, input)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestModuleConfig(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
with func(ModuleConfig) ModuleConfig
|
|
expected string
|
|
}{
|
|
{
|
|
name: "WithName",
|
|
with: func(c ModuleConfig) ModuleConfig {
|
|
return c.WithName("wazero")
|
|
},
|
|
expected: "wazero",
|
|
},
|
|
{
|
|
name: "WithName empty",
|
|
with: func(c ModuleConfig) ModuleConfig {
|
|
return c.WithName("")
|
|
},
|
|
},
|
|
{
|
|
name: "WithName twice",
|
|
with: func(c ModuleConfig) ModuleConfig {
|
|
return c.WithName("wazero").WithName("wa0")
|
|
},
|
|
expected: "wa0",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
input := NewModuleConfig()
|
|
rc := tc.with(input)
|
|
require.Equal(t, tc.expected, rc.(*moduleConfig).name)
|
|
// The source wasn't modified
|
|
require.Equal(t, NewModuleConfig(), input)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestModuleConfig_toSysContext only tests the cases that change the inputs to
|
|
// sys.NewContext.
|
|
func TestModuleConfig_toSysContext(t *testing.T) {
|
|
// Always assigns clocks so that pointers are constant.
|
|
var wt sys.Walltime = func(context.Context) (int64, int32) {
|
|
return 0, 0
|
|
}
|
|
var nt sys.Nanotime = func(context.Context) int64 {
|
|
return 0
|
|
}
|
|
base := NewModuleConfig()
|
|
base.(*moduleConfig).walltime = &wt
|
|
base.(*moduleConfig).walltimeResolution = 1
|
|
base.(*moduleConfig).nanotime = &nt
|
|
base.(*moduleConfig).nanotimeResolution = 1
|
|
|
|
testFS := testfs.FS{}
|
|
testFS2 := testfs.FS{"/": &testfs.File{}}
|
|
|
|
tests := []struct {
|
|
name string
|
|
input ModuleConfig
|
|
expected *internalsys.Context
|
|
}{
|
|
{
|
|
name: "empty",
|
|
input: base,
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
nil, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // randSource
|
|
&wt, 1, // walltime, walltimeResolution
|
|
&nt, 1, // nanotime, nanotimeResolution
|
|
nil, // nanosleep
|
|
nil, // fs
|
|
),
|
|
},
|
|
{
|
|
name: "WithArgs",
|
|
input: base.WithArgs("a", "bc"),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
[]string{"a", "bc"}, // args
|
|
nil, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // randSource
|
|
&wt, 1, // walltime, walltimeResolution
|
|
&nt, 1, // nanotime, nanotimeResolution
|
|
nil, // nanosleep
|
|
nil, // fs
|
|
),
|
|
},
|
|
{
|
|
name: "WithArgs empty ok", // Particularly argv[0] can be empty, and we have no rules about others.
|
|
input: base.WithArgs("", "bc"),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
[]string{"", "bc"}, // args
|
|
nil, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // randSource
|
|
&wt, 1, // walltime, walltimeResolution
|
|
&nt, 1, // nanotime, nanotimeResolution
|
|
nil, // nanosleep
|
|
nil, // fs
|
|
),
|
|
},
|
|
{
|
|
name: "WithArgs second call overwrites",
|
|
input: base.WithArgs("a", "bc").WithArgs("bc", "a"),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
[]string{"bc", "a"}, // args
|
|
nil, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // randSource
|
|
&wt, 1, // walltime, walltimeResolution
|
|
&nt, 1, // nanotime, nanotimeResolution
|
|
nil, // nanosleep
|
|
nil, // fs
|
|
),
|
|
},
|
|
{
|
|
name: "WithEnv",
|
|
input: base.WithEnv("a", "b"),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
[]string{"a=b"}, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // randSource
|
|
&wt, 1, // walltime, walltimeResolution
|
|
&nt, 1, // nanotime, nanotimeResolution
|
|
nil, // nanosleep
|
|
nil, // fs
|
|
),
|
|
},
|
|
{
|
|
name: "WithEnv empty value",
|
|
input: base.WithEnv("a", ""),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
[]string{"a="}, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // randSource
|
|
&wt, 1, // walltime, walltimeResolution
|
|
&nt, 1, // nanotime, nanotimeResolution
|
|
nil, // nanosleep
|
|
nil, // fs
|
|
),
|
|
},
|
|
{
|
|
name: "WithEnv twice",
|
|
input: base.WithEnv("a", "b").WithEnv("c", "de"),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
[]string{"a=b", "c=de"}, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // randSource
|
|
&wt, 1, // walltime, walltimeResolution
|
|
&nt, 1, // nanotime, nanotimeResolution
|
|
nil, // nanosleep
|
|
nil, // fs
|
|
),
|
|
},
|
|
{
|
|
name: "WithEnv overwrites",
|
|
input: base.WithEnv("a", "bc").WithEnv("c", "de").WithEnv("a", "de"),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
[]string{"a=de", "c=de"}, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // randSource
|
|
&wt, 1, // walltime, walltimeResolution
|
|
&nt, 1, // nanotime, nanotimeResolution
|
|
nil, // nanosleep
|
|
nil, // fs
|
|
),
|
|
},
|
|
{
|
|
name: "WithEnv twice",
|
|
input: base.WithEnv("a", "b").WithEnv("c", "de"),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
[]string{"a=b", "c=de"}, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // randSource
|
|
&wt, 1, // walltime, walltimeResolution
|
|
&nt, 1, // nanotime, nanotimeResolution
|
|
nil, // nanosleep
|
|
nil, // fs
|
|
),
|
|
},
|
|
{
|
|
name: "WithFS",
|
|
input: base.WithFS(testFS),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
nil, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // randSource
|
|
&wt, 1, // walltime, walltimeResolution
|
|
&nt, 1, // nanotime, nanotimeResolution
|
|
nil, // nanosleep
|
|
testFS,
|
|
),
|
|
},
|
|
{
|
|
name: "WithFS overwrites",
|
|
input: base.WithFS(testFS).WithFS(testFS2),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
nil, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // randSource
|
|
&wt, 1, // walltime, walltimeResolution
|
|
&nt, 1, // nanotime, nanotimeResolution
|
|
nil, // nanosleep
|
|
testFS2, // fs
|
|
),
|
|
},
|
|
{
|
|
name: "WithRandSource",
|
|
input: base.WithRandSource(rand.Reader),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
nil, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
rand.Reader, // randSource
|
|
&wt, 1, // walltime, walltimeResolution
|
|
&nt, 1, // nanotime, nanotimeResolution
|
|
nil, // nanosleep
|
|
nil, // fs
|
|
),
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
sysCtx, err := tc.input.(*moduleConfig).toSysContext()
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expected, sysCtx)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestModuleConfig_toSysContext_WithWalltime has to test differently because we can't
|
|
// compare function pointers when functions are passed by value.
|
|
func TestModuleConfig_toSysContext_WithWalltime(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input ModuleConfig
|
|
expectedSec int64
|
|
expectedNsec int32
|
|
expectedResolution sys.ClockResolution
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "ok",
|
|
input: NewModuleConfig().
|
|
WithWalltime(func(context.Context) (sec int64, nsec int32) {
|
|
return 1, 2
|
|
}, 3),
|
|
expectedSec: 1,
|
|
expectedNsec: 2,
|
|
expectedResolution: 3,
|
|
},
|
|
{
|
|
name: "overwrites",
|
|
input: NewModuleConfig().
|
|
WithWalltime(func(context.Context) (sec int64, nsec int32) {
|
|
return 3, 4
|
|
}, 5).
|
|
WithWalltime(func(context.Context) (sec int64, nsec int32) {
|
|
return 1, 2
|
|
}, 3),
|
|
expectedSec: 1,
|
|
expectedNsec: 2,
|
|
expectedResolution: 3,
|
|
},
|
|
{
|
|
name: "invalid resolution",
|
|
input: NewModuleConfig().
|
|
WithWalltime(func(context.Context) (sec int64, nsec int32) {
|
|
return 1, 2
|
|
}, 0),
|
|
expectedErr: "invalid Walltime resolution: 0",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
sysCtx, err := tc.input.(*moduleConfig).toSysContext()
|
|
if tc.expectedErr == "" {
|
|
require.Nil(t, err)
|
|
sec, nsec := sysCtx.Walltime(testCtx)
|
|
require.Equal(t, tc.expectedSec, sec)
|
|
require.Equal(t, tc.expectedNsec, nsec)
|
|
require.Equal(t, tc.expectedResolution, sysCtx.WalltimeResolution())
|
|
} else {
|
|
require.EqualError(t, err, tc.expectedErr)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("context", func(t *testing.T) {
|
|
sysCtx, err := NewModuleConfig().
|
|
WithWalltime(func(ctx context.Context) (sec int64, nsec int32) {
|
|
require.Equal(t, testCtx, ctx)
|
|
return 1, 2
|
|
}, 3).(*moduleConfig).toSysContext()
|
|
require.NoError(t, err)
|
|
sec, nsec := sysCtx.Walltime(testCtx)
|
|
// If below pass, the context was correct!
|
|
require.Equal(t, int64(1), sec)
|
|
require.Equal(t, int32(2), nsec)
|
|
})
|
|
}
|
|
|
|
// TestModuleConfig_toSysContext_WithNanotime has to test differently because we can't
|
|
// compare function pointers when functions are passed by value.
|
|
func TestModuleConfig_toSysContext_WithNanotime(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input ModuleConfig
|
|
expectedNanos int64
|
|
expectedResolution sys.ClockResolution
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "ok",
|
|
input: NewModuleConfig().
|
|
WithNanotime(func(context.Context) int64 {
|
|
return 1
|
|
}, 2),
|
|
expectedNanos: 1,
|
|
expectedResolution: 2,
|
|
},
|
|
{
|
|
name: "overwrites",
|
|
input: NewModuleConfig().
|
|
WithNanotime(func(context.Context) int64 {
|
|
return 3
|
|
}, 4).
|
|
WithNanotime(func(context.Context) int64 {
|
|
return 1
|
|
}, 2),
|
|
expectedNanos: 1,
|
|
expectedResolution: 2,
|
|
},
|
|
{
|
|
name: "invalid resolution",
|
|
input: NewModuleConfig().
|
|
WithNanotime(func(context.Context) int64 {
|
|
return 1
|
|
}, 0),
|
|
expectedErr: "invalid Nanotime resolution: 0",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
sysCtx, err := tc.input.(*moduleConfig).toSysContext()
|
|
if tc.expectedErr == "" {
|
|
require.Nil(t, err)
|
|
nanos := sysCtx.Nanotime(testCtx)
|
|
require.Equal(t, tc.expectedNanos, nanos)
|
|
require.Equal(t, tc.expectedResolution, sysCtx.NanotimeResolution())
|
|
} else {
|
|
require.EqualError(t, err, tc.expectedErr)
|
|
}
|
|
})
|
|
}
|
|
|
|
t.Run("context", func(t *testing.T) {
|
|
sysCtx, err := NewModuleConfig().
|
|
WithNanotime(func(ctx context.Context) int64 {
|
|
require.Equal(t, testCtx, ctx)
|
|
return 1
|
|
}, 2).(*moduleConfig).toSysContext()
|
|
require.NoError(t, err)
|
|
// If below pass, the context was correct!
|
|
require.Equal(t, int64(1), sysCtx.Nanotime(testCtx))
|
|
})
|
|
}
|
|
|
|
// TestModuleConfig_toSysContext_WithNanosleep has to test differently because
|
|
// we can't compare function pointers when functions are passed by value.
|
|
func TestModuleConfig_toSysContext_WithNanosleep(t *testing.T) {
|
|
sysCtx, err := NewModuleConfig().
|
|
WithNanosleep(func(ctx context.Context, ns int64) {
|
|
require.Equal(t, testCtx, ctx)
|
|
}).(*moduleConfig).toSysContext()
|
|
require.NoError(t, err)
|
|
// If below pass, the context was correct!
|
|
sysCtx.Nanosleep(testCtx, 2)
|
|
}
|
|
|
|
func TestModuleConfig_toSysContext_Errors(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input ModuleConfig
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "WithArgs arg contains NUL",
|
|
input: NewModuleConfig().WithArgs("", string([]byte{'a', 0})),
|
|
expectedErr: "args invalid: contains NUL character",
|
|
},
|
|
{
|
|
name: "WithEnv key contains NUL",
|
|
input: NewModuleConfig().WithEnv(string([]byte{'a', 0}), "a"),
|
|
expectedErr: "environ invalid: contains NUL character",
|
|
},
|
|
{
|
|
name: "WithEnv value contains NUL",
|
|
input: NewModuleConfig().WithEnv("a", string([]byte{'a', 0})),
|
|
expectedErr: "environ invalid: contains NUL character",
|
|
},
|
|
{
|
|
name: "WithEnv key contains equals",
|
|
input: NewModuleConfig().WithEnv("a=", "a"),
|
|
expectedErr: "environ invalid: key contains '=' character",
|
|
},
|
|
{
|
|
name: "WithEnv empty key",
|
|
input: NewModuleConfig().WithEnv("", "a"),
|
|
expectedErr: "environ invalid: empty key",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := tc.input.(*moduleConfig).toSysContext()
|
|
require.EqualError(t, err, tc.expectedErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestModuleConfig_clone(t *testing.T) {
|
|
mc := NewModuleConfig().(*moduleConfig)
|
|
cloned := mc.clone()
|
|
|
|
// Make post-clone changes
|
|
mc.fs = fstest.MapFS{}
|
|
mc.environKeys["2"] = 2
|
|
|
|
cloned.environKeys["1"] = 1
|
|
|
|
// Ensure the maps are not shared
|
|
require.Equal(t, map[string]int{"2": 2}, mc.environKeys)
|
|
require.Equal(t, map[string]int{"1": 1}, cloned.environKeys)
|
|
|
|
// Ensure the fs is not shared
|
|
require.Nil(t, cloned.fs)
|
|
}
|
|
|
|
func Test_compiledModule_Name(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input *compiledModule
|
|
expected string
|
|
}{
|
|
{
|
|
name: "no name section",
|
|
input: &compiledModule{module: &wasm.Module{}},
|
|
},
|
|
{
|
|
name: "empty name",
|
|
input: &compiledModule{module: &wasm.Module{NameSection: &wasm.NameSection{}}},
|
|
},
|
|
{
|
|
name: "name",
|
|
input: &compiledModule{module: &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "foo"}}},
|
|
expected: "foo",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
require.Equal(t, tc.expected, tc.input.Name())
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_compiledModule_Close(t *testing.T) {
|
|
for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil!
|
|
e := &mockEngine{name: "1", cachedModules: map[*wasm.Module]struct{}{}}
|
|
|
|
var cs []*compiledModule
|
|
for i := 0; i < 10; i++ {
|
|
m := &wasm.Module{}
|
|
err := e.CompileModule(ctx, m)
|
|
require.NoError(t, err)
|
|
cs = append(cs, &compiledModule{module: m, compiledEngine: e})
|
|
}
|
|
|
|
// Before Close.
|
|
require.Equal(t, 10, len(e.cachedModules))
|
|
|
|
for _, c := range cs {
|
|
require.NoError(t, c.Close(ctx))
|
|
}
|
|
|
|
// After Close.
|
|
require.Zero(t, len(e.cachedModules))
|
|
}
|
|
}
|
|
|
|
// requireSysContext ensures wasm.NewContext doesn't return an error, which makes it usable in test matrices.
|
|
func requireSysContext(
|
|
t *testing.T,
|
|
max uint32,
|
|
args, environ []string,
|
|
stdin io.Reader,
|
|
stdout, stderr io.Writer,
|
|
randSource io.Reader,
|
|
walltime *sys.Walltime, walltimeResolution sys.ClockResolution,
|
|
nanotime *sys.Nanotime, nanotimeResolution sys.ClockResolution,
|
|
nanosleep *sys.Nanosleep,
|
|
fs fs.FS,
|
|
) *internalsys.Context {
|
|
sysCtx, err := internalsys.NewContext(
|
|
max,
|
|
args,
|
|
environ,
|
|
stdin,
|
|
stdout,
|
|
stderr,
|
|
randSource,
|
|
walltime, walltimeResolution,
|
|
nanotime, nanotimeResolution,
|
|
nanosleep,
|
|
fs,
|
|
)
|
|
require.NoError(t, err)
|
|
return sysCtx
|
|
}
|