wa-lang.org/wazero@v1.0.2/imports/wasi_snapshot_preview1/wasi_test.go (about)

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