wa-lang.org/wazero@v1.0.2/internal/integration_test/engine/hammer_test.go (about) 1 package adhoc 2 3 import ( 4 "context" 5 "sync" 6 "testing" 7 8 "wa-lang.org/wazero" 9 "wa-lang.org/wazero/api" 10 "wa-lang.org/wazero/internal/platform" 11 "wa-lang.org/wazero/internal/testing/hammer" 12 "wa-lang.org/wazero/internal/testing/require" 13 "wa-lang.org/wazero/sys" 14 ) 15 16 var hammers = map[string]func(t *testing.T, r wazero.Runtime){ 17 // Tests here are similar to what's described in /RATIONALE.md, but deviate as they involve blocking functions. 18 "close importing module while in use": closeImportingModuleWhileInUse, 19 "close imported module while in use": closeImportedModuleWhileInUse, 20 } 21 22 func TestEngineCompiler_hammer(t *testing.T) { 23 if !platform.CompilerSupported() { 24 t.Skip() 25 } 26 runAllTests(t, hammers, wazero.NewRuntimeConfigCompiler()) 27 } 28 29 func TestEngineInterpreter_hammer(t *testing.T) { 30 runAllTests(t, hammers, wazero.NewRuntimeConfigInterpreter()) 31 } 32 33 func closeImportingModuleWhileInUse(t *testing.T, r wazero.Runtime) { 34 closeModuleWhileInUse(t, r, func(imported, importing api.Module) (api.Module, api.Module) { 35 // Close the importing module, despite calls being in-flight. 36 require.NoError(t, importing.Close(testCtx)) 37 38 // Prove a module can be redefined even with in-flight calls. 39 binary := callReturnImportWasm(t, imported.Name(), importing.Name(), i32) 40 importing, err := r.InstantiateModuleFromBinary(testCtx, binary) 41 require.NoError(t, err) 42 return imported, importing 43 }) 44 } 45 46 func closeImportedModuleWhileInUse(t *testing.T, r wazero.Runtime) { 47 closeModuleWhileInUse(t, r, func(imported, importing api.Module) (api.Module, api.Module) { 48 // Close the importing and imported module, despite calls being in-flight. 49 require.NoError(t, importing.Close(testCtx)) 50 require.NoError(t, imported.Close(testCtx)) 51 52 // Redefine the imported module, with a function that no longer blocks. 53 imported, err := r.NewHostModuleBuilder(imported.Name()). 54 NewFunctionBuilder(). 55 WithFunc(func(ctx context.Context, x uint32) uint32 { 56 return x 57 }). 58 Export("return_input"). 59 Instantiate(testCtx, r) 60 require.NoError(t, err) 61 62 // Redefine the importing module, which should link to the redefined host module. 63 binary := callReturnImportWasm(t, imported.Name(), importing.Name(), i32) 64 importing, err = r.InstantiateModuleFromBinary(testCtx, binary) 65 require.NoError(t, err) 66 67 return imported, importing 68 }) 69 } 70 71 func closeModuleWhileInUse(t *testing.T, r wazero.Runtime, closeFn func(imported, importing api.Module) (api.Module, api.Module)) { 72 P := 8 // max count of goroutines 73 if testing.Short() { // Adjust down if `-test.short` 74 P = 4 75 } 76 77 // To know return path works on a closed module, we need to block calls. 78 var calls sync.WaitGroup 79 calls.Add(P) 80 blockAndReturn := func(ctx context.Context, x uint32) uint32 { 81 calls.Wait() 82 return x 83 } 84 85 // Create the host module, which exports the blocking function. 86 imported, err := r.NewHostModuleBuilder(t.Name()+"-imported"). 87 NewFunctionBuilder().WithFunc(blockAndReturn).Export("return_input"). 88 Instantiate(testCtx, r) 89 require.NoError(t, err) 90 defer imported.Close(testCtx) 91 92 // Import that module. 93 binary := callReturnImportWasm(t, imported.Name(), t.Name()+"-importing", i32) 94 importing, err := r.InstantiateModuleFromBinary(testCtx, binary) 95 require.NoError(t, err) 96 defer importing.Close(testCtx) 97 98 // As this is a blocking function call, only run 1 per goroutine. 99 i := importing // pin the module used inside goroutines 100 hammer.NewHammer(t, P, 1).Run(func(name string) { 101 // In all cases, the importing module is closed, so the error should have that as its module name. 102 requireFunctionCallExits(t, i.Name(), i.ExportedFunction("call_return_input")) 103 }, func() { // When all functions are in-flight, re-assign the modules. 104 imported, importing = closeFn(imported, importing) 105 // Unblock all the calls 106 calls.Add(-P) 107 }) 108 // As references may have changed, ensure we close both. 109 defer imported.Close(testCtx) 110 defer importing.Close(testCtx) 111 if t.Failed() { 112 return // At least one test failed, so return now. 113 } 114 115 // If unloading worked properly, a new function call should route to the newly instantiated module. 116 requireFunctionCall(t, importing.ExportedFunction("call_return_input")) 117 } 118 119 func requireFunctionCall(t *testing.T, fn api.Function) { 120 res, err := fn.Call(testCtx, 3) 121 require.NoError(t, err) 122 require.Equal(t, uint64(3), res[0]) 123 } 124 125 func requireFunctionCallExits(t *testing.T, moduleName string, fn api.Function) { 126 _, err := fn.Call(testCtx, 3) 127 require.Equal(t, sys.NewExitError(moduleName, 0), err) 128 }