Makes fake clocks increment and fixes mutability bug (#630)
This ensures fake clocks increment so that compilers that implement sleep with them don't spin. This also fixes a mutability bug in config where we weren't really doing clone properly because map references are shared. Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
@@ -425,6 +425,14 @@ of requiring configuration to opt-into real clocks.
|
||||
|
||||
See https://gruss.cc/files/fantastictimers.pdf for an example attacks.
|
||||
|
||||
## Why does fake time increase on reading?
|
||||
|
||||
Both the fake nanotime and walltime increase by 1ms on reading. Particularly in
|
||||
the case of nanotime, this prevents spinning. For example, when Go compiles
|
||||
`time.Sleep` using `GOOS=js GOARCH=wasm`, nanotime is used in a loop. If that
|
||||
never increases, the gouroutine is mistaken for being busy. This would be worse
|
||||
if a compiler implement sleep using nanotime, yet doesn't check for spinning!
|
||||
|
||||
## Why not `time.Clock`?
|
||||
|
||||
wazero can't use `time.Clock` as a plugin for clock implementation as it is
|
||||
|
||||
144
config.go
144
config.go
@@ -110,7 +110,7 @@ type RuntimeConfig interface {
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/sign-extension-ops/Overview.md
|
||||
WithFeatureSignExtensionOps(bool) RuntimeConfig
|
||||
|
||||
// WithFeatureSIMD enables the vector value type and vector instructions (aka SIMD). This defaults to false
|
||||
// WithFeatureSIMD enables the vector value type and vector instructions (aka SIMD). This defaults to false
|
||||
// as the feature was not in WebAssembly 1.0.
|
||||
//
|
||||
// See https://github.com/WebAssembly/spec/blob/main/proposals/simd/SIMD.md
|
||||
@@ -171,83 +171,89 @@ var engineLessConfig = &runtimeConfig{
|
||||
// support Compiler. Use NewRuntimeConfig to safely detect and fallback to
|
||||
// NewRuntimeConfigInterpreter if needed.
|
||||
func NewRuntimeConfigCompiler() RuntimeConfig {
|
||||
ret := *engineLessConfig // copy
|
||||
ret := engineLessConfig.clone()
|
||||
ret.newEngine = compiler.NewEngine
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// NewRuntimeConfigInterpreter interprets WebAssembly modules instead of compiling them into assembly.
|
||||
func NewRuntimeConfigInterpreter() RuntimeConfig {
|
||||
ret := *engineLessConfig // copy
|
||||
ret := engineLessConfig.clone()
|
||||
ret.newEngine = interpreter.NewEngine
|
||||
return ret
|
||||
}
|
||||
|
||||
// clone makes a deep copy of this runtime config.
|
||||
func (c *runtimeConfig) clone() *runtimeConfig {
|
||||
ret := *c // copy except maps which share a ref
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithFeatureBulkMemoryOperations implements RuntimeConfig.WithFeatureBulkMemoryOperations
|
||||
func (c *runtimeConfig) WithFeatureBulkMemoryOperations(enabled bool) RuntimeConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureBulkMemoryOperations, enabled)
|
||||
// bulk-memory-operations proposal is mutually-dependant with reference-types proposal.
|
||||
ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureReferenceTypes, enabled)
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithFeatureMultiValue implements RuntimeConfig.WithFeatureMultiValue
|
||||
func (c *runtimeConfig) WithFeatureMultiValue(enabled bool) RuntimeConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureMultiValue, enabled)
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithFeatureMutableGlobal implements RuntimeConfig.WithFeatureMutableGlobal
|
||||
func (c *runtimeConfig) WithFeatureMutableGlobal(enabled bool) RuntimeConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureMutableGlobal, enabled)
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithFeatureNonTrappingFloatToIntConversion implements RuntimeConfig.WithFeatureNonTrappingFloatToIntConversion
|
||||
func (c *runtimeConfig) WithFeatureNonTrappingFloatToIntConversion(enabled bool) RuntimeConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureNonTrappingFloatToIntConversion, enabled)
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithFeatureReferenceTypes implements RuntimeConfig.WithFeatureReferenceTypes
|
||||
func (c *runtimeConfig) WithFeatureReferenceTypes(enabled bool) RuntimeConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureReferenceTypes, enabled)
|
||||
// reference-types proposal is mutually-dependant with bulk-memory-operations proposal.
|
||||
ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureBulkMemoryOperations, enabled)
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithFeatureSignExtensionOps implements RuntimeConfig.WithFeatureSignExtensionOps
|
||||
func (c *runtimeConfig) WithFeatureSignExtensionOps(enabled bool) RuntimeConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureSignExtensionOps, enabled)
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithFeatureSIMD implements RuntimeConfig.WithFeatureSIMD
|
||||
func (c *runtimeConfig) WithFeatureSIMD(enabled bool) RuntimeConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.enabledFeatures = ret.enabledFeatures.Set(wasm.FeatureSIMD, enabled)
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithWasmCore1 implements RuntimeConfig.WithWasmCore1
|
||||
func (c *runtimeConfig) WithWasmCore1() RuntimeConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.enabledFeatures = wasm.Features20191205
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithWasmCore2 implements RuntimeConfig.WithWasmCore2
|
||||
func (c *runtimeConfig) WithWasmCore2() RuntimeConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.enabledFeatures = wasm.Features20220419
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// CompiledModule is a WebAssembly 1.0 module ready to be instantiated (Runtime.InstantiateModule) as an api.Module.
|
||||
@@ -314,14 +320,20 @@ func NewCompileConfig() CompileConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// clone makes a deep copy of this compile config.
|
||||
func (c *compileConfig) clone() *compileConfig {
|
||||
ret := *c // copy except maps which share a ref
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithImportRenamer implements CompileConfig.WithImportRenamer
|
||||
func (c *compileConfig) WithImportRenamer(importRenamer api.ImportRenamer) CompileConfig {
|
||||
if importRenamer == nil {
|
||||
return c
|
||||
}
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.importRenamer = importRenamer
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithMemorySizer implements CompileConfig.WithMemorySizer
|
||||
@@ -329,9 +341,9 @@ func (c *compileConfig) WithMemorySizer(memorySizer api.MemorySizer) CompileConf
|
||||
if memorySizer == nil {
|
||||
return c
|
||||
}
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.memorySizer = memorySizer
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// ModuleConfig configures resources needed by functions that have low-level interactions with the host operating
|
||||
@@ -446,7 +458,8 @@ type ModuleConfig interface {
|
||||
WithStdout(io.Writer) ModuleConfig
|
||||
|
||||
// WithWalltime configures the wall clock, sometimes referred to as the
|
||||
// real time clock. Defaults to a constant fake result.
|
||||
// real time clock. Defaults to a fake result that increases by 1ms on
|
||||
// each reading.
|
||||
//
|
||||
// Ex. To override with your own clock:
|
||||
// moduleConfig = moduleConfig.
|
||||
@@ -465,7 +478,8 @@ type ModuleConfig interface {
|
||||
WithSysWalltime() ModuleConfig
|
||||
|
||||
// WithNanotime configures the monotonic clock, used to measure elapsed
|
||||
// time in nanoseconds. Defaults to a constant fake result.
|
||||
// time in nanoseconds. Defaults to a fake result that increases by 1ms
|
||||
// on each reading.
|
||||
//
|
||||
// Ex. To override with your own clock:
|
||||
// moduleConfig = moduleConfig.
|
||||
@@ -512,9 +526,9 @@ type moduleConfig struct {
|
||||
stdout io.Writer
|
||||
stderr io.Writer
|
||||
randSource io.Reader
|
||||
walltimeTime *sys.Walltime
|
||||
walltime *sys.Walltime
|
||||
walltimeResolution sys.ClockResolution
|
||||
nanotimeTime *sys.Nanotime
|
||||
nanotime *sys.Nanotime
|
||||
nanotimeResolution sys.ClockResolution
|
||||
args []string
|
||||
// environ is pair-indexed to retain order similar to os.Environ.
|
||||
@@ -529,21 +543,31 @@ func NewModuleConfig() ModuleConfig {
|
||||
return &moduleConfig{
|
||||
startFunctions: []string{"_start"},
|
||||
environKeys: map[string]int{},
|
||||
|
||||
fs: internalsys.NewFSConfig(),
|
||||
fs: internalsys.NewFSConfig(),
|
||||
}
|
||||
}
|
||||
|
||||
// clone makes a deep copy of this module config.
|
||||
func (c *moduleConfig) clone() *moduleConfig {
|
||||
ret := *c // copy except maps which share a ref
|
||||
ret.environKeys = make(map[string]int, len(c.environKeys))
|
||||
for key, value := range c.environKeys {
|
||||
ret.environKeys[key] = value
|
||||
}
|
||||
ret.fs = c.fs.Clone()
|
||||
return &ret
|
||||
}
|
||||
|
||||
// WithArgs implements ModuleConfig.WithArgs
|
||||
func (c *moduleConfig) WithArgs(args ...string) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.args = args
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithEnv implements ModuleConfig.WithEnv
|
||||
func (c *moduleConfig) WithEnv(key, value string) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
// Check to see if this key already exists and update it.
|
||||
if i, ok := ret.environKeys[key]; ok {
|
||||
ret.environ[i+1] = value // environ is pair-indexed, so the value is 1 after the key.
|
||||
@@ -551,57 +575,57 @@ func (c *moduleConfig) WithEnv(key, value string) ModuleConfig {
|
||||
ret.environKeys[key] = len(ret.environ)
|
||||
ret.environ = append(ret.environ, key, value)
|
||||
}
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithFS implements ModuleConfig.WithFS
|
||||
func (c *moduleConfig) WithFS(fs fs.FS) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.fs = ret.fs.WithFS(fs)
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithName implements ModuleConfig.WithName
|
||||
func (c *moduleConfig) WithName(name string) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.name = name
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithStartFunctions implements ModuleConfig.WithStartFunctions
|
||||
func (c *moduleConfig) WithStartFunctions(startFunctions ...string) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.startFunctions = startFunctions
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithStderr implements ModuleConfig.WithStderr
|
||||
func (c *moduleConfig) WithStderr(stderr io.Writer) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.stderr = stderr
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithStdin implements ModuleConfig.WithStdin
|
||||
func (c *moduleConfig) WithStdin(stdin io.Reader) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.stdin = stdin
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithStdout implements ModuleConfig.WithStdout
|
||||
func (c *moduleConfig) WithStdout(stdout io.Writer) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.stdout = stdout
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithWalltime implements ModuleConfig.WithWalltime
|
||||
func (c *moduleConfig) WithWalltime(walltime sys.Walltime, resolution sys.ClockResolution) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret.walltimeTime = &walltime
|
||||
ret := c.clone()
|
||||
ret.walltime = &walltime
|
||||
ret.walltimeResolution = resolution
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// We choose arbitrary resolutions here because there's no perfect alternative. For example, according to the
|
||||
@@ -615,10 +639,10 @@ func (c *moduleConfig) WithSysWalltime() ModuleConfig {
|
||||
|
||||
// WithNanotime implements ModuleConfig.WithNanotime
|
||||
func (c *moduleConfig) WithNanotime(nanotime sys.Nanotime, resolution sys.ClockResolution) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret.nanotimeTime = &nanotime
|
||||
ret := c.clone()
|
||||
ret.nanotime = &nanotime
|
||||
ret.nanotimeResolution = resolution
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithSysNanotime implements ModuleConfig.WithSysNanotime
|
||||
@@ -628,16 +652,16 @@ func (c *moduleConfig) WithSysNanotime() ModuleConfig {
|
||||
|
||||
// WithRandSource implements ModuleConfig.WithRandSource
|
||||
func (c *moduleConfig) WithRandSource(source io.Reader) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.randSource = source
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// WithWorkDirFS implements ModuleConfig.WithWorkDirFS
|
||||
func (c *moduleConfig) WithWorkDirFS(fs fs.FS) ModuleConfig {
|
||||
ret := *c // copy
|
||||
ret := c.clone()
|
||||
ret.fs = ret.fs.WithWorkDirFS(fs)
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
// toSysContext creates a baseline wasm.Context configured by ModuleConfig.
|
||||
@@ -672,8 +696,8 @@ func (c *moduleConfig) toSysContext() (sysCtx *internalsys.Context, err error) {
|
||||
c.stdout,
|
||||
c.stderr,
|
||||
c.randSource,
|
||||
c.walltimeTime, c.walltimeResolution,
|
||||
c.nanotimeTime, c.nanotimeResolution,
|
||||
c.walltime, c.walltimeResolution,
|
||||
c.nanotime, c.nanotimeResolution,
|
||||
preopens,
|
||||
)
|
||||
}
|
||||
|
||||
191
config_test.go
191
config_test.go
@@ -252,48 +252,58 @@ func TestModuleConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
with func(ModuleConfig) ModuleConfig
|
||||
expected ModuleConfig
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "WithName",
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithName("wazero")
|
||||
},
|
||||
expected: &moduleConfig{
|
||||
name: "wazero",
|
||||
},
|
||||
expected: "wazero",
|
||||
},
|
||||
{
|
||||
name: "WithName empty",
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithName("")
|
||||
},
|
||||
expected: &moduleConfig{},
|
||||
},
|
||||
{
|
||||
name: "WithName twice",
|
||||
with: func(c ModuleConfig) ModuleConfig {
|
||||
return c.WithName("wazero").WithName("wa0")
|
||||
},
|
||||
expected: &moduleConfig{
|
||||
name: "wa0",
|
||||
},
|
||||
expected: "wa0",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
input := &moduleConfig{}
|
||||
input := NewModuleConfig()
|
||||
rc := tc.with(input)
|
||||
require.Equal(t, tc.expected, rc)
|
||||
require.Equal(t, tc.expected, rc.(*moduleConfig).name)
|
||||
// The source wasn't modified
|
||||
require.Equal(t, &moduleConfig{}, input)
|
||||
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 := fstest.MapFS{}
|
||||
testFS2 := fstest.MapFS{}
|
||||
|
||||
@@ -304,7 +314,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
input: NewModuleConfig(),
|
||||
input: base,
|
||||
expected: requireSysContext(t,
|
||||
math.MaxUint32, // max
|
||||
nil, // args
|
||||
@@ -313,14 +323,14 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
nil, // stdout
|
||||
nil, // stderr
|
||||
nil, // randSource
|
||||
nil, 0, // walltime, walltimeResolution
|
||||
nil, 0, // nanotime, nanotimeResolution
|
||||
&wt, 1, // walltime, walltimeResolution
|
||||
&nt, 1, // nanotime, nanotimeResolution
|
||||
nil, // openedFiles
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "WithArgs",
|
||||
input: NewModuleConfig().WithArgs("a", "bc"),
|
||||
input: base.WithArgs("a", "bc"),
|
||||
expected: requireSysContext(t,
|
||||
math.MaxUint32, // max
|
||||
[]string{"a", "bc"}, // args
|
||||
@@ -329,14 +339,15 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
nil, // stdout
|
||||
nil, // stderr
|
||||
nil, // randSource
|
||||
nil, 0, // walltime, walltimeResolution
|
||||
nil, 0, // nanotime, nanotimeResolution
|
||||
&wt, 1, // walltime, walltimeResolution
|
||||
&nt, 1, // nanotime, nanotimeResolution
|
||||
|
||||
nil, // openedFiles
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "WithArgs empty ok", // Particularly argv[0] can be empty, and we have no rules about others.
|
||||
input: NewModuleConfig().WithArgs("", "bc"),
|
||||
input: base.WithArgs("", "bc"),
|
||||
expected: requireSysContext(t,
|
||||
math.MaxUint32, // max
|
||||
[]string{"", "bc"}, // args
|
||||
@@ -345,14 +356,15 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
nil, // stdout
|
||||
nil, // stderr
|
||||
nil, // randSource
|
||||
nil, 0, // walltime, walltimeResolution
|
||||
nil, 0, // nanotime, nanotimeResolution
|
||||
&wt, 1, // walltime, walltimeResolution
|
||||
&nt, 1, // nanotime, nanotimeResolution
|
||||
|
||||
nil, // openedFiles
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "WithArgs second call overwrites",
|
||||
input: NewModuleConfig().WithArgs("a", "bc").WithArgs("bc", "a"),
|
||||
input: base.WithArgs("a", "bc").WithArgs("bc", "a"),
|
||||
expected: requireSysContext(t,
|
||||
math.MaxUint32, // max
|
||||
[]string{"bc", "a"}, // args
|
||||
@@ -361,14 +373,15 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
nil, // stdout
|
||||
nil, // stderr
|
||||
nil, // randSource
|
||||
nil, 0, // walltime, walltimeResolution
|
||||
nil, 0, // nanotime, nanotimeResolution
|
||||
&wt, 1, // walltime, walltimeResolution
|
||||
&nt, 1, // nanotime, nanotimeResolution
|
||||
|
||||
nil, // openedFiles
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "WithEnv",
|
||||
input: NewModuleConfig().WithEnv("a", "b"),
|
||||
input: base.WithEnv("a", "b"),
|
||||
expected: requireSysContext(t,
|
||||
math.MaxUint32, // max
|
||||
nil, // args
|
||||
@@ -377,14 +390,15 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
nil, // stdout
|
||||
nil, // stderr
|
||||
nil, // randSource
|
||||
nil, 0, // walltime, walltimeResolution
|
||||
nil, 0, // nanotime, nanotimeResolution
|
||||
&wt, 1, // walltime, walltimeResolution
|
||||
&nt, 1, // nanotime, nanotimeResolution
|
||||
|
||||
nil, // openedFiles
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "WithEnv empty value",
|
||||
input: NewModuleConfig().WithEnv("a", ""),
|
||||
input: base.WithEnv("a", ""),
|
||||
expected: requireSysContext(t,
|
||||
math.MaxUint32, // max
|
||||
nil, // args
|
||||
@@ -393,14 +407,14 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
nil, // stdout
|
||||
nil, // stderr
|
||||
nil, // randSource
|
||||
nil, 0, // walltime, walltimeResolution
|
||||
nil, 0, // nanotime, nanotimeResolution
|
||||
&wt, 1, // walltime, walltimeResolution
|
||||
&nt, 1, // nanotime, nanotimeResolution
|
||||
nil, // openedFiles
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "WithEnv twice",
|
||||
input: NewModuleConfig().WithEnv("a", "b").WithEnv("c", "de"),
|
||||
input: base.WithEnv("a", "b").WithEnv("c", "de"),
|
||||
expected: requireSysContext(t,
|
||||
math.MaxUint32, // max
|
||||
nil, // args
|
||||
@@ -409,14 +423,15 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
nil, // stdout
|
||||
nil, // stderr
|
||||
nil, // randSource
|
||||
nil, 0, // walltime, walltimeResolution
|
||||
nil, 0, // nanotime, nanotimeResolution
|
||||
&wt, 1, // walltime, walltimeResolution
|
||||
&nt, 1, // nanotime, nanotimeResolution
|
||||
|
||||
nil, // openedFiles
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "WithEnv overwrites",
|
||||
input: NewModuleConfig().WithEnv("a", "bc").WithEnv("c", "de").WithEnv("a", "de"),
|
||||
input: base.WithEnv("a", "bc").WithEnv("c", "de").WithEnv("a", "de"),
|
||||
expected: requireSysContext(t,
|
||||
math.MaxUint32, // max
|
||||
nil, // args
|
||||
@@ -425,14 +440,15 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
nil, // stdout
|
||||
nil, // stderr
|
||||
nil, // randSource
|
||||
nil, 0, // walltime, walltimeResolution
|
||||
nil, 0, // nanotime, nanotimeResolution
|
||||
&wt, 1, // walltime, walltimeResolution
|
||||
&nt, 1, // nanotime, nanotimeResolution
|
||||
|
||||
nil, // openedFiles
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "WithEnv twice",
|
||||
input: NewModuleConfig().WithEnv("a", "b").WithEnv("c", "de"),
|
||||
input: base.WithEnv("a", "b").WithEnv("c", "de"),
|
||||
expected: requireSysContext(t,
|
||||
math.MaxUint32, // max
|
||||
nil, // args
|
||||
@@ -441,14 +457,15 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
nil, // stdout
|
||||
nil, // stderr
|
||||
nil, // randSource
|
||||
nil, 0, // walltime, walltimeResolution
|
||||
nil, 0, // nanotime, nanotimeResolution
|
||||
&wt, 1, // walltime, walltimeResolution
|
||||
&nt, 1, // nanotime, nanotimeResolution
|
||||
|
||||
nil, // openedFiles
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "WithFS",
|
||||
input: NewModuleConfig().WithFS(testFS),
|
||||
input: base.WithFS(testFS),
|
||||
expected: requireSysContext(t,
|
||||
math.MaxUint32, // max
|
||||
nil, // args
|
||||
@@ -457,8 +474,9 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
nil, // stdout
|
||||
nil, // stderr
|
||||
nil, // randSource
|
||||
nil, 0, // walltime, walltimeResolution
|
||||
nil, 0, // nanotime, nanotimeResolution
|
||||
&wt, 1, // walltime, walltimeResolution
|
||||
&nt, 1, // nanotime, nanotimeResolution
|
||||
|
||||
map[uint32]*internalsys.FileEntry{ // openedFiles
|
||||
3: {Path: "/", FS: testFS},
|
||||
4: {Path: ".", FS: testFS},
|
||||
@@ -467,7 +485,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "WithFS overwrites",
|
||||
input: NewModuleConfig().WithFS(testFS).WithFS(testFS2),
|
||||
input: base.WithFS(testFS).WithFS(testFS2),
|
||||
expected: requireSysContext(t,
|
||||
math.MaxUint32, // max
|
||||
nil, // args
|
||||
@@ -476,8 +494,8 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
nil, // stdout
|
||||
nil, // stderr
|
||||
nil, // randSource
|
||||
nil, 0, // walltime, walltimeResolution
|
||||
nil, 0, // nanotime, nanotimeResolution
|
||||
&wt, 1, // walltime, walltimeResolution
|
||||
&nt, 1, // nanotime, nanotimeResolution
|
||||
map[uint32]*internalsys.FileEntry{ // openedFiles
|
||||
3: {Path: "/", FS: testFS2},
|
||||
4: {Path: ".", FS: testFS2},
|
||||
@@ -486,7 +504,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "WithWorkDirFS",
|
||||
input: NewModuleConfig().WithWorkDirFS(testFS),
|
||||
input: base.WithWorkDirFS(testFS),
|
||||
expected: requireSysContext(t,
|
||||
math.MaxUint32, // max
|
||||
nil, // args
|
||||
@@ -495,8 +513,8 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
nil, // stdout
|
||||
nil, // stderr
|
||||
nil, // randSource
|
||||
nil, 0, // walltime, walltimeResolution
|
||||
nil, 0, // nanotime, nanotimeResolution
|
||||
&wt, 1, // walltime, walltimeResolution
|
||||
&nt, 1, // nanotime, nanotimeResolution
|
||||
map[uint32]*internalsys.FileEntry{ // openedFiles
|
||||
3: {Path: ".", FS: testFS},
|
||||
},
|
||||
@@ -504,7 +522,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "WithFS and WithWorkDirFS",
|
||||
input: NewModuleConfig().WithFS(testFS).WithWorkDirFS(testFS2),
|
||||
input: base.WithFS(testFS).WithWorkDirFS(testFS2),
|
||||
expected: requireSysContext(t,
|
||||
math.MaxUint32, // max
|
||||
nil, // args
|
||||
@@ -513,8 +531,8 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
nil, // stdout
|
||||
nil, // stderr
|
||||
nil, // randSource
|
||||
nil, 0, // walltime, walltimeResolution
|
||||
nil, 0, // nanotime, nanotimeResolution
|
||||
&wt, 1, // walltime, walltimeResolution
|
||||
&nt, 1, // nanotime, nanotimeResolution
|
||||
map[uint32]*internalsys.FileEntry{ // openedFiles
|
||||
3: {Path: "/", FS: testFS},
|
||||
4: {Path: ".", FS: testFS2},
|
||||
@@ -523,7 +541,7 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "WithWorkDirFS and WithFS",
|
||||
input: NewModuleConfig().WithWorkDirFS(testFS).WithFS(testFS2),
|
||||
input: base.WithWorkDirFS(testFS).WithFS(testFS2),
|
||||
expected: requireSysContext(t,
|
||||
math.MaxUint32, // max
|
||||
nil, // args
|
||||
@@ -532,8 +550,8 @@ func TestModuleConfig_toSysContext(t *testing.T) {
|
||||
nil, // stdout
|
||||
nil, // stderr
|
||||
nil, // randSource
|
||||
nil, 0, // walltime, walltimeResolution
|
||||
nil, 0, // nanotime, nanotimeResolution
|
||||
&wt, 1, // walltime, walltimeResolution
|
||||
&nt, 1, // nanotime, nanotimeResolution
|
||||
map[uint32]*internalsys.FileEntry{ // openedFiles
|
||||
3: {Path: ".", FS: testFS},
|
||||
4: {Path: "/", FS: testFS2},
|
||||
@@ -748,6 +766,49 @@ func TestModuleConfig_toSysContext_Errors(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestModuleConfig_clone(t *testing.T) {
|
||||
mc := NewModuleConfig().(*moduleConfig)
|
||||
cloned := mc.clone()
|
||||
|
||||
fs1 := fstest.MapFS{}
|
||||
mc.fs.WithWorkDirFS(fs1)
|
||||
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
|
||||
preopens, err := cloned.fs.Preopens()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[uint32]*internalsys.FileEntry{}, preopens)
|
||||
}
|
||||
|
||||
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 []*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(
|
||||
@@ -776,27 +837,3 @@ func requireSysContext(
|
||||
require.NoError(t, err)
|
||||
return sysCtx
|
||||
}
|
||||
|
||||
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 []*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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,19 +2,39 @@ package platform
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
const FakeEpochNanos = int64(1640995200000000000) // midnight UTC 2022-01-01
|
||||
const (
|
||||
ms = int64(time.Millisecond)
|
||||
// FakeEpochNanos is midnight UTC 2022-01-01 and exposed for testing
|
||||
FakeEpochNanos = 1640995200000 * ms
|
||||
)
|
||||
|
||||
// FakeWalltime implements sys.Walltime with FakeEpochNanos.
|
||||
func FakeWalltime(context.Context) (sec int64, nsec int32) {
|
||||
return FakeEpochNanos / 1e9, int32(FakeEpochNanos % 1e9)
|
||||
// NewFakeWalltime implements sys.Walltime with FakeEpochNanos that increases by 1ms each reading.
|
||||
// See /RATIONALE.md
|
||||
func NewFakeWalltime() *sys.Walltime {
|
||||
// AddInt64 returns the new value. Adjust so the first reading will be FakeEpochNanos
|
||||
t := FakeEpochNanos - ms
|
||||
var wt sys.Walltime = func(context.Context) (sec int64, nsec int32) {
|
||||
wt := atomic.AddInt64(&t, ms)
|
||||
return wt / 1e9, int32(wt % 1e9)
|
||||
}
|
||||
return &wt
|
||||
}
|
||||
|
||||
// FakeNanotime implements sys.Nanotime with FakeEpochNanos.
|
||||
func FakeNanotime(context.Context) int64 {
|
||||
return FakeEpochNanos
|
||||
// NewFakeNanotime implements sys.Nanotime that increases by 1ms each reading.
|
||||
// See /RATIONALE.md
|
||||
func NewFakeNanotime() *sys.Nanotime {
|
||||
// AddInt64 returns the new value. Adjust so the first reading will be zero.
|
||||
t := int64(0) - ms
|
||||
var nt sys.Nanotime = func(context.Context) int64 {
|
||||
return atomic.AddInt64(&t, ms)
|
||||
}
|
||||
return &nt
|
||||
}
|
||||
|
||||
// Walltime implements sys.Walltime with time.Now.
|
||||
|
||||
@@ -9,6 +9,28 @@ import (
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
|
||||
func Test_NewFakeWalltime(t *testing.T) {
|
||||
wt := NewFakeWalltime()
|
||||
|
||||
// Base should be the same as FakeEpochNanos
|
||||
sec, nsec := (*wt)(context.Background())
|
||||
ft := time.UnixMicro(FakeEpochNanos / time.Microsecond.Nanoseconds()).UTC()
|
||||
require.Equal(t, ft, time.Unix(sec, int64(nsec)).UTC())
|
||||
|
||||
// next reading should increase by 1ms
|
||||
sec, nsec = (*wt)(context.Background())
|
||||
require.Equal(t, ft.Add(time.Millisecond), time.Unix(sec, int64(nsec)).UTC())
|
||||
}
|
||||
|
||||
func Test_NewFakeNanotime(t *testing.T) {
|
||||
nt := NewFakeNanotime()
|
||||
|
||||
require.Equal(t, int64(0), (*nt)(context.Background()))
|
||||
|
||||
// next reading should increase by 1ms
|
||||
require.Equal(t, int64(time.Millisecond), (*nt)(context.Background()))
|
||||
}
|
||||
|
||||
func Test_Walltime(t *testing.T) {
|
||||
now := time.Now().Unix()
|
||||
sec, nsec := Walltime(context.Background())
|
||||
|
||||
@@ -121,6 +121,20 @@ func NewFSConfig() *FSConfig {
|
||||
}
|
||||
}
|
||||
|
||||
// Clone makes a deep copy of this FS config.
|
||||
func (c *FSConfig) Clone() *FSConfig {
|
||||
ret := *c // copy except maps which share a ref
|
||||
ret.preopens = make(map[uint32]*FileEntry, len(c.preopens))
|
||||
for key, value := range c.preopens {
|
||||
ret.preopens[key] = value
|
||||
}
|
||||
ret.preopenPaths = make(map[string]uint32, len(c.preopenPaths))
|
||||
for key, value := range c.preopenPaths {
|
||||
ret.preopenPaths[key] = value
|
||||
}
|
||||
return &ret
|
||||
}
|
||||
|
||||
// setFS maps a path to a file-system. This is only used for base paths: "/" and ".".
|
||||
func (c *FSConfig) setFS(path string, fs fs.FS) {
|
||||
// Check to see if this key already exists and update it.
|
||||
@@ -135,15 +149,15 @@ func (c *FSConfig) setFS(path string, fs fs.FS) {
|
||||
}
|
||||
|
||||
func (c *FSConfig) WithFS(fs fs.FS) *FSConfig {
|
||||
ret := *c // copy
|
||||
ret := c.Clone()
|
||||
ret.setFS("/", fs)
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *FSConfig) WithWorkDirFS(fs fs.FS) *FSConfig {
|
||||
ret := *c // copy
|
||||
ret := c.Clone()
|
||||
ret.setFS(".", fs)
|
||||
return &ret
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *FSConfig) Preopens() (map[uint32]*FileEntry, error) {
|
||||
|
||||
@@ -57,3 +57,20 @@ func (f *testFile) Close() error { return f.closeErr }
|
||||
func (f *testFile) Stat() (fs.FileInfo, error) { return nil, nil }
|
||||
func (f *testFile) Read(_ []byte) (int, error) { return 0, nil }
|
||||
func (f *testFile) Seek(_ int64, _ int) (int64, error) { return 0, nil }
|
||||
|
||||
func TestFSConfig_Clone(t *testing.T) {
|
||||
fsc := NewFSConfig()
|
||||
cloned := fsc.Clone()
|
||||
|
||||
fsc.preopens[2] = nil
|
||||
fsc.preopenPaths["2"] = 2
|
||||
|
||||
cloned.preopens[1] = nil
|
||||
cloned.preopenPaths["1"] = 1
|
||||
|
||||
// Ensure the maps are not shared
|
||||
require.Equal(t, map[uint32]*FileEntry{2: nil}, fsc.preopens)
|
||||
require.Equal(t, map[string]uint32{"2": 2}, fsc.preopenPaths)
|
||||
require.Equal(t, map[uint32]*FileEntry{1: nil}, cloned.preopens)
|
||||
require.Equal(t, map[string]uint32{"1": 1}, cloned.preopenPaths)
|
||||
}
|
||||
|
||||
@@ -136,8 +136,6 @@ func DefaultContext() *Context {
|
||||
}
|
||||
|
||||
var _ = DefaultContext() // Force panic on bug.
|
||||
var wt sys.Walltime = platform.FakeWalltime
|
||||
var nt sys.Nanotime = platform.FakeNanotime
|
||||
|
||||
// NewContext is a factory function which helps avoid needing to know defaults or exporting all fields.
|
||||
// Note: max is exposed for testing. max is only used for env/args validation.
|
||||
@@ -192,7 +190,7 @@ func NewContext(
|
||||
sysCtx.walltime = walltime
|
||||
sysCtx.walltimeResolution = walltimeResolution
|
||||
} else {
|
||||
sysCtx.walltime = &wt
|
||||
sysCtx.walltime = platform.NewFakeWalltime()
|
||||
sysCtx.walltimeResolution = sys.ClockResolution(time.Microsecond.Nanoseconds())
|
||||
}
|
||||
|
||||
@@ -203,7 +201,7 @@ func NewContext(
|
||||
sysCtx.nanotime = nanotime
|
||||
sysCtx.nanotimeResolution = nanotimeResolution
|
||||
} else {
|
||||
sysCtx.nanotime = &nt
|
||||
sysCtx.nanotime = platform.NewFakeNanotime()
|
||||
sysCtx.nanotimeResolution = sys.ClockResolution(time.Nanosecond)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
"github.com/tetratelabs/wazero/sys"
|
||||
)
|
||||
@@ -33,9 +34,12 @@ func TestDefaultSysContext(t *testing.T) {
|
||||
require.Equal(t, eofReader{}, sysCtx.Stdin())
|
||||
require.Equal(t, io.Discard, sysCtx.Stdout())
|
||||
require.Equal(t, io.Discard, sysCtx.Stderr())
|
||||
require.Equal(t, &wt, sysCtx.walltime) // To compare functions, we can only compare pointers.
|
||||
// To compare functions, we can only compare pointers, but the pointer will
|
||||
// change. Hence, we have to compare the results instead.
|
||||
sec, _ := sysCtx.Walltime(testCtx)
|
||||
require.Equal(t, platform.FakeEpochNanos/time.Second.Nanoseconds(), sec)
|
||||
require.Equal(t, sys.ClockResolution(1_000), sysCtx.WalltimeResolution())
|
||||
require.Equal(t, &nt, sysCtx.nanotime) // To compare functions, we can only compare pointers.
|
||||
require.Zero(t, sysCtx.Nanotime(testCtx)) // See above on functions.
|
||||
require.Equal(t, sys.ClockResolution(1), sysCtx.NanotimeResolution())
|
||||
require.Equal(t, rand.Reader, sysCtx.RandSource())
|
||||
require.Equal(t, NewFSContext(map[uint32]*FileEntry{}), sysCtx.FS())
|
||||
@@ -172,12 +176,12 @@ func TestNewContext_Walltime(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "ok",
|
||||
time: &wt,
|
||||
time: platform.NewFakeWalltime(),
|
||||
resolution: 3,
|
||||
},
|
||||
{
|
||||
name: "invalid resolution",
|
||||
time: &wt,
|
||||
time: platform.NewFakeWalltime(),
|
||||
resolution: 0,
|
||||
expectedErr: "invalid Walltime resolution: 0",
|
||||
},
|
||||
@@ -219,12 +223,12 @@ func TestNewContext_Nanotime(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "ok",
|
||||
time: &nt,
|
||||
time: platform.NewFakeNanotime(),
|
||||
resolution: 3,
|
||||
},
|
||||
{
|
||||
name: "invalid resolution",
|
||||
time: &nt,
|
||||
time: platform.NewFakeNanotime(),
|
||||
resolution: 0,
|
||||
expectedErr: "invalid Nanotime resolution: 0",
|
||||
},
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/tetratelabs/wazero"
|
||||
"github.com/tetratelabs/wazero/api"
|
||||
"github.com/tetratelabs/wazero/experimental"
|
||||
"github.com/tetratelabs/wazero/internal/platform"
|
||||
internalsys "github.com/tetratelabs/wazero/internal/sys"
|
||||
"github.com/tetratelabs/wazero/internal/testing/require"
|
||||
"github.com/tetratelabs/wazero/internal/wasm"
|
||||
@@ -520,90 +519,79 @@ func TestSnapshotPreview1_ClockResGet_Unsupported(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapshotPreview1_ClockTimeGet_Realtime(t *testing.T) {
|
||||
resultTimestamp := uint32(1) // arbitrary offset
|
||||
expectedMemory := []byte{
|
||||
'?', // resultTimestamp is after this
|
||||
0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, // little endian-encoded epochNanos
|
||||
'?', // stopped after encoding
|
||||
}
|
||||
|
||||
mod, fn := instantiateModule(testCtx, t, functionClockTimeGet, importClockTimeGet, nil)
|
||||
defer mod.Close(testCtx)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
invocation func() Errno
|
||||
}{
|
||||
{
|
||||
name: "wasi.ClockTimeGet",
|
||||
invocation: func() Errno {
|
||||
// invoke ClockTimeGet directly and check the memory side effects!
|
||||
return a.ClockTimeGet(testCtx, mod, 0 /* REALTIME */, 0 /* TODO: precision */, resultTimestamp)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: functionClockTimeGet,
|
||||
invocation: func() Errno {
|
||||
results, err := fn.Call(testCtx, 0 /* REALTIME */, 0 /* TODO: precision */, uint64(resultTimestamp))
|
||||
require.NoError(t, err)
|
||||
errno := Errno(results[0]) // results[0] is the errno
|
||||
return errno
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
maskMemory(t, testCtx, mod, len(expectedMemory))
|
||||
|
||||
errno := tc.invocation()
|
||||
require.Zero(t, errno, ErrnoName(errno))
|
||||
|
||||
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(expectedMemory)))
|
||||
require.True(t, ok)
|
||||
require.Equal(t, expectedMemory, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapshotPreview1_ClockTimeGet_Monotonic(t *testing.T) {
|
||||
func TestSnapshotPreview1_ClockTimeGet(t *testing.T) {
|
||||
resultTimestamp := uint32(1) // arbitrary offset
|
||||
|
||||
mod, fn := instantiateModule(testCtx, t, functionClockTimeGet, importClockTimeGet, nil)
|
||||
defer mod.Close(testCtx)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
invocation func() Errno
|
||||
clocks := []struct {
|
||||
clock string
|
||||
id uint32
|
||||
expectedMemory []byte
|
||||
}{
|
||||
{
|
||||
name: "wasi.ClockTimeGet",
|
||||
invocation: func() Errno {
|
||||
return a.ClockTimeGet(testCtx, mod, 1 /* MONOTONIC */, 0 /* TODO: precision */, resultTimestamp)
|
||||
clock: "Realtime",
|
||||
id: clockIDRealtime,
|
||||
expectedMemory: []byte{
|
||||
'?', // resultTimestamp is after this
|
||||
0x0, 0x0, 0x1f, 0xa6, 0x70, 0xfc, 0xc5, 0x16, // little endian-encoded epochNanos
|
||||
'?', // stopped after encoding
|
||||
},
|
||||
},
|
||||
{
|
||||
name: functionClockTimeGet,
|
||||
invocation: func() Errno {
|
||||
results, err := fn.Call(testCtx, 1 /* MONOTONIC */, 0 /* TODO: precision */, uint64(resultTimestamp))
|
||||
require.NoError(t, err)
|
||||
errno := Errno(results[0]) // results[0] is the errno
|
||||
return errno
|
||||
clock: "Monotonic",
|
||||
id: clockIDMonotonic,
|
||||
expectedMemory: []byte{
|
||||
'?', // resultTimestamp is after this
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // fake nanotime starts at zero
|
||||
'?', // stopped after encoding
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
errno := tc.invocation()
|
||||
require.Zero(t, errno, ErrnoName(errno))
|
||||
for _, c := range clocks {
|
||||
cc := c
|
||||
t.Run(cc.clock, func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
invocation func() Errno
|
||||
}{
|
||||
{
|
||||
name: "wasi.ClockTimeGet",
|
||||
invocation: func() Errno {
|
||||
return a.ClockTimeGet(testCtx, mod, cc.id, 0 /* TODO: precision */, resultTimestamp)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: functionClockTimeGet,
|
||||
invocation: func() Errno {
|
||||
results, err := fn.Call(testCtx, uint64(cc.id), 0 /* TODO: precision */, uint64(resultTimestamp))
|
||||
require.NoError(t, err)
|
||||
errno := Errno(results[0]) // results[0] is the errno
|
||||
return errno
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tick, ok := mod.Memory().ReadUint64Le(testCtx, resultTimestamp)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, uint64(platform.FakeEpochNanos), tick)
|
||||
for _, tt := range tests {
|
||||
tc := tt
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Reset the fake clock
|
||||
sysCtx, err := newSysContext(nil, nil, nil)
|
||||
require.NoError(t, err)
|
||||
mod.(*wasm.CallContext).Sys = sysCtx
|
||||
|
||||
maskMemory(t, testCtx, mod, len(cc.expectedMemory))
|
||||
|
||||
errno := tc.invocation()
|
||||
require.Zero(t, errno, ErrnoName(errno))
|
||||
|
||||
actual, ok := mod.Memory().Read(testCtx, 0, uint32(len(cc.expectedMemory)))
|
||||
require.True(t, ok)
|
||||
require.Equal(t, cc.expectedMemory, actual)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user