wa-lang.org/wazero@v1.0.2/internal/wasm/call_context_test.go (about) 1 package wasm 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "testing" 8 9 "wa-lang.org/wazero/internal/sys" 10 testfs "wa-lang.org/wazero/internal/testing/fs" 11 "wa-lang.org/wazero/internal/testing/require" 12 ) 13 14 func TestCallContext_WithMemory(t *testing.T) { 15 tests := []struct { 16 name string 17 mod *CallContext 18 mem *MemoryInstance 19 expectSame bool 20 }{ 21 { 22 name: "nil->nil: same", 23 mod: &CallContext{}, 24 mem: nil, 25 expectSame: true, 26 }, 27 { 28 name: "nil->mem: not same", 29 mod: &CallContext{}, 30 mem: &MemoryInstance{}, 31 expectSame: false, 32 }, 33 { 34 name: "mem->nil: same", 35 mod: &CallContext{memory: &MemoryInstance{}}, 36 mem: nil, 37 expectSame: true, 38 }, 39 { 40 name: "mem1->mem2: not same", 41 mod: &CallContext{memory: &MemoryInstance{}}, 42 mem: &MemoryInstance{}, 43 expectSame: false, 44 }, 45 } 46 47 for _, tt := range tests { 48 tc := tt 49 50 t.Run(tc.name, func(t *testing.T) { 51 mod2 := tc.mod.WithMemory(tc.mem) 52 if tc.expectSame { 53 require.Same(t, tc.mod, mod2) 54 } else { 55 require.NotSame(t, tc.mod, mod2) 56 require.Equal(t, tc.mem, mod2.memory) 57 } 58 }) 59 } 60 } 61 62 func TestCallContext_String(t *testing.T) { 63 s, ns := newStore() 64 65 tests := []struct { 66 name, moduleName, expected string 67 }{ 68 { 69 name: "empty", 70 moduleName: "", 71 expected: "Module[]", 72 }, 73 { 74 name: "not empty", 75 moduleName: "math", 76 expected: "Module[math]", 77 }, 78 } 79 80 for _, tt := range tests { 81 tc := tt 82 83 t.Run(tc.name, func(t *testing.T) { 84 // Ensure paths that can create the host module can see the name. 85 m, err := s.Instantiate(context.Background(), ns, &Module{}, tc.moduleName, nil) 86 defer m.Close(testCtx) //nolint 87 88 require.NoError(t, err) 89 require.Equal(t, tc.expected, m.String()) 90 require.Equal(t, tc.expected, ns.Module(m.Name()).String()) 91 }) 92 } 93 } 94 95 func TestCallContext_Close(t *testing.T) { 96 s, ns := newStore() 97 98 tests := []struct { 99 name string 100 closer func(context.Context, *CallContext) error 101 expectedClosed uint64 102 }{ 103 { 104 name: "Close()", 105 closer: func(ctx context.Context, callContext *CallContext) error { 106 return callContext.Close(ctx) 107 }, 108 expectedClosed: uint64(1), 109 }, 110 { 111 name: "CloseWithExitCode(255)", 112 closer: func(ctx context.Context, callContext *CallContext) error { 113 return callContext.CloseWithExitCode(ctx, 255) 114 }, 115 expectedClosed: uint64(255)<<32 + 1, 116 }, 117 } 118 119 for _, tt := range tests { 120 tc := tt 121 t.Run(fmt.Sprintf("%s calls ns.CloseWithExitCode(module.name))", tc.name), func(t *testing.T) { 122 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 123 moduleName := t.Name() 124 m, err := s.Instantiate(ctx, ns, &Module{}, moduleName, nil) 125 require.NoError(t, err) 126 127 // We use side effects to see if Close called ns.CloseWithExitCode (without repeating store_test.go). 128 // One side effect of ns.CloseWithExitCode is that the moduleName can no longer be looked up. 129 require.Equal(t, ns.Module(moduleName), m) 130 131 // Closing should not err. 132 require.NoError(t, tc.closer(ctx, m)) 133 134 require.Equal(t, tc.expectedClosed, *m.closed) 135 136 // Verify our intended side-effect 137 require.Nil(t, ns.Module(moduleName)) 138 139 // Verify no error closing again. 140 require.NoError(t, tc.closer(ctx, m)) 141 } 142 }) 143 } 144 145 t.Run("calls Context.Close()", func(t *testing.T) { 146 sysCtx := sys.DefaultContext(testfs.FS{"foo": &testfs.File{}}) 147 fsCtx := sysCtx.FS(testCtx) 148 149 _, err := fsCtx.OpenFile(testCtx, "/foo") 150 require.NoError(t, err) 151 152 m, err := s.Instantiate(context.Background(), ns, &Module{}, t.Name(), sysCtx) 153 require.NoError(t, err) 154 155 // We use side effects to determine if Close in fact called Context.Close (without repeating sys_test.go). 156 // One side effect of Context.Close is that it clears the openedFiles map. Verify our base case. 157 _, ok := fsCtx.OpenedFile(testCtx, 3) 158 require.True(t, ok, "sysCtx.openedFiles was empty") 159 160 // Closing should not err. 161 require.NoError(t, m.Close(testCtx)) 162 163 // Verify our intended side-effect 164 _, ok = fsCtx.OpenedFile(testCtx, 3) 165 require.False(t, ok, "expected no opened files") 166 167 // Verify no error closing again. 168 require.NoError(t, m.Close(testCtx)) 169 }) 170 171 t.Run("error closing", func(t *testing.T) { 172 // Right now, the only way to err closing the sys context is if a File.Close erred. 173 testFS := testfs.FS{"foo": &testfs.File{CloseErr: errors.New("error closing")}} 174 sysCtx := sys.DefaultContext(testFS) 175 fsCtx := sysCtx.FS(testCtx) 176 177 _, err := fsCtx.OpenFile(testCtx, "/foo") 178 require.NoError(t, err) 179 180 m, err := s.Instantiate(context.Background(), ns, &Module{}, t.Name(), sysCtx) 181 require.NoError(t, err) 182 183 require.EqualError(t, m.Close(testCtx), "error closing") 184 185 // Verify our intended side-effect 186 _, ok := fsCtx.OpenedFile(testCtx, 3) 187 require.False(t, ok, "expected no opened files") 188 }) 189 } 190 191 func TestCallContext_CallDynamic(t *testing.T) { 192 s, ns := newStore() 193 194 tests := []struct { 195 name string 196 closer func(context.Context, *CallContext) error 197 expectedClosed uint64 198 }{ 199 { 200 name: "Close()", 201 closer: func(ctx context.Context, callContext *CallContext) error { 202 return callContext.Close(ctx) 203 }, 204 expectedClosed: uint64(1), 205 }, 206 { 207 name: "CloseWithExitCode(255)", 208 closer: func(ctx context.Context, callContext *CallContext) error { 209 return callContext.CloseWithExitCode(ctx, 255) 210 }, 211 expectedClosed: uint64(255)<<32 + 1, 212 }, 213 } 214 215 for _, tt := range tests { 216 tc := tt 217 t.Run(fmt.Sprintf("%s calls ns.CloseWithExitCode(module.name))", tc.name), func(t *testing.T) { 218 for _, ctx := range []context.Context{nil, testCtx} { // Ensure it doesn't crash on nil! 219 moduleName := t.Name() 220 m, err := s.Instantiate(ctx, ns, &Module{}, moduleName, nil) 221 require.NoError(t, err) 222 223 // We use side effects to see if Close called ns.CloseWithExitCode (without repeating store_test.go). 224 // One side effect of ns.CloseWithExitCode is that the moduleName can no longer be looked up. 225 require.Equal(t, ns.Module(moduleName), m) 226 227 // Closing should not err. 228 require.NoError(t, tc.closer(ctx, m)) 229 230 require.Equal(t, tc.expectedClosed, *m.closed) 231 232 // Verify our intended side-effect 233 require.Nil(t, ns.Module(moduleName)) 234 235 // Verify no error closing again. 236 require.NoError(t, tc.closer(ctx, m)) 237 } 238 }) 239 } 240 241 t.Run("calls Context.Close()", func(t *testing.T) { 242 sysCtx := sys.DefaultContext(testfs.FS{"foo": &testfs.File{}}) 243 fsCtx := sysCtx.FS(testCtx) 244 245 _, err := fsCtx.OpenFile(testCtx, "/foo") 246 require.NoError(t, err) 247 248 m, err := s.Instantiate(context.Background(), ns, &Module{}, t.Name(), sysCtx) 249 require.NoError(t, err) 250 251 // We use side effects to determine if Close in fact called Context.Close (without repeating sys_test.go). 252 // One side effect of Context.Close is that it clears the openedFiles map. Verify our base case. 253 _, ok := fsCtx.OpenedFile(testCtx, 3) 254 require.True(t, ok, "sysCtx.openedFiles was empty") 255 256 // Closing should not err. 257 require.NoError(t, m.Close(testCtx)) 258 259 // Verify our intended side-effect 260 _, ok = fsCtx.OpenedFile(testCtx, 3) 261 require.False(t, ok, "expected no opened files") 262 263 // Verify no error closing again. 264 require.NoError(t, m.Close(testCtx)) 265 }) 266 267 t.Run("error closing", func(t *testing.T) { 268 // Right now, the only way to err closing the sys context is if a File.Close erred. 269 testFS := testfs.FS{"foo": &testfs.File{CloseErr: errors.New("error closing")}} 270 sysCtx := sys.DefaultContext(testFS) 271 fsCtx := sysCtx.FS(testCtx) 272 273 _, err := fsCtx.OpenFile(testCtx, "/foo") 274 require.NoError(t, err) 275 276 m, err := s.Instantiate(context.Background(), ns, &Module{}, t.Name(), sysCtx) 277 require.NoError(t, err) 278 279 require.EqualError(t, m.Close(testCtx), "error closing") 280 281 // Verify our intended side-effect 282 _, ok := fsCtx.OpenedFile(testCtx, 3) 283 require.False(t, ok, "expected no opened files") 284 }) 285 }