Files
wazero/internal/wasm/namespace_test.go
Crypt Keeper 15dc3d7d37 Adds experimental write support and implements on gojs (#970)
This adds writefs.FS, allowing functions to create and delete files.
This begins by implementing them on `GOARCH=js GOOS=wasm`. The current
status is a lot farther than before, even if completing write on WASI is
left for a later PR (possibly by another volunteer).

Signed-off-by: Adrian Cole <adrian@tetrate.io>
2022-12-28 19:49:46 +08:00

303 lines
8.6 KiB
Go

package wasm
import (
"context"
"errors"
"os"
"testing"
"github.com/tetratelabs/wazero/internal/sys"
testfs "github.com/tetratelabs/wazero/internal/testing/fs"
"github.com/tetratelabs/wazero/internal/testing/require"
)
func Test_newNamespace(t *testing.T) {
ns := newNamespace()
require.NotNil(t, ns.nameToNode)
}
func TestNamespace_setModule(t *testing.T) {
ns := newNamespace()
m1 := &ModuleInstance{Name: "m1"}
t.Run("errors if not required", func(t *testing.T) {
require.Error(t, ns.setModule(m1))
})
t.Run("adds module", func(t *testing.T) {
ns.nameToNode[m1.Name] = &moduleListNode{name: m1.Name}
require.NoError(t, ns.setModule(m1))
require.Equal(t, map[string]*moduleListNode{m1.Name: {name: m1.Name, module: m1}}, ns.nameToNode)
// Doesn't affect module names
require.Nil(t, ns.moduleList)
})
t.Run("redundant ok", func(t *testing.T) {
require.NoError(t, ns.setModule(m1))
require.Equal(t, map[string]*moduleListNode{m1.Name: {name: m1.Name, module: m1}}, ns.nameToNode)
// Doesn't affect module names
require.Nil(t, ns.moduleList)
})
t.Run("adds second module", func(t *testing.T) {
m2 := &ModuleInstance{Name: "m2"}
ns.nameToNode[m2.Name] = &moduleListNode{name: m2.Name}
require.NoError(t, ns.setModule(m2))
require.Equal(t, map[string]*moduleListNode{m1.Name: {name: m1.Name, module: m1}, m2.Name: {name: m2.Name, module: m2}}, ns.nameToNode)
// Doesn't affect module names
require.Nil(t, ns.moduleList)
})
t.Run("error on closed", func(t *testing.T) {
require.NoError(t, ns.CloseWithExitCode(context.Background(), 0))
require.Error(t, ns.setModule(m1))
})
}
func TestNamespace_deleteModule(t *testing.T) {
ns, m1, m2 := newTestNamespace()
t.Run("delete one module", func(t *testing.T) {
require.NoError(t, ns.deleteModule(m2.Name))
// Leaves the other module alone
m1Node := &moduleListNode{name: m1.Name, module: m1}
require.Equal(t, map[string]*moduleListNode{m1.Name: m1Node}, ns.nameToNode)
require.Equal(t, m1Node, ns.moduleList)
})
t.Run("ok if missing", func(t *testing.T) {
require.NoError(t, ns.deleteModule(m2.Name))
})
t.Run("delete last module", func(t *testing.T) {
require.NoError(t, ns.deleteModule(m1.Name))
require.Zero(t, len(ns.nameToNode))
require.Nil(t, ns.moduleList)
})
t.Run("error on closed", func(t *testing.T) {
require.NoError(t, ns.CloseWithExitCode(context.Background(), 0))
require.Error(t, ns.deleteModule(m1.Name))
require.Zero(t, len(ns.nameToNode))
require.Nil(t, ns.moduleList)
})
}
func TestNamespace_module(t *testing.T) {
ns, m1, _ := newTestNamespace()
t.Run("ok", func(t *testing.T) {
got, err := ns.module(m1.Name)
require.NoError(t, err)
require.Equal(t, m1, got)
})
t.Run("unknown", func(t *testing.T) {
got, err := ns.module("unknown")
require.Error(t, err)
require.Nil(t, got)
})
t.Run("not set", func(t *testing.T) {
ns.nameToNode["not set"] = &moduleListNode{name: "not set"}
got, err := ns.module("not set")
require.Error(t, err)
require.Nil(t, got)
})
t.Run("namespace closed", func(t *testing.T) {
require.NoError(t, ns.CloseWithExitCode(context.Background(), 0))
got, err := ns.module(m1.Name)
require.Error(t, err)
require.Nil(t, got)
})
}
func TestNamespace_requireModules(t *testing.T) {
t.Run("ok", func(t *testing.T) {
ns, m1, _ := newTestNamespace()
modules, err := ns.requireModules(map[string]struct{}{m1.Name: {}})
require.NoError(t, err)
require.Equal(t, map[string]*ModuleInstance{m1.Name: m1}, modules)
})
t.Run("module not instantiated", func(t *testing.T) {
ns, _, _ := newTestNamespace()
_, err := ns.requireModules(map[string]struct{}{"unknown": {}})
require.EqualError(t, err, "module[unknown] not instantiated")
})
t.Run("namespace closed", func(t *testing.T) {
ns, _, _ := newTestNamespace()
require.NoError(t, ns.CloseWithExitCode(context.Background(), 0))
_, err := ns.requireModules(map[string]struct{}{"unknown": {}})
require.Error(t, err)
})
}
func TestNamespace_requireModuleName(t *testing.T) {
ns := &Namespace{nameToNode: map[string]*moduleListNode{}, closed: new(uint32)}
t.Run("first", func(t *testing.T) {
err := ns.requireModuleName("m1")
require.NoError(t, err)
// Ensure it adds the module name, and doesn't impact the module list.
require.Equal(t, &moduleListNode{name: "m1"}, ns.moduleList)
require.Equal(t, map[string]*moduleListNode{"m1": {name: "m1"}}, ns.nameToNode)
})
t.Run("second", func(t *testing.T) {
err := ns.requireModuleName("m2")
require.NoError(t, err)
m2Node := &moduleListNode{name: "m2"}
m1Node := &moduleListNode{name: "m1", prev: m2Node}
m2Node.next = m1Node
// Appends in order.
require.Equal(t, m2Node, ns.moduleList)
require.Equal(t, map[string]*moduleListNode{"m1": m1Node, "m2": m2Node}, ns.nameToNode)
})
t.Run("existing", func(t *testing.T) {
err := ns.requireModuleName("m2")
require.EqualError(t, err, "module[m2] has already been instantiated")
})
t.Run("namespace closed", func(t *testing.T) {
require.NoError(t, ns.CloseWithExitCode(context.Background(), 0))
require.Error(t, ns.requireModuleName("m3"))
})
}
func TestNamespace_AliasModule(t *testing.T) {
ns := newNamespace()
m1 := &ModuleInstance{Name: "m1"}
ns.nameToNode[m1.Name] = &moduleListNode{name: m1.Name, module: m1}
t.Run("alias module", func(t *testing.T) {
require.NoError(t, ns.AliasModule("m1", "m2"))
m1node := &moduleListNode{name: "m1", module: m1}
require.Equal(t, map[string]*moduleListNode{"m1": m1node, "m2": m1node}, ns.nameToNode)
// Doesn't affect module names
require.Nil(t, ns.moduleList)
})
t.Run("namespace closed", func(t *testing.T) {
require.NoError(t, ns.CloseWithExitCode(context.Background(), 0))
require.Error(t, ns.AliasModule("m3", "m4"))
require.Nil(t, ns.nameToNode)
require.Nil(t, ns.moduleList)
})
}
func TestNamespace_CloseWithExitCode(t *testing.T) {
tests := []struct {
name string
testClosed bool
}{
{
name: "nothing closed",
testClosed: false,
},
{
name: "partially closed",
testClosed: true,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
ns, m1, m2 := newTestNamespace()
if tc.testClosed {
err := m2.CallCtx.CloseWithExitCode(testCtx, 2)
require.NoError(t, err)
}
err := ns.CloseWithExitCode(testCtx, 2)
require.NoError(t, err)
// Both modules were closed
require.Equal(t, uint64(1)+uint64(2)<<32, *m1.CallCtx.closed)
require.Equal(t, uint64(1)+uint64(2)<<32, *m2.CallCtx.closed)
// Namespace state zeroed
require.Zero(t, len(ns.nameToNode))
require.Nil(t, ns.moduleList)
})
}
t.Run("error closing", func(t *testing.T) {
// Right now, the only way to err closing the sys context is if a File.Close erred.
testFS := testfs.FS{"foo": &testfs.File{CloseErr: errors.New("error closing")}}
sysCtx := sys.DefaultContext(testFS)
fsCtx := sysCtx.FS()
_, err := fsCtx.OpenFile("/foo", os.O_RDONLY, 0)
require.NoError(t, err)
ns, m1, m2 := newTestNamespace()
m1.CallCtx.Sys = sysCtx // This should err, but both should close
err = ns.CloseWithExitCode(testCtx, 2)
require.EqualError(t, err, "error closing")
// Both modules were closed
require.Equal(t, uint64(1)+uint64(2)<<32, *m1.CallCtx.closed)
require.Equal(t, uint64(1)+uint64(2)<<32, *m2.CallCtx.closed)
// Namespace state zeroed
require.Zero(t, len(ns.nameToNode))
require.Nil(t, ns.moduleList)
})
t.Run("multiple closes", func(t *testing.T) {
ns, m1, m2 := newTestNamespace()
require.NoError(t, ns.CloseWithExitCode(testCtx, 2))
// Both modules were closed
require.Equal(t, uint64(1)+uint64(2)<<32, *m1.CallCtx.closed)
require.Equal(t, uint64(1)+uint64(2)<<32, *m2.CallCtx.closed)
// Namespace state zeroed
require.Zero(t, len(ns.nameToNode))
require.Nil(t, ns.moduleList)
require.NoError(t, ns.CloseWithExitCode(testCtx, 2))
})
}
func TestNamespace_Module(t *testing.T) {
ns, m1, _ := newTestNamespace()
t.Run("ok", func(t *testing.T) {
require.Equal(t, m1.CallCtx, ns.Module(m1.Name))
})
t.Run("unknown", func(t *testing.T) {
require.Nil(t, ns.Module("unknown"))
})
}
// newTestNamespace sets up a new Namespace without adding test coverage its functions.
func newTestNamespace() (*Namespace, *ModuleInstance, *ModuleInstance) {
ns := &Namespace{closed: new(uint32)}
m1 := &ModuleInstance{Name: "m1"}
m1.CallCtx = NewCallContext(ns, m1, nil)
m2 := &ModuleInstance{Name: "m2"}
m2.CallCtx = NewCallContext(ns, m2, nil)
node1 := &moduleListNode{name: m1.Name, module: m1}
node2 := &moduleListNode{name: m2.Name, module: m2, next: node1}
node1.prev = node2
ns.nameToNode = map[string]*moduleListNode{m1.Name: node1, m2.Name: node2}
ns.moduleList = node2
return ns, m1, m2
}