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