1 package wazero
2
3 import (
4 "context"
5 _ "embed"
6 "errors"
7 "sync"
8 "testing"
9 "time"
10
11 "github.com/tetratelabs/wazero/api"
12 "github.com/tetratelabs/wazero/experimental"
13 "github.com/tetratelabs/wazero/internal/filecache"
14 "github.com/tetratelabs/wazero/internal/leb128"
15 "github.com/tetratelabs/wazero/internal/platform"
16 "github.com/tetratelabs/wazero/internal/testing/binaryencoding"
17 "github.com/tetratelabs/wazero/internal/testing/require"
18 "github.com/tetratelabs/wazero/internal/wasm"
19 "github.com/tetratelabs/wazero/sys"
20 )
21
22 var (
23 binaryNamedZero = binaryencoding.EncodeModule(&wasm.Module{NameSection: &wasm.NameSection{ModuleName: "0"}})
24
25 testCtx = context.WithValue(context.Background(), struct{}{}, "arbitrary")
26 )
27
28 var _ context.Context = &HostContext{}
29
30
31 type HostContext struct {
32 Content string
33 }
34
35 func (h *HostContext) Deadline() (deadline time.Time, ok bool) { return }
36
37 func (h *HostContext) Done() <-chan struct{} { return nil }
38
39 func (h *HostContext) Err() error { return nil }
40
41 func (h *HostContext) Value(key interface{}) interface{} { return nil }
42
43 func TestRuntime_CompileModule(t *testing.T) {
44 tests := []struct {
45 name string
46 runtime Runtime
47 wasm *wasm.Module
48 moduleBuilder HostModuleBuilder
49 expected func(CompiledModule)
50 }{
51 {
52 name: "no name section",
53 wasm: &wasm.Module{},
54 },
55 {
56 name: "empty NameSection.ModuleName",
57 wasm: &wasm.Module{NameSection: &wasm.NameSection{}},
58 },
59 {
60 name: "NameSection.ModuleName",
61 wasm: &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "test"}},
62 expected: func(compiled CompiledModule) {
63 require.Equal(t, "test", compiled.Name())
64 },
65 },
66 {
67 name: "FunctionSection, but not exported",
68 wasm: &wasm.Module{
69 TypeSection: []wasm.FunctionType{{Params: []api.ValueType{api.ValueTypeI32}}},
70 FunctionSection: []wasm.Index{0},
71 CodeSection: []wasm.Code{{Body: []byte{wasm.OpcodeEnd}}},
72 },
73 expected: func(compiled CompiledModule) {
74 require.Nil(t, compiled.ImportedFunctions())
75 require.Zero(t, len(compiled.ExportedFunctions()))
76 },
77 },
78 {
79 name: "FunctionSection exported",
80 wasm: &wasm.Module{
81 TypeSection: []wasm.FunctionType{{Params: []api.ValueType{api.ValueTypeI32}}},
82 FunctionSection: []wasm.Index{0},
83 CodeSection: []wasm.Code{{Body: []byte{wasm.OpcodeEnd}}},
84 ExportSection: []wasm.Export{{
85 Type: wasm.ExternTypeFunc,
86 Name: "function",
87 Index: 0,
88 }},
89 },
90 expected: func(compiled CompiledModule) {
91 require.Nil(t, compiled.ImportedFunctions())
92 f := compiled.ExportedFunctions()["function"]
93 require.Equal(t, []api.ValueType{api.ValueTypeI32}, f.ParamTypes())
94 },
95 },
96 {
97 name: "MemorySection, but not exported",
98 wasm: &wasm.Module{
99 MemorySection: &wasm.Memory{Min: 2, Max: 3, IsMaxEncoded: true},
100 },
101 expected: func(compiled CompiledModule) {
102 require.Nil(t, compiled.ImportedMemories())
103 require.Zero(t, len(compiled.ExportedMemories()))
104 },
105 },
106 {
107 name: "MemorySection exported",
108 wasm: &wasm.Module{
109 MemorySection: &wasm.Memory{Min: 2, Max: 3, IsMaxEncoded: true},
110 ExportSection: []wasm.Export{{
111 Type: wasm.ExternTypeMemory,
112 Name: "memory",
113 Index: 0,
114 }},
115 },
116 expected: func(compiled CompiledModule) {
117 require.Nil(t, compiled.ImportedMemories())
118 mem := compiled.ExportedMemories()["memory"]
119 require.Equal(t, uint32(2), mem.Min())
120 max, ok := mem.Max()
121 require.Equal(t, uint32(3), max)
122 require.True(t, ok)
123 },
124 },
125 }
126
127 _r := NewRuntime(testCtx)
128 defer _r.Close(testCtx)
129
130 r := _r.(*runtime)
131
132 for _, tt := range tests {
133 tc := tt
134
135 t.Run(tc.name, func(t *testing.T) {
136 bin := binaryencoding.EncodeModule(tc.wasm)
137
138 m, err := r.CompileModule(testCtx, bin)
139 require.NoError(t, err)
140 if tc.expected == nil {
141 tc.expected = func(CompiledModule) {}
142 }
143 tc.expected(m)
144 require.Equal(t, r.store.Engine, m.(*compiledModule).compiledEngine)
145
146
147 expTypeIDs, err := r.store.GetFunctionTypeIDs(tc.wasm.TypeSection)
148 require.NoError(t, err)
149 require.Equal(t, expTypeIDs, m.(*compiledModule).typeIDs)
150 })
151 }
152 }
153
154 func TestRuntime_CompileModule_Errors(t *testing.T) {
155 tests := []struct {
156 name string
157 wasm []byte
158 expectedErr string
159 }{
160 {
161 name: "nil",
162 expectedErr: "invalid magic number",
163 },
164 {
165 name: "invalid binary",
166 wasm: append(binaryencoding.Magic, []byte("yolo")...),
167 expectedErr: "invalid version header",
168 },
169 {
170 name: "memory has too many pages",
171 wasm: binaryencoding.EncodeModule(&wasm.Module{MemorySection: &wasm.Memory{Min: 2, Cap: 2, Max: 70000, IsMaxEncoded: true}}),
172 expectedErr: "section memory: max 70000 pages (4 Gi) over limit of 65536 pages (4 Gi)",
173 },
174 }
175
176 r := NewRuntime(testCtx)
177 defer r.Close(testCtx)
178
179 for _, tt := range tests {
180 tc := tt
181
182 t.Run(tc.name, func(t *testing.T) {
183 _, err := r.CompileModule(testCtx, tc.wasm)
184 require.EqualError(t, err, tc.expectedErr)
185 })
186 }
187 }
188
189
190 func TestModule_Memory(t *testing.T) {
191 tests := []struct {
192 name string
193 wasm []byte
194 expected bool
195 expectedLen uint32
196 }{
197 {
198 name: "no memory",
199 wasm: binaryencoding.EncodeModule(&wasm.Module{}),
200 },
201 {
202 name: "memory exported, one page",
203 wasm: binaryencoding.EncodeModule(&wasm.Module{
204 MemorySection: &wasm.Memory{Min: 1},
205 ExportSection: []wasm.Export{{Name: "memory", Type: api.ExternTypeMemory}},
206 }),
207 expected: true,
208 expectedLen: 65536,
209 },
210 }
211
212 for _, tt := range tests {
213 tc := tt
214
215 t.Run(tc.name, func(t *testing.T) {
216 r := NewRuntime(testCtx)
217 defer r.Close(testCtx)
218
219
220 module, err := r.Instantiate(testCtx, tc.wasm)
221 require.NoError(t, err)
222
223 mem := module.ExportedMemory("memory")
224 if tc.expected {
225 require.Equal(t, tc.expectedLen, mem.Size())
226 defs := module.ExportedMemoryDefinitions()
227 require.Equal(t, 1, len(defs))
228 def := defs["memory"]
229 require.Equal(t, tc.expectedLen>>16, def.Min())
230 } else {
231 require.Nil(t, mem)
232 require.Zero(t, len(module.ExportedMemoryDefinitions()))
233 }
234 })
235 }
236 }
237
238
239 func TestModule_Global(t *testing.T) {
240 globalVal := int64(100)
241
242 tests := []struct {
243 name string
244 module *wasm.Module
245 expected, expectedMutable bool
246 }{
247 {
248 name: "no global",
249 module: &wasm.Module{},
250 },
251 {
252 name: "global not exported",
253 module: &wasm.Module{
254 GlobalSection: []wasm.Global{
255 {
256 Type: wasm.GlobalType{ValType: wasm.ValueTypeI64, Mutable: true},
257 Init: wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)},
258 },
259 },
260 },
261 },
262 {
263 name: "global exported",
264 module: &wasm.Module{
265 GlobalSection: []wasm.Global{
266 {
267 Type: wasm.GlobalType{ValType: wasm.ValueTypeI64},
268 Init: wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)},
269 },
270 },
271 Exports: map[string]*wasm.Export{
272 "global": {Type: wasm.ExternTypeGlobal, Name: "global"},
273 },
274 },
275 expected: true,
276 },
277 {
278 name: "global exported and mutable",
279 module: &wasm.Module{
280 GlobalSection: []wasm.Global{
281 {
282 Type: wasm.GlobalType{ValType: wasm.ValueTypeI64, Mutable: true},
283 Init: wasm.ConstantExpression{Opcode: wasm.OpcodeI64Const, Data: leb128.EncodeInt64(globalVal)},
284 },
285 },
286 Exports: map[string]*wasm.Export{
287 "global": {Type: wasm.ExternTypeGlobal, Name: "global"},
288 },
289 },
290 expected: true,
291 expectedMutable: true,
292 },
293 }
294
295 for _, tt := range tests {
296 tc := tt
297
298 t.Run(tc.name, func(t *testing.T) {
299 r := NewRuntime(testCtx).(*runtime)
300 defer r.Close(testCtx)
301
302 code := &compiledModule{module: tc.module}
303
304 err := r.store.Engine.CompileModule(testCtx, code.module, nil, false)
305 require.NoError(t, err)
306
307
308 module, err := r.InstantiateModule(testCtx, code, NewModuleConfig())
309 require.NoError(t, err)
310
311 global := module.ExportedGlobal("global")
312 if !tc.expected {
313 require.Nil(t, global)
314 return
315 }
316 require.Equal(t, uint64(globalVal), global.Get())
317
318 mutable, ok := global.(api.MutableGlobal)
319 require.Equal(t, tc.expectedMutable, ok)
320 if ok {
321 mutable.Set(2)
322 require.Equal(t, uint64(2), global.Get())
323 }
324 })
325 }
326 }
327
328 func TestRuntime_InstantiateModule_UsesContext(t *testing.T) {
329 r := NewRuntime(testCtx)
330 defer r.Close(testCtx)
331
332
333 var calledStart bool
334 start := func(ctx context.Context) {
335 calledStart = true
336 require.Equal(t, testCtx, ctx)
337 }
338
339 _, err := r.NewHostModuleBuilder("env").
340 NewFunctionBuilder().WithFunc(start).Export("start").
341 Instantiate(testCtx)
342 require.NoError(t, err)
343
344 one := uint32(1)
345 binary := binaryencoding.EncodeModule(&wasm.Module{
346 TypeSection: []wasm.FunctionType{{}},
347 ImportSection: []wasm.Import{{Module: "env", Name: "start", Type: wasm.ExternTypeFunc, DescFunc: 0}},
348 FunctionSection: []wasm.Index{0},
349 CodeSection: []wasm.Code{
350 {Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}},
351 },
352 StartSection: &one,
353 })
354
355 code, err := r.CompileModule(testCtx, binary)
356 require.NoError(t, err)
357
358
359 mod, err := r.InstantiateModule(testCtx, code, NewModuleConfig())
360 require.NoError(t, err)
361
362 require.True(t, calledStart)
363
364
365 require.NoError(t, mod.Close(testCtx))
366 require.Equal(t, uint32(2), r.(*runtime).store.Engine.CompiledModuleCount())
367 }
368
369
370
371 func TestRuntime_Instantiate_DoesntEnforce_Start(t *testing.T) {
372 r := NewRuntime(testCtx)
373 defer r.Close(testCtx)
374
375 binary := binaryencoding.EncodeModule(&wasm.Module{
376 MemorySection: &wasm.Memory{Min: 1},
377 ExportSection: []wasm.Export{{Name: "memory", Type: wasm.ExternTypeMemory, Index: 0}},
378 })
379
380 mod, err := r.Instantiate(testCtx, binary)
381 require.NoError(t, err)
382 require.NoError(t, mod.Close(testCtx))
383 }
384
385 func TestRuntime_Instantiate_ErrorOnStart(t *testing.T) {
386 tests := []struct {
387 name, wasm string
388 }{
389 {
390 name: "_start function",
391 wasm: `(module
392 (import "" "start" (func $start))
393 (export "_start" (func $start))
394 )`,
395 },
396 {
397 name: ".start function",
398 wasm: `(module
399 (import "" "start" (func $start))
400 (start $start)
401 )`,
402 },
403 }
404
405 for _, tt := range tests {
406 tc := tt
407
408 t.Run(tc.name, func(t *testing.T) {
409 r := NewRuntime(testCtx)
410 defer r.Close(testCtx)
411
412 start := func() {
413 panic(errors.New("ice cream"))
414 }
415
416 host, err := r.NewHostModuleBuilder("host").
417 NewFunctionBuilder().WithFunc(start).Export("start").
418 Instantiate(testCtx)
419 require.NoError(t, err)
420
421
422 _, err = r.Instantiate(testCtx, []byte(tc.wasm))
423 require.Error(t, err)
424
425
426 require.NoError(t, host.Close(testCtx))
427
428
429 require.Zero(t, r.(*runtime).store.Engine.CompiledModuleCount())
430 })
431 }
432 }
433
434
435
436 func TestRuntime_InstantiateModule_WithName(t *testing.T) {
437 r := NewRuntime(testCtx)
438 defer r.Close(testCtx)
439
440 base, err := r.CompileModule(testCtx, binaryNamedZero)
441 require.NoError(t, err)
442
443 require.Equal(t, "0", base.(*compiledModule).module.NameSection.ModuleName)
444
445
446 internal := r.(*runtime)
447 m1, err := r.InstantiateModule(testCtx, base, NewModuleConfig().WithName("1"))
448 require.NoError(t, err)
449 require.Equal(t, "1", m1.Name())
450
451 require.Nil(t, internal.Module("0"))
452 require.Equal(t, internal.Module("1"), m1)
453
454 m2, err := r.InstantiateModule(testCtx, base, NewModuleConfig().WithName("2"))
455 require.NoError(t, err)
456 require.Equal(t, "2", m2.Name())
457
458 require.Nil(t, internal.Module("0"))
459 require.Equal(t, internal.Module("2"), m2)
460
461
462 m3, err := r.InstantiateModule(testCtx, base, NewModuleConfig().WithName(""))
463 require.NoError(t, err)
464 require.Equal(t, "", m3.Name())
465
466 ret := internal.Module("")
467 require.Nil(t, ret)
468 }
469
470 func TestRuntime_InstantiateModule_ExitError(t *testing.T) {
471 r := NewRuntime(testCtx)
472 defer r.Close(testCtx)
473
474 tests := []struct {
475 name string
476 exitCode uint32
477 export bool
478 expectedErr error
479 }{
480 {
481 name: "start: exit code 0",
482 exitCode: 0,
483 expectedErr: sys.NewExitError(0),
484 },
485 {
486 name: "start: exit code 2",
487 exitCode: 2,
488 expectedErr: sys.NewExitError(2),
489 },
490 {
491 name: "_start: exit code 0",
492 exitCode: 0,
493 export: true,
494 },
495 {
496 name: "_start: exit code 2",
497 exitCode: 2,
498 export: true,
499 expectedErr: sys.NewExitError(2),
500 },
501 }
502
503 for _, tt := range tests {
504 tc := tt
505 t.Run(tc.name, func(t *testing.T) {
506 start := func(ctx context.Context, m api.Module) {
507 require.NoError(t, m.CloseWithExitCode(ctx, tc.exitCode))
508 }
509
510 env, err := r.NewHostModuleBuilder("env").
511 NewFunctionBuilder().WithFunc(start).Export("exit").
512 Instantiate(testCtx)
513 require.NoError(t, err)
514 defer env.Close(testCtx)
515
516 mod := &wasm.Module{
517 TypeSection: []wasm.FunctionType{{}},
518 ImportSection: []wasm.Import{{Module: "env", Name: "exit", Type: wasm.ExternTypeFunc, DescFunc: 0}},
519 FunctionSection: []wasm.Index{0},
520 CodeSection: []wasm.Code{
521 {Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}},
522 },
523 }
524 if tc.export {
525 mod.ExportSection = []wasm.Export{
526 {Name: "_start", Type: wasm.ExternTypeFunc, Index: 1},
527 }
528 } else {
529 one := uint32(1)
530 mod.StartSection = &one
531 }
532 binary := binaryencoding.EncodeModule(mod)
533
534
535 m, err := r.InstantiateWithConfig(testCtx, binary,
536 NewModuleConfig().WithName("call-exit"))
537
538
539 require.Equal(t, tc.expectedErr, err)
540
541
542 if err == nil {
543 require.NoError(t, m.Close(testCtx))
544 }
545 })
546 }
547 }
548
549 func TestRuntime_CloseWithExitCode(t *testing.T) {
550 bin := binaryencoding.EncodeModule(&wasm.Module{
551 TypeSection: []wasm.FunctionType{{}},
552 FunctionSection: []wasm.Index{0},
553 CodeSection: []wasm.Code{{Body: []byte{wasm.OpcodeEnd}}},
554 ExportSection: []wasm.Export{{Type: wasm.ExternTypeFunc, Index: 0, Name: "func"}},
555 })
556
557 tests := []struct {
558 name string
559 exitCode uint32
560 }{
561 {
562 name: "exit code 0",
563 exitCode: uint32(0),
564 },
565 {
566 name: "exit code 2",
567 exitCode: uint32(2),
568 },
569 }
570
571 for _, tt := range tests {
572 tc := tt
573 t.Run(tc.name, func(t *testing.T) {
574 r := NewRuntime(testCtx)
575
576 code, err := r.CompileModule(testCtx, bin)
577 require.NoError(t, err)
578
579
580 m1, err := r.InstantiateModule(testCtx, code, NewModuleConfig().WithName("mod1"))
581 require.NoError(t, err)
582 m2, err := r.InstantiateModule(testCtx, code, NewModuleConfig().WithName("mod2"))
583 require.NoError(t, err)
584
585 func1 := m1.ExportedFunction("func")
586 require.Equal(t, map[string]api.FunctionDefinition{"func": func1.Definition()},
587 m1.ExportedFunctionDefinitions())
588 func2 := m2.ExportedFunction("func")
589 require.Equal(t, map[string]api.FunctionDefinition{"func": func2.Definition()},
590 m2.ExportedFunctionDefinitions())
591
592
593
594 _, err = func1.Call(testCtx)
595 require.NoError(t, err)
596
597 _, err = func2.Call(testCtx)
598 require.NoError(t, err)
599
600 if tc.exitCode == 0 {
601 err = r.Close(testCtx)
602 } else {
603 err = r.CloseWithExitCode(testCtx, tc.exitCode)
604 }
605 require.NoError(t, err)
606
607
608 _, err = func1.Call(testCtx)
609 require.ErrorIs(t, err, sys.NewExitError(tc.exitCode))
610
611 _, err = func2.Call(testCtx)
612 require.ErrorIs(t, err, sys.NewExitError(tc.exitCode))
613 })
614 }
615 }
616
617 func TestHostFunctionWithCustomContext(t *testing.T) {
618 for _, tc := range []struct {
619 name string
620 config RuntimeConfig
621 }{
622 {name: "compiler", config: NewRuntimeConfigCompiler()},
623 {name: "interpreter", config: NewRuntimeConfigInterpreter()},
624 } {
625 t.Run(tc.name, func(t *testing.T) {
626 const fistString = "hello"
627 const secondString = "hello call"
628 hostCtx := &HostContext{fistString}
629 r := NewRuntimeWithConfig(hostCtx, tc.config)
630 defer r.Close(hostCtx)
631
632
633 var calledStart bool
634 var calledCall bool
635 start := func(ctx context.Context, module api.Module) {
636 hts, ok := ctx.(*HostContext)
637 if !ok {
638 t.Fatal("decorate call context could effect host ctx cast failed, please consider it.")
639 }
640 calledStart = true
641 require.NotNil(t, hts)
642 require.Equal(t, fistString, hts.Content)
643 }
644
645 callFunc := func(ctx context.Context, module api.Module) {
646 hts, ok := ctx.(*HostContext)
647 if !ok {
648 t.Fatal("decorate call context could effect host ctx cast failed, please consider it.")
649 }
650 calledCall = true
651 require.NotNil(t, hts)
652 require.Equal(t, secondString, hts.Content)
653 }
654
655 _, err := r.NewHostModuleBuilder("env").
656 NewFunctionBuilder().WithFunc(start).Export("host").
657 NewFunctionBuilder().WithFunc(callFunc).Export("host2").
658 Instantiate(hostCtx)
659 require.NoError(t, err)
660
661 startFnIndex := uint32(2)
662 binary := binaryencoding.EncodeModule(&wasm.Module{
663 TypeSection: []wasm.FunctionType{{}},
664 ImportSection: []wasm.Import{
665 {Module: "env", Name: "host", Type: wasm.ExternTypeFunc, DescFunc: 0},
666 {Module: "env", Name: "host2", Type: wasm.ExternTypeFunc, DescFunc: 0},
667 },
668 FunctionSection: []wasm.Index{0, 0},
669 CodeSection: []wasm.Code{
670 {Body: []byte{wasm.OpcodeCall, 0, wasm.OpcodeEnd}},
671 {Body: []byte{wasm.OpcodeCall, 1, wasm.OpcodeEnd}},
672 },
673 ExportSection: []wasm.Export{
674 {Type: api.ExternTypeFunc, Name: "callHost", Index: uint32(3)},
675 },
676 StartSection: &startFnIndex,
677 })
678
679
680 ins, err := r.Instantiate(hostCtx, binary)
681 require.NoError(t, err)
682 require.True(t, calledStart)
683
684
685 hostCtx.Content = secondString
686 _, err = ins.ExportedFunction("callHost").Call(hostCtx)
687 require.NoError(t, err)
688 require.True(t, calledCall)
689 })
690 }
691 }
692
693 func TestRuntime_Close_ClosesCompiledModules(t *testing.T) {
694 for _, tc := range []struct {
695 name string
696 withCompilationCache bool
697 }{
698 {name: "with cache", withCompilationCache: true},
699 {name: "without cache", withCompilationCache: false},
700 } {
701 t.Run(tc.name, func(t *testing.T) {
702 engine := &mockEngine{name: "mock", cachedModules: map[*wasm.Module]struct{}{}}
703 conf := *engineLessConfig
704 conf.newEngine = func(context.Context, api.CoreFeatures, filecache.Cache) wasm.Engine { return engine }
705 if tc.withCompilationCache {
706 conf.cache = NewCompilationCache()
707 }
708 r := NewRuntimeWithConfig(testCtx, &conf)
709 defer r.Close(testCtx)
710
711
712 _, err := r.CompileModule(testCtx, binaryNamedZero)
713 require.NoError(t, err)
714 require.Equal(t, uint32(1), engine.CompiledModuleCount())
715
716 err = r.Close(testCtx)
717 require.NoError(t, err)
718
719
720 require.Equal(t, !tc.withCompilationCache, engine.closed)
721 })
722 }
723 }
724
725
726 func TestRuntime_Closed(t *testing.T) {
727 for _, tc := range []struct {
728 name string
729 errFunc func(r Runtime, mod CompiledModule) error
730 }{
731 {
732 name: "InstantiateModule",
733 errFunc: func(r Runtime, mod CompiledModule) error {
734 _, err := r.InstantiateModule(testCtx, mod, NewModuleConfig())
735 return err
736 },
737 },
738 {
739 name: "Instantiate",
740 errFunc: func(r Runtime, mod CompiledModule) error {
741 _, err := r.Instantiate(testCtx, binaryNamedZero)
742 return err
743 },
744 },
745 {
746 name: "CompileModule",
747 errFunc: func(r Runtime, mod CompiledModule) error {
748 _, err := r.CompileModule(testCtx, binaryNamedZero)
749 return err
750 },
751 },
752 } {
753 t.Run(tc.name, func(t *testing.T) {
754 engine := &mockEngine{name: "mock", cachedModules: map[*wasm.Module]struct{}{}}
755 conf := *engineLessConfig
756 conf.newEngine = func(context.Context, api.CoreFeatures, filecache.Cache) wasm.Engine { return engine }
757 r := NewRuntimeWithConfig(testCtx, &conf)
758 defer r.Close(testCtx)
759
760
761 mod, err := r.CompileModule(testCtx, binaryNamedZero)
762 require.NoError(t, err)
763 require.Equal(t, uint32(1), engine.CompiledModuleCount())
764
765 err = r.Close(testCtx)
766 require.NoError(t, err)
767
768
769 require.True(t, engine.closed)
770
771 require.EqualError(t, tc.errFunc(r, mod), "runtime closed with exit_code(0)")
772 })
773 }
774 }
775
776 type mockEngine struct {
777 name string
778 cachedModules map[*wasm.Module]struct{}
779 closed bool
780 }
781
782
783 func (e *mockEngine) CompileModule(_ context.Context, module *wasm.Module, _ []experimental.FunctionListener, _ bool) error {
784 e.cachedModules[module] = struct{}{}
785 return nil
786 }
787
788
789 func (e *mockEngine) CompiledModuleCount() uint32 {
790 return uint32(len(e.cachedModules))
791 }
792
793
794 func (e *mockEngine) DeleteCompiledModule(module *wasm.Module) {
795 delete(e.cachedModules, module)
796 }
797
798
799 func (e *mockEngine) NewModuleEngine(_ *wasm.Module, _ *wasm.ModuleInstance) (wasm.ModuleEngine, error) {
800 return nil, nil
801 }
802
803
804 func (e *mockEngine) Close() (err error) {
805 e.closed = true
806 return
807 }
808
809
810
811 func TestNewRuntime_concurrent(t *testing.T) {
812 const num = 100
813 var wg sync.WaitGroup
814 c := NewCompilationCache()
815
816 configs := [2]RuntimeConfig{NewRuntimeConfigInterpreter().WithCompilationCache(c)}
817 if platform.CompilerSupported() {
818 configs[1] = NewRuntimeConfigCompiler().WithCompilationCache(c)
819 } else {
820 configs[1] = NewRuntimeConfigInterpreter().WithCompilationCache(c)
821 }
822 wg.Add(num)
823 for i := 0; i < num; i++ {
824 i := i
825 go func() {
826 defer wg.Done()
827 r := NewRuntimeWithConfig(testCtx, configs[i%2])
828 err := r.Close(testCtx)
829 require.NoError(t, err)
830 }()
831 }
832 wg.Wait()
833 }
834
View as plain text