This prepares for exposing operations like Memory.Grow while keeping the ability to trace what did that, by adding a `context.Context` initial parameter. This adds this to all API methods that mutate or return mutated data. Before, we made a change to trace functions and general lifecycle commands, but we missed this part. Ex. We track functions, but can't track what closed the module, changed memory or a mutable constant. Changing to do this now is not only more consistent, but helps us optimize at least the interpreter to help users identify otherwise opaque code that can cause harm. This is critical before we add more functions that can cause harm, such as Memory.Grow. Signed-off-by: Adrian Cole <adrian@tetrate.io>
805 lines
21 KiB
Go
805 lines
21 KiB
Go
package wazero
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"math"
|
|
"testing"
|
|
"testing/fstest"
|
|
|
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
"github.com/tetratelabs/wazero/internal/wasm"
|
|
)
|
|
|
|
func TestRuntimeConfig(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
with func(*RuntimeConfig) *RuntimeConfig
|
|
expected *RuntimeConfig
|
|
}{
|
|
{
|
|
name: "WithMemoryMaxPages",
|
|
with: func(c *RuntimeConfig) *RuntimeConfig {
|
|
return c.WithMemoryMaxPages(1)
|
|
},
|
|
expected: &RuntimeConfig{
|
|
memoryMaxPages: 1,
|
|
},
|
|
},
|
|
{
|
|
name: "mutable-global",
|
|
with: func(c *RuntimeConfig) *RuntimeConfig {
|
|
return c.WithFeatureMutableGlobal(true)
|
|
},
|
|
expected: &RuntimeConfig{
|
|
enabledFeatures: wasm.FeatureMutableGlobal,
|
|
},
|
|
},
|
|
{
|
|
name: "sign-extension-ops",
|
|
with: func(c *RuntimeConfig) *RuntimeConfig {
|
|
return c.WithFeatureSignExtensionOps(true)
|
|
},
|
|
expected: &RuntimeConfig{
|
|
enabledFeatures: wasm.FeatureSignExtensionOps,
|
|
},
|
|
},
|
|
{
|
|
name: "nontrapping-float-to-int-conversion",
|
|
with: func(c *RuntimeConfig) *RuntimeConfig {
|
|
return c.WithFeatureNonTrappingFloatToIntConversion(true)
|
|
},
|
|
expected: &RuntimeConfig{
|
|
enabledFeatures: wasm.FeatureNonTrappingFloatToIntConversion,
|
|
},
|
|
},
|
|
}
|
|
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: "mutable-global",
|
|
feature: wasm.FeatureMutableGlobal,
|
|
expectDefault: true,
|
|
setFeature: func(c *RuntimeConfig, v bool) *RuntimeConfig {
|
|
return c.WithFeatureMutableGlobal(v)
|
|
},
|
|
},
|
|
{
|
|
name: "sign-extension-ops",
|
|
feature: wasm.FeatureSignExtensionOps,
|
|
expectDefault: false,
|
|
setFeature: func(c *RuntimeConfig, v bool) *RuntimeConfig {
|
|
return c.WithFeatureSignExtensionOps(v)
|
|
},
|
|
},
|
|
{
|
|
name: "nontrapping-float-to-int-conversion",
|
|
feature: wasm.FeatureNonTrappingFloatToIntConversion,
|
|
expectDefault: false,
|
|
setFeature: func(c *RuntimeConfig, v bool) *RuntimeConfig {
|
|
return c.WithFeatureNonTrappingFloatToIntConversion(v)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c := NewRuntimeConfig()
|
|
require.Equal(t, tc.expectDefault, c.enabledFeatures.Get(tc.feature))
|
|
|
|
// Set to false even if it was initially false.
|
|
c = tc.setFeature(c, false)
|
|
require.False(t, c.enabledFeatures.Get(tc.feature))
|
|
|
|
// Set true makes it true
|
|
c = tc.setFeature(c, true)
|
|
require.True(t, c.enabledFeatures.Get(tc.feature))
|
|
|
|
// Set false makes it false again
|
|
c = tc.setFeature(c, false)
|
|
require.False(t, c.enabledFeatures.Get(tc.feature))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestModuleConfig(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
with func(*ModuleConfig) *ModuleConfig
|
|
expected *ModuleConfig
|
|
}{
|
|
{
|
|
name: "WithName",
|
|
with: func(c *ModuleConfig) *ModuleConfig {
|
|
return c.WithName("wazero")
|
|
},
|
|
expected: &ModuleConfig{
|
|
name: "wazero",
|
|
},
|
|
},
|
|
{
|
|
name: "WithName - empty",
|
|
with: func(c *ModuleConfig) *ModuleConfig {
|
|
return c.WithName("")
|
|
},
|
|
expected: &ModuleConfig{},
|
|
},
|
|
{
|
|
name: "WithImport",
|
|
with: func(c *ModuleConfig) *ModuleConfig {
|
|
return c.WithImport("env", "abort", "assemblyscript", "abort")
|
|
},
|
|
expected: &ModuleConfig{
|
|
replacedImports: map[string][2]string{"env\000abort": {"assemblyscript", "abort"}},
|
|
},
|
|
},
|
|
{
|
|
name: "WithImport - empty to non-empty - module",
|
|
with: func(c *ModuleConfig) *ModuleConfig {
|
|
return c.WithImport("", "abort", "assemblyscript", "abort")
|
|
},
|
|
expected: &ModuleConfig{
|
|
replacedImports: map[string][2]string{"\000abort": {"assemblyscript", "abort"}},
|
|
},
|
|
},
|
|
{
|
|
name: "WithImport - non-empty to empty - module",
|
|
with: func(c *ModuleConfig) *ModuleConfig {
|
|
return c.WithImport("env", "abort", "", "abort")
|
|
},
|
|
expected: &ModuleConfig{
|
|
replacedImports: map[string][2]string{"env\000abort": {"", "abort"}},
|
|
},
|
|
},
|
|
{
|
|
name: "WithImport - empty to non-empty - name",
|
|
with: func(c *ModuleConfig) *ModuleConfig {
|
|
return c.WithImport("env", "", "assemblyscript", "abort")
|
|
},
|
|
expected: &ModuleConfig{
|
|
replacedImports: map[string][2]string{"env\000": {"assemblyscript", "abort"}},
|
|
},
|
|
},
|
|
{
|
|
name: "WithImport - non-empty to empty - name",
|
|
with: func(c *ModuleConfig) *ModuleConfig {
|
|
return c.WithImport("env", "abort", "assemblyscript", "")
|
|
},
|
|
expected: &ModuleConfig{
|
|
replacedImports: map[string][2]string{"env\000abort": {"assemblyscript", ""}},
|
|
},
|
|
},
|
|
{
|
|
name: "WithImport - override",
|
|
with: func(c *ModuleConfig) *ModuleConfig {
|
|
return c.WithImport("env", "abort", "assemblyscript", "abort").
|
|
WithImport("env", "abort", "go", "exit")
|
|
},
|
|
expected: &ModuleConfig{
|
|
replacedImports: map[string][2]string{"env\000abort": {"go", "exit"}},
|
|
},
|
|
},
|
|
{
|
|
name: "WithImport - twice",
|
|
with: func(c *ModuleConfig) *ModuleConfig {
|
|
return c.WithImport("env", "abort", "assemblyscript", "abort").
|
|
WithImport("wasi_unstable", "proc_exit", "wasi_snapshot_preview1", "proc_exit")
|
|
},
|
|
expected: &ModuleConfig{
|
|
replacedImports: map[string][2]string{
|
|
"env\000abort": {"assemblyscript", "abort"},
|
|
"wasi_unstable\000proc_exit": {"wasi_snapshot_preview1", "proc_exit"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "WithImportModule",
|
|
with: func(c *ModuleConfig) *ModuleConfig {
|
|
return c.WithImportModule("env", "assemblyscript")
|
|
},
|
|
expected: &ModuleConfig{
|
|
replacedImportModules: map[string]string{"env": "assemblyscript"},
|
|
},
|
|
},
|
|
{
|
|
name: "WithImportModule - empty to non-empty",
|
|
with: func(c *ModuleConfig) *ModuleConfig {
|
|
return c.WithImportModule("", "assemblyscript")
|
|
},
|
|
expected: &ModuleConfig{
|
|
replacedImportModules: map[string]string{"": "assemblyscript"},
|
|
},
|
|
},
|
|
{
|
|
name: "WithImportModule - non-empty to empty",
|
|
with: func(c *ModuleConfig) *ModuleConfig {
|
|
return c.WithImportModule("env", "")
|
|
},
|
|
expected: &ModuleConfig{
|
|
replacedImportModules: map[string]string{"env": ""},
|
|
},
|
|
},
|
|
{
|
|
name: "WithImportModule - override",
|
|
with: func(c *ModuleConfig) *ModuleConfig {
|
|
return c.WithImportModule("env", "assemblyscript").
|
|
WithImportModule("env", "go")
|
|
},
|
|
expected: &ModuleConfig{
|
|
replacedImportModules: map[string]string{"env": "go"},
|
|
},
|
|
},
|
|
{
|
|
name: "WithImportModule - twice",
|
|
with: func(c *ModuleConfig) *ModuleConfig {
|
|
return c.WithImportModule("env", "go").
|
|
WithImportModule("wasi_unstable", "wasi_snapshot_preview1")
|
|
},
|
|
expected: &ModuleConfig{
|
|
replacedImportModules: map[string]string{
|
|
"env": "go",
|
|
"wasi_unstable": "wasi_snapshot_preview1",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
input := &ModuleConfig{}
|
|
rc := tc.with(input)
|
|
require.Equal(t, tc.expected, rc)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestModuleConfig_replaceImports(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
config *ModuleConfig
|
|
input *wasm.Module
|
|
expected *wasm.Module
|
|
expectSame bool
|
|
}{
|
|
{
|
|
name: "no config, no imports",
|
|
config: &ModuleConfig{},
|
|
input: &wasm.Module{},
|
|
expected: &wasm.Module{},
|
|
expectSame: true,
|
|
},
|
|
{
|
|
name: "no config",
|
|
config: &ModuleConfig{},
|
|
input: &wasm.Module{
|
|
ImportSection: []*wasm.Import{
|
|
{
|
|
Module: "wasi_snapshot_preview1", Name: "args_sizes_get",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 0,
|
|
},
|
|
{
|
|
Module: "wasi_snapshot_preview1", Name: "fd_write",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 2,
|
|
},
|
|
},
|
|
},
|
|
expectSame: true,
|
|
},
|
|
{
|
|
name: "replacedImportModules",
|
|
config: &ModuleConfig{
|
|
replacedImportModules: map[string]string{"wasi_unstable": "wasi_snapshot_preview1"},
|
|
},
|
|
input: &wasm.Module{
|
|
ImportSection: []*wasm.Import{
|
|
{
|
|
Module: "wasi_unstable", Name: "args_sizes_get",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 0,
|
|
},
|
|
{
|
|
Module: "wasi_unstable", Name: "fd_write",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 2,
|
|
},
|
|
},
|
|
},
|
|
expected: &wasm.Module{
|
|
ImportSection: []*wasm.Import{
|
|
{
|
|
Module: "wasi_snapshot_preview1", Name: "args_sizes_get",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 0,
|
|
},
|
|
{
|
|
Module: "wasi_snapshot_preview1", Name: "fd_write",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "replacedImportModules doesn't match",
|
|
config: &ModuleConfig{
|
|
replacedImportModules: map[string]string{"env": ""},
|
|
},
|
|
input: &wasm.Module{
|
|
ImportSection: []*wasm.Import{
|
|
{
|
|
Module: "wasi_snapshot_preview1", Name: "args_sizes_get",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 0,
|
|
},
|
|
{
|
|
Module: "wasi_snapshot_preview1", Name: "fd_write",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 2,
|
|
},
|
|
},
|
|
},
|
|
expectSame: true,
|
|
},
|
|
{
|
|
name: "replacedImports",
|
|
config: &ModuleConfig{
|
|
replacedImports: map[string][2]string{"env\000abort": {"assemblyscript", "abort"}},
|
|
},
|
|
input: &wasm.Module{
|
|
ImportSection: []*wasm.Import{
|
|
{
|
|
Module: "env", Name: "abort",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 0,
|
|
},
|
|
{
|
|
Module: "env", Name: "seed",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 2,
|
|
},
|
|
},
|
|
},
|
|
expected: &wasm.Module{
|
|
ImportSection: []*wasm.Import{
|
|
{
|
|
Module: "assemblyscript", Name: "abort",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 0,
|
|
},
|
|
{
|
|
Module: "env", Name: "seed",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 2,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "replacedImports don't match",
|
|
config: &ModuleConfig{
|
|
replacedImports: map[string][2]string{"env\000abort": {"assemblyscript", "abort"}},
|
|
},
|
|
input: &wasm.Module{
|
|
ImportSection: []*wasm.Import{
|
|
{
|
|
Module: "wasi_snapshot_preview1", Name: "args_sizes_get",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 0,
|
|
},
|
|
{
|
|
Module: "wasi_snapshot_preview1", Name: "fd_write",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 2,
|
|
},
|
|
},
|
|
},
|
|
expectSame: true,
|
|
},
|
|
{
|
|
name: "replacedImportModules and replacedImports",
|
|
config: &ModuleConfig{
|
|
replacedImportModules: map[string]string{"js": "wasm"},
|
|
replacedImports: map[string][2]string{
|
|
"wasm\000increment": {"go", "increment"},
|
|
"wasm\000decrement": {"go", "decrement"},
|
|
},
|
|
},
|
|
input: &wasm.Module{
|
|
ImportSection: []*wasm.Import{
|
|
{
|
|
Module: "js", Name: "tbl",
|
|
Type: wasm.ExternTypeTable,
|
|
DescTable: &wasm.Table{Min: 4},
|
|
},
|
|
{
|
|
Module: "js", Name: "increment",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 0,
|
|
},
|
|
{
|
|
Module: "js", Name: "decrement",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 0,
|
|
},
|
|
{
|
|
Module: "js", Name: "wasm_increment",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 0,
|
|
},
|
|
{
|
|
Module: "js", Name: "wasm_increment",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 0,
|
|
},
|
|
},
|
|
},
|
|
expected: &wasm.Module{
|
|
ImportSection: []*wasm.Import{
|
|
{
|
|
Module: "wasm", Name: "tbl",
|
|
Type: wasm.ExternTypeTable,
|
|
DescTable: &wasm.Table{Min: 4},
|
|
},
|
|
{
|
|
Module: "go", Name: "increment",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 0,
|
|
},
|
|
{
|
|
Module: "go", Name: "decrement",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 0,
|
|
},
|
|
{
|
|
Module: "wasm", Name: "wasm_increment",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 0,
|
|
},
|
|
{
|
|
Module: "wasm", Name: "wasm_increment",
|
|
Type: wasm.ExternTypeFunc,
|
|
DescFunc: 0,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
actual := tc.config.replaceImports(tc.input)
|
|
if tc.expectSame {
|
|
require.Same(t, tc.input, actual)
|
|
} else {
|
|
require.NotSame(t, tc.input, actual)
|
|
require.Equal(t, tc.expected, actual)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestModuleConfig_toSysContext(t *testing.T) {
|
|
testFS := fstest.MapFS{}
|
|
testFS2 := fstest.MapFS{}
|
|
|
|
tests := []struct {
|
|
name string
|
|
input *ModuleConfig
|
|
expected *wasm.SysContext
|
|
}{
|
|
{
|
|
name: "empty",
|
|
input: NewModuleConfig(),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
nil, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // openedFiles
|
|
),
|
|
},
|
|
{
|
|
name: "WithArgs",
|
|
input: NewModuleConfig().WithArgs("a", "bc"),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
[]string{"a", "bc"}, // args
|
|
nil, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // openedFiles
|
|
),
|
|
},
|
|
{
|
|
name: "WithArgs - empty ok", // Particularly argv[0] can be empty, and we have no rules about others.
|
|
input: NewModuleConfig().WithArgs("", "bc"),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
[]string{"", "bc"}, // args
|
|
nil, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // openedFiles
|
|
),
|
|
},
|
|
{
|
|
name: "WithArgs - second call overwrites",
|
|
input: NewModuleConfig().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, // openedFiles
|
|
),
|
|
},
|
|
{
|
|
name: "WithEnv",
|
|
input: NewModuleConfig().WithEnv("a", "b"),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
[]string{"a=b"}, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // openedFiles
|
|
),
|
|
},
|
|
{
|
|
name: "WithEnv - empty value",
|
|
input: NewModuleConfig().WithEnv("a", ""),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
[]string{"a="}, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
nil, // openedFiles
|
|
),
|
|
},
|
|
{
|
|
name: "WithEnv twice",
|
|
input: NewModuleConfig().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, // openedFiles
|
|
),
|
|
},
|
|
{
|
|
name: "WithEnv overwrites",
|
|
input: NewModuleConfig().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, // openedFiles
|
|
),
|
|
},
|
|
|
|
{
|
|
name: "WithEnv twice",
|
|
input: NewModuleConfig().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, // openedFiles
|
|
),
|
|
},
|
|
{
|
|
name: "WithFS",
|
|
input: NewModuleConfig().WithFS(testFS),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
nil, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
map[uint32]*wasm.FileEntry{ // openedFiles
|
|
3: {Path: "/", FS: testFS},
|
|
4: {Path: ".", FS: testFS},
|
|
},
|
|
),
|
|
},
|
|
{
|
|
name: "WithFS - overwrites",
|
|
input: NewModuleConfig().WithFS(testFS).WithFS(testFS2),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
nil, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
map[uint32]*wasm.FileEntry{ // openedFiles
|
|
3: {Path: "/", FS: testFS2},
|
|
4: {Path: ".", FS: testFS2},
|
|
},
|
|
),
|
|
},
|
|
{
|
|
name: "WithWorkDirFS",
|
|
input: NewModuleConfig().WithWorkDirFS(testFS),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
nil, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
map[uint32]*wasm.FileEntry{ // openedFiles
|
|
3: {Path: ".", FS: testFS},
|
|
},
|
|
),
|
|
},
|
|
{
|
|
name: "WithFS and WithWorkDirFS",
|
|
input: NewModuleConfig().WithFS(testFS).WithWorkDirFS(testFS2),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
nil, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
map[uint32]*wasm.FileEntry{ // openedFiles
|
|
3: {Path: "/", FS: testFS},
|
|
4: {Path: ".", FS: testFS2},
|
|
},
|
|
),
|
|
},
|
|
{
|
|
name: "WithWorkDirFS and WithFS",
|
|
input: NewModuleConfig().WithWorkDirFS(testFS).WithFS(testFS2),
|
|
expected: requireSysContext(t,
|
|
math.MaxUint32, // max
|
|
nil, // args
|
|
nil, // environ
|
|
nil, // stdin
|
|
nil, // stdout
|
|
nil, // stderr
|
|
map[uint32]*wasm.FileEntry{ // openedFiles
|
|
3: {Path: ".", FS: testFS},
|
|
4: {Path: "/", FS: testFS2},
|
|
},
|
|
),
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
sys, err := tc.input.toSysContext()
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.expected, sys)
|
|
})
|
|
}
|
|
}
|
|
|
|
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",
|
|
},
|
|
{
|
|
name: "WithFS - nil",
|
|
input: NewModuleConfig().WithFS(nil),
|
|
expectedErr: "FS for / is nil",
|
|
},
|
|
{
|
|
name: "WithWorkDirFS - nil",
|
|
input: NewModuleConfig().WithWorkDirFS(nil),
|
|
expectedErr: "FS for . is nil",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := tc.input.toSysContext()
|
|
require.EqualError(t, err, tc.expectedErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
// requireSysContext ensures wasm.NewSysContext 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, openedFiles map[uint32]*wasm.FileEntry) *wasm.SysContext {
|
|
sys, err := wasm.NewSysContext(max, args, environ, stdin, stdout, stderr, openedFiles)
|
|
require.NoError(t, err)
|
|
return sys
|
|
}
|
|
|
|
func TestCompiledCode_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 []*CompiledCode
|
|
for i := 0; i < 10; i++ {
|
|
m := &wasm.Module{}
|
|
err := e.CompileModule(ctx, m)
|
|
require.NoError(t, err)
|
|
cs = append(cs, &CompiledCode{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))
|
|
}
|
|
}
|