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