github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/config_test.go (about) 1 package wazero 2 3 import ( 4 "bytes" 5 "context" 6 _ "embed" 7 "testing" 8 "time" 9 10 "github.com/bananabytelabs/wazero/api" 11 experimentalsys "github.com/bananabytelabs/wazero/experimental/sys" 12 "github.com/bananabytelabs/wazero/internal/fstest" 13 "github.com/bananabytelabs/wazero/internal/platform" 14 internalsys "github.com/bananabytelabs/wazero/internal/sys" 15 "github.com/bananabytelabs/wazero/internal/sysfs" 16 testfs "github.com/bananabytelabs/wazero/internal/testing/fs" 17 "github.com/bananabytelabs/wazero/internal/testing/require" 18 "github.com/bananabytelabs/wazero/internal/wasm" 19 "github.com/bananabytelabs/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, // dwarf is a more technical name and ok here. 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 // The source wasn't modified 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 // The source wasn't modified 158 require.Equal(t, NewModuleConfig(), input) 159 }) 160 } 161 } 162 163 // TestModuleConfig_toSysContext only tests the cases that change the inputs to 164 // sys.NewContext. 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", // Particularly argv[0] can be empty, and we have no rules about others. 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 // TestModuleConfig_toSysContext_WithWalltime has to test differently because we can't 390 // compare function pointers when functions are passed by value. 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 // If below pass, the context was correct! 458 require.Equal(t, int64(1), sec) 459 require.Equal(t, int32(2), nsec) 460 }) 461 } 462 463 // TestModuleConfig_toSysContext_WithNanotime has to test differently because we can't 464 // compare function pointers when functions are passed by value. 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 // TestModuleConfig_toSysContext_WithNanosleep has to test differently because 522 // we can't compare function pointers when functions are passed by value. 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 // TestModuleConfig_toSysContext_WithOsyield has to test differently because 533 // we can't compare function pointers when functions are passed by value. 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 // Make post-clone changes 592 mc.fsConfig = NewFSConfig().WithFSMount(fstest.FS, "/") 593 mc.environKeys["2"] = 2 594 595 cloned.environKeys["1"] = 1 596 597 // Ensure the maps are not shared 598 require.Equal(t, map[string]int{"2": 2}, mc.environKeys) 599 require.Equal(t, map[string]int{"1": 1}, cloned.environKeys) 600 601 // Ensure the fs is not shared 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} { // Ensure it doesn't crash on nil! 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 // Before Close. 691 require.Equal(t, 10, len(e.cachedModules)) 692 693 for _, c := range cs { 694 require.NoError(t, c.Close(ctx)) 695 } 696 697 // After Close. 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 // Should be cloned from the source. 706 require.NotEqual(t, engineLessConfig, c) 707 // Ensures if the correct engine is selected. 708 if platform.CompilerSupported() { 709 require.Equal(t, engineKindCompiler, c.engineKind) 710 } else { 711 require.Equal(t, engineKindInterpreter, c.engineKind) 712 } 713 }