1 package wazero
2
3 import (
4 "bytes"
5 "context"
6 _ "embed"
7 "testing"
8 "time"
9
10 "github.com/tetratelabs/wazero/api"
11 experimentalsys "github.com/tetratelabs/wazero/experimental/sys"
12 "github.com/tetratelabs/wazero/internal/fstest"
13 "github.com/tetratelabs/wazero/internal/platform"
14 internalsys "github.com/tetratelabs/wazero/internal/sys"
15 "github.com/tetratelabs/wazero/internal/sysfs"
16 testfs "github.com/tetratelabs/wazero/internal/testing/fs"
17 "github.com/tetratelabs/wazero/internal/testing/require"
18 "github.com/tetratelabs/wazero/internal/wasm"
19 "github.com/tetratelabs/wazero/sys"
20 )
21
22 func TestRuntimeConfig(t *testing.T) {
23 tests := []struct {
24 name string
25 with func(RuntimeConfig) RuntimeConfig
26 expected RuntimeConfig
27 }{
28 {
29 name: "features",
30 with: func(c RuntimeConfig) RuntimeConfig {
31 return c.WithCoreFeatures(api.CoreFeaturesV1)
32 },
33 expected: &runtimeConfig{
34 enabledFeatures: api.CoreFeaturesV1,
35 },
36 },
37 {
38 name: "memoryLimitPages",
39 with: func(c RuntimeConfig) RuntimeConfig {
40 return c.WithMemoryLimitPages(10)
41 },
42 expected: &runtimeConfig{
43 memoryLimitPages: 10,
44 },
45 },
46 {
47 name: "memoryCapacityFromMax",
48 with: func(c RuntimeConfig) RuntimeConfig {
49 return c.WithMemoryCapacityFromMax(true)
50 },
51 expected: &runtimeConfig{
52 memoryCapacityFromMax: true,
53 },
54 },
55 {
56 name: "WithDebugInfoEnabled",
57 with: func(c RuntimeConfig) RuntimeConfig {
58 return c.WithDebugInfoEnabled(false)
59 },
60 expected: &runtimeConfig{
61 dwarfDisabled: true,
62 },
63 },
64 {
65 name: "WithCustomSections",
66 with: func(c RuntimeConfig) RuntimeConfig {
67 return c.WithCustomSections(true)
68 },
69 expected: &runtimeConfig{
70 storeCustomSections: true,
71 },
72 },
73 {
74 name: "WithCloseOnContextDone",
75 with: func(c RuntimeConfig) RuntimeConfig { return c.WithCloseOnContextDone(true) },
76 expected: &runtimeConfig{ensureTermination: true},
77 },
78 }
79
80 for _, tt := range tests {
81 tc := tt
82
83 t.Run(tc.name, func(t *testing.T) {
84 input := &runtimeConfig{}
85 rc := tc.with(input)
86 require.Equal(t, tc.expected, rc)
87
88 require.Equal(t, &runtimeConfig{}, input)
89 })
90 }
91
92 t.Run("memoryLimitPages invalid panics", func(t *testing.T) {
93 err := require.CapturePanic(func() {
94 input := &runtimeConfig{}
95 input.WithMemoryLimitPages(wasm.MemoryLimitPages + 1)
96 })
97 require.EqualError(t, err, "memoryLimitPages invalid: 65537 > 65536")
98 })
99 }
100
101 func TestModuleConfig(t *testing.T) {
102 tests := []struct {
103 name string
104 with func(ModuleConfig) ModuleConfig
105 expectNameSet bool
106 expectedName string
107 }{
108 {
109 name: "WithName default",
110 with: func(c ModuleConfig) ModuleConfig {
111 return c
112 },
113 expectNameSet: false,
114 expectedName: "",
115 },
116 {
117 name: "WithName",
118 with: func(c ModuleConfig) ModuleConfig {
119 return c.WithName("wazero")
120 },
121 expectNameSet: true,
122 expectedName: "wazero",
123 },
124 {
125 name: "WithName empty",
126 with: func(c ModuleConfig) ModuleConfig {
127 return c.WithName("")
128 },
129 expectNameSet: true,
130 expectedName: "",
131 },
132 {
133 name: "WithName twice",
134 with: func(c ModuleConfig) ModuleConfig {
135 return c.WithName("wazero").WithName("wa0")
136 },
137 expectNameSet: true,
138 expectedName: "wa0",
139 },
140 {
141 name: "WithName can clear",
142 with: func(c ModuleConfig) ModuleConfig {
143 return c.WithName("wazero").WithName("")
144 },
145 expectNameSet: true,
146 expectedName: "",
147 },
148 }
149 for _, tt := range tests {
150 tc := tt
151
152 t.Run(tc.name, func(t *testing.T) {
153 input := NewModuleConfig()
154 rc := tc.with(input)
155 require.Equal(t, tc.expectNameSet, rc.(*moduleConfig).nameSet)
156 require.Equal(t, tc.expectedName, rc.(*moduleConfig).name)
157
158 require.Equal(t, NewModuleConfig(), input)
159 })
160 }
161 }
162
163
164
165 func TestModuleConfig_toSysContext(t *testing.T) {
166 base := NewModuleConfig()
167
168 tests := []struct {
169 name string
170 input func() (mc ModuleConfig, verify func(t *testing.T, sys *internalsys.Context))
171 }{
172 {
173 name: "empty",
174 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
175 return base, func(t *testing.T, sys *internalsys.Context) { require.NotNil(t, sys) }
176 },
177 },
178 {
179 name: "WithNanotime",
180 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
181 config := base.WithNanotime(func() int64 { return 1234567 }, 54321)
182 return config, func(t *testing.T, sys *internalsys.Context) {
183 require.Equal(t, 1234567, int(sys.Nanotime()))
184 require.Equal(t, 54321, int(sys.NanotimeResolution()))
185 }
186 },
187 },
188 {
189 name: "WithSysNanotime",
190 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
191 config := base.WithSysNanotime()
192 return config, func(t *testing.T, sys *internalsys.Context) {
193 require.Equal(t, int(1), int(sys.NanotimeResolution()))
194 }
195 },
196 },
197 {
198 name: "WithWalltime",
199 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
200 config := base.WithWalltime(func() (sec int64, nsec int32) { return 5, 10 }, 54321)
201 return config, func(t *testing.T, sys *internalsys.Context) {
202 actualSec, actualNano := sys.Walltime()
203 require.Equal(t, 5, int(actualSec))
204 require.Equal(t, 10, int(actualNano))
205 require.Equal(t, 54321, int(sys.WalltimeResolution()))
206 }
207 },
208 },
209 {
210 name: "WithSysWalltime",
211 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
212 config := base.WithSysWalltime()
213 return config, func(t *testing.T, sys *internalsys.Context) {
214 require.Equal(t, int(time.Microsecond.Nanoseconds()), int(sys.WalltimeResolution()))
215 }
216 },
217 },
218 {
219 name: "WithArgs empty",
220 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
221 config := base.WithArgs()
222 return config, func(t *testing.T, sys *internalsys.Context) {
223 args := sys.Args()
224 require.Equal(t, 0, len(args))
225 }
226 },
227 },
228 {
229 name: "WithArgs",
230 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
231 config := base.WithArgs("a", "bc")
232 return config, func(t *testing.T, sys *internalsys.Context) {
233 args := sys.Args()
234 require.Equal(t, 2, len(args))
235 require.Equal(t, "a", string(args[0]))
236 require.Equal(t, "bc", string(args[1]))
237 }
238 },
239 },
240 {
241 name: "WithArgs empty ok",
242 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
243 config := base.WithArgs("", "bc")
244 return config, func(t *testing.T, sys *internalsys.Context) {
245 args := sys.Args()
246 require.Equal(t, 2, len(args))
247 require.Equal(t, "", string(args[0]))
248 require.Equal(t, "bc", string(args[1]))
249 }
250 },
251 },
252 {
253 name: "WithArgs second call overwrites",
254 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
255 config := base.WithArgs("a", "bc").WithArgs("bc", "a")
256 return config, func(t *testing.T, sys *internalsys.Context) {
257 args := sys.Args()
258 require.Equal(t, 2, len(args))
259 require.Equal(t, "bc", string(args[0]))
260 require.Equal(t, "a", string(args[1]))
261 }
262 },
263 },
264 {
265 name: "WithEnv",
266 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
267 config := base.WithEnv("a", "b")
268 return config, func(t *testing.T, sys *internalsys.Context) {
269 envs := sys.Environ()
270 require.Equal(t, 1, len(envs))
271 require.Equal(t, "a=b", string(envs[0]))
272 }
273 },
274 },
275 {
276 name: "WithEnv empty value",
277 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
278 config := base.WithEnv("a", "")
279 return config, func(t *testing.T, sys *internalsys.Context) {
280 envs := sys.Environ()
281 require.Equal(t, 1, len(envs))
282 require.Equal(t, "a=", string(envs[0]))
283 }
284 },
285 },
286 {
287 name: "WithEnv twice",
288 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
289 config := base.WithEnv("a", "b").WithEnv("c", "de")
290 return config, func(t *testing.T, sys *internalsys.Context) {
291 envs := sys.Environ()
292 require.Equal(t, 2, len(envs))
293 require.Equal(t, "a=b", string(envs[0]))
294 require.Equal(t, "c=de", string(envs[1]))
295 }
296 },
297 },
298 {
299 name: "WithEnv overwrites",
300 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
301 config := base.WithEnv("a", "bc").WithEnv("c", "de").WithEnv("a", "ff")
302 return config, func(t *testing.T, sys *internalsys.Context) {
303 envs := sys.Environ()
304 require.Equal(t, 2, len(envs))
305 require.Equal(t, "a=ff", string(envs[0]))
306 require.Equal(t, "c=de", string(envs[1]))
307 }
308 },
309 },
310 {
311 name: "WithEnv twice",
312 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
313 config := base.WithEnv("a", "b").WithEnv("c", "de")
314 return config, func(t *testing.T, sys *internalsys.Context) {
315 envs := sys.Environ()
316 require.Equal(t, 2, len(envs))
317 require.Equal(t, "a=b", string(envs[0]))
318 require.Equal(t, "c=de", string(envs[1]))
319 }
320 },
321 },
322 {
323 name: "WithFS",
324 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
325 testFS := &testfs.FS{}
326 config := base.WithFS(testFS)
327 return config, func(t *testing.T, sys *internalsys.Context) {
328 rootfs := sys.FS().RootFS()
329 require.Equal(t, &sysfs.AdaptFS{FS: testFS}, rootfs)
330 }
331 },
332 },
333 {
334 name: "WithFS overwrites",
335 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
336 testFS, testFS2 := &testfs.FS{}, &testfs.FS{}
337 config := base.WithFS(testFS).WithFS(testFS2)
338 return config, func(t *testing.T, sys *internalsys.Context) {
339 rootfs := sys.FS().RootFS()
340 require.Equal(t, &sysfs.AdaptFS{FS: testFS2}, rootfs)
341 }
342 },
343 },
344 {
345 name: "WithFS nil",
346 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
347 config := base.WithFS(nil)
348 return config, func(t *testing.T, sys *internalsys.Context) {
349 rootfs := sys.FS().RootFS()
350 require.Equal(t, experimentalsys.UnimplementedFS{}, rootfs)
351 }
352 },
353 },
354 {
355 name: "WithRandSource",
356 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
357 r := bytes.NewReader([]byte{1, 2, 3, 4})
358 config := base.WithRandSource(r)
359 return config, func(t *testing.T, sys *internalsys.Context) {
360 actual := sys.RandSource()
361 require.Equal(t, r, actual)
362 }
363 },
364 },
365 {
366 name: "WithRandSource nil",
367 input: func() (ModuleConfig, func(t *testing.T, sys *internalsys.Context)) {
368 config := base.WithRandSource(nil)
369 return config, func(t *testing.T, sys *internalsys.Context) {
370 actual := sys.RandSource()
371 require.Equal(t, platform.NewFakeRandSource(), actual)
372 }
373 },
374 },
375 }
376
377 for _, tt := range tests {
378 tc := tt
379
380 t.Run(tc.name, func(t *testing.T) {
381 config, verify := tc.input()
382 actual, err := config.(*moduleConfig).toSysContext()
383 require.NoError(t, err)
384 verify(t, actual)
385 })
386 }
387 }
388
389
390
391 func TestModuleConfig_toSysContext_WithWalltime(t *testing.T) {
392 tests := []struct {
393 name string
394 input ModuleConfig
395 expectedSec int64
396 expectedNsec int32
397 expectedResolution sys.ClockResolution
398 expectedErr string
399 }{
400 {
401 name: "ok",
402 input: NewModuleConfig().
403 WithWalltime(func() (sec int64, nsec int32) {
404 return 1, 2
405 }, 3),
406 expectedSec: 1,
407 expectedNsec: 2,
408 expectedResolution: 3,
409 },
410 {
411 name: "overwrites",
412 input: NewModuleConfig().
413 WithWalltime(func() (sec int64, nsec int32) {
414 return 3, 4
415 }, 5).
416 WithWalltime(func() (sec int64, nsec int32) {
417 return 1, 2
418 }, 3),
419 expectedSec: 1,
420 expectedNsec: 2,
421 expectedResolution: 3,
422 },
423 {
424 name: "invalid resolution",
425 input: NewModuleConfig().
426 WithWalltime(func() (sec int64, nsec int32) {
427 return 1, 2
428 }, 0),
429 expectedErr: "invalid Walltime resolution: 0",
430 },
431 }
432
433 for _, tt := range tests {
434 tc := tt
435
436 t.Run(tc.name, func(t *testing.T) {
437 sysCtx, err := tc.input.(*moduleConfig).toSysContext()
438 if tc.expectedErr == "" {
439 require.Nil(t, err)
440 sec, nsec := sysCtx.Walltime()
441 require.Equal(t, tc.expectedSec, sec)
442 require.Equal(t, tc.expectedNsec, nsec)
443 require.Equal(t, tc.expectedResolution, sysCtx.WalltimeResolution())
444 } else {
445 require.EqualError(t, err, tc.expectedErr)
446 }
447 })
448 }
449
450 t.Run("context", func(t *testing.T) {
451 sysCtx, err := NewModuleConfig().
452 WithWalltime(func() (sec int64, nsec int32) {
453 return 1, 2
454 }, 3).(*moduleConfig).toSysContext()
455 require.NoError(t, err)
456 sec, nsec := sysCtx.Walltime()
457
458 require.Equal(t, int64(1), sec)
459 require.Equal(t, int32(2), nsec)
460 })
461 }
462
463
464
465 func TestModuleConfig_toSysContext_WithNanotime(t *testing.T) {
466 tests := []struct {
467 name string
468 input ModuleConfig
469 expectedNanos int64
470 expectedResolution sys.ClockResolution
471 expectedErr string
472 }{
473 {
474 name: "ok",
475 input: NewModuleConfig().
476 WithNanotime(func() int64 {
477 return 1
478 }, 2),
479 expectedNanos: 1,
480 expectedResolution: 2,
481 },
482 {
483 name: "overwrites",
484 input: NewModuleConfig().
485 WithNanotime(func() int64 {
486 return 3
487 }, 4).
488 WithNanotime(func() int64 {
489 return 1
490 }, 2),
491 expectedNanos: 1,
492 expectedResolution: 2,
493 },
494 {
495 name: "invalid resolution",
496 input: NewModuleConfig().
497 WithNanotime(func() int64 {
498 return 1
499 }, 0),
500 expectedErr: "invalid Nanotime resolution: 0",
501 },
502 }
503
504 for _, tt := range tests {
505 tc := tt
506
507 t.Run(tc.name, func(t *testing.T) {
508 sysCtx, err := tc.input.(*moduleConfig).toSysContext()
509 if tc.expectedErr == "" {
510 require.Nil(t, err)
511 nanos := sysCtx.Nanotime()
512 require.Equal(t, tc.expectedNanos, nanos)
513 require.Equal(t, tc.expectedResolution, sysCtx.NanotimeResolution())
514 } else {
515 require.EqualError(t, err, tc.expectedErr)
516 }
517 })
518 }
519 }
520
521
522
523 func TestModuleConfig_toSysContext_WithNanosleep(t *testing.T) {
524 sysCtx, err := NewModuleConfig().
525 WithNanosleep(func(ns int64) {
526 require.Equal(t, int64(2), ns)
527 }).(*moduleConfig).toSysContext()
528 require.NoError(t, err)
529 sysCtx.Nanosleep(2)
530 }
531
532
533
534 func TestModuleConfig_toSysContext_WithOsyield(t *testing.T) {
535 var yielded bool
536 sysCtx, err := NewModuleConfig().
537 WithOsyield(func() {
538 yielded = true
539 }).(*moduleConfig).toSysContext()
540 require.NoError(t, err)
541 sysCtx.Osyield()
542 require.True(t, yielded)
543 }
544
545 func TestModuleConfig_toSysContext_Errors(t *testing.T) {
546 tests := []struct {
547 name string
548 input ModuleConfig
549 expectedErr string
550 }{
551 {
552 name: "WithArgs arg contains NUL",
553 input: NewModuleConfig().WithArgs("", string([]byte{'a', 0})),
554 expectedErr: "args invalid: contains NUL character",
555 },
556 {
557 name: "WithEnv key contains NUL",
558 input: NewModuleConfig().WithEnv(string([]byte{'a', 0}), "a"),
559 expectedErr: "environ invalid: contains NUL character",
560 },
561 {
562 name: "WithEnv value contains NUL",
563 input: NewModuleConfig().WithEnv("a", string([]byte{'a', 0})),
564 expectedErr: "environ invalid: contains NUL character",
565 },
566 {
567 name: "WithEnv key contains equals",
568 input: NewModuleConfig().WithEnv("a=", "a"),
569 expectedErr: "environ invalid: key contains '=' character",
570 },
571 {
572 name: "WithEnv empty key",
573 input: NewModuleConfig().WithEnv("", "a"),
574 expectedErr: "environ invalid: empty key",
575 },
576 }
577 for _, tt := range tests {
578 tc := tt
579
580 t.Run(tc.name, func(t *testing.T) {
581 _, err := tc.input.(*moduleConfig).toSysContext()
582 require.EqualError(t, err, tc.expectedErr)
583 })
584 }
585 }
586
587 func TestModuleConfig_clone(t *testing.T) {
588 mc := NewModuleConfig().(*moduleConfig)
589 cloned := mc.clone()
590
591
592 mc.fsConfig = NewFSConfig().WithFSMount(fstest.FS, "/")
593 mc.environKeys["2"] = 2
594
595 cloned.environKeys["1"] = 1
596
597
598 require.Equal(t, map[string]int{"2": 2}, mc.environKeys)
599 require.Equal(t, map[string]int{"1": 1}, cloned.environKeys)
600
601
602 require.Nil(t, cloned.fsConfig)
603 }
604
605 func Test_compiledModule_Name(t *testing.T) {
606 tests := []struct {
607 name string
608 input *compiledModule
609 expected string
610 }{
611 {
612 name: "no name section",
613 input: &compiledModule{module: &wasm.Module{}},
614 },
615 {
616 name: "empty name",
617 input: &compiledModule{module: &wasm.Module{NameSection: &wasm.NameSection{}}},
618 },
619 {
620 name: "name",
621 input: &compiledModule{module: &wasm.Module{NameSection: &wasm.NameSection{ModuleName: "foo"}}},
622 expected: "foo",
623 },
624 }
625
626 for _, tt := range tests {
627 tc := tt
628
629 t.Run(tc.name, func(t *testing.T) {
630 require.Equal(t, tc.expected, tc.input.Name())
631 })
632 }
633 }
634
635 func Test_compiledModule_CustomSections(t *testing.T) {
636 tests := []struct {
637 name string
638 input *compiledModule
639 expected []string
640 }{
641 {
642 name: "no custom section",
643 input: &compiledModule{module: &wasm.Module{}},
644 expected: []string{},
645 },
646 {
647 name: "name",
648 input: &compiledModule{module: &wasm.Module{
649 CustomSections: []*wasm.CustomSection{
650 {Name: "custom1"},
651 {Name: "custom2"},
652 {Name: "customDup"},
653 {Name: "customDup"},
654 },
655 }},
656 expected: []string{
657 "custom1",
658 "custom2",
659 "customDup",
660 "customDup",
661 },
662 },
663 }
664
665 for _, tt := range tests {
666 tc := tt
667
668 t.Run(tc.name, func(t *testing.T) {
669 customSections := tc.input.CustomSections()
670 require.Equal(t, len(tc.expected), len(customSections))
671 for i := 0; i < len(tc.expected); i++ {
672 require.Equal(t, tc.expected[i], customSections[i].Name())
673 }
674 })
675 }
676 }
677
678 func Test_compiledModule_Close(t *testing.T) {
679 for _, ctx := range []context.Context{nil, testCtx} {
680 e := &mockEngine{name: "1", cachedModules: map[*wasm.Module]struct{}{}}
681
682 var cs []*compiledModule
683 for i := 0; i < 10; i++ {
684 m := &wasm.Module{}
685 err := e.CompileModule(ctx, m, nil, false)
686 require.NoError(t, err)
687 cs = append(cs, &compiledModule{module: m, compiledEngine: e})
688 }
689
690
691 require.Equal(t, 10, len(e.cachedModules))
692
693 for _, c := range cs {
694 require.NoError(t, c.Close(ctx))
695 }
696
697
698 require.Zero(t, len(e.cachedModules))
699 }
700 }
701
702 func TestNewRuntimeConfig(t *testing.T) {
703 c, ok := NewRuntimeConfig().(*runtimeConfig)
704 require.True(t, ok)
705
706 require.NotEqual(t, engineLessConfig, c)
707
708 if platform.CompilerSupported() {
709 require.Equal(t, engineKindCompiler, c.engineKind)
710 } else {
711 require.Equal(t, engineKindInterpreter, c.engineKind)
712 }
713 }
714
View as plain text