github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/imports/wasi_snapshot_preview1/wasi_test.go (about) 1 package wasi_snapshot_preview1_test 2 3 import ( 4 "bytes" 5 "context" 6 _ "embed" 7 "testing" 8 "time" 9 10 "github.com/bananabytelabs/wazero" 11 "github.com/bananabytelabs/wazero/api" 12 "github.com/bananabytelabs/wazero/experimental" 13 "github.com/bananabytelabs/wazero/experimental/logging" 14 "github.com/bananabytelabs/wazero/imports/wasi_snapshot_preview1" 15 "github.com/bananabytelabs/wazero/internal/testing/proxy" 16 "github.com/bananabytelabs/wazero/internal/testing/require" 17 "github.com/bananabytelabs/wazero/internal/wasip1" 18 "github.com/bananabytelabs/wazero/sys" 19 ) 20 21 // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors. 22 var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary") 23 24 const testMemoryPageSize = 1 25 26 // exitOnStartUnstableWasm was generated by the following: 27 // 28 // cd testdata; wat2wasm --debug-names exit_on_start_unstable.wat 29 // 30 //go:embed testdata/exit_on_start_unstable.wasm 31 var exitOnStartUnstableWasm []byte 32 33 func TestNewFunctionExporter(t *testing.T) { 34 t.Run("export as wasi_unstable", func(t *testing.T) { 35 r := wazero.NewRuntime(testCtx) 36 defer r.Close(testCtx) 37 38 // Instantiate the current WASI functions under the wasi_unstable 39 // instead of wasi_snapshot_preview1. 40 wasiBuilder := r.NewHostModuleBuilder("wasi_unstable") 41 wasi_snapshot_preview1.NewFunctionExporter().ExportFunctions(wasiBuilder) 42 _, err := wasiBuilder.Instantiate(testCtx) 43 require.NoError(t, err) 44 45 // Instantiate our test binary, but using the old import names. 46 _, err = r.Instantiate(testCtx, exitOnStartUnstableWasm) 47 48 // Ensure the test binary worked. It should return exit code 2. 49 require.Equal(t, uint32(2), err.(*sys.ExitError).ExitCode()) 50 }) 51 52 t.Run("override function", func(t *testing.T) { 53 r := wazero.NewRuntime(testCtx) 54 defer r.Close(testCtx) 55 56 // Export the default WASI functions 57 wasiBuilder := r.NewHostModuleBuilder(wasi_snapshot_preview1.ModuleName) 58 wasi_snapshot_preview1.NewFunctionExporter().ExportFunctions(wasiBuilder) 59 60 // Override proc_exit to prove the point that you can add or replace 61 // functions like this. 62 wasiBuilder.NewFunctionBuilder(). 63 WithFunc(func(ctx context.Context, mod api.Module, exitCode uint32) { 64 require.Equal(t, uint32(2), exitCode) 65 // ignore the code instead! 66 mod.Close(ctx) 67 }).Export("proc_exit") 68 69 _, err := wasiBuilder.Instantiate(testCtx) 70 require.NoError(t, err) 71 72 // Instantiate our test binary which will use our modified WASI. 73 _, err = r.Instantiate(testCtx, exitOnStartWasm) 74 75 // Ensure the modified function was used! 76 require.Nil(t, err) 77 }) 78 } 79 80 // maskMemory sets the first memory in the store to '?' * size, so tests can see what's written. 81 func maskMemory(t *testing.T, mod api.Module, size int) { 82 for i := uint32(0); i < uint32(size); i++ { 83 require.True(t, mod.Memory().WriteByte(i, '?')) 84 } 85 } 86 87 func requireProxyModule(t *testing.T, config wazero.ModuleConfig) (api.Module, api.Closer, *bytes.Buffer) { 88 return requireProxyModuleWithContext(testCtx, t, config) 89 } 90 91 func requireProxyModuleWithContext(ctx context.Context, t *testing.T, config wazero.ModuleConfig) (api.Module, api.Closer, *bytes.Buffer) { 92 var log bytes.Buffer 93 94 // Set context to one that has an experimental listener 95 ctx = context.WithValue(ctx, experimental.FunctionListenerFactoryKey{}, 96 proxy.NewLoggingListenerFactory(&log, logging.LogScopeAll)) 97 98 r := wazero.NewRuntime(ctx) 99 100 wasiModuleCompiled, err := wasi_snapshot_preview1.NewBuilder(r).Compile(ctx) 101 require.NoError(t, err) 102 103 _, err = r.InstantiateModule(ctx, wasiModuleCompiled, config) 104 require.NoError(t, err) 105 106 proxyBin := proxy.NewModuleBinary(wasi_snapshot_preview1.ModuleName, wasiModuleCompiled) 107 108 proxyCompiled, err := r.CompileModule(ctx, proxyBin) 109 require.NoError(t, err) 110 111 mod, err := r.InstantiateModule(ctx, proxyCompiled, config) 112 require.NoError(t, err) 113 114 return mod, r, &log 115 } 116 117 // requireErrnoNosys ensures a call of the given function returns errno. The log 118 // message returned can verify the output is wasm `-->` or a host `==>` 119 // function. 120 func requireErrnoNosys(t *testing.T, funcName string, params ...uint64) string { 121 var log bytes.Buffer 122 123 // Set context to one that has an experimental listener 124 ctx := context.WithValue(testCtx, experimental.FunctionListenerFactoryKey{}, 125 proxy.NewLoggingListenerFactory(&log, logging.LogScopeAll)) 126 127 r := wazero.NewRuntime(ctx) 128 defer r.Close(ctx) 129 130 // Instantiate the wasi module. 131 wasiModuleCompiled, err := wasi_snapshot_preview1.NewBuilder(r).Compile(ctx) 132 require.NoError(t, err) 133 134 _, err = r.InstantiateModule(ctx, wasiModuleCompiled, wazero.NewModuleConfig()) 135 require.NoError(t, err) 136 137 proxyBin := proxy.NewModuleBinary(wasi_snapshot_preview1.ModuleName, wasiModuleCompiled) 138 139 proxyCompiled, err := r.CompileModule(ctx, proxyBin) 140 require.NoError(t, err) 141 142 mod, err := r.InstantiateModule(ctx, proxyCompiled, wazero.NewModuleConfig()) 143 require.NoError(t, err) 144 145 requireErrnoResult(t, wasip1.ErrnoNosys, mod, funcName, params...) 146 return "\n" + log.String() 147 } 148 149 func requireErrnoResult(t *testing.T, expectedErrno wasip1.Errno, mod api.Closer, funcName string, params ...uint64) { 150 results, err := mod.(api.Module).ExportedFunction(funcName).Call(testCtx, params...) 151 require.NoError(t, err) 152 errno := wasip1.Errno(results[0]) 153 require.Equal(t, expectedErrno, errno, "want %s but have %s", wasip1.ErrnoName(expectedErrno), wasip1.ErrnoName(errno)) 154 } 155 156 func newBlockingReader(t *testing.T) blockingReader { 157 timeout, cancelFunc := context.WithTimeout(testCtx, 5*time.Second) 158 t.Cleanup(cancelFunc) 159 return blockingReader{ctx: timeout} 160 } 161 162 // blockingReader is an io.Reader that never terminates its read 163 // unless the embedded context is Done() 164 type blockingReader struct { 165 ctx context.Context 166 } 167 168 // Read implements io.Reader 169 func (b blockingReader) Read(buf []byte) (n int, err error) { 170 <-b.ctx.Done() 171 return 0, nil 172 }