wa-lang.org/wazero@v1.0.2/builder_test.go (about) 1 package wazero 2 3 import ( 4 "context" 5 "testing" 6 7 "wa-lang.org/wazero/api" 8 "wa-lang.org/wazero/internal/testing/require" 9 "wa-lang.org/wazero/internal/wasm" 10 ) 11 12 // TestNewHostModuleBuilder_Compile only covers a few scenarios to avoid duplicating tests in internal/wasm/host_test.go 13 func TestNewHostModuleBuilder_Compile(t *testing.T) { 14 i32, i64 := api.ValueTypeI32, api.ValueTypeI64 15 16 uint32_uint32 := func(context.Context, uint32) uint32 { 17 return 0 18 } 19 uint64_uint32 := func(context.Context, uint64) uint32 { 20 return 0 21 } 22 23 gofunc1 := api.GoFunc(func(ctx context.Context, stack []uint64) { 24 stack[0] = 0 25 }) 26 gofunc2 := api.GoFunc(func(ctx context.Context, stack []uint64) { 27 stack[0] = 0 28 }) 29 30 tests := []struct { 31 name string 32 input func(Runtime) HostModuleBuilder 33 expected *wasm.Module 34 }{ 35 { 36 name: "empty", 37 input: func(r Runtime) HostModuleBuilder { 38 return r.NewHostModuleBuilder("") 39 }, 40 expected: &wasm.Module{}, 41 }, 42 { 43 name: "only name", 44 input: func(r Runtime) HostModuleBuilder { 45 return r.NewHostModuleBuilder("env") 46 }, 47 expected: &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "env"}}, 48 }, 49 { 50 name: "WithFunc", 51 input: func(r Runtime) HostModuleBuilder { 52 return r.NewHostModuleBuilder(""). 53 NewFunctionBuilder().WithFunc(uint32_uint32).Export("1") 54 }, 55 expected: &wasm.Module{ 56 TypeSection: []*wasm.FunctionType{ 57 {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}}, 58 }, 59 FunctionSection: []wasm.Index{0}, 60 CodeSection: []*wasm.Code{wasm.MustParseGoReflectFuncCode(uint32_uint32)}, 61 ExportSection: []*wasm.Export{ 62 {Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, 63 }, 64 NameSection: &wasm.NameSection{ 65 FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}}, 66 }, 67 }, 68 }, 69 { 70 name: "WithFunc WithName WithParameterNames", 71 input: func(r Runtime) HostModuleBuilder { 72 return r.NewHostModuleBuilder("").NewFunctionBuilder(). 73 WithFunc(uint32_uint32). 74 WithName("get").WithParameterNames("x"). 75 Export("1") 76 }, 77 expected: &wasm.Module{ 78 TypeSection: []*wasm.FunctionType{ 79 {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}}, 80 }, 81 FunctionSection: []wasm.Index{0}, 82 CodeSection: []*wasm.Code{wasm.MustParseGoReflectFuncCode(uint32_uint32)}, 83 ExportSection: []*wasm.Export{ 84 {Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, 85 }, 86 NameSection: &wasm.NameSection{ 87 FunctionNames: wasm.NameMap{{Index: 0, Name: "get"}}, 88 LocalNames: []*wasm.NameMapAssoc{{Index: 0, NameMap: wasm.NameMap{{Index: 0, Name: "x"}}}}, 89 }, 90 }, 91 }, 92 { 93 name: "WithFunc overwrites existing", 94 input: func(r Runtime) HostModuleBuilder { 95 return r.NewHostModuleBuilder(""). 96 NewFunctionBuilder().WithFunc(uint32_uint32).Export("1"). 97 NewFunctionBuilder().WithFunc(uint64_uint32).Export("1") 98 }, 99 expected: &wasm.Module{ 100 TypeSection: []*wasm.FunctionType{ 101 {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}}, 102 }, 103 FunctionSection: []wasm.Index{0}, 104 CodeSection: []*wasm.Code{wasm.MustParseGoReflectFuncCode(uint64_uint32)}, 105 ExportSection: []*wasm.Export{ 106 {Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, 107 }, 108 NameSection: &wasm.NameSection{ 109 FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}}, 110 }, 111 }, 112 }, 113 { 114 name: "WithFunc twice", 115 input: func(r Runtime) HostModuleBuilder { 116 // Intentionally out of order 117 return r.NewHostModuleBuilder(""). 118 NewFunctionBuilder().WithFunc(uint64_uint32).Export("2"). 119 NewFunctionBuilder().WithFunc(uint32_uint32).Export("1") 120 }, 121 expected: &wasm.Module{ 122 TypeSection: []*wasm.FunctionType{ 123 {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}}, 124 {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}}, 125 }, 126 FunctionSection: []wasm.Index{0, 1}, 127 CodeSection: []*wasm.Code{wasm.MustParseGoReflectFuncCode(uint32_uint32), wasm.MustParseGoReflectFuncCode(uint64_uint32)}, 128 ExportSection: []*wasm.Export{ 129 {Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, 130 {Name: "2", Type: wasm.ExternTypeFunc, Index: 1}, 131 }, 132 NameSection: &wasm.NameSection{ 133 FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}, {Index: 1, Name: "2"}}, 134 }, 135 }, 136 }, 137 { 138 name: "WithGoFunction", 139 input: func(r Runtime) HostModuleBuilder { 140 return r.NewHostModuleBuilder(""). 141 NewFunctionBuilder(). 142 WithGoFunction(gofunc1, []api.ValueType{i32}, []api.ValueType{i32}). 143 Export("1") 144 }, 145 expected: &wasm.Module{ 146 TypeSection: []*wasm.FunctionType{ 147 {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}}, 148 }, 149 FunctionSection: []wasm.Index{0}, 150 CodeSection: []*wasm.Code{ 151 {IsHostFunction: true, GoFunc: gofunc1}, 152 }, 153 ExportSection: []*wasm.Export{ 154 {Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, 155 }, 156 NameSection: &wasm.NameSection{ 157 FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}}, 158 }, 159 }, 160 }, 161 { 162 name: "WithGoFunction WithName WithParameterNames", 163 input: func(r Runtime) HostModuleBuilder { 164 return r.NewHostModuleBuilder("").NewFunctionBuilder(). 165 WithGoFunction(gofunc1, []api.ValueType{i32}, []api.ValueType{i32}). 166 WithName("get").WithParameterNames("x"). 167 Export("1") 168 }, 169 expected: &wasm.Module{ 170 TypeSection: []*wasm.FunctionType{ 171 {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}}, 172 }, 173 FunctionSection: []wasm.Index{0}, 174 CodeSection: []*wasm.Code{ 175 {IsHostFunction: true, GoFunc: gofunc1}, 176 }, 177 ExportSection: []*wasm.Export{ 178 {Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, 179 }, 180 NameSection: &wasm.NameSection{ 181 FunctionNames: wasm.NameMap{{Index: 0, Name: "get"}}, 182 LocalNames: []*wasm.NameMapAssoc{{Index: 0, NameMap: wasm.NameMap{{Index: 0, Name: "x"}}}}, 183 }, 184 }, 185 }, 186 { 187 name: "WithGoFunction overwrites existing", 188 input: func(r Runtime) HostModuleBuilder { 189 return r.NewHostModuleBuilder(""). 190 NewFunctionBuilder(). 191 WithGoFunction(gofunc1, []api.ValueType{i32}, []api.ValueType{i32}). 192 Export("1"). 193 NewFunctionBuilder(). 194 WithGoFunction(gofunc2, []api.ValueType{i64}, []api.ValueType{i32}). 195 Export("1") 196 }, 197 expected: &wasm.Module{ 198 TypeSection: []*wasm.FunctionType{ 199 {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}}, 200 }, 201 FunctionSection: []wasm.Index{0}, 202 CodeSection: []*wasm.Code{ 203 {IsHostFunction: true, GoFunc: gofunc2}, 204 }, 205 ExportSection: []*wasm.Export{ 206 {Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, 207 }, 208 NameSection: &wasm.NameSection{ 209 FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}}, 210 }, 211 }, 212 }, 213 { 214 name: "WithGoFunction twice", 215 input: func(r Runtime) HostModuleBuilder { 216 // Intentionally out of order 217 return r.NewHostModuleBuilder(""). 218 NewFunctionBuilder(). 219 WithGoFunction(gofunc2, []api.ValueType{i64}, []api.ValueType{i32}). 220 Export("2"). 221 NewFunctionBuilder(). 222 WithGoFunction(gofunc1, []api.ValueType{i32}, []api.ValueType{i32}). 223 Export("1") 224 }, 225 expected: &wasm.Module{ 226 TypeSection: []*wasm.FunctionType{ 227 {Params: []api.ValueType{i32}, Results: []api.ValueType{i32}}, 228 {Params: []api.ValueType{i64}, Results: []api.ValueType{i32}}, 229 }, 230 FunctionSection: []wasm.Index{0, 1}, 231 CodeSection: []*wasm.Code{ 232 {IsHostFunction: true, GoFunc: gofunc1}, 233 {IsHostFunction: true, GoFunc: gofunc2}, 234 }, 235 ExportSection: []*wasm.Export{ 236 {Name: "1", Type: wasm.ExternTypeFunc, Index: 0}, 237 {Name: "2", Type: wasm.ExternTypeFunc, Index: 1}, 238 }, 239 NameSection: &wasm.NameSection{ 240 FunctionNames: wasm.NameMap{{Index: 0, Name: "1"}, {Index: 1, Name: "2"}}, 241 }, 242 }, 243 }, 244 } 245 246 for _, tt := range tests { 247 tc := tt 248 249 t.Run(tc.name, func(t *testing.T) { 250 b := tc.input(NewRuntime(testCtx)).(*hostModuleBuilder) 251 compiled, err := b.Compile(testCtx) 252 require.NoError(t, err) 253 m := compiled.(*compiledModule) 254 255 requireHostModuleEquals(t, tc.expected, m.module) 256 257 require.Equal(t, b.r.store.Engine, m.compiledEngine) 258 259 // Built module must be instantiable by Engine. 260 mod, err := b.r.InstantiateModule(testCtx, m, NewModuleConfig()) 261 require.NoError(t, err) 262 263 // Closing the module shouldn't remove the compiler cache 264 require.NoError(t, mod.Close(testCtx)) 265 require.Equal(t, uint32(1), b.r.store.Engine.CompiledModuleCount()) 266 }) 267 } 268 } 269 270 // TestNewHostModuleBuilder_Compile_Errors only covers a few scenarios to avoid 271 // duplicating tests in internal/wasm/host_test.go 272 func TestNewHostModuleBuilder_Compile_Errors(t *testing.T) { 273 tests := []struct { 274 name string 275 input func(Runtime) HostModuleBuilder 276 expectedErr string 277 }{ 278 { 279 name: "error compiling", // should fail due to missing result. 280 input: func(rt Runtime) HostModuleBuilder { 281 return rt.NewHostModuleBuilder("").NewFunctionBuilder(). 282 WithFunc(&wasm.HostFunc{ 283 ExportNames: []string{"fn"}, 284 ResultTypes: []wasm.ValueType{wasm.ValueTypeI32}, 285 Code: &wasm.Code{IsHostFunction: true, Body: []byte{wasm.OpcodeEnd}}, 286 }).Export("fn") 287 }, 288 expectedErr: `invalid function[0] export["fn"]: not enough results 289 have () 290 want (i32)`, 291 }, 292 } 293 294 for _, tt := range tests { 295 tc := tt 296 297 t.Run(tc.name, func(t *testing.T) { 298 _, e := tc.input(NewRuntime(testCtx)).Compile(testCtx) 299 require.EqualError(t, e, tc.expectedErr) 300 }) 301 } 302 } 303 304 // TestNewHostModuleBuilder_Instantiate ensures Runtime.InstantiateModule is called on success. 305 func TestNewHostModuleBuilder_Instantiate(t *testing.T) { 306 r := NewRuntime(testCtx) 307 m, err := r.NewHostModuleBuilder("env").Instantiate(testCtx, r) 308 require.NoError(t, err) 309 310 // If this was instantiated, it would be added to the store under the same name 311 require.Equal(t, r.(*runtime).ns.Module("env"), m) 312 313 // Closing the module should remove the compiler cache 314 require.NoError(t, m.Close(testCtx)) 315 require.Zero(t, r.(*runtime).store.Engine.CompiledModuleCount()) 316 } 317 318 // TestNewHostModuleBuilder_Instantiate_Errors ensures errors propagate from Runtime.InstantiateModule 319 func TestNewHostModuleBuilder_Instantiate_Errors(t *testing.T) { 320 r := NewRuntime(testCtx) 321 _, err := r.NewHostModuleBuilder("env").Instantiate(testCtx, r) 322 require.NoError(t, err) 323 324 _, err = r.NewHostModuleBuilder("env").Instantiate(testCtx, r) 325 require.EqualError(t, err, "module[env] has already been instantiated") 326 } 327 328 // requireHostModuleEquals is redefined from internal/wasm/host_test.go to avoid an import cycle extracting it. 329 func requireHostModuleEquals(t *testing.T, expected, actual *wasm.Module) { 330 // `require.Equal(t, expected, actual)` fails reflect pointers don't match, so brute compare: 331 for _, tp := range expected.TypeSection { 332 tp.CacheNumInUint64() 333 } 334 require.Equal(t, expected.TypeSection, actual.TypeSection) 335 require.Equal(t, expected.ImportSection, actual.ImportSection) 336 require.Equal(t, expected.FunctionSection, actual.FunctionSection) 337 require.Equal(t, expected.TableSection, actual.TableSection) 338 require.Equal(t, expected.MemorySection, actual.MemorySection) 339 require.Equal(t, expected.GlobalSection, actual.GlobalSection) 340 require.Equal(t, expected.ExportSection, actual.ExportSection) 341 require.Equal(t, expected.StartSection, actual.StartSection) 342 require.Equal(t, expected.ElementSection, actual.ElementSection) 343 require.Equal(t, expected.DataSection, actual.DataSection) 344 require.Equal(t, expected.NameSection, actual.NameSection) 345 346 // Special case because reflect.Value can't be compared with Equals 347 // TODO: This is copy/paste with /internal/wasm/host_test.go 348 require.Equal(t, len(expected.CodeSection), len(actual.CodeSection)) 349 for i, c := range expected.CodeSection { 350 actualCode := actual.CodeSection[i] 351 require.True(t, actualCode.IsHostFunction) 352 require.Equal(t, c.GoFunc, actualCode.GoFunc) 353 354 // Not wasm 355 require.Nil(t, actualCode.Body) 356 require.Nil(t, actualCode.LocalTypes) 357 } 358 }