wa-lang.org/wazero@v1.0.2/internal/wasm/namespace_test.go (about)

     1  package wasm
     2  
     3  import (
     4  	"errors"
     5  	"testing"
     6  
     7  	"wa-lang.org/wazero/internal/sys"
     8  	testfs "wa-lang.org/wazero/internal/testing/fs"
     9  	"wa-lang.org/wazero/internal/testing/require"
    10  )
    11  
    12  func Test_newNamespace(t *testing.T) {
    13  	ns := newNamespace()
    14  	require.NotNil(t, ns.modules)
    15  }
    16  
    17  func TestNamespace_addModule(t *testing.T) {
    18  	ns := newNamespace()
    19  	m1 := &ModuleInstance{Name: "m1"}
    20  
    21  	t.Run("adds module", func(t *testing.T) {
    22  		ns.addModule(m1)
    23  
    24  		require.Equal(t, map[string]*ModuleInstance{m1.Name: m1}, ns.modules)
    25  		// Doesn't affect module names
    26  		require.Zero(t, len(ns.moduleNamesSet))
    27  		require.Nil(t, ns.moduleNamesList)
    28  	})
    29  
    30  	t.Run("redundant ok", func(t *testing.T) {
    31  		ns.addModule(m1)
    32  		require.Equal(t, map[string]*ModuleInstance{m1.Name: m1}, ns.modules)
    33  	})
    34  
    35  	t.Run("adds second module", func(t *testing.T) {
    36  		m2 := &ModuleInstance{Name: "m2"}
    37  		ns.addModule(m2)
    38  		require.Equal(t, map[string]*ModuleInstance{m1.Name: m1, m2.Name: m2}, ns.modules)
    39  	})
    40  }
    41  
    42  func TestNamespace_deleteModule(t *testing.T) {
    43  	ns, m1, m2 := newTestNamespace()
    44  
    45  	t.Run("delete one module", func(t *testing.T) {
    46  		ns.deleteModule(m2.Name)
    47  
    48  		// Leaves the other module alone
    49  		require.Equal(t, map[string]*ModuleInstance{m1.Name: m1}, ns.modules)
    50  		require.Equal(t, map[string]struct{}{m1.Name: {}}, ns.moduleNamesSet)
    51  		require.Equal(t, []string{m1.Name}, ns.moduleNamesList)
    52  	})
    53  
    54  	t.Run("ok if missing", func(t *testing.T) {
    55  		ns.deleteModule(m2.Name)
    56  	})
    57  
    58  	t.Run("delete last module", func(t *testing.T) {
    59  		ns.deleteModule(m1.Name)
    60  
    61  		require.Zero(t, len(ns.modules))
    62  		require.Zero(t, len(ns.moduleNamesSet))
    63  		require.Zero(t, len(ns.moduleNamesList))
    64  	})
    65  }
    66  
    67  func TestNamespace_module(t *testing.T) {
    68  	ns, m1, _ := newTestNamespace()
    69  
    70  	t.Run("ok", func(t *testing.T) {
    71  		require.Equal(t, m1, ns.module(m1.Name))
    72  	})
    73  
    74  	t.Run("unknown", func(t *testing.T) {
    75  		require.Nil(t, ns.module("unknown"))
    76  	})
    77  }
    78  
    79  func TestNamespace_requireModules(t *testing.T) {
    80  	t.Run("ok", func(t *testing.T) {
    81  		ns, m1, _ := newTestNamespace()
    82  
    83  		modules, err := ns.requireModules(map[string]struct{}{m1.Name: {}})
    84  		require.NoError(t, err)
    85  		require.Equal(t, map[string]*ModuleInstance{m1.Name: m1}, modules)
    86  	})
    87  	t.Run("module not instantiated", func(t *testing.T) {
    88  		ns, _, _ := newTestNamespace()
    89  
    90  		_, err := ns.requireModules(map[string]struct{}{"unknown": {}})
    91  		require.EqualError(t, err, "module[unknown] not instantiated")
    92  	})
    93  }
    94  
    95  func TestNamespace_requireModuleName(t *testing.T) {
    96  	ns := &Namespace{moduleNamesSet: map[string]struct{}{}}
    97  
    98  	t.Run("first", func(t *testing.T) {
    99  		err := ns.requireModuleName("m1")
   100  		require.NoError(t, err)
   101  
   102  		// Ensure it adds the module name, and doesn't impact the module list.
   103  		require.Equal(t, []string{"m1"}, ns.moduleNamesList)
   104  		require.Equal(t, map[string]struct{}{"m1": {}}, ns.moduleNamesSet)
   105  		require.Zero(t, len(ns.modules))
   106  	})
   107  	t.Run("second", func(t *testing.T) {
   108  		err := ns.requireModuleName("m2")
   109  		require.NoError(t, err)
   110  
   111  		// Appends in order.
   112  		require.Equal(t, []string{"m1", "m2"}, ns.moduleNamesList)
   113  		require.Equal(t, map[string]struct{}{"m1": {}, "m2": {}}, ns.moduleNamesSet)
   114  	})
   115  	t.Run("existing", func(t *testing.T) {
   116  		err := ns.requireModuleName("m2")
   117  		require.EqualError(t, err, "module[m2] has already been instantiated")
   118  	})
   119  }
   120  
   121  func TestNamespace_AliasModule(t *testing.T) {
   122  	ns := newNamespace()
   123  	m1 := &ModuleInstance{Name: "m1"}
   124  	ns.addModule(m1)
   125  
   126  	ns.AliasModule("m1", "m2")
   127  	require.Equal(t, map[string]*ModuleInstance{"m1": m1, "m2": m1}, ns.modules)
   128  	// Doesn't affect module names
   129  	require.Zero(t, len(ns.moduleNamesSet))
   130  	require.Nil(t, ns.moduleNamesList)
   131  }
   132  
   133  func TestNamespace_CloseWithExitCode(t *testing.T) {
   134  	tests := []struct {
   135  		name       string
   136  		testClosed bool
   137  	}{
   138  		{
   139  			name:       "nothing closed",
   140  			testClosed: false,
   141  		},
   142  		{
   143  			name:       "partially closed",
   144  			testClosed: true,
   145  		},
   146  	}
   147  
   148  	for _, tt := range tests {
   149  		tc := tt
   150  		t.Run(tc.name, func(t *testing.T) {
   151  			ns, m1, m2 := newTestNamespace()
   152  
   153  			if tc.testClosed {
   154  				err := m2.CallCtx.CloseWithExitCode(testCtx, 2)
   155  				require.NoError(t, err)
   156  			}
   157  
   158  			err := ns.CloseWithExitCode(testCtx, 2)
   159  			require.NoError(t, err)
   160  
   161  			// Both modules were closed
   162  			require.Equal(t, uint64(1)+uint64(2)<<32, *m1.CallCtx.closed)
   163  			require.Equal(t, uint64(1)+uint64(2)<<32, *m2.CallCtx.closed)
   164  
   165  			// Namespace state zeroed
   166  			require.Zero(t, len(ns.modules))
   167  			require.Zero(t, len(ns.moduleNamesSet))
   168  			require.Zero(t, len(ns.moduleNamesList))
   169  		})
   170  	}
   171  
   172  	t.Run("error closing", func(t *testing.T) {
   173  		// Right now, the only way to err closing the sys context is if a File.Close erred.
   174  		testFS := testfs.FS{"foo": &testfs.File{CloseErr: errors.New("error closing")}}
   175  		sysCtx := sys.DefaultContext(testFS)
   176  		fsCtx := sysCtx.FS(testCtx)
   177  
   178  		_, err := fsCtx.OpenFile(testCtx, "/foo")
   179  		require.NoError(t, err)
   180  
   181  		ns, m1, m2 := newTestNamespace()
   182  		m1.CallCtx.Sys = sysCtx // This should err, but both should close
   183  
   184  		err = ns.CloseWithExitCode(testCtx, 2)
   185  		require.EqualError(t, err, "error closing")
   186  
   187  		// Both modules were closed
   188  		require.Equal(t, uint64(1)+uint64(2)<<32, *m1.CallCtx.closed)
   189  		require.Equal(t, uint64(1)+uint64(2)<<32, *m2.CallCtx.closed)
   190  
   191  		// Namespace state zeroed
   192  		require.Zero(t, len(ns.modules))
   193  		require.Zero(t, len(ns.moduleNamesSet))
   194  		require.Zero(t, len(ns.moduleNamesList))
   195  	})
   196  }
   197  
   198  func TestNamespace_Module(t *testing.T) {
   199  	ns, m1, _ := newTestNamespace()
   200  
   201  	t.Run("ok", func(t *testing.T) {
   202  		require.Equal(t, m1.CallCtx, ns.Module(m1.Name))
   203  	})
   204  
   205  	t.Run("unknown", func(t *testing.T) {
   206  		require.Nil(t, ns.Module("unknown"))
   207  	})
   208  }
   209  
   210  // newTestNamespace sets up a new Namespace without adding test coverage its functions.
   211  func newTestNamespace() (*Namespace, *ModuleInstance, *ModuleInstance) {
   212  	ns := &Namespace{}
   213  	m1 := &ModuleInstance{Name: "m1"}
   214  	m1.CallCtx = NewCallContext(ns, m1, nil)
   215  
   216  	m2 := &ModuleInstance{Name: "m2"}
   217  	m2.CallCtx = NewCallContext(ns, m2, nil)
   218  
   219  	ns.modules = map[string]*ModuleInstance{m1.Name: m1, m2.Name: m2}
   220  	ns.moduleNamesSet = map[string]struct{}{m1.Name: {}, m2.Name: {}}
   221  	ns.moduleNamesList = []string{m1.Name, m2.Name}
   222  	return ns, m1, m2
   223  }