wa-lang.org/wazero@v1.0.2/internal/wasm/namespace_test.go (about) 1 package wasm 2 3 import ( 4 "errors" 5 "testing" 6 7 "wa-lang.org/wazero/internal/sys" 8 testfs "wa-lang.org/wazero/internal/testing/fs" 9 "wa-lang.org/wazero/internal/testing/require" 10 ) 11 12 func Test_newNamespace(t *testing.T) { 13 ns := newNamespace() 14 require.NotNil(t, ns.modules) 15 } 16 17 func TestNamespace_addModule(t *testing.T) { 18 ns := newNamespace() 19 m1 := &ModuleInstance{Name: "m1"} 20 21 t.Run("adds module", func(t *testing.T) { 22 ns.addModule(m1) 23 24 require.Equal(t, map[string]*ModuleInstance{m1.Name: m1}, ns.modules) 25 // Doesn't affect module names 26 require.Zero(t, len(ns.moduleNamesSet)) 27 require.Nil(t, ns.moduleNamesList) 28 }) 29 30 t.Run("redundant ok", func(t *testing.T) { 31 ns.addModule(m1) 32 require.Equal(t, map[string]*ModuleInstance{m1.Name: m1}, ns.modules) 33 }) 34 35 t.Run("adds second module", func(t *testing.T) { 36 m2 := &ModuleInstance{Name: "m2"} 37 ns.addModule(m2) 38 require.Equal(t, map[string]*ModuleInstance{m1.Name: m1, m2.Name: m2}, ns.modules) 39 }) 40 } 41 42 func TestNamespace_deleteModule(t *testing.T) { 43 ns, m1, m2 := newTestNamespace() 44 45 t.Run("delete one module", func(t *testing.T) { 46 ns.deleteModule(m2.Name) 47 48 // Leaves the other module alone 49 require.Equal(t, map[string]*ModuleInstance{m1.Name: m1}, ns.modules) 50 require.Equal(t, map[string]struct{}{m1.Name: {}}, ns.moduleNamesSet) 51 require.Equal(t, []string{m1.Name}, ns.moduleNamesList) 52 }) 53 54 t.Run("ok if missing", func(t *testing.T) { 55 ns.deleteModule(m2.Name) 56 }) 57 58 t.Run("delete last module", func(t *testing.T) { 59 ns.deleteModule(m1.Name) 60 61 require.Zero(t, len(ns.modules)) 62 require.Zero(t, len(ns.moduleNamesSet)) 63 require.Zero(t, len(ns.moduleNamesList)) 64 }) 65 } 66 67 func TestNamespace_module(t *testing.T) { 68 ns, m1, _ := newTestNamespace() 69 70 t.Run("ok", func(t *testing.T) { 71 require.Equal(t, m1, ns.module(m1.Name)) 72 }) 73 74 t.Run("unknown", func(t *testing.T) { 75 require.Nil(t, ns.module("unknown")) 76 }) 77 } 78 79 func TestNamespace_requireModules(t *testing.T) { 80 t.Run("ok", func(t *testing.T) { 81 ns, m1, _ := newTestNamespace() 82 83 modules, err := ns.requireModules(map[string]struct{}{m1.Name: {}}) 84 require.NoError(t, err) 85 require.Equal(t, map[string]*ModuleInstance{m1.Name: m1}, modules) 86 }) 87 t.Run("module not instantiated", func(t *testing.T) { 88 ns, _, _ := newTestNamespace() 89 90 _, err := ns.requireModules(map[string]struct{}{"unknown": {}}) 91 require.EqualError(t, err, "module[unknown] not instantiated") 92 }) 93 } 94 95 func TestNamespace_requireModuleName(t *testing.T) { 96 ns := &Namespace{moduleNamesSet: map[string]struct{}{}} 97 98 t.Run("first", func(t *testing.T) { 99 err := ns.requireModuleName("m1") 100 require.NoError(t, err) 101 102 // Ensure it adds the module name, and doesn't impact the module list. 103 require.Equal(t, []string{"m1"}, ns.moduleNamesList) 104 require.Equal(t, map[string]struct{}{"m1": {}}, ns.moduleNamesSet) 105 require.Zero(t, len(ns.modules)) 106 }) 107 t.Run("second", func(t *testing.T) { 108 err := ns.requireModuleName("m2") 109 require.NoError(t, err) 110 111 // Appends in order. 112 require.Equal(t, []string{"m1", "m2"}, ns.moduleNamesList) 113 require.Equal(t, map[string]struct{}{"m1": {}, "m2": {}}, ns.moduleNamesSet) 114 }) 115 t.Run("existing", func(t *testing.T) { 116 err := ns.requireModuleName("m2") 117 require.EqualError(t, err, "module[m2] has already been instantiated") 118 }) 119 } 120 121 func TestNamespace_AliasModule(t *testing.T) { 122 ns := newNamespace() 123 m1 := &ModuleInstance{Name: "m1"} 124 ns.addModule(m1) 125 126 ns.AliasModule("m1", "m2") 127 require.Equal(t, map[string]*ModuleInstance{"m1": m1, "m2": m1}, ns.modules) 128 // Doesn't affect module names 129 require.Zero(t, len(ns.moduleNamesSet)) 130 require.Nil(t, ns.moduleNamesList) 131 } 132 133 func TestNamespace_CloseWithExitCode(t *testing.T) { 134 tests := []struct { 135 name string 136 testClosed bool 137 }{ 138 { 139 name: "nothing closed", 140 testClosed: false, 141 }, 142 { 143 name: "partially closed", 144 testClosed: true, 145 }, 146 } 147 148 for _, tt := range tests { 149 tc := tt 150 t.Run(tc.name, func(t *testing.T) { 151 ns, m1, m2 := newTestNamespace() 152 153 if tc.testClosed { 154 err := m2.CallCtx.CloseWithExitCode(testCtx, 2) 155 require.NoError(t, err) 156 } 157 158 err := ns.CloseWithExitCode(testCtx, 2) 159 require.NoError(t, err) 160 161 // Both modules were closed 162 require.Equal(t, uint64(1)+uint64(2)<<32, *m1.CallCtx.closed) 163 require.Equal(t, uint64(1)+uint64(2)<<32, *m2.CallCtx.closed) 164 165 // Namespace state zeroed 166 require.Zero(t, len(ns.modules)) 167 require.Zero(t, len(ns.moduleNamesSet)) 168 require.Zero(t, len(ns.moduleNamesList)) 169 }) 170 } 171 172 t.Run("error closing", func(t *testing.T) { 173 // Right now, the only way to err closing the sys context is if a File.Close erred. 174 testFS := testfs.FS{"foo": &testfs.File{CloseErr: errors.New("error closing")}} 175 sysCtx := sys.DefaultContext(testFS) 176 fsCtx := sysCtx.FS(testCtx) 177 178 _, err := fsCtx.OpenFile(testCtx, "/foo") 179 require.NoError(t, err) 180 181 ns, m1, m2 := newTestNamespace() 182 m1.CallCtx.Sys = sysCtx // This should err, but both should close 183 184 err = ns.CloseWithExitCode(testCtx, 2) 185 require.EqualError(t, err, "error closing") 186 187 // Both modules were closed 188 require.Equal(t, uint64(1)+uint64(2)<<32, *m1.CallCtx.closed) 189 require.Equal(t, uint64(1)+uint64(2)<<32, *m2.CallCtx.closed) 190 191 // Namespace state zeroed 192 require.Zero(t, len(ns.modules)) 193 require.Zero(t, len(ns.moduleNamesSet)) 194 require.Zero(t, len(ns.moduleNamesList)) 195 }) 196 } 197 198 func TestNamespace_Module(t *testing.T) { 199 ns, m1, _ := newTestNamespace() 200 201 t.Run("ok", func(t *testing.T) { 202 require.Equal(t, m1.CallCtx, ns.Module(m1.Name)) 203 }) 204 205 t.Run("unknown", func(t *testing.T) { 206 require.Nil(t, ns.Module("unknown")) 207 }) 208 } 209 210 // newTestNamespace sets up a new Namespace without adding test coverage its functions. 211 func newTestNamespace() (*Namespace, *ModuleInstance, *ModuleInstance) { 212 ns := &Namespace{} 213 m1 := &ModuleInstance{Name: "m1"} 214 m1.CallCtx = NewCallContext(ns, m1, nil) 215 216 m2 := &ModuleInstance{Name: "m2"} 217 m2.CallCtx = NewCallContext(ns, m2, nil) 218 219 ns.modules = map[string]*ModuleInstance{m1.Name: m1, m2.Name: m2} 220 ns.moduleNamesSet = map[string]struct{}{m1.Name: {}, m2.Name: {}} 221 ns.moduleNamesList = []string{m1.Name, m2.Name} 222 return ns, m1, m2 223 }