github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/wasm/host_test.go (about)

     1  package wasm
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/bananabytelabs/wazero/api"
     8  	"github.com/bananabytelabs/wazero/internal/testing/require"
     9  	. "github.com/bananabytelabs/wazero/internal/wasip1"
    10  )
    11  
    12  func argsSizesGet(ctx context.Context, mod api.Module, resultArgc, resultArgvBufSize uint32) uint32 {
    13  	return 0
    14  }
    15  
    16  func fdWrite(ctx context.Context, mod api.Module, fd, iovs, iovsCount, resultSize uint32) uint32 {
    17  	return 0
    18  }
    19  
    20  func swap(ctx context.Context, x, y uint32) (uint32, uint32) {
    21  	return y, x
    22  }
    23  
    24  func TestNewHostModule(t *testing.T) {
    25  	t.Run("empty name not allowed", func(t *testing.T) {
    26  		_, err := NewHostModule("", nil, nil, api.CoreFeaturesV2)
    27  		require.Error(t, err)
    28  	})
    29  
    30  	swapName := "swap"
    31  	tests := []struct {
    32  		name, moduleName string
    33  		exportNames      []string
    34  		nameToHostFunc   map[string]*HostFunc
    35  		expected         *Module
    36  	}{
    37  		{
    38  			name:       "only name",
    39  			moduleName: "test",
    40  			expected:   &Module{NameSection: &NameSection{ModuleName: "test"}},
    41  		},
    42  		{
    43  			name:        "funcs",
    44  			moduleName:  InternalModuleName,
    45  			exportNames: []string{ArgsSizesGetName, FdWriteName},
    46  			nameToHostFunc: map[string]*HostFunc{
    47  				ArgsSizesGetName: {
    48  					ExportName:  ArgsSizesGetName,
    49  					ParamNames:  []string{"result.argc", "result.argv_len"},
    50  					ResultNames: []string{"errno"},
    51  					Code:        Code{GoFunc: argsSizesGet},
    52  				},
    53  				FdWriteName: {
    54  					ExportName:  FdWriteName,
    55  					ParamNames:  []string{"fd", "iovs", "iovs_len", "result.size"},
    56  					ResultNames: []string{"errno"},
    57  					Code:        Code{GoFunc: fdWrite},
    58  				},
    59  			},
    60  			expected: &Module{
    61  				TypeSection: []FunctionType{
    62  					{Params: []ValueType{i32, i32}, Results: []ValueType{i32}},
    63  					{Params: []ValueType{i32, i32, i32, i32}, Results: []ValueType{i32}},
    64  				},
    65  				FunctionSection: []Index{0, 1},
    66  				CodeSection:     []Code{MustParseGoReflectFuncCode(argsSizesGet), MustParseGoReflectFuncCode(fdWrite)},
    67  				ExportSection: []Export{
    68  					{Name: ArgsSizesGetName, Type: ExternTypeFunc, Index: 0},
    69  					{Name: FdWriteName, Type: ExternTypeFunc, Index: 1},
    70  				},
    71  				Exports: map[string]*Export{
    72  					ArgsSizesGetName: {Name: ArgsSizesGetName, Type: ExternTypeFunc, Index: 0},
    73  					FdWriteName:      {Name: FdWriteName, Type: ExternTypeFunc, Index: 1},
    74  				},
    75  				NameSection: &NameSection{
    76  					ModuleName: InternalModuleName,
    77  					FunctionNames: NameMap{
    78  						{Index: 0, Name: ArgsSizesGetName},
    79  						{Index: 1, Name: FdWriteName},
    80  					},
    81  					LocalNames: IndirectNameMap{
    82  						{Index: 0, NameMap: NameMap{
    83  							{Index: 0, Name: "result.argc"},
    84  							{Index: 1, Name: "result.argv_len"},
    85  						}},
    86  						{Index: 1, NameMap: NameMap{
    87  							{Index: 0, Name: "fd"},
    88  							{Index: 1, Name: "iovs"},
    89  							{Index: 2, Name: "iovs_len"},
    90  							{Index: 3, Name: "result.size"},
    91  						}},
    92  					},
    93  					ResultNames: IndirectNameMap{
    94  						{Index: 0, NameMap: NameMap{{Index: 0, Name: "errno"}}},
    95  						{Index: 1, NameMap: NameMap{{Index: 0, Name: "errno"}}},
    96  					},
    97  				},
    98  			},
    99  		},
   100  		{
   101  			name:           "multi-value",
   102  			moduleName:     "swapper",
   103  			exportNames:    []string{swapName},
   104  			nameToHostFunc: map[string]*HostFunc{swapName: {ExportName: swapName, Code: Code{GoFunc: swap}}},
   105  			expected: &Module{
   106  				TypeSection:     []FunctionType{{Params: []ValueType{i32, i32}, Results: []ValueType{i32, i32}}},
   107  				FunctionSection: []Index{0},
   108  				CodeSection:     []Code{MustParseGoReflectFuncCode(swap)},
   109  				ExportSection:   []Export{{Name: "swap", Type: ExternTypeFunc, Index: 0}},
   110  				Exports:         map[string]*Export{"swap": {Name: "swap", Type: ExternTypeFunc, Index: 0}},
   111  				NameSection:     &NameSection{ModuleName: "swapper", FunctionNames: NameMap{{Index: 0, Name: "swap"}}},
   112  			},
   113  		},
   114  	}
   115  
   116  	for _, tt := range tests {
   117  		tc := tt
   118  
   119  		t.Run(tc.name, func(t *testing.T) {
   120  			m, e := NewHostModule(tc.moduleName, tc.exportNames, tc.nameToHostFunc, api.CoreFeaturesV2)
   121  			require.NoError(t, e)
   122  			requireHostModuleEquals(t, tc.expected, m)
   123  			require.True(t, m.IsHostModule)
   124  		})
   125  	}
   126  }
   127  
   128  func requireHostModuleEquals(t *testing.T, expected, actual *Module) {
   129  	// `require.Equal(t, expected, actual)` fails reflect pointers don't match, so brute compare:
   130  	require.Equal(t, expected.TypeSection, actual.TypeSection)
   131  	require.Equal(t, expected.ImportSection, actual.ImportSection)
   132  	require.Equal(t, expected.FunctionSection, actual.FunctionSection)
   133  	require.Equal(t, expected.TableSection, actual.TableSection)
   134  	require.Equal(t, expected.MemorySection, actual.MemorySection)
   135  	require.Equal(t, expected.GlobalSection, actual.GlobalSection)
   136  	require.Equal(t, expected.ExportSection, actual.ExportSection)
   137  	require.Equal(t, expected.StartSection, actual.StartSection)
   138  	require.Equal(t, expected.ElementSection, actual.ElementSection)
   139  	require.Equal(t, expected.DataSection, actual.DataSection)
   140  	require.Equal(t, expected.NameSection, actual.NameSection)
   141  
   142  	// Special case because reflect.Value can't be compared with Equals
   143  	// TODO: This is copy/paste with /builder_test.go
   144  	require.Equal(t, len(expected.CodeSection), len(actual.CodeSection))
   145  	for i, c := range expected.CodeSection {
   146  		actualCode := actual.CodeSection[i]
   147  		require.Equal(t, c.GoFunc, actualCode.GoFunc)
   148  
   149  		// Not wasm
   150  		require.Nil(t, actualCode.Body)
   151  		require.Nil(t, actualCode.LocalTypes)
   152  	}
   153  }
   154  
   155  func TestNewHostModule_Errors(t *testing.T) {
   156  	tests := []struct {
   157  		name, moduleName string
   158  		exportNames      []string
   159  		nameToHostFunc   map[string]*HostFunc
   160  		expectedErr      string
   161  	}{
   162  		{
   163  			name:           "not a function",
   164  			moduleName:     "modname",
   165  			exportNames:    []string{"fn"},
   166  			nameToHostFunc: map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: t}}},
   167  			expectedErr:    "func[modname.fn] kind != func: ptr",
   168  		},
   169  		{
   170  			name:           "function has multiple results",
   171  			moduleName:     "yetanother",
   172  			exportNames:    []string{"fn"},
   173  			nameToHostFunc: map[string]*HostFunc{"fn": {ExportName: "fn", Code: Code{GoFunc: func() (uint32, uint32) { return 0, 0 }}}},
   174  			expectedErr:    "func[yetanother.fn] multiple result types invalid as feature \"multi-value\" is disabled",
   175  		},
   176  	}
   177  
   178  	for _, tt := range tests {
   179  		tc := tt
   180  
   181  		t.Run(tc.name, func(t *testing.T) {
   182  			_, e := NewHostModule(tc.moduleName, tc.exportNames, tc.nameToHostFunc, api.CoreFeaturesV1)
   183  			require.EqualError(t, e, tc.expectedErr)
   184  		})
   185  	}
   186  }