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  }