wa-lang.org/wazero@v1.0.2/imports/emscripten/emscripten_test.go (about)

     1  package emscripten
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	_ "embed"
     7  	"testing"
     8  
     9  	"wa-lang.org/wazero"
    10  	. "wa-lang.org/wazero/experimental"
    11  	"wa-lang.org/wazero/experimental/logging"
    12  	"wa-lang.org/wazero/imports/wasi_snapshot_preview1"
    13  	"wa-lang.org/wazero/internal/testing/require"
    14  	"wa-lang.org/wazero/sys"
    15  )
    16  
    17  // growWasm was compiled from testdata/grow.cc
    18  //
    19  //go:embed testdata/grow.wasm
    20  var growWasm []byte
    21  
    22  // invokeWasm was generated by the following:
    23  //
    24  //	cd testdata; wat2wasm --debug-names invoke.wat
    25  //
    26  //go:embed testdata/invoke.wasm
    27  var invokeWasm []byte
    28  
    29  // testCtx is an arbitrary, non-default context. Non-nil also prevents linter errors.
    30  var testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
    31  
    32  // TestGrow is an integration test until we have an Emscripten example.
    33  func TestGrow(t *testing.T) {
    34  	var log bytes.Buffer
    35  
    36  	// Set context to one that has an experimental listener
    37  	ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&log))
    38  
    39  	r := wazero.NewRuntime(ctx)
    40  	defer r.Close(ctx)
    41  
    42  	wasi_snapshot_preview1.MustInstantiate(ctx, r)
    43  
    44  	_, err := Instantiate(ctx, r)
    45  	require.NoError(t, err)
    46  
    47  	// Emscripten exits main with zero by default
    48  	_, err = r.InstantiateModuleFromBinary(ctx, growWasm)
    49  	require.Error(t, err)
    50  	require.Zero(t, err.(*sys.ExitError).ExitCode())
    51  
    52  	// We expect the memory no-op memory growth hook to be invoked as wasm.
    53  	require.Contains(t, log.String(), "--> env.emscripten_notify_memory_growth(memory_index=0)")
    54  }
    55  
    56  func TestInvoke(t *testing.T) {
    57  	var log bytes.Buffer
    58  
    59  	// Set context to one that has an experimental listener
    60  	ctx := context.WithValue(testCtx, FunctionListenerFactoryKey{}, logging.NewLoggingListenerFactory(&log))
    61  
    62  	r := wazero.NewRuntime(ctx)
    63  	defer r.Close(ctx)
    64  
    65  	_, err := Instantiate(ctx, r)
    66  	require.NoError(t, err)
    67  
    68  	mod, err := r.InstantiateModuleFromBinary(ctx, invokeWasm)
    69  	require.NoError(t, err)
    70  
    71  	tests := []struct {
    72  		name, funcName          string
    73  		tableOffset             int
    74  		params, expectedResults []uint64
    75  		expectedLog             string
    76  	}{
    77  		{
    78  			name:            "invoke_i",
    79  			funcName:        "call_v_i32",
    80  			expectedResults: []uint64{42},
    81  			expectedLog: `--> .call_v_i32(0)
    82  	==> env.invoke_i(index=0)
    83  		--> .v_i32()
    84  		<-- (42)
    85  	<== (42)
    86  <-- (42)
    87  `,
    88  		},
    89  		{
    90  			name:            "invoke_ii",
    91  			funcName:        "call_i32_i32",
    92  			tableOffset:     2,
    93  			params:          []uint64{42},
    94  			expectedResults: []uint64{42},
    95  			expectedLog: `--> .call_i32_i32(2,42)
    96  	==> env.invoke_ii(index=2,a1=42)
    97  		--> .i32_i32(42)
    98  		<-- (42)
    99  	<== (42)
   100  <-- (42)
   101  `,
   102  		},
   103  		{
   104  			name:            "invoke_iii",
   105  			funcName:        "call_i32i32_i32",
   106  			tableOffset:     4,
   107  			params:          []uint64{1, 2},
   108  			expectedResults: []uint64{3},
   109  			expectedLog: `--> .call_i32i32_i32(4,1,2)
   110  	==> env.invoke_iii(index=4,a1=1,a2=2)
   111  		--> .i32i32_i32(1,2)
   112  		<-- (3)
   113  	<== (3)
   114  <-- (3)
   115  `,
   116  		},
   117  		{
   118  			name:            "invoke_iiii",
   119  			funcName:        "call_i32i32i32_i32",
   120  			tableOffset:     6,
   121  			params:          []uint64{1, 2, 4},
   122  			expectedResults: []uint64{7},
   123  			expectedLog: `--> .call_i32i32i32_i32(6,1,2,4)
   124  	==> env.invoke_iiii(index=6,a1=1,a2=2,a3=4)
   125  		--> .i32i32i32_i32(1,2,4)
   126  		<-- (7)
   127  	<== (7)
   128  <-- (7)
   129  `,
   130  		},
   131  		{
   132  			name:            "invoke_iiiii",
   133  			funcName:        "calli32_i32i32i32i32_i32",
   134  			tableOffset:     8,
   135  			params:          []uint64{1, 2, 4, 8},
   136  			expectedResults: []uint64{15},
   137  			expectedLog: `--> .calli32_i32i32i32i32_i32(8,1,2,4,8)
   138  	==> env.invoke_iiiii(index=8,a1=1,a2=2,a3=4,a4=8)
   139  		--> .i32i32i32i32_i32(1,2,4,8)
   140  		<-- (15)
   141  	<== (15)
   142  <-- (15)
   143  `,
   144  		},
   145  		{
   146  			name:        "invoke_v",
   147  			funcName:    "call_v_v",
   148  			tableOffset: 10,
   149  			expectedLog: `--> .call_v_v(10)
   150  	==> env.invoke_v(index=10)
   151  		--> .v_v()
   152  		<-- ()
   153  	<== ()
   154  <-- ()
   155  `,
   156  		},
   157  		{
   158  			name:        "invoke_vi",
   159  			funcName:    "call_i32_v",
   160  			tableOffset: 12,
   161  			params:      []uint64{42},
   162  			expectedLog: `--> .call_i32_v(12,42)
   163  	==> env.invoke_vi(index=12,a1=42)
   164  		--> .i32_v(42)
   165  		<-- ()
   166  	<== ()
   167  <-- ()
   168  `,
   169  		},
   170  		{
   171  			name:        "invoke_vii",
   172  			funcName:    "call_i32i32_v",
   173  			tableOffset: 14,
   174  			params:      []uint64{1, 2},
   175  			expectedLog: `--> .call_i32i32_v(14,1,2)
   176  	==> env.invoke_vii(index=14,a1=1,a2=2)
   177  		--> .i32i32_v(1,2)
   178  		<-- ()
   179  	<== ()
   180  <-- ()
   181  `,
   182  		},
   183  		{
   184  			name:        "invoke_viii",
   185  			funcName:    "call_i32i32i32_v",
   186  			tableOffset: 16,
   187  			params:      []uint64{1, 2, 4},
   188  			expectedLog: `--> .call_i32i32i32_v(16,1,2,4)
   189  	==> env.invoke_viii(index=16,a1=1,a2=2,a3=4)
   190  		--> .i32i32i32_v(1,2,4)
   191  		<-- ()
   192  	<== ()
   193  <-- ()
   194  `,
   195  		},
   196  		{
   197  			name:        "invoke_viiii",
   198  			funcName:    "calli32_i32i32i32i32_v",
   199  			tableOffset: 18,
   200  			params:      []uint64{1, 2, 4, 8},
   201  			expectedLog: `--> .calli32_i32i32i32i32_v(18,1,2,4,8)
   202  	==> env.invoke_viiii(index=18,a1=1,a2=2,a3=4,a4=8)
   203  		--> .i32i32i32i32_v(1,2,4,8)
   204  		<-- ()
   205  	<== ()
   206  <-- ()
   207  `,
   208  		},
   209  	}
   210  
   211  	for _, tt := range tests {
   212  		tc := tt
   213  
   214  		t.Run(tc.name, func(t *testing.T) {
   215  			defer log.Reset()
   216  
   217  			params := tc.params
   218  			params = append([]uint64{uint64(tc.tableOffset)}, params...)
   219  
   220  			results, err := mod.ExportedFunction(tc.funcName).Call(testCtx, params...)
   221  			require.NoError(t, err)
   222  			require.Equal(t, tc.expectedResults, results)
   223  
   224  			// We expect to see the dynamic function call target
   225  			require.Equal(t, log.String(), tc.expectedLog)
   226  
   227  			// We expect an unreachable function to err
   228  			params[0]++
   229  			_, err = mod.ExportedFunction(tc.funcName).Call(testCtx, params...)
   230  			require.Error(t, err)
   231  		})
   232  	}
   233  }