1 #![cfg(not(miri))] 2 3 use std::fs::File; 4 use std::io::Write; 5 use std::path::Path; 6 use std::process::{Command, ExitStatus, Output, Stdio}; 7 use tempfile::{NamedTempFile, TempDir}; 8 use wasmtime::{Result, bail}; 9 10 // Run the wasmtime CLI with the provided args and return the `Output`. 11 // If the `stdin` is `Some`, opens the file and redirects to the child's stdin. 12 pub fn run_wasmtime_for_output(args: &[&str], stdin: Option<&Path>) -> Result<Output> { 13 let mut cmd = get_wasmtime_command()?; 14 cmd.args(args); 15 if let Some(file) = stdin { 16 cmd.stdin(File::open(file)?); 17 } 18 cmd.output().map_err(Into::into) 19 } 20 21 /// Get the Wasmtime CLI as a [Command]. 22 pub fn get_wasmtime_command() -> Result<Command> { 23 let mut cmd = wasmtime_test_util::command(get_wasmtime_path()); 24 25 // Ignore this if it's specified in the environment to allow tests to run in 26 // "default mode" by default. 27 cmd.env_remove("WASMTIME_NEW_CLI"); 28 29 Ok(cmd) 30 } 31 32 fn get_wasmtime_path() -> &'static str { 33 env!("CARGO_BIN_EXE_wasmtime") 34 } 35 36 // Run the wasmtime CLI with the provided args and, if it succeeds, return 37 // the standard output in a `String`. 38 pub fn run_wasmtime(args: &[&str]) -> Result<String> { 39 let output = run_wasmtime_for_output(args, None)?; 40 if !output.status.success() { 41 bail!( 42 "Failed to execute wasmtime with: {:?}\nstatus: {}\n{}", 43 args, 44 output.status, 45 String::from_utf8_lossy(&output.stderr) 46 ); 47 } 48 Ok(String::from_utf8(output.stdout).unwrap()) 49 } 50 51 fn build_wasm(wat_path: impl AsRef<Path>) -> Result<NamedTempFile> { 52 let mut wasm_file = NamedTempFile::new()?; 53 let wasm = wat::parse_file(wat_path)?; 54 wasm_file.write(&wasm)?; 55 Ok(wasm_file) 56 } 57 58 // Very basic use case: compile binary wasm file and run specific function with arguments. 59 #[test] 60 fn run_wasmtime_simple() -> Result<()> { 61 let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; 62 run_wasmtime(&[ 63 "run", 64 "--invoke", 65 "simple", 66 "-Ccache=n", 67 wasm.path().to_str().unwrap(), 68 "4", 69 ])?; 70 Ok(()) 71 } 72 73 // Wasmtime shall fail when not enough arguments were provided. 74 #[test] 75 fn run_wasmtime_simple_fail_no_args() -> Result<()> { 76 let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; 77 assert!( 78 run_wasmtime(&[ 79 "run", 80 "-Ccache=n", 81 "--invoke", 82 "simple", 83 wasm.path().to_str().unwrap(), 84 ]) 85 .is_err(), 86 "shall fail" 87 ); 88 Ok(()) 89 } 90 91 #[test] 92 fn run_coredump_smoketest() -> Result<()> { 93 let wasm = build_wasm("tests/all/cli_tests/coredump_smoketest.wat")?; 94 let coredump_file = NamedTempFile::new()?; 95 let coredump_arg = format!("-Dcoredump={}", coredump_file.path().display()); 96 let err = run_wasmtime(&[ 97 "run", 98 "--invoke", 99 "a", 100 "-Ccache=n", 101 &coredump_arg, 102 wasm.path().to_str().unwrap(), 103 ]) 104 .unwrap_err(); 105 assert!(err.to_string().contains(&format!( 106 "core dumped at {}", 107 coredump_file.path().display() 108 ))); 109 Ok(()) 110 } 111 112 // Running simple wat 113 #[test] 114 fn run_wasmtime_simple_wat() -> Result<()> { 115 let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; 116 run_wasmtime(&[ 117 "run", 118 "--invoke", 119 "simple", 120 "-Ccache=n", 121 wasm.path().to_str().unwrap(), 122 "4", 123 ])?; 124 assert_eq!( 125 run_wasmtime(&[ 126 "run", 127 "--invoke", 128 "get_f32", 129 "-Ccache=n", 130 wasm.path().to_str().unwrap(), 131 ])?, 132 "100\n" 133 ); 134 assert_eq!( 135 run_wasmtime(&[ 136 "run", 137 "--invoke", 138 "get_f64", 139 "-Ccache=n", 140 wasm.path().to_str().unwrap(), 141 ])?, 142 "100\n" 143 ); 144 Ok(()) 145 } 146 147 // Running a wat that traps. 148 #[test] 149 fn run_wasmtime_unreachable_wat() -> Result<()> { 150 let wasm = build_wasm("tests/all/cli_tests/unreachable.wat")?; 151 let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?; 152 153 assert_ne!(output.stderr, b""); 154 assert_eq!(output.stdout, b""); 155 156 assert_trap_code(&output.status); 157 Ok(()) 158 } 159 160 fn assert_trap_code(status: &ExitStatus) { 161 let code = status 162 .code() 163 .expect("wasmtime process should exit normally"); 164 165 // Test for the specific error code Wasmtime uses to indicate a trap return. 166 #[cfg(unix)] 167 assert_eq!(code, 128 + libc::SIGABRT); 168 #[cfg(windows)] 169 assert_eq!(code, 3); 170 } 171 172 // Run a simple WASI hello world, snapshot0 edition. 173 #[test] 174 fn hello_wasi_snapshot0() -> Result<()> { 175 for preview2 in ["-Spreview2=n", "-Spreview2=y"] { 176 let stdout = run_wasmtime(&[ 177 "-Ccache=n", 178 preview2, 179 "tests/all/cli_tests/hello_wasi_snapshot0.wat", 180 ])?; 181 assert_eq!(stdout, "Hello, world!\n"); 182 } 183 Ok(()) 184 } 185 186 // Run a simple WASI hello world, snapshot1 edition. 187 #[test] 188 fn hello_wasi_snapshot1() -> Result<()> { 189 let stdout = run_wasmtime(&["-Ccache=n", "tests/all/cli_tests/hello_wasi_snapshot1.wat"])?; 190 assert_eq!(stdout, "Hello, world!\n"); 191 Ok(()) 192 } 193 194 #[test] 195 fn timeout_in_start() -> Result<()> { 196 let wasm = build_wasm("tests/all/cli_tests/iloop-start.wat")?; 197 let output = run_wasmtime_for_output( 198 &[ 199 "run", 200 "-Wtimeout=1ms", 201 "-Ccache=n", 202 wasm.path().to_str().unwrap(), 203 ], 204 None, 205 )?; 206 assert!(!output.status.success()); 207 assert_eq!(output.stdout, b""); 208 let stderr = String::from_utf8_lossy(&output.stderr); 209 assert!( 210 stderr.contains("wasm trap: interrupt"), 211 "bad stderr: {stderr}" 212 ); 213 Ok(()) 214 } 215 216 #[test] 217 fn timeout_in_invoke() -> Result<()> { 218 let wasm = build_wasm("tests/all/cli_tests/iloop-invoke.wat")?; 219 let output = run_wasmtime_for_output( 220 &[ 221 "run", 222 "-Wtimeout=1ms", 223 "-Ccache=n", 224 wasm.path().to_str().unwrap(), 225 ], 226 None, 227 )?; 228 assert!(!output.status.success()); 229 assert_eq!(output.stdout, b""); 230 let stderr = String::from_utf8_lossy(&output.stderr); 231 assert!( 232 stderr.contains("wasm trap: interrupt"), 233 "bad stderr: {stderr}" 234 ); 235 Ok(()) 236 } 237 238 // Exit with a valid non-zero exit code, snapshot0 edition. 239 #[test] 240 fn exit2_wasi_snapshot0() -> Result<()> { 241 let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot0.wat")?; 242 243 for preview2 in ["-Spreview2=n", "-Spreview2=y"] { 244 let output = run_wasmtime_for_output( 245 &["-Ccache=n", preview2, wasm.path().to_str().unwrap()], 246 None, 247 )?; 248 assert_eq!(output.status.code().unwrap(), 2); 249 } 250 Ok(()) 251 } 252 253 // Exit with a valid non-zero exit code, snapshot1 edition. 254 #[test] 255 fn exit2_wasi_snapshot1() -> Result<()> { 256 let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot1.wat")?; 257 let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?; 258 assert_eq!(output.status.code().unwrap(), 2); 259 Ok(()) 260 } 261 262 // Exit with a valid non-zero exit code, snapshot0 edition. 263 #[test] 264 fn exit125_wasi_snapshot0() -> Result<()> { 265 let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot0.wat")?; 266 for preview2 in ["-Spreview2=n", "-Spreview2=y"] { 267 let output = run_wasmtime_for_output( 268 &["-Ccache=n", preview2, wasm.path().to_str().unwrap()], 269 None, 270 )?; 271 dbg!(&output); 272 assert_eq!(output.status.code().unwrap(), 125); 273 } 274 Ok(()) 275 } 276 277 // Exit with a valid non-zero exit code, snapshot1 edition. 278 #[test] 279 fn exit125_wasi_snapshot1() -> Result<()> { 280 let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot1.wat")?; 281 let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?; 282 assert_eq!(output.status.code().unwrap(), 125); 283 Ok(()) 284 } 285 286 // Exit with an invalid non-zero exit code, snapshot0 edition. 287 #[test] 288 fn exit126_wasi_snapshot0() -> Result<()> { 289 let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot0.wat")?; 290 291 for preview2 in ["-Spreview2=n", "-Spreview2=y"] { 292 let output = run_wasmtime_for_output( 293 &["-Ccache=n", preview2, wasm.path().to_str().unwrap()], 294 None, 295 )?; 296 assert_eq!(output.status.code().unwrap(), 1); 297 assert!(output.stdout.is_empty()); 298 assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status")); 299 } 300 Ok(()) 301 } 302 303 // Exit with an invalid non-zero exit code, snapshot1 edition. 304 #[test] 305 fn exit126_wasi_snapshot1() -> Result<()> { 306 let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot1.wat")?; 307 let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?; 308 assert_eq!(output.status.code().unwrap(), 1); 309 assert!(output.stdout.is_empty()); 310 assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status")); 311 Ok(()) 312 } 313 314 // Run a minimal command program. 315 #[test] 316 fn minimal_command() -> Result<()> { 317 let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?; 318 let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?; 319 assert_eq!(stdout, ""); 320 Ok(()) 321 } 322 323 // Run a minimal reactor program. 324 #[test] 325 fn minimal_reactor() -> Result<()> { 326 let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?; 327 let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?; 328 assert_eq!(stdout, ""); 329 Ok(()) 330 } 331 332 // Attempt to call invoke on a command. 333 #[test] 334 fn command_invoke() -> Result<()> { 335 let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?; 336 run_wasmtime(&[ 337 "run", 338 "--invoke", 339 "_start", 340 "-Ccache=n", 341 wasm.path().to_str().unwrap(), 342 ])?; 343 Ok(()) 344 } 345 346 // Attempt to call invoke on a command. 347 #[test] 348 fn reactor_invoke() -> Result<()> { 349 let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?; 350 run_wasmtime(&[ 351 "run", 352 "--invoke", 353 "_initialize", 354 "-Ccache=n", 355 wasm.path().to_str().unwrap(), 356 ])?; 357 Ok(()) 358 } 359 360 // Run the greeter test, which runs a preloaded reactor and a command. 361 #[test] 362 fn greeter() -> Result<()> { 363 let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?; 364 let stdout = run_wasmtime(&[ 365 "run", 366 "-Ccache=n", 367 "--preload", 368 "reactor=tests/all/cli_tests/greeter_reactor.wat", 369 wasm.path().to_str().unwrap(), 370 ])?; 371 assert_eq!( 372 stdout, 373 "Hello _initialize\nHello _start\nHello greet\nHello done\n" 374 ); 375 Ok(()) 376 } 377 378 // Run the greeter test, but this time preload a command. 379 #[test] 380 fn greeter_preload_command() -> Result<()> { 381 let wasm = build_wasm("tests/all/cli_tests/greeter_reactor.wat")?; 382 let stdout = run_wasmtime(&[ 383 "run", 384 "-Ccache=n", 385 "--preload", 386 "reactor=tests/all/cli_tests/hello_wasi_snapshot1.wat", 387 wasm.path().to_str().unwrap(), 388 ])?; 389 assert_eq!(stdout, "Hello _initialize\n"); 390 Ok(()) 391 } 392 393 // Run the greeter test, which runs a preloaded reactor and a command. 394 #[test] 395 fn greeter_preload_callable_command() -> Result<()> { 396 let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?; 397 let stdout = run_wasmtime(&[ 398 "run", 399 "-Ccache=n", 400 "--preload", 401 "reactor=tests/all/cli_tests/greeter_callable_command.wat", 402 wasm.path().to_str().unwrap(), 403 ])?; 404 assert_eq!(stdout, "Hello _start\nHello callable greet\nHello done\n"); 405 Ok(()) 406 } 407 408 // Ensure successful WASI exit call with FPR saving frames on stack for Windows x64 409 // See https://github.com/bytecodealliance/wasmtime/issues/1967 410 #[test] 411 fn exit_with_saved_fprs() -> Result<()> { 412 let wasm = build_wasm("tests/all/cli_tests/exit_with_saved_fprs.wat")?; 413 let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?; 414 assert_eq!(output.status.code().unwrap(), 0); 415 assert!(output.stdout.is_empty()); 416 Ok(()) 417 } 418 419 #[test] 420 fn run_cwasm() -> Result<()> { 421 let td = TempDir::new()?; 422 let cwasm = td.path().join("foo.cwasm"); 423 let stdout = run_wasmtime(&[ 424 "compile", 425 "tests/all/cli_tests/simple.wat", 426 "-o", 427 cwasm.to_str().unwrap(), 428 ])?; 429 assert_eq!(stdout, ""); 430 let stdout = run_wasmtime(&["run", "--allow-precompiled", cwasm.to_str().unwrap()])?; 431 assert_eq!(stdout, ""); 432 Ok(()) 433 } 434 435 #[cfg(unix)] 436 #[test] 437 fn hello_wasi_snapshot0_from_stdin() -> Result<()> { 438 // Run a simple WASI hello world, snapshot0 edition. 439 // The module is piped from standard input. 440 let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?; 441 for preview2 in ["-Spreview2=n", "-Spreview2=y"] { 442 let stdout = { 443 let path = wasm.path(); 444 let args: &[&str] = &["-Ccache=n", preview2, "-"]; 445 let output = run_wasmtime_for_output(args, Some(path))?; 446 if !output.status.success() { 447 bail!( 448 "Failed to execute wasmtime with: {:?}\n{}", 449 args, 450 String::from_utf8_lossy(&output.stderr) 451 ); 452 } 453 Ok::<_, wasmtime::Error>(String::from_utf8(output.stdout).unwrap()) 454 }?; 455 assert_eq!(stdout, "Hello, world!\n"); 456 } 457 Ok(()) 458 } 459 460 #[test] 461 fn specify_env() -> Result<()> { 462 // By default no env is inherited 463 let output = get_wasmtime_command()? 464 .args(&["run", "tests/all/cli_tests/print_env.wat"]) 465 .env("THIS_WILL_NOT", "show up in the output") 466 .output()?; 467 assert!(output.status.success()); 468 assert_eq!(String::from_utf8_lossy(&output.stdout), ""); 469 470 // Specify a single env var 471 let output = get_wasmtime_command()? 472 .args(&[ 473 "run", 474 "--env", 475 "FOO=bar", 476 "tests/all/cli_tests/print_env.wat", 477 ]) 478 .output()?; 479 assert!(output.status.success()); 480 assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n"); 481 482 // Inherit a single env var 483 let output = get_wasmtime_command()? 484 .args(&["run", "--env", "FOO", "tests/all/cli_tests/print_env.wat"]) 485 .env("FOO", "bar") 486 .output()?; 487 assert!(output.status.success()); 488 assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n"); 489 490 // Inherit a nonexistent env var 491 let output = get_wasmtime_command()? 492 .args(&[ 493 "run", 494 "--env", 495 "SURELY_THIS_ENV_VAR_DOES_NOT_EXIST_ANYWHERE_RIGHT", 496 "tests/all/cli_tests/print_env.wat", 497 ]) 498 .output()?; 499 assert!(output.status.success()); 500 501 // Inherit all env vars 502 let output = get_wasmtime_command()? 503 .args(&["run", "-Sinherit-env", "tests/all/cli_tests/print_env.wat"]) 504 .env("FOO", "bar") 505 .output()?; 506 assert!(output.status.success()); 507 let stdout = String::from_utf8_lossy(&output.stdout); 508 assert!(stdout.contains("FOO=bar"), "bad output: {stdout}"); 509 510 Ok(()) 511 } 512 513 #[cfg(unix)] 514 #[test] 515 fn run_cwasm_from_stdin() -> Result<()> { 516 use std::process::Stdio; 517 518 let td = TempDir::new()?; 519 let cwasm = td.path().join("foo.cwasm"); 520 let stdout = run_wasmtime(&[ 521 "compile", 522 "tests/all/cli_tests/simple.wat", 523 "-o", 524 cwasm.to_str().unwrap(), 525 ])?; 526 assert_eq!(stdout, ""); 527 528 // If stdin is literally the file itself then that should work 529 let args: &[&str] = &["run", "--allow-precompiled", "-"]; 530 let output = get_wasmtime_command()? 531 .args(args) 532 .stdin(File::open(&cwasm)?) 533 .output()?; 534 assert!(output.status.success(), "a file as stdin should work"); 535 536 // If stdin is a pipe, that should also work 537 let input = std::fs::read(&cwasm)?; 538 let mut child = get_wasmtime_command()? 539 .args(args) 540 .stdin(Stdio::piped()) 541 .stdout(Stdio::piped()) 542 .stderr(Stdio::piped()) 543 .spawn()?; 544 let mut stdin = child.stdin.take().unwrap(); 545 let t = std::thread::spawn(move || { 546 let _ = stdin.write_all(&input); 547 }); 548 let output = child.wait_with_output()?; 549 assert!(output.status.success()); 550 t.join().unwrap(); 551 Ok(()) 552 } 553 554 #[cfg(feature = "wasi-threads")] 555 #[test] 556 fn run_threads() -> Result<()> { 557 // Only run threaded tests on platforms that support threads. Also skip 558 // these tests with ASAN as it, rightfully, complains about a memory leak. 559 // The memory leak at this time is that child threads aren't joined with the 560 // main thread, meaning that allocations done on child threads are indeed 561 // leaked. 562 if crate::threads::engine().is_none() || cfg!(asan) { 563 return Ok(()); 564 } 565 let wasm = build_wasm("tests/all/cli_tests/threads.wat")?; 566 let stdout = run_wasmtime(&[ 567 "run", 568 "-Wthreads,shared-memory", 569 "-Sthreads", 570 "-Ccache=n", 571 wasm.path().to_str().unwrap(), 572 ])?; 573 574 assert!( 575 stdout 576 == "Called _start\n\ 577 Running wasi_thread_start\n\ 578 Running wasi_thread_start\n\ 579 Running wasi_thread_start\n\ 580 Done\n" 581 ); 582 Ok(()) 583 } 584 585 #[cfg(feature = "wasi-threads")] 586 #[test] 587 fn run_simple_with_wasi_threads() -> Result<()> { 588 // Skip this test on platforms that don't support threads. 589 if crate::threads::engine().is_none() { 590 return Ok(()); 591 } 592 // We expect to be able to run Wasm modules that do not have correct 593 // wasi-thread entry points or imported shared memory as long as no threads 594 // are spawned. 595 let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; 596 let stdout = run_wasmtime(&[ 597 "run", 598 "-Wthreads", 599 "-Sthreads", 600 "-Ccache=n", 601 "--invoke", 602 "simple", 603 wasm.path().to_str().unwrap(), 604 "4", 605 ])?; 606 assert_eq!(stdout, "4\n"); 607 Ok(()) 608 } 609 610 #[test] 611 fn wasm_flags() -> Result<()> { 612 // Any argument after the wasm module should be interpreted as for the 613 // command itself 614 let stdout = run_wasmtime(&[ 615 "run", 616 "--", 617 "tests/all/cli_tests/print-arguments.wat", 618 "--argument", 619 "-for", 620 "the", 621 "command", 622 ])?; 623 assert_eq!( 624 stdout, 625 "\ 626 print-arguments.wat\n\ 627 --argument\n\ 628 -for\n\ 629 the\n\ 630 command\n\ 631 " 632 ); 633 let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "-"])?; 634 assert_eq!( 635 stdout, 636 "\ 637 print-arguments.wat\n\ 638 -\n\ 639 " 640 ); 641 let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "--"])?; 642 assert_eq!( 643 stdout, 644 "\ 645 print-arguments.wat\n\ 646 --\n\ 647 " 648 ); 649 let stdout = run_wasmtime(&[ 650 "run", 651 "--", 652 "tests/all/cli_tests/print-arguments.wat", 653 "--", 654 "--", 655 "-a", 656 "b", 657 ])?; 658 assert_eq!( 659 stdout, 660 "\ 661 print-arguments.wat\n\ 662 --\n\ 663 --\n\ 664 -a\n\ 665 b\n\ 666 " 667 ); 668 Ok(()) 669 } 670 671 #[test] 672 fn name_same_as_builtin_command() -> Result<()> { 673 // a bare subcommand shouldn't run successfully 674 let output = get_wasmtime_command()? 675 .current_dir("tests/all/cli_tests") 676 .arg("run") 677 .output()?; 678 assert!(!output.status.success()); 679 680 // a `--` prefix should let everything else get interpreted as a wasm 681 // module and arguments, even if the module has a name like `run` 682 let output = get_wasmtime_command()? 683 .current_dir("tests/all/cli_tests") 684 .arg("--") 685 .arg("run") 686 .output()?; 687 assert!(output.status.success(), "expected success got {output:#?}"); 688 689 // Passing options before the subcommand should work and doesn't require 690 // `--` to disambiguate 691 let output = get_wasmtime_command()? 692 .current_dir("tests/all/cli_tests") 693 .arg("-Ccache=n") 694 .arg("run") 695 .output()?; 696 assert!(output.status.success(), "expected success got {output:#?}"); 697 Ok(()) 698 } 699 700 #[test] 701 #[cfg(unix)] 702 fn run_just_stdin_argument() -> Result<()> { 703 let output = get_wasmtime_command()? 704 .arg("-") 705 .stdin(File::open("tests/all/cli_tests/simple.wat")?) 706 .output()?; 707 assert!(output.status.success()); 708 Ok(()) 709 } 710 711 #[test] 712 fn wasm_flags_without_subcommand() -> Result<()> { 713 let output = get_wasmtime_command()? 714 .current_dir("tests/all/cli_tests/") 715 .arg("print-arguments.wat") 716 .arg("-foo") 717 .arg("bar") 718 .output()?; 719 assert!(output.status.success()); 720 assert_eq!( 721 String::from_utf8_lossy(&output.stdout), 722 "\ 723 print-arguments.wat\n\ 724 -foo\n\ 725 bar\n\ 726 " 727 ); 728 Ok(()) 729 } 730 731 #[test] 732 fn wasi_misaligned_pointer() -> Result<()> { 733 let output = get_wasmtime_command()? 734 .arg("./tests/all/cli_tests/wasi_misaligned_pointer.wat") 735 .output()?; 736 assert!(!output.status.success()); 737 let stderr = String::from_utf8_lossy(&output.stderr); 738 assert!( 739 stderr.contains("Pointer not aligned"), 740 "bad stderr: {stderr}", 741 ); 742 Ok(()) 743 } 744 745 #[test] 746 #[cfg_attr(not(feature = "component-model"), ignore)] 747 fn hello_with_preview2() -> Result<()> { 748 let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?; 749 let stdout = run_wasmtime(&["-Ccache=n", "-Spreview2", wasm.path().to_str().unwrap()])?; 750 assert_eq!(stdout, "Hello, world!\n"); 751 Ok(()) 752 } 753 754 #[test] 755 #[cfg_attr(not(feature = "component-model"), ignore)] 756 fn component_missing_feature() -> Result<()> { 757 let path = "tests/all/cli_tests/empty-component.wat"; 758 let wasm = build_wasm(path)?; 759 let output = get_wasmtime_command()? 760 .arg("-Ccache=n") 761 .arg("-Wcomponent-model=n") 762 .arg(wasm.path()) 763 .output()?; 764 assert!(!output.status.success()); 765 let stderr = String::from_utf8_lossy(&output.stderr); 766 assert!( 767 stderr.contains("cannot execute a component without `--wasm component-model`"), 768 "bad stderr: {stderr}" 769 ); 770 771 // also tests with raw *.wat input 772 let output = get_wasmtime_command()? 773 .arg("-Ccache=n") 774 .arg("-Wcomponent-model=n") 775 .arg(path) 776 .output()?; 777 assert!(!output.status.success()); 778 let stderr = String::from_utf8_lossy(&output.stderr); 779 assert!( 780 stderr.contains("cannot execute a component without `--wasm component-model`"), 781 "bad stderr: {stderr}" 782 ); 783 784 Ok(()) 785 } 786 787 #[test] 788 #[cfg_attr(not(feature = "component-model"), ignore)] 789 fn component_enabled_by_default() -> Result<()> { 790 let path = "tests/all/cli_tests/component-basic.wat"; 791 let wasm = build_wasm(path)?; 792 let output = get_wasmtime_command()? 793 .arg("-Ccache=n") 794 .arg(wasm.path()) 795 .output()?; 796 assert!(output.status.success()); 797 798 // also tests with raw *.wat input 799 let output = get_wasmtime_command()? 800 .arg("-Ccache=n") 801 .arg(path) 802 .output()?; 803 assert!(output.status.success()); 804 805 Ok(()) 806 } 807 808 // If the text format is invalid then the filename should be mentioned in the 809 // error message. 810 #[test] 811 fn bad_text_syntax() -> Result<()> { 812 let output = get_wasmtime_command()? 813 .arg("-Ccache=n") 814 .arg("tests/all/cli_tests/bad-syntax.wat") 815 .output()?; 816 assert!(!output.status.success()); 817 let stderr = String::from_utf8_lossy(&output.stderr); 818 assert!( 819 stderr.contains("--> tests/all/cli_tests/bad-syntax.wat"), 820 "bad stderr: {stderr}" 821 ); 822 Ok(()) 823 } 824 825 #[test] 826 #[cfg_attr(not(feature = "component-model"), ignore)] 827 fn run_basic_component() -> Result<()> { 828 let path = "tests/all/cli_tests/component-basic.wat"; 829 let wasm = build_wasm(path)?; 830 831 // Run both the `*.wasm` binary and the text format 832 run_wasmtime(&[ 833 "-Ccache=n", 834 "-Wcomponent-model", 835 wasm.path().to_str().unwrap(), 836 ])?; 837 run_wasmtime(&["-Ccache=n", "-Wcomponent-model", path])?; 838 839 Ok(()) 840 } 841 842 #[test] 843 #[cfg_attr(not(feature = "component-model"), ignore)] 844 fn run_precompiled_component() -> Result<()> { 845 let td = TempDir::new()?; 846 let cwasm = td.path().join("component-basic.cwasm"); 847 let stdout = run_wasmtime(&[ 848 "compile", 849 "tests/all/cli_tests/component-basic.wat", 850 "-o", 851 cwasm.to_str().unwrap(), 852 "-Wcomponent-model", 853 ])?; 854 assert_eq!(stdout, ""); 855 let stdout = run_wasmtime(&[ 856 "run", 857 "-Wcomponent-model", 858 "--allow-precompiled", 859 cwasm.to_str().unwrap(), 860 ])?; 861 assert_eq!(stdout, ""); 862 863 Ok(()) 864 } 865 866 // Disable test on s390x because the large allocation may actually succeed; 867 // the whole 64-bit address space is available on this platform. 868 #[test] 869 #[cfg(not(target_arch = "s390x"))] 870 fn memory_growth_failure() -> Result<()> { 871 let output = get_wasmtime_command()? 872 .args(&[ 873 "run", 874 "-Wmemory64", 875 "-Wtrap-on-grow-failure", 876 "tests/all/cli_tests/memory-grow-failure.wat", 877 ]) 878 .output()?; 879 assert!(!output.status.success()); 880 let stderr = String::from_utf8_lossy(&output.stderr); 881 assert!( 882 stderr.contains("forcing a memory growth failure to be a trap"), 883 "bad stderr: {stderr}" 884 ); 885 Ok(()) 886 } 887 888 #[test] 889 fn table_growth_failure() -> Result<()> { 890 let output = get_wasmtime_command()? 891 .args(&[ 892 "run", 893 "-Wtrap-on-grow-failure", 894 "tests/all/cli_tests/table-grow-failure.wat", 895 ]) 896 .output()?; 897 assert!(!output.status.success()); 898 let stderr = String::from_utf8_lossy(&output.stderr); 899 assert!( 900 stderr.contains("forcing trap when growing table"), 901 "bad stderr: {stderr}" 902 ); 903 Ok(()) 904 } 905 906 #[test] 907 fn table_growth_failure2() -> Result<()> { 908 let output = get_wasmtime_command()? 909 .args(&[ 910 "run", 911 "-Wtrap-on-grow-failure", 912 "tests/all/cli_tests/table-grow-failure2.wat", 913 ]) 914 .output()?; 915 assert!(!output.status.success()); 916 let stderr = String::from_utf8_lossy(&output.stderr); 917 let expected = if cfg!(target_pointer_width = "32") { 918 "overflow calculating new table size" 919 } else { 920 "forcing trap when growing table to 4294967296 elements" 921 }; 922 assert!(stderr.contains(expected), "bad stderr: {stderr}"); 923 Ok(()) 924 } 925 926 #[test] 927 fn option_group_help() -> Result<()> { 928 run_wasmtime(&["run", "-Whelp"])?; 929 run_wasmtime(&["run", "-O", "help"])?; 930 run_wasmtime(&["run", "--codegen", "help"])?; 931 run_wasmtime(&["run", "--debug=help"])?; 932 run_wasmtime(&["run", "-Shelp"])?; 933 run_wasmtime(&["run", "-Whelp-long"])?; 934 Ok(()) 935 } 936 937 #[test] 938 fn option_group_comma_separated() -> Result<()> { 939 run_wasmtime(&[ 940 "run", 941 "-Wrelaxed-simd,simd", 942 "tests/all/cli_tests/simple.wat", 943 ])?; 944 Ok(()) 945 } 946 947 #[test] 948 fn option_group_boolean_parsing() -> Result<()> { 949 run_wasmtime(&["run", "-Wrelaxed-simd", "tests/all/cli_tests/simple.wat"])?; 950 run_wasmtime(&["run", "-Wrelaxed-simd=n", "tests/all/cli_tests/simple.wat"])?; 951 run_wasmtime(&["run", "-Wrelaxed-simd=y", "tests/all/cli_tests/simple.wat"])?; 952 run_wasmtime(&["run", "-Wrelaxed-simd=no", "tests/all/cli_tests/simple.wat"])?; 953 run_wasmtime(&[ 954 "run", 955 "-Wrelaxed-simd=yes", 956 "tests/all/cli_tests/simple.wat", 957 ])?; 958 run_wasmtime(&[ 959 "run", 960 "-Wrelaxed-simd=true", 961 "tests/all/cli_tests/simple.wat", 962 ])?; 963 run_wasmtime(&[ 964 "run", 965 "-Wrelaxed-simd=false", 966 "tests/all/cli_tests/simple.wat", 967 ])?; 968 Ok(()) 969 } 970 971 #[test] 972 fn preview2_stdin() -> Result<()> { 973 let test = "tests/all/cli_tests/count-stdin.wat"; 974 let cmd = || -> Result<_> { 975 let mut cmd = get_wasmtime_command()?; 976 cmd.arg("--invoke=count").arg("-Spreview2").arg(test); 977 Ok(cmd) 978 }; 979 980 // read empty pipe is ok 981 let output = cmd()?.output()?; 982 assert!(output.status.success()); 983 assert_eq!(String::from_utf8_lossy(&output.stdout), "0\n"); 984 985 // read itself is ok 986 let file = File::open(test)?; 987 let size = file.metadata()?.len(); 988 let output = cmd()?.stdin(File::open(test)?).output()?; 989 assert!(output.status.success()); 990 assert_eq!(String::from_utf8_lossy(&output.stdout), format!("{size}\n")); 991 992 // read piped input ok is ok 993 let mut child = cmd()? 994 .stdin(Stdio::piped()) 995 .stdout(Stdio::piped()) 996 .stderr(Stdio::piped()) 997 .spawn()?; 998 let mut stdin = child.stdin.take().unwrap(); 999 std::thread::spawn(move || { 1000 stdin.write_all(b"hello").unwrap(); 1001 }); 1002 let output = child.wait_with_output()?; 1003 assert!(output.status.success()); 1004 assert_eq!(String::from_utf8_lossy(&output.stdout), "5\n"); 1005 1006 let count_up_to = |n: usize| -> Result<_> { 1007 let mut child = get_wasmtime_command()? 1008 .arg("--invoke=count-up-to") 1009 .arg("-Spreview2") 1010 .arg(test) 1011 .arg(n.to_string()) 1012 .stdin(Stdio::piped()) 1013 .stdout(Stdio::piped()) 1014 .stderr(Stdio::piped()) 1015 .spawn()?; 1016 let mut stdin = child.stdin.take().unwrap(); 1017 let t = std::thread::spawn(move || { 1018 let mut written = 0; 1019 let bytes = [0; 64 * 1024]; 1020 loop { 1021 written += match stdin.write(&bytes) { 1022 Ok(n) => n, 1023 Err(_) => break written, 1024 }; 1025 } 1026 }); 1027 let output = child.wait_with_output()?; 1028 assert!(output.status.success()); 1029 let written = t.join().unwrap(); 1030 let read = String::from_utf8_lossy(&output.stdout) 1031 .trim() 1032 .parse::<usize>() 1033 .unwrap(); 1034 // The test reads in 1000 byte chunks so make sure that it doesn't read 1035 // more than 1000 bytes than requested. 1036 assert!(read < n + 1000, "test read too much {read}"); 1037 Ok(written) 1038 }; 1039 1040 // wasmtime shouldn't eat information that the guest never actually tried to 1041 // read. 1042 // 1043 // NB: this may be a bit flaky. Exactly how much we wrote in the above 1044 // helper thread depends on how much the OS buffers for us. For now give 1045 // some some slop and assume that OSes are unlikely to buffer more than 1046 // that. 1047 let slop = 256 * 1024; 1048 for amt in [0, 100, 100_000] { 1049 let written = count_up_to(amt)?; 1050 assert!(written < slop + amt, "wrote too much {written}"); 1051 } 1052 Ok(()) 1053 } 1054 1055 #[test] 1056 fn float_args() -> Result<()> { 1057 let result = run_wasmtime(&[ 1058 "--invoke", 1059 "echo_f32", 1060 "tests/all/cli_tests/simple.wat", 1061 "1.0", 1062 ])?; 1063 assert_eq!(result, "1\n"); 1064 let result = run_wasmtime(&[ 1065 "--invoke", 1066 "echo_f64", 1067 "tests/all/cli_tests/simple.wat", 1068 "1.1", 1069 ])?; 1070 assert_eq!(result, "1.1\n"); 1071 Ok(()) 1072 } 1073 1074 #[test] 1075 fn mpk_without_pooling() -> Result<()> { 1076 let output = get_wasmtime_command()? 1077 .args(&[ 1078 "run", 1079 "-O", 1080 "memory-protection-keys=y", 1081 "--invoke", 1082 "echo_f32", 1083 "tests/all/cli_tests/simple.wat", 1084 "1.0", 1085 ]) 1086 .env("WASMTIME_NEW_CLI", "1") 1087 .output()?; 1088 assert!(!output.status.success()); 1089 Ok(()) 1090 } 1091 1092 // Very basic use case: compile binary wasm file and run specific function with arguments. 1093 #[test] 1094 fn increase_stack_size() -> Result<()> { 1095 run_wasmtime(&[ 1096 "run", 1097 "--invoke", 1098 "simple", 1099 &format!("-Wmax-wasm-stack={}", 5 << 20), 1100 "-Ccache=n", 1101 "tests/all/cli_tests/simple.wat", 1102 "4", 1103 ])?; 1104 Ok(()) 1105 } 1106 1107 mod test_programs { 1108 use super::{get_wasmtime_command, run_wasmtime}; 1109 use http_body_util::BodyExt; 1110 use hyper::header::HeaderValue; 1111 use std::io::{self, BufRead, BufReader, Read, Write}; 1112 use std::iter; 1113 use std::net::SocketAddr; 1114 use std::process::{Child, Command, Stdio}; 1115 use std::thread::{self, JoinHandle}; 1116 use test_programs_artifacts::*; 1117 use tokio::net::TcpStream; 1118 use wasmtime::{Result, bail, error::Context as _, format_err}; 1119 1120 macro_rules! assert_test_exists { 1121 ($name:ident) => { 1122 #[expect(unused_imports, reason = "just here to assert the test is here")] 1123 use self::$name as _; 1124 }; 1125 } 1126 foreach_cli!(assert_test_exists); 1127 1128 #[test] 1129 fn p2_cli_hello_stdout() -> Result<()> { 1130 run_wasmtime(&["run", "-Wcomponent-model", P2_CLI_HELLO_STDOUT_COMPONENT])?; 1131 Ok(()) 1132 } 1133 1134 #[test] 1135 fn p2_cli_args() -> Result<()> { 1136 run_wasmtime(&[ 1137 "run", 1138 "-Wcomponent-model", 1139 P2_CLI_ARGS_COMPONENT, 1140 "hello", 1141 "this", 1142 "", 1143 "is an argument", 1144 "with emoji", 1145 ])?; 1146 Ok(()) 1147 } 1148 1149 #[test] 1150 fn p2_cli_stdin_empty() -> Result<()> { 1151 let mut child = get_wasmtime_command()? 1152 .args(&["run", "-Wcomponent-model", P2_CLI_STDIN_EMPTY_COMPONENT]) 1153 .stdout(Stdio::piped()) 1154 .stderr(Stdio::piped()) 1155 .stdin(Stdio::piped()) 1156 .spawn()?; 1157 child 1158 .stdin 1159 .take() 1160 .unwrap() 1161 .write_all(b"not to be read") 1162 .unwrap(); 1163 let output = child.wait_with_output()?; 1164 println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); 1165 println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 1166 assert!(output.status.success()); 1167 Ok(()) 1168 } 1169 1170 #[test] 1171 fn p2_cli_stdin() -> Result<()> { 1172 let mut child = get_wasmtime_command()? 1173 .args(&["run", "-Wcomponent-model", P2_CLI_STDIN_COMPONENT]) 1174 .stdout(Stdio::piped()) 1175 .stderr(Stdio::piped()) 1176 .stdin(Stdio::piped()) 1177 .spawn()?; 1178 child 1179 .stdin 1180 .take() 1181 .unwrap() 1182 .write_all(b"So rested he by the Tumtum tree") 1183 .unwrap(); 1184 let output = child.wait_with_output()?; 1185 println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); 1186 println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 1187 assert!(output.status.success()); 1188 Ok(()) 1189 } 1190 1191 #[test] 1192 fn p2_cli_splice_stdin() -> Result<()> { 1193 let mut child = get_wasmtime_command()? 1194 .args(&["run", "-Wcomponent-model", P2_CLI_SPLICE_STDIN_COMPONENT]) 1195 .stdout(Stdio::piped()) 1196 .stderr(Stdio::piped()) 1197 .stdin(Stdio::piped()) 1198 .spawn()?; 1199 let msg = "So rested he by the Tumtum tree"; 1200 child 1201 .stdin 1202 .take() 1203 .unwrap() 1204 .write_all(msg.as_bytes()) 1205 .unwrap(); 1206 let output = child.wait_with_output()?; 1207 assert!(output.status.success()); 1208 let stdout = String::from_utf8_lossy(&output.stdout); 1209 let stderr = String::from_utf8_lossy(&output.stderr); 1210 if !stderr.is_empty() { 1211 eprintln!("{stderr}"); 1212 } 1213 1214 assert_eq!( 1215 format!( 1216 "before splice\n{msg}\ncompleted splicing {} bytes\n", 1217 msg.as_bytes().len() 1218 ), 1219 stdout 1220 ); 1221 Ok(()) 1222 } 1223 1224 #[test] 1225 fn p2_cli_env() -> Result<()> { 1226 run_wasmtime(&[ 1227 "run", 1228 "-Wcomponent-model", 1229 "--env=frabjous=day", 1230 "--env=callooh=callay", 1231 P2_CLI_ENV_COMPONENT, 1232 ])?; 1233 Ok(()) 1234 } 1235 1236 #[test] 1237 fn p2_cli_file_read() -> Result<()> { 1238 let dir = tempfile::tempdir()?; 1239 1240 std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?; 1241 1242 run_wasmtime(&[ 1243 "run", 1244 "-Wcomponent-model", 1245 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1246 P2_CLI_FILE_READ_COMPONENT, 1247 ])?; 1248 Ok(()) 1249 } 1250 1251 #[test] 1252 fn p2_cli_file_append() -> Result<()> { 1253 let dir = tempfile::tempdir()?; 1254 1255 std::fs::File::create(dir.path().join("bar.txt"))? 1256 .write_all(b"'Twas brillig, and the slithy toves.\n")?; 1257 1258 run_wasmtime(&[ 1259 "run", 1260 "-Wcomponent-model", 1261 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1262 P2_CLI_FILE_APPEND_COMPONENT, 1263 ])?; 1264 1265 let contents = std::fs::read(dir.path().join("bar.txt"))?; 1266 assert_eq!( 1267 std::str::from_utf8(&contents).unwrap(), 1268 "'Twas brillig, and the slithy toves.\n\ 1269 Did gyre and gimble in the wabe;\n\ 1270 All mimsy were the borogoves,\n\ 1271 And the mome raths outgrabe.\n" 1272 ); 1273 Ok(()) 1274 } 1275 1276 #[test] 1277 fn p2_cli_file_dir_sync() -> Result<()> { 1278 let dir = tempfile::tempdir()?; 1279 1280 std::fs::File::create(dir.path().join("bar.txt"))? 1281 .write_all(b"'Twas brillig, and the slithy toves.\n")?; 1282 1283 run_wasmtime(&[ 1284 "run", 1285 "-Wcomponent-model", 1286 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1287 P2_CLI_FILE_DIR_SYNC_COMPONENT, 1288 ])?; 1289 1290 Ok(()) 1291 } 1292 1293 #[test] 1294 fn p2_cli_exit_success() -> Result<()> { 1295 run_wasmtime(&["run", "-Wcomponent-model", P2_CLI_EXIT_SUCCESS_COMPONENT])?; 1296 Ok(()) 1297 } 1298 1299 #[test] 1300 fn p2_cli_exit_default() -> Result<()> { 1301 run_wasmtime(&["run", "-Wcomponent-model", P2_CLI_EXIT_DEFAULT_COMPONENT])?; 1302 Ok(()) 1303 } 1304 1305 #[test] 1306 fn p2_cli_exit_failure() -> Result<()> { 1307 let output = get_wasmtime_command()? 1308 .args(&["run", "-Wcomponent-model", P2_CLI_EXIT_FAILURE_COMPONENT]) 1309 .output()?; 1310 assert!(!output.status.success()); 1311 assert_eq!(output.status.code(), Some(1)); 1312 Ok(()) 1313 } 1314 1315 #[test] 1316 fn p2_cli_exit_with_code() -> Result<()> { 1317 let output = get_wasmtime_command()? 1318 .args(&[ 1319 "run", 1320 "-Wcomponent-model", 1321 "-Scli-exit-with-code", 1322 P2_CLI_EXIT_WITH_CODE_COMPONENT, 1323 ]) 1324 .output()?; 1325 assert!(!output.status.success()); 1326 assert_eq!(output.status.code(), Some(42)); 1327 Ok(()) 1328 } 1329 1330 #[test] 1331 fn p2_cli_exit_panic() -> Result<()> { 1332 let output = get_wasmtime_command()? 1333 .args(&["run", "-Wcomponent-model", P2_CLI_EXIT_PANIC_COMPONENT]) 1334 .output()?; 1335 assert!(!output.status.success()); 1336 let stderr = String::from_utf8_lossy(&output.stderr); 1337 assert!(stderr.contains("Curiouser and curiouser!")); 1338 Ok(()) 1339 } 1340 1341 #[test] 1342 fn p2_cli_directory_list() -> Result<()> { 1343 let dir = tempfile::tempdir()?; 1344 1345 std::fs::File::create(dir.path().join("foo.txt"))?; 1346 std::fs::File::create(dir.path().join("bar.txt"))?; 1347 std::fs::File::create(dir.path().join("baz.txt"))?; 1348 std::fs::create_dir(dir.path().join("sub"))?; 1349 std::fs::File::create(dir.path().join("sub").join("wow.txt"))?; 1350 std::fs::File::create(dir.path().join("sub").join("yay.txt"))?; 1351 1352 run_wasmtime(&[ 1353 "run", 1354 "-Wcomponent-model", 1355 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1356 P2_CLI_DIRECTORY_LIST_COMPONENT, 1357 ])?; 1358 Ok(()) 1359 } 1360 1361 #[test] 1362 fn p2_cli_default_clocks() -> Result<()> { 1363 run_wasmtime(&["run", "-Wcomponent-model", P2_CLI_DEFAULT_CLOCKS_COMPONENT])?; 1364 Ok(()) 1365 } 1366 1367 #[test] 1368 fn p2_cli_export_cabi_realloc() -> Result<()> { 1369 run_wasmtime(&[ 1370 "run", 1371 "-Wcomponent-model", 1372 P2_CLI_EXPORT_CABI_REALLOC_COMPONENT, 1373 ])?; 1374 Ok(()) 1375 } 1376 1377 #[test] 1378 fn run_wasi_http_component() -> Result<()> { 1379 let output = super::run_wasmtime_for_output( 1380 &[ 1381 "-Ccache=no", 1382 "-Wcomponent-model", 1383 "-Scli,http,preview2", 1384 P2_HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT, 1385 ], 1386 None, 1387 )?; 1388 println!("{}", String::from_utf8_lossy(&output.stderr)); 1389 let stdout = String::from_utf8_lossy(&output.stdout); 1390 println!("{stdout}"); 1391 assert!(stdout.starts_with("Called _start\n")); 1392 assert!(stdout.ends_with("Done\n")); 1393 assert!(output.status.success()); 1394 Ok(()) 1395 } 1396 1397 // Test to ensure that prints in the guest aren't buffered on the host by 1398 // accident. The test here will print something without a newline and then 1399 // wait for input on stdin, and the test here is to ensure that the 1400 // character shows up here even as the guest is waiting on input via stdin. 1401 #[test] 1402 fn p2_cli_stdio_write_flushes() -> Result<()> { 1403 fn run(args: &[&str]) -> Result<()> { 1404 println!("running {args:?}"); 1405 let mut child = get_wasmtime_command()? 1406 .args(args) 1407 .stdin(Stdio::piped()) 1408 .stdout(Stdio::piped()) 1409 .spawn()?; 1410 let mut stdout = child.stdout.take().unwrap(); 1411 let mut buf = [0; 10]; 1412 match stdout.read(&mut buf) { 1413 Ok(2) => assert_eq!(&buf[..2], b"> "), 1414 e => panic!("unexpected read result {e:?}"), 1415 } 1416 drop(stdout); 1417 drop(child.stdin.take().unwrap()); 1418 let status = child.wait()?; 1419 assert!(status.success()); 1420 Ok(()) 1421 } 1422 1423 run(&["run", "-Spreview2=n", P2_CLI_STDIO_WRITE_FLUSHES])?; 1424 run(&["run", "-Spreview2=y", P2_CLI_STDIO_WRITE_FLUSHES])?; 1425 run(&[ 1426 "run", 1427 "-Wcomponent-model", 1428 P2_CLI_STDIO_WRITE_FLUSHES_COMPONENT, 1429 ])?; 1430 Ok(()) 1431 } 1432 1433 #[test] 1434 fn p2_cli_no_tcp() -> Result<()> { 1435 let output = super::run_wasmtime_for_output( 1436 &[ 1437 "-Wcomponent-model", 1438 // Turn on network but turn off TCP 1439 "-Sinherit-network,tcp=no", 1440 P2_CLI_NO_TCP_COMPONENT, 1441 ], 1442 None, 1443 )?; 1444 println!("{}", String::from_utf8_lossy(&output.stderr)); 1445 assert!(output.status.success()); 1446 Ok(()) 1447 } 1448 1449 #[test] 1450 fn p2_cli_no_udp() -> Result<()> { 1451 let output = super::run_wasmtime_for_output( 1452 &[ 1453 "-Wcomponent-model", 1454 // Turn on network but turn off UDP 1455 "-Sinherit-network,udp=no", 1456 P2_CLI_NO_UDP_COMPONENT, 1457 ], 1458 None, 1459 )?; 1460 println!("{}", String::from_utf8_lossy(&output.stderr)); 1461 assert!(output.status.success()); 1462 Ok(()) 1463 } 1464 1465 #[test] 1466 fn p2_cli_no_ip_name_lookup() -> Result<()> { 1467 let output = super::run_wasmtime_for_output( 1468 &[ 1469 "-Wcomponent-model", 1470 // Turn on network but ensure name lookup is disabled 1471 "-Sinherit-network,allow-ip-name-lookup=no", 1472 P2_CLI_NO_IP_NAME_LOOKUP_COMPONENT, 1473 ], 1474 None, 1475 )?; 1476 println!("{}", String::from_utf8_lossy(&output.stderr)); 1477 assert!(output.status.success()); 1478 Ok(()) 1479 } 1480 1481 #[test] 1482 fn p2_cli_sleep() -> Result<()> { 1483 run_wasmtime(&["run", P2_CLI_SLEEP])?; 1484 run_wasmtime(&["run", P2_CLI_SLEEP_COMPONENT])?; 1485 Ok(()) 1486 } 1487 1488 #[test] 1489 fn p2_cli_sleep_forever() -> Result<()> { 1490 for timeout in [ 1491 // Tests still pass when we race with going to sleep. 1492 "-Wtimeout=1ns", 1493 // Tests pass when we wait till the Wasm has (likely) gone to sleep. 1494 "-Wtimeout=250ms", 1495 ] { 1496 let e = run_wasmtime(&["run", timeout, P2_CLI_SLEEP_FOREVER]).unwrap_err(); 1497 let e = e.to_string(); 1498 println!("Got error: {e}"); 1499 assert!(e.contains("interrupt")); 1500 1501 let e = run_wasmtime(&["run", timeout, P2_CLI_SLEEP_FOREVER_COMPONENT]).unwrap_err(); 1502 let e = e.to_string(); 1503 println!("Got error: {e}"); 1504 assert!(e.contains("interrupt")); 1505 } 1506 1507 Ok(()) 1508 } 1509 1510 /// Helper structure to manage an invocation of `wasmtime serve` 1511 struct WasmtimeServe { 1512 child: Option<Child>, 1513 stdout: Option<JoinHandle<io::Result<Vec<u8>>>>, 1514 stderr: Option<JoinHandle<io::Result<Vec<u8>>>>, 1515 addr: SocketAddr, 1516 shutdown_addr: SocketAddr, 1517 } 1518 1519 impl WasmtimeServe { 1520 /// Creates a new server which will serve the wasm component pointed to 1521 /// by `wasm`. 1522 /// 1523 /// A `configure` callback is provided to specify how `wasmtime serve` 1524 /// will be invoked and configure arguments such as headers. 1525 fn new(wasm: &str, configure: impl FnOnce(&mut Command)) -> Result<WasmtimeServe> { 1526 // Spawn `wasmtime serve` on port 0 which will randomly assign it a 1527 // port. 1528 let mut cmd = super::get_wasmtime_command()?; 1529 cmd.arg("serve").arg("--addr=127.0.0.1:0").arg(wasm); 1530 configure(&mut cmd); 1531 Self::spawn(&mut cmd) 1532 } 1533 1534 fn spawn(cmd: &mut Command) -> Result<WasmtimeServe> { 1535 cmd.arg("--shutdown-addr=127.0.0.1:0"); 1536 cmd.stdin(Stdio::null()); 1537 cmd.stdout(Stdio::piped()); 1538 cmd.stderr(Stdio::piped()); 1539 let mut child = cmd.spawn()?; 1540 1541 // Read the first few lines of stderr which will say which address 1542 // it's listening on. The first line is the shutdown line (with 1543 // `--shutdown-addr`) and the second is what `--addr` was bound to. 1544 // This is done to figure out what `:0` was bound to in the child 1545 // process. 1546 let mut line = String::new(); 1547 let mut stderr = BufReader::new(child.stderr.take().unwrap()); 1548 let mut read_addr_from_line = |prefix: &str| -> Result<SocketAddr> { 1549 stderr.read_line(&mut line)?; 1550 1551 if !line.starts_with(prefix) { 1552 bail!("input line `{line}` didn't start with `{prefix}`"); 1553 } 1554 match line.find("127.0.0.1").and_then(|addr_start| { 1555 let addr = &line[addr_start..]; 1556 let addr_end = addr.find("/")?; 1557 addr[..addr_end].parse().ok() 1558 }) { 1559 Some(addr) => { 1560 line.truncate(0); 1561 Ok(addr) 1562 } 1563 None => bail!("failed to address from: {line}"), 1564 } 1565 }; 1566 let shutdown_addr = read_addr_from_line("Listening for shutdown"); 1567 let addr = read_addr_from_line("Serving HTTP on"); 1568 let (shutdown_addr, addr) = match (shutdown_addr, addr) { 1569 (Ok(a), Ok(b)) => (a, b), 1570 // If either failed kill the child and otherwise try to shepherd 1571 // along any contextual information we have. 1572 (Err(a), _) | (_, Err(a)) => { 1573 child.kill()?; 1574 child.wait()?; 1575 stderr.read_to_string(&mut line)?; 1576 return Err(a.context(line)); 1577 } 1578 }; 1579 let mut stdout = child.stdout.take().unwrap(); 1580 Ok(WasmtimeServe { 1581 stdout: Some(thread::spawn(move || { 1582 let mut dst = Vec::new(); 1583 stdout.read_to_end(&mut dst).map(|_| dst) 1584 })), 1585 1586 stderr: Some(thread::spawn(move || { 1587 let mut dst = Vec::new(); 1588 stderr.read_to_end(&mut dst).map(|_| dst) 1589 })), 1590 1591 child: Some(child), 1592 addr, 1593 shutdown_addr, 1594 }) 1595 } 1596 1597 /// Completes this server gracefully by printing the output on failure. 1598 fn finish(mut self) -> Result<(String, String)> { 1599 self._finish() 1600 } 1601 1602 fn _finish(&mut self) -> Result<(String, String)> { 1603 let mut child = self.child.take().unwrap(); 1604 1605 // If the child process has already exited, then great! Otherwise 1606 // the server is still running and it shouldn't be possible to exit 1607 // until a shutdown signal is sent, so do that here. Make a TCP 1608 // connection to the shutdown port which is used as a shutdown 1609 // signal. 1610 if child.try_wait()?.is_none() { 1611 std::net::TcpStream::connect(&self.shutdown_addr) 1612 .context("failed to initiate graceful shutdown")?; 1613 } 1614 1615 // Regardless of whether we just shut the server down or whether it 1616 // was already shut down (e.g. panicked or similar), wait for the 1617 // result here. The result should succeed (e.g. 0 exit status), and 1618 // if it did then the stdout/stderr are the caller's problem. 1619 let mut output = child.wait_with_output()?; 1620 output.stdout = self.stdout.take().unwrap().join().unwrap()?; 1621 output.stderr = self.stderr.take().unwrap().join().unwrap()?; 1622 if !output.status.success() { 1623 bail!("child failed {output:?}"); 1624 } 1625 1626 Ok(( 1627 String::from_utf8_lossy(&output.stdout).into_owned(), 1628 String::from_utf8_lossy(&output.stderr).into_owned(), 1629 )) 1630 } 1631 1632 /// Send a request to this server and wait for the response. 1633 async fn send_request(&self, req: http::Request<String>) -> Result<http::Response<String>> { 1634 let (mut send, conn_task) = self.start_requests().await?; 1635 1636 let response = Self::send_request_with(&mut send, req).await?; 1637 drop(send); 1638 1639 conn_task.await??; 1640 1641 Ok(response) 1642 } 1643 1644 async fn send_request_with( 1645 send: &mut hyper::client::conn::http1::SendRequest<String>, 1646 req: http::Request<String>, 1647 ) -> Result<http::Response<String>> { 1648 let response = send 1649 .send_request(req) 1650 .await 1651 .context("error sending request")?; 1652 1653 let (parts, body) = response.into_parts(); 1654 1655 let body = body.collect().await.context("failed to read body")?; 1656 assert!(body.trailers().is_none()); 1657 let body = std::str::from_utf8(&body.to_bytes())?.to_string(); 1658 1659 Ok(http::Response::from_parts(parts, body)) 1660 } 1661 1662 async fn start_requests( 1663 &self, 1664 ) -> Result<( 1665 hyper::client::conn::http1::SendRequest<String>, 1666 tokio::task::JoinHandle<hyper::Result<()>>, 1667 )> { 1668 let tcp = TcpStream::connect(&self.addr) 1669 .await 1670 .context("failed to connect")?; 1671 let tcp = wasmtime_wasi_http::io::TokioIo::new(tcp); 1672 let (send, conn) = hyper::client::conn::http1::handshake(tcp) 1673 .await 1674 .context("failed http handshake")?; 1675 Ok((send, tokio::task::spawn(conn))) 1676 } 1677 } 1678 1679 // Don't leave child processes running by accident so kill the child process 1680 // if our server goes away. 1681 impl Drop for WasmtimeServe { 1682 fn drop(&mut self) { 1683 match &mut self.child { 1684 Some(child) => match child.kill() { 1685 Ok(()) => {} 1686 Err(e) => { 1687 eprintln!("failed to kill child process {e}"); 1688 return; 1689 } 1690 }, 1691 None => return, 1692 } 1693 match self._finish() { 1694 Ok((stdout, stderr)) => { 1695 if !stdout.is_empty() { 1696 println!("server stdout:\n{stdout}"); 1697 } 1698 if !stderr.is_empty() { 1699 println!("server stderr:\n{stderr}"); 1700 } 1701 } 1702 Err(e) => println!("failed to wait for child or read stdio: {e}"), 1703 } 1704 } 1705 } 1706 1707 #[tokio::test] 1708 async fn p2_cli_serve_echo_env() -> Result<()> { 1709 let server = WasmtimeServe::new(P2_CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| { 1710 cmd.arg("--env=FOO=bar"); 1711 cmd.arg("--env=BAR"); 1712 cmd.arg("-Scli"); 1713 cmd.env_remove("BAR"); 1714 })?; 1715 1716 let foo_env = server 1717 .send_request( 1718 hyper::Request::builder() 1719 .uri("http://localhost/") 1720 .header("env", "FOO") 1721 .body(String::new()) 1722 .context("failed to make request")?, 1723 ) 1724 .await?; 1725 1726 assert!(foo_env.status().is_success()); 1727 assert!(foo_env.body().is_empty()); 1728 let headers = foo_env.headers(); 1729 assert_eq!(headers.get("env"), Some(&HeaderValue::from_static("bar"))); 1730 1731 let bar_env = server 1732 .send_request( 1733 hyper::Request::builder() 1734 .uri("http://localhost/") 1735 .header("env", "BAR") 1736 .body(String::new()) 1737 .context("failed to make request")?, 1738 ) 1739 .await?; 1740 1741 assert!(bar_env.status().is_success()); 1742 assert!(bar_env.body().is_empty()); 1743 let headers = bar_env.headers(); 1744 assert_eq!(headers.get("env"), None); 1745 1746 server.finish()?; 1747 Ok(()) 1748 } 1749 1750 #[tokio::test] 1751 async fn p2_cli_serve_outgoing_body_config() -> Result<()> { 1752 let server = WasmtimeServe::new(P2_CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| { 1753 cmd.arg("-Scli"); 1754 cmd.arg("-Shttp-outgoing-body-buffer-chunks=2"); 1755 cmd.arg("-Shttp-outgoing-body-chunk-size=1024"); 1756 })?; 1757 1758 let resp = server 1759 .send_request( 1760 hyper::Request::builder() 1761 .uri("http://localhost/") 1762 .header("env", "FOO") 1763 .body(String::new()) 1764 .context("failed to make request")?, 1765 ) 1766 .await?; 1767 1768 assert!(resp.status().is_success()); 1769 1770 server.finish()?; 1771 Ok(()) 1772 } 1773 1774 #[tokio::test] 1775 #[ignore] // TODO: printing stderr in the child and killing the child at the 1776 // end of this test race so the stderr may be present or not. Need 1777 // to implement a more graceful shutdown routine for `wasmtime 1778 // serve`. 1779 async fn p2_cli_serve_respect_pooling_options() -> Result<()> { 1780 let server = WasmtimeServe::new(P2_CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| { 1781 cmd.arg("-Opooling-total-memories=0").arg("-Scli"); 1782 })?; 1783 1784 let result = server 1785 .send_request( 1786 hyper::Request::builder() 1787 .uri("http://localhost/") 1788 .header("env", "FOO") 1789 .body(String::new()) 1790 .context("failed to make request")?, 1791 ) 1792 .await; 1793 assert!(result.is_err()); 1794 let (_, stderr) = server.finish()?; 1795 assert!( 1796 stderr.contains("maximum concurrent memory limit of 0 reached"), 1797 "bad stderr: {stderr}", 1798 ); 1799 Ok(()) 1800 } 1801 1802 #[test] 1803 fn p2_cli_large_env() -> Result<()> { 1804 for wasm in [P2_CLI_LARGE_ENV, P2_CLI_LARGE_ENV_COMPONENT] { 1805 println!("run {wasm:?}"); 1806 let mut cmd = get_wasmtime_command()?; 1807 cmd.arg("run").arg("-Sinherit-env").arg(wasm); 1808 1809 let debug_cmd = format!("{cmd:?}"); 1810 for i in 0..512 { 1811 let var = format!("KEY{i}"); 1812 let val = (0..1024).map(|_| 'x').collect::<String>(); 1813 cmd.env(&var, &val); 1814 } 1815 let output = cmd.output()?; 1816 if !output.status.success() { 1817 bail!( 1818 "Failed to execute wasmtime with: {debug_cmd}\n{}", 1819 String::from_utf8_lossy(&output.stderr) 1820 ); 1821 } 1822 } 1823 Ok(()) 1824 } 1825 1826 #[tokio::test] 1827 async fn p2_cli_serve_only_one_process_allowed() -> Result<()> { 1828 let wasm = P2_CLI_SERVE_ECHO_ENV_COMPONENT; 1829 let server = WasmtimeServe::new(wasm, |cmd| { 1830 cmd.arg("-Scli"); 1831 })?; 1832 1833 let err = WasmtimeServe::spawn( 1834 super::get_wasmtime_command()? 1835 .arg("serve") 1836 .arg("-Scli") 1837 .arg(format!("--addr={}", server.addr)) 1838 .arg(wasm), 1839 ) 1840 .err() 1841 .expect("server spawn should have failed but it succeeded"); 1842 drop(server); 1843 1844 let err = format!("{err:?}"); 1845 println!("{err}"); 1846 assert!(err.contains("os error")); 1847 Ok(()) 1848 } 1849 1850 // Technically this test is a little racy. This binds port 0 to acquire a 1851 // random port, issues a single request to this port, but then kills this 1852 // server while the request is still processing. The port is then rebound 1853 // in the next process while it technically could be stolen by another 1854 // process. 1855 #[tokio::test] 1856 async fn p2_cli_serve_quick_rebind_allowed() -> Result<()> { 1857 let wasm = P2_CLI_SERVE_ECHO_ENV_COMPONENT; 1858 let server = WasmtimeServe::new(wasm, |cmd| { 1859 cmd.arg("-Scli"); 1860 })?; 1861 let addr = server.addr; 1862 1863 // Start up a `send` and `conn_task` which represents a connection to 1864 // this server. 1865 let (mut send, conn_task) = server.start_requests().await?; 1866 let _ = send 1867 .send_request( 1868 hyper::Request::builder() 1869 .uri("http://localhost/") 1870 .header("env", "FOO") 1871 .body(String::new()) 1872 .context("failed to make request")?, 1873 ) 1874 .await; 1875 1876 // ... once a response has been received (or at least the status 1877 // code/headers) then kill the server. THis is done while `conn_task` 1878 // and `send` are still alive so we're guaranteed that the other side 1879 // got a request (we got a response) and our connection is still open. 1880 // 1881 // This forces the address/port into the `TIME_WAIT` state. The rebind 1882 // below in the next process will fail if `SO_REUSEADDR` isn't set. 1883 drop(server); 1884 drop(send); 1885 let _ = conn_task.await; 1886 1887 // If this is successfully bound then we'll create `WasmtimeServe` 1888 // which reads off the first line of output to know which address was 1889 // bound. 1890 let _server2 = WasmtimeServe::spawn( 1891 super::get_wasmtime_command()? 1892 .arg("serve") 1893 .arg("-Scli") 1894 .arg(format!("--addr={addr}")) 1895 .arg(wasm), 1896 )?; 1897 1898 Ok(()) 1899 } 1900 1901 #[tokio::test] 1902 async fn p2_cli_serve_with_print() -> Result<()> { 1903 let server = WasmtimeServe::new(P2_CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| { 1904 cmd.arg("-Scli"); 1905 })?; 1906 1907 for _ in 0..2 { 1908 let resp = server 1909 .send_request( 1910 hyper::Request::builder() 1911 .uri("http://localhost/") 1912 .body(String::new()) 1913 .context("failed to make request")?, 1914 ) 1915 .await?; 1916 assert!(resp.status().is_success()); 1917 } 1918 1919 let (out, err) = server.finish()?; 1920 assert_eq!( 1921 out, 1922 "\ 1923 stdout [0] :: this is half a print to stdout 1924 stdout [0] :: \n\ 1925 stdout [0] :: after empty 1926 stdout [1] :: this is half a print to stdout 1927 stdout [1] :: \n\ 1928 stdout [1] :: after empty 1929 " 1930 ); 1931 assert!( 1932 err.contains( 1933 "\ 1934 stderr [0] :: this is half a print to stderr 1935 stderr [0] :: \n\ 1936 stderr [0] :: after empty 1937 stderr [0] :: start a print 1234 1938 stderr [1] :: this is half a print to stderr 1939 stderr [1] :: \n\ 1940 stderr [1] :: after empty 1941 stderr [1] :: start a print 1234 1942 " 1943 ), 1944 "bad stderr: {err}" 1945 ); 1946 1947 Ok(()) 1948 } 1949 1950 #[tokio::test] 1951 async fn p2_cli_serve_with_print_no_prefix() -> Result<()> { 1952 let server = WasmtimeServe::new(P2_CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| { 1953 cmd.arg("-Scli"); 1954 cmd.arg("--no-logging-prefix"); 1955 })?; 1956 1957 for _ in 0..2 { 1958 let resp = server 1959 .send_request( 1960 hyper::Request::builder() 1961 .uri("http://localhost/") 1962 .body(String::new()) 1963 .context("failed to make request")?, 1964 ) 1965 .await?; 1966 assert!(resp.status().is_success()); 1967 } 1968 1969 let (out, err) = server.finish()?; 1970 assert_eq!( 1971 out, 1972 "\ 1973 this is half a print to stdout 1974 \n\ 1975 after empty 1976 this is half a print to stdout 1977 \n\ 1978 after empty 1979 " 1980 ); 1981 assert!( 1982 err.contains( 1983 "\ 1984 this is half a print to stderr 1985 \n\ 1986 after empty 1987 start a print 1234 1988 this is half a print to stderr 1989 \n\ 1990 after empty 1991 start a print 1234 1992 " 1993 ), 1994 "bad stderr {err}", 1995 ); 1996 1997 Ok(()) 1998 } 1999 2000 #[tokio::test] 2001 async fn p2_cli_serve_authority_and_scheme() -> Result<()> { 2002 let server = WasmtimeServe::new(P2_CLI_SERVE_AUTHORITY_AND_SCHEME_COMPONENT, |cmd| { 2003 cmd.arg("-Scli"); 2004 })?; 2005 2006 let resp = server 2007 .send_request( 2008 hyper::Request::builder() 2009 .uri("/") 2010 .header("Host", "localhost") 2011 .body(String::new()) 2012 .context("failed to make request")?, 2013 ) 2014 .await?; 2015 assert!(resp.status().is_success()); 2016 2017 let resp = server 2018 .send_request( 2019 hyper::Request::builder() 2020 .method("CONNECT") 2021 .uri("http://localhost/") 2022 .body(String::new()) 2023 .context("failed to make request")?, 2024 ) 2025 .await?; 2026 assert!(resp.status().is_success()); 2027 2028 Ok(()) 2029 } 2030 2031 #[test] 2032 fn p2_cli_argv0() -> Result<()> { 2033 run_wasmtime(&["run", "--argv0=a", P2_CLI_ARGV0, "a"])?; 2034 run_wasmtime(&["run", "--argv0=b", P2_CLI_ARGV0_COMPONENT, "b"])?; 2035 run_wasmtime(&["run", "--argv0=foo.wasm", P2_CLI_ARGV0, "foo.wasm"])?; 2036 Ok(()) 2037 } 2038 2039 #[tokio::test] 2040 async fn p2_cli_serve_config() -> Result<()> { 2041 let server = WasmtimeServe::new(P2_CLI_SERVE_CONFIG_COMPONENT, |cmd| { 2042 cmd.arg("-Scli"); 2043 cmd.arg("-Sconfig"); 2044 cmd.arg("-Sconfig-var=hello=world"); 2045 })?; 2046 2047 let resp = server 2048 .send_request( 2049 hyper::Request::builder() 2050 .uri("http://localhost/") 2051 .body(String::new()) 2052 .context("failed to make request")?, 2053 ) 2054 .await?; 2055 2056 assert!(resp.status().is_success()); 2057 assert_eq!(resp.body(), "world"); 2058 Ok(()) 2059 } 2060 2061 #[test] 2062 fn p2_cli_config() -> Result<()> { 2063 run_wasmtime(&[ 2064 "run", 2065 "-Sconfig", 2066 "-Sconfig-var=hello=world", 2067 CONFIG_GET_COMPONENT, 2068 ])?; 2069 Ok(()) 2070 } 2071 2072 #[tokio::test] 2073 async fn p2_cli_serve_keyvalue() -> Result<()> { 2074 let server = WasmtimeServe::new(P2_CLI_SERVE_KEYVALUE_COMPONENT, |cmd| { 2075 cmd.arg("-Scli"); 2076 cmd.arg("-Skeyvalue"); 2077 cmd.arg("-Skeyvalue-in-memory-data=hello=world"); 2078 })?; 2079 2080 let resp = server 2081 .send_request( 2082 hyper::Request::builder() 2083 .uri("http://localhost/") 2084 .body(String::new()) 2085 .context("failed to make request")?, 2086 ) 2087 .await?; 2088 2089 assert!(resp.status().is_success()); 2090 assert_eq!(resp.body(), "world"); 2091 Ok(()) 2092 } 2093 2094 #[test] 2095 fn p2_cli_keyvalue() -> Result<()> { 2096 run_wasmtime(&[ 2097 "run", 2098 "-Skeyvalue", 2099 "-Skeyvalue-in-memory-data=atomics_key=5", 2100 KEYVALUE_MAIN_COMPONENT, 2101 ])?; 2102 Ok(()) 2103 } 2104 2105 #[test] 2106 fn p2_cli_multiple_preopens() -> Result<()> { 2107 run_wasmtime(&[ 2108 "run", 2109 "--dir=/::/a", 2110 "--dir=/::/b", 2111 "--dir=/::/c", 2112 P2_CLI_MULTIPLE_PREOPENS_COMPONENT, 2113 ])?; 2114 Ok(()) 2115 } 2116 2117 async fn p2_cli_serve_guest_never_invoked_set(wasm: &str) -> Result<()> { 2118 let server = WasmtimeServe::new(wasm, |cmd| { 2119 cmd.arg("-Scli"); 2120 })?; 2121 2122 for _ in 0..2 { 2123 let res = server 2124 .send_request( 2125 hyper::Request::builder() 2126 .uri("http://localhost/") 2127 .body(String::new()) 2128 .context("failed to make request")?, 2129 ) 2130 .await 2131 .expect("got response from wasmtime"); 2132 assert_eq!(res.status(), http::StatusCode::INTERNAL_SERVER_ERROR); 2133 } 2134 2135 let (stdout, stderr) = server.finish()?; 2136 println!("stdout: {stdout}"); 2137 println!("stderr: {stderr}"); 2138 assert!(stderr.contains("guest never invoked `response-outparam::set` method")); 2139 assert!(!stderr.contains("panicked")); 2140 Ok(()) 2141 } 2142 2143 #[tokio::test] 2144 async fn p2_cli_serve_return_before_set() -> Result<()> { 2145 p2_cli_serve_guest_never_invoked_set(P2_CLI_SERVE_RETURN_BEFORE_SET_COMPONENT).await 2146 } 2147 2148 #[tokio::test] 2149 async fn p2_cli_serve_trap_before_set() -> Result<()> { 2150 p2_cli_serve_guest_never_invoked_set(P2_CLI_SERVE_TRAP_BEFORE_SET_COMPONENT).await 2151 } 2152 2153 #[test] 2154 fn p3_cli_hello_stdout() -> Result<()> { 2155 let output = run_wasmtime(&[ 2156 "run", 2157 "-Wcomponent-model-async", 2158 "-Sp3", 2159 P3_CLI_HELLO_STDOUT_COMPONENT, 2160 ]); 2161 if cfg!(feature = "component-model-async") { 2162 let output = output?; 2163 assert_eq!(output, "hello, world\n"); 2164 } else { 2165 assert!(output.is_err()); 2166 } 2167 Ok(()) 2168 } 2169 2170 #[test] 2171 fn p2_cli_hello_stdout_invoke() -> Result<()> { 2172 println!("{P2_CLI_HELLO_STDOUT_COMPONENT}"); 2173 let output = run_wasmtime(&[ 2174 "run", 2175 "-Wcomponent-model", 2176 "--invoke", 2177 "run()", 2178 P2_CLI_HELLO_STDOUT_COMPONENT, 2179 ])?; 2180 // First this component prints "hello, world", then the invoke 2181 // result is printed as "ok". 2182 assert_eq!(output, "hello, world\nok\n"); 2183 Ok(()) 2184 } 2185 2186 #[test] 2187 fn p3_cli_hello_stdout_post_return() -> Result<()> { 2188 let output = run_wasmtime(&[ 2189 "run", 2190 "-Wcomponent-model-async", 2191 "-Sp3", 2192 P3_CLI_HELLO_STDOUT_POST_RETURN_COMPONENT, 2193 ]); 2194 if cfg!(feature = "component-model-async") { 2195 let output = output?; 2196 assert_eq!(output, "hello, world\n"); 2197 } else { 2198 assert!(output.is_err()); 2199 } 2200 Ok(()) 2201 } 2202 2203 #[test] 2204 fn p3_cli_hello_stdout_post_return_invoke() -> Result<()> { 2205 let output = run_wasmtime(&[ 2206 "run", 2207 "-Wcomponent-model-async", 2208 "-Sp3", 2209 "--invoke", 2210 "run()", 2211 P3_CLI_HELLO_STDOUT_POST_RETURN_COMPONENT, 2212 ]); 2213 if cfg!(feature = "component-model-async") { 2214 let output = output?; 2215 assert_eq!(output, "hello, world\nok\n"); 2216 } else { 2217 assert!(output.is_err()); 2218 } 2219 Ok(()) 2220 } 2221 2222 #[test] 2223 fn p2_random_limits() -> Result<()> { 2224 println!("{P2_RANDOM_COMPONENT}"); 2225 2226 // By default, p2_random.rs fits within the random limits - always 2227 // asks for 256 bytes. 2228 let output = run_wasmtime(&["run", P2_RANDOM_COMPONENT])?; 2229 assert_eq!(output, ""); 2230 2231 // Lowering limit to 255 bytes should produce a trap in the first 2232 // call, which goes by way of the wasip1 adapter: 2233 let output = run_wasmtime(&["run", "-Smax-random-size=255", P2_RANDOM_COMPONENT]) 2234 .err() 2235 .ok_or_else(|| format_err!("execution with max-random-size=255 should trap"))?; 2236 let output = format!("{output:?}"); 2237 assert!( 2238 output.contains("lib_generated::random_get"), 2239 "expected error stack frames to contain 'wasip1::lib_generated::random_get'. Got:\n{output}" 2240 ); 2241 assert!( 2242 output.contains("requested len 256 exceeds limit 255"), 2243 "expected error stack frames to contain 'requested len 256 exceeds limit 255'. Got:\n{output}" 2244 ); 2245 2246 // Lowering limit to 255 bytes and setting environment variable for 2247 // the first call to only request 255 bytes should be OK, and then the 2248 // program produce a trap in the second call, which calls the wasip2 2249 // random import directly: 2250 let output = run_wasmtime(&[ 2251 "run", 2252 "-Smax-random-size=255", 2253 "--env", 2254 "TEST_P1_RANDOM_LEN=255", 2255 P2_RANDOM_COMPONENT, 2256 ]) 2257 .err() 2258 .ok_or_else(|| format_err!("execution with max-random-size=255 should trap"))?; 2259 let output = format!("{output:?}"); 2260 assert!( 2261 output.contains("random::random::get_random_bytes"), 2262 "expected error stack frames to contain 'wasi::random::random::get_random_bytes'. Got:\n{output}" 2263 ); 2264 assert!( 2265 output.contains("requested len 256 exceeds limit 255"), 2266 "expected error stack frames to contain 'requested len 256 exceeds limit 255'. Got:\n{output}" 2267 ); 2268 2269 // Lowering limit to 255 bytes and setting environment variable for 2270 // the first and second calls to be at the limit should produce a trap 2271 // in the third call, which calls the wasip2 insecure random import: 2272 let output = run_wasmtime(&[ 2273 "run", 2274 "-Smax-random-size=255", 2275 "--env", 2276 "TEST_P1_RANDOM_LEN=255", 2277 "--env", 2278 "TEST_P2_RANDOM_LEN=255", 2279 P2_RANDOM_COMPONENT, 2280 ]) 2281 .err() 2282 .ok_or_else(|| format_err!("execution with max-random-size=255 should trap"))?; 2283 let output = format!("{output:?}"); 2284 assert!( 2285 output.contains("random::insecure::get_insecure_random_bytes"), 2286 "expected error stack frames to contain 'wasi::random::insecure::get_insecure_random_bytes'. Got:\n{output}" 2287 ); 2288 assert!( 2289 output.contains("requested len 256 exceeds limit 255"), 2290 "expected error stack frames to contain 'requested len 256 exceeds limit 255'. Got:\n{output}" 2291 ); 2292 2293 // Lowering limit to 255 bytes and setting environment variable for 2294 // the first and second calls to be under the limit should trap in the 2295 // third call, which calls the wasip2 insecure random import: 2296 let output = run_wasmtime(&[ 2297 "run", 2298 "-Smax-random-size=255", 2299 "--env", 2300 "TEST_P1_RANDOM_LEN=255", 2301 "--env", 2302 "TEST_P2_RANDOM_LEN=255", 2303 "--env", 2304 "TEST_P2_INSECURE_RANDOM_LEN=255", 2305 P2_RANDOM_COMPONENT, 2306 ]) 2307 .context("setting all calls to be equal to the limit should pass")?; 2308 assert_eq!(output, ""); 2309 2310 Ok(()) 2311 } 2312 2313 #[test] 2314 fn p2_cli_http_headers() -> Result<()> { 2315 for test in ["append", "append-empty", "append-same", "append-same-empty"] { 2316 let err = run_wasmtime(&[ 2317 "run", 2318 "-Shttp", 2319 "-Smax-http-fields-size=1048576", 2320 P2_CLI_HTTP_HEADERS_COMPONENT, 2321 test, 2322 ]) 2323 .unwrap_err(); 2324 assert!( 2325 err.to_string() 2326 .contains("Field size limit 1048576 exceeded") 2327 || err.to_string().contains("max size reached"), 2328 "bad error message: {err:?}" 2329 ); 2330 2331 // gated by default too 2332 let err = 2333 run_wasmtime(&["run", "-Shttp", P2_CLI_HTTP_HEADERS_COMPONENT, test]).unwrap_err(); 2334 assert!( 2335 err.to_string().contains("Field size limit"), 2336 "bad error message: {err:?}" 2337 ); 2338 } 2339 2340 // With an extremely large limit Wasmtime still shouldn't panic. 2341 let err = run_wasmtime(&[ 2342 "run", 2343 "-Shttp", 2344 &format!("-Smax-http-fields-size={}", 1 << 30), 2345 P2_CLI_HTTP_HEADERS_COMPONENT, 2346 "append", 2347 ]) 2348 .unwrap_err(); 2349 assert!( 2350 err.to_string().contains("max size reached"), 2351 "bad error message: {err:?}" 2352 ); 2353 Ok(()) 2354 } 2355 2356 #[test] 2357 #[cfg_attr(not(feature = "component-model-async"), ignore)] 2358 fn p2_cli_invoke_async() -> Result<()> { 2359 let output = run_wasmtime(&[ 2360 "run", 2361 "-Wcomponent-model-async", 2362 "--invoke", 2363 "echo(\"hello?\")", 2364 P2_CLI_INVOKE_ASYNC_COMPONENT, 2365 ])?; 2366 assert_eq!(output, "\"hello?\"\n"); 2367 Ok(()) 2368 } 2369 2370 fn run_much_stdout(component: &str, extra_flags: &[&str]) -> Result<()> { 2371 let total_write_size = 1 << 18; 2372 let expected = iter::repeat('a').take(total_write_size).collect::<String>(); 2373 2374 for i in 10..15 { 2375 let string = iter::repeat('a').take(1 << i).collect::<String>(); 2376 let times = (total_write_size >> i).to_string(); 2377 println!("writing {} bytes {times} times", string.len()); 2378 2379 let mut args = Vec::new(); 2380 args.push("run"); 2381 args.extend_from_slice(extra_flags); 2382 args.push(component); 2383 args.push(&string); 2384 args.push(×); 2385 let output = run_wasmtime(&args)?; 2386 println!( 2387 "expected {} bytes, got {} bytes", 2388 expected.len(), 2389 output.len() 2390 ); 2391 assert!(output == expected); 2392 } 2393 2394 Ok(()) 2395 } 2396 2397 #[test] 2398 fn p1_cli_much_stdout() -> Result<()> { 2399 run_much_stdout(P1_CLI_MUCH_STDOUT_COMPONENT, &[]) 2400 } 2401 2402 #[test] 2403 fn p2_cli_much_stdout() -> Result<()> { 2404 run_much_stdout(P2_CLI_MUCH_STDOUT_COMPONENT, &[]) 2405 } 2406 2407 #[test] 2408 #[cfg_attr(not(feature = "component-model-async"), ignore)] 2409 fn p3_cli_much_stdout() -> Result<()> { 2410 run_much_stdout( 2411 P3_CLI_MUCH_STDOUT_COMPONENT, 2412 &["-Wcomponent-model-async", "-Sp3"], 2413 ) 2414 } 2415 2416 #[test] 2417 fn p2_cli_max_resources() -> Result<()> { 2418 let err = run_wasmtime(&["run", "-Smax-resources=50", P2_CLI_MAX_RESOURCES_COMPONENT]) 2419 .unwrap_err(); 2420 assert!( 2421 err.to_string().contains("resource table has no free keys"), 2422 "bad error message: {err}" 2423 ); 2424 run_wasmtime(&["run", "-Smax-resources=200", P2_CLI_MAX_RESOURCES_COMPONENT])?; 2425 Ok(()) 2426 } 2427 2428 #[tokio::test] 2429 #[cfg_attr(not(feature = "component-model-async"), ignore)] 2430 async fn p3_cli_serve_hello_world() -> Result<()> { 2431 cli_serve_hello_world(P3_CLI_SERVE_HELLO_WORLD_COMPONENT, 1, 1, |cmd| { 2432 cmd.arg("-Wcomponent-model-async"); 2433 cmd.arg("-Sp3,cli"); 2434 }) 2435 .await 2436 } 2437 2438 #[tokio::test] 2439 async fn p2_cli_serve_hello_world() -> Result<()> { 2440 cli_serve_hello_world(P2_CLI_SERVE_HELLO_WORLD_COMPONENT, 1, 1, |cmd| { 2441 cmd.arg("-Scli"); 2442 }) 2443 .await 2444 } 2445 2446 const CONNECTION_COUNT_MANY: usize = 20; 2447 const REQUESTS_PER_CONNECTION_MANY: usize = 5; 2448 2449 #[tokio::test] 2450 #[cfg_attr(not(feature = "component-model-async"), ignore)] 2451 async fn p3_cli_serve_hello_world_many() -> Result<()> { 2452 cli_serve_hello_world( 2453 P3_CLI_SERVE_HELLO_WORLD_COMPONENT, 2454 CONNECTION_COUNT_MANY, 2455 REQUESTS_PER_CONNECTION_MANY, 2456 |cmd| { 2457 cmd.arg("-Wcomponent-model-async"); 2458 cmd.arg("-Sp3,cli"); 2459 }, 2460 ) 2461 .await 2462 } 2463 2464 #[tokio::test] 2465 #[cfg_attr(not(feature = "component-model-async"), ignore)] 2466 async fn p3_cli_serve_hello_world_many_no_reuse() -> Result<()> { 2467 cli_serve_hello_world( 2468 P3_CLI_SERVE_HELLO_WORLD_COMPONENT, 2469 CONNECTION_COUNT_MANY, 2470 REQUESTS_PER_CONNECTION_MANY, 2471 |cmd| { 2472 cmd.arg("-Wcomponent-model-async"); 2473 cmd.arg("-Sp3,cli"); 2474 cmd.arg("--max-instance-reuse-count=1"); 2475 }, 2476 ) 2477 .await 2478 } 2479 2480 #[tokio::test] 2481 #[cfg_attr(not(feature = "component-model-async"), ignore)] 2482 async fn p3_cli_serve_hello_world_many_no_concurrent_reuse() -> Result<()> { 2483 cli_serve_hello_world( 2484 P3_CLI_SERVE_HELLO_WORLD_COMPONENT, 2485 CONNECTION_COUNT_MANY, 2486 REQUESTS_PER_CONNECTION_MANY, 2487 |cmd| { 2488 cmd.arg("-Wcomponent-model-async"); 2489 cmd.arg("-Sp3,cli"); 2490 cmd.arg("--max-instance-concurrent-reuse-count=1"); 2491 }, 2492 ) 2493 .await 2494 } 2495 2496 #[tokio::test] 2497 async fn p2_cli_serve_hello_world_many() -> Result<()> { 2498 cli_serve_hello_world( 2499 P2_CLI_SERVE_HELLO_WORLD_COMPONENT, 2500 CONNECTION_COUNT_MANY, 2501 REQUESTS_PER_CONNECTION_MANY, 2502 |cmd| { 2503 cmd.arg("-Scli"); 2504 }, 2505 ) 2506 .await 2507 } 2508 2509 #[tokio::test] 2510 async fn p2_cli_serve_hello_world_many_with_reuse() -> Result<()> { 2511 cli_serve_hello_world( 2512 P2_CLI_SERVE_HELLO_WORLD_COMPONENT, 2513 CONNECTION_COUNT_MANY, 2514 REQUESTS_PER_CONNECTION_MANY, 2515 |cmd| { 2516 cmd.arg("-Scli"); 2517 cmd.arg("--max-instance-reuse-count=128"); 2518 }, 2519 ) 2520 .await 2521 } 2522 2523 async fn cli_serve_hello_world( 2524 component: &str, 2525 connection_count: usize, 2526 requests_per_connection: usize, 2527 configure: impl FnOnce(&mut Command), 2528 ) -> Result<()> { 2529 let server = std::sync::Arc::new(WasmtimeServe::new(component, configure)?); 2530 2531 let tasks = (0..connection_count).map({ 2532 let server = server.clone(); 2533 move |_| { 2534 tokio::task::spawn({ 2535 let server = server.clone(); 2536 async move { 2537 let (mut send, conn_task) = server.start_requests().await?; 2538 2539 for _ in 0..requests_per_connection { 2540 let result = WasmtimeServe::send_request_with( 2541 &mut send, 2542 hyper::Request::builder() 2543 .uri("http://localhost/") 2544 .body(String::new()) 2545 .context("failed to make request")?, 2546 ) 2547 .await?; 2548 2549 assert!(result.status().is_success()); 2550 assert_eq!(result.body(), "Hello, WASI!"); 2551 } 2552 2553 drop(send); 2554 2555 conn_task.await??; 2556 2557 wasmtime::error::Ok(()) 2558 } 2559 }) 2560 } 2561 }); 2562 2563 for task in tasks { 2564 task.await??; 2565 } 2566 2567 std::sync::Arc::into_inner(server).unwrap().finish()?; 2568 Ok(()) 2569 } 2570 2571 #[tokio::test] 2572 async fn p2_cli_serve_sleep() -> Result<()> { 2573 cli_serve_sleep(P2_CLI_SERVE_SLEEP_COMPONENT, 1, 1, |cmd| { 2574 cmd.arg("-Scli"); 2575 }) 2576 .await 2577 } 2578 2579 #[tokio::test] 2580 #[cfg_attr(not(feature = "component-model-async"), ignore)] 2581 async fn p3_cli_serve_sleep() -> Result<()> { 2582 cli_serve_sleep(P3_CLI_SERVE_SLEEP_COMPONENT, 1, 1, |cmd| { 2583 cmd.arg("-Wcomponent-model-async"); 2584 cmd.arg("-Sp3,cli"); 2585 }) 2586 .await 2587 } 2588 2589 #[tokio::test] 2590 async fn p2_cli_serve_sleep_many() -> Result<()> { 2591 cli_serve_sleep( 2592 P2_CLI_SERVE_SLEEP_COMPONENT, 2593 CONNECTION_COUNT_MANY, 2594 REQUESTS_PER_CONNECTION_MANY, 2595 |cmd| { 2596 cmd.arg("-Scli"); 2597 }, 2598 ) 2599 .await 2600 } 2601 2602 #[tokio::test] 2603 #[cfg_attr(not(feature = "component-model-async"), ignore)] 2604 async fn p3_cli_serve_sleep_many() -> Result<()> { 2605 cli_serve_sleep( 2606 P3_CLI_SERVE_SLEEP_COMPONENT, 2607 CONNECTION_COUNT_MANY, 2608 REQUESTS_PER_CONNECTION_MANY, 2609 |cmd| { 2610 cmd.arg("-Wcomponent-model-async"); 2611 cmd.arg("-Sp3,cli"); 2612 }, 2613 ) 2614 .await 2615 } 2616 2617 async fn cli_serve_sleep( 2618 component: &str, 2619 connection_count: usize, 2620 requests_per_connection: usize, 2621 configure: impl FnOnce(&mut Command), 2622 ) -> Result<()> { 2623 let server = std::sync::Arc::new(WasmtimeServe::new(component, move |cmd| { 2624 configure(cmd); 2625 cmd.arg("-Wtimeout=100us"); 2626 })?); 2627 2628 let tasks = (0..connection_count).map({ 2629 let server = server.clone(); 2630 move |_| { 2631 tokio::task::spawn({ 2632 let server = server.clone(); 2633 async move { 2634 let (mut send, conn_task) = server.start_requests().await?; 2635 2636 for _ in 0..requests_per_connection { 2637 let result = WasmtimeServe::send_request_with( 2638 &mut send, 2639 hyper::Request::builder() 2640 .uri("http://localhost/") 2641 .body(String::new()) 2642 .context("failed to make request")?, 2643 ) 2644 .await?; 2645 2646 assert!(result.status().is_server_error()); 2647 } 2648 2649 drop(send); 2650 2651 conn_task.await??; 2652 2653 wasmtime::error::Ok(()) 2654 } 2655 }) 2656 } 2657 }); 2658 2659 for task in tasks { 2660 task.await??; 2661 } 2662 2663 let (stdout, stderr) = std::sync::Arc::into_inner(server).unwrap().finish()?; 2664 assert_eq!(stdout, ""); 2665 assert!(stderr.contains("guest timed out"), "bad stderr: {stderr}"); 2666 Ok(()) 2667 } 2668 2669 #[test] 2670 fn p2_cli_many_resources() -> Result<()> { 2671 let err = run_wasmtime(&["run", P2_CLI_MANY_RESOURCES_COMPONENT]).unwrap_err(); 2672 assert!( 2673 err.to_string().contains("resource table has no free keys"), 2674 "bad error message: {err}" 2675 ); 2676 Ok(()) 2677 } 2678 2679 #[test] 2680 fn p1_cli_hostcall_fuel() -> Result<()> { 2681 let dir = tempfile::tempdir()?; 2682 run_wasmtime(&[ 2683 "run", 2684 &format!("--dir={}::.", dir.path().to_str().unwrap()), 2685 "-Shostcall-fuel=1000", 2686 P1_CLI_HOSTCALL_FUEL, 2687 ])?; 2688 Ok(()) 2689 } 2690 2691 #[test] 2692 fn p2_cli_hostcall_fuel() -> Result<()> { 2693 enum Exit { 2694 Ok, 2695 NoFuel, 2696 TooManyZeroes, 2697 BufferTooLarge, 2698 } 2699 2700 let dir = tempfile::tempdir()?; 2701 let file = dir.path().join("1mb"); 2702 std::fs::write(&file, vec![0; 1024 * 1024])?; 2703 for (arg, exit) in [ 2704 ("poll", Exit::NoFuel), 2705 ("read", Exit::Ok), 2706 ("write", Exit::NoFuel), 2707 ("mkdir", Exit::NoFuel), 2708 ("write-stream", Exit::NoFuel), 2709 ("write-stream-blocking", Exit::NoFuel), 2710 ("resolve", Exit::NoFuel), 2711 ("udp-send-many", Exit::NoFuel), 2712 ("udp-send-big", Exit::NoFuel), 2713 ("write-zeroes", Exit::TooManyZeroes), 2714 ("write-stream-buffer-too-large", Exit::BufferTooLarge), 2715 ("write-zeroes-buffer-too-large", Exit::BufferTooLarge), 2716 ("read-file-big", Exit::Ok), 2717 ("read-tcp-big", Exit::Ok), 2718 ] { 2719 println!("test: {arg}"); 2720 let result = run_wasmtime(&[ 2721 "run", 2722 "-Shostcall-fuel=5000", 2723 "-Sinherit-network", 2724 &format!("--dir={}::.", dir.path().to_str().unwrap()), 2725 P2_CLI_HOSTCALL_FUEL_COMPONENT, 2726 arg, 2727 ]); 2728 2729 match exit { 2730 Exit::Ok => { 2731 result.unwrap(); 2732 } 2733 Exit::NoFuel => { 2734 let err = result.unwrap_err(); 2735 assert!( 2736 err.to_string() 2737 .contains("fuel allocated for hostcalls has been exhausted"), 2738 "bad error message: {err}" 2739 ); 2740 } 2741 Exit::TooManyZeroes => { 2742 let err = result.unwrap_err(); 2743 assert!( 2744 err.to_string() 2745 .contains("cannot write more zeroes than `check_write` allows"), 2746 "bad error message: {err}" 2747 ); 2748 } 2749 Exit::BufferTooLarge => { 2750 let err = result.unwrap_err(); 2751 assert!( 2752 err.to_string().contains("Buffer too large"), 2753 "bad error message: {err}" 2754 ); 2755 } 2756 } 2757 } 2758 Ok(()) 2759 } 2760 } 2761 2762 #[test] 2763 fn settings_command() -> Result<()> { 2764 // Skip this test on platforms that Cranelift doesn't support. 2765 if cranelift_native::builder().is_err() { 2766 return Ok(()); 2767 } 2768 let output = run_wasmtime(&["settings"])?; 2769 assert!(output.contains("Cranelift settings for target")); 2770 Ok(()) 2771 } 2772 2773 #[cfg(target_arch = "x86_64")] 2774 #[test] 2775 fn profile_with_vtune() -> Result<()> { 2776 if !is_vtune_available() { 2777 println!("> `vtune` is not available on the system path; skipping test"); 2778 return Ok(()); 2779 } 2780 2781 let mut bin = Command::new("vtune"); 2782 bin.args(&[ 2783 // Configure VTune... 2784 "-verbose", 2785 "-collect", 2786 "hotspots", 2787 "-user-data-dir", 2788 &std::env::temp_dir().to_string_lossy(), 2789 // ...then run Wasmtime with profiling enabled: 2790 get_wasmtime_path(), 2791 "--profile=vtune", 2792 "tests/all/cli_tests/simple.wat", 2793 ]); 2794 2795 println!("> executing: {bin:?}"); 2796 let output = bin.output()?; 2797 2798 let stdout = String::from_utf8_lossy(&output.stdout); 2799 let stderr = String::from_utf8_lossy(&output.stderr); 2800 println!("> stdout:\n{stdout}"); 2801 println!("> stderr:\n{stderr}"); 2802 2803 assert!(output.status.success()); 2804 assert!(!stderr.contains("Error")); 2805 assert!(stdout.contains("CPU Time")); 2806 Ok(()) 2807 } 2808 2809 #[cfg(target_arch = "x86_64")] 2810 fn is_vtune_available() -> bool { 2811 Command::new("vtune").arg("-version").output().is_ok() 2812 } 2813 2814 #[test] 2815 fn profile_guest() -> Result<()> { 2816 let tmpdir = std::env::temp_dir(); 2817 let dir = tmpdir.to_string_lossy(); 2818 2819 let output = run_wasmtime_for_output( 2820 &[ 2821 &format!("--profile=guest,{dir}/out.json"), 2822 "--env", 2823 "FOO=bar", 2824 "tests/all/cli_tests/print_env.wat", 2825 ], 2826 None, 2827 )?; 2828 2829 assert!(output.status.success()); 2830 let stdout = String::from_utf8_lossy(&output.stdout); 2831 let stderr = String::from_utf8_lossy(&output.stderr); 2832 println!("> stdout:\n{stdout}"); 2833 println!("> stderr:\n{stderr}"); 2834 assert!(!stderr.contains("Error")); 2835 let out_json = std::fs::read_to_string(format!("{dir}/out.json")).unwrap(); 2836 println!("> out.json:\n{out_json}"); 2837 Ok(()) 2838 } 2839 2840 #[test] 2841 fn unreachable_without_wasi() -> Result<()> { 2842 let output = run_wasmtime_for_output( 2843 &[ 2844 "-Scli=n", 2845 "-Ccache=n", 2846 "tests/all/cli_tests/unreachable.wat", 2847 ], 2848 None, 2849 )?; 2850 2851 assert_ne!(output.stderr, b""); 2852 assert_eq!(output.stdout, b""); 2853 assert_trap_code(&output.status); 2854 Ok(()) 2855 } 2856 2857 #[test] 2858 fn config_cli_flag() -> Result<()> { 2859 let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; 2860 2861 // Test some valid TOML values 2862 let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts(); 2863 cfg.write_all( 2864 br#" 2865 [optimize] 2866 opt-level = 2 2867 signals-based-traps = false 2868 2869 [codegen] 2870 collector = "null" 2871 2872 [debug] 2873 address-map = true 2874 2875 [wasm] 2876 max-wasm-stack = 65536 2877 2878 [wasi] 2879 cli = true 2880 "#, 2881 )?; 2882 let output = run_wasmtime(&[ 2883 "run", 2884 "--config", 2885 cfg_path.to_str().unwrap(), 2886 "--invoke", 2887 "get_f64", 2888 wasm.path().to_str().unwrap(), 2889 ])?; 2890 assert_eq!(output, "100\n"); 2891 2892 // Make sure CLI flags overrides TOML values 2893 let output = run_wasmtime(&[ 2894 "run", 2895 "--config", 2896 cfg_path.to_str().unwrap(), 2897 "--invoke", 2898 "get_f64", 2899 "-W", 2900 "max-wasm-stack=0", // should override TOML value 65536 specified above and execution should fail 2901 wasm.path().to_str().unwrap(), 2902 ]); 2903 assert!( 2904 output 2905 .as_ref() 2906 .unwrap_err() 2907 .to_string() 2908 .contains("max_wasm_stack size cannot be zero"), 2909 "'{output:?}' did not contain expected error message", 2910 ); 2911 2912 // Test invalid TOML key 2913 let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts(); 2914 cfg.write_all( 2915 br#" 2916 [optimize] 2917 this-key-does-not-exist = true 2918 "#, 2919 )?; 2920 let output = run_wasmtime(&[ 2921 "run", 2922 "--config", 2923 cfg_path.to_str().unwrap(), 2924 wasm.path().to_str().unwrap(), 2925 ]); 2926 assert!( 2927 output 2928 .as_ref() 2929 .unwrap_err() 2930 .to_string() 2931 .contains("unknown field `this-key-does-not-exist`"), 2932 "'{output:?}' did not contain expected error message" 2933 ); 2934 2935 // Test invalid TOML table 2936 let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts(); 2937 cfg.write_all( 2938 br#" 2939 [invalid_table] 2940 "#, 2941 )?; 2942 let output = run_wasmtime(&[ 2943 "run", 2944 "--config", 2945 cfg_path.to_str().unwrap(), 2946 wasm.path().to_str().unwrap(), 2947 ]); 2948 assert!( 2949 output 2950 .as_ref() 2951 .unwrap_err() 2952 .to_string() 2953 .contains("unknown field `invalid_table`, expected one of `optimize`, `codegen`, `debug`, `wasm`, `wasi`"), 2954 "'{output:?}' did not contain expected error message", 2955 ); 2956 2957 Ok(()) 2958 } 2959 2960 #[test] 2961 fn invalid_subcommand() -> Result<()> { 2962 let output = run_wasmtime_for_output(&["invalid-subcommand"], None)?; 2963 dbg!(&output); 2964 assert!(!output.status.success()); 2965 assert!(String::from_utf8_lossy(&output.stderr).contains("invalid-subcommand")); 2966 Ok(()) 2967 } 2968 2969 #[test] 2970 fn numeric_args() -> Result<()> { 2971 let wasm = build_wasm("tests/all/cli_tests/numeric_args.wat")?; 2972 // Test decimal i32 2973 let output = run_wasmtime_for_output( 2974 &[ 2975 "run", 2976 "--invoke", 2977 "i32_test", 2978 wasm.path().to_str().unwrap(), 2979 "42", 2980 ], 2981 None, 2982 )?; 2983 assert_eq!(output.status.success(), true); 2984 assert_eq!(output.stdout, b"42\n"); 2985 // Test hexadecimal i32 with lowercase prefix 2986 let output = run_wasmtime_for_output( 2987 &[ 2988 "run", 2989 "--invoke", 2990 "i32_test", 2991 wasm.path().to_str().unwrap(), 2992 "0x2A", 2993 ], 2994 None, 2995 )?; 2996 assert_eq!(output.status.success(), true); 2997 assert_eq!(output.stdout, b"42\n"); 2998 // Test hexadecimal i32 with uppercase prefix 2999 let output = run_wasmtime_for_output( 3000 &[ 3001 "run", 3002 "--invoke", 3003 "i32_test", 3004 wasm.path().to_str().unwrap(), 3005 "0X2a", 3006 ], 3007 None, 3008 )?; 3009 assert_eq!(output.status.success(), true); 3010 assert_eq!(output.stdout, b"42\n"); 3011 // Test that non-prefixed hex strings are not interpreted as hex 3012 let output = run_wasmtime_for_output( 3013 &[ 3014 "run", 3015 "--invoke", 3016 "i32_test", 3017 wasm.path().to_str().unwrap(), 3018 "ff", 3019 ], 3020 None, 3021 )?; 3022 assert!(!output.status.success()); // Should fail as "ff" is not a valid decimal number 3023 3024 // Test decimal i64 3025 let output = run_wasmtime_for_output( 3026 &[ 3027 "run", 3028 "--invoke", 3029 "i64_test", 3030 wasm.path().to_str().unwrap(), 3031 "42", 3032 ], 3033 None, 3034 )?; 3035 assert_eq!(output.status.success(), true); 3036 assert_eq!(output.stdout, b"42\n"); 3037 // Test hexadecimal i64 3038 let output = run_wasmtime_for_output( 3039 &[ 3040 "run", 3041 "--invoke", 3042 "i64_test", 3043 wasm.path().to_str().unwrap(), 3044 "0x2A", 3045 ], 3046 None, 3047 )?; 3048 assert_eq!(output.status.success(), true); 3049 assert_eq!(output.stdout, b"42\n"); 3050 Ok(()) 3051 } 3052 3053 #[test] 3054 fn compilation_logs() -> Result<()> { 3055 let temp = tempfile::NamedTempFile::new()?; 3056 let output = get_wasmtime_command()? 3057 .args(&[ 3058 "compile", 3059 "-Wgc", 3060 "tests/all/cli_tests/issue-10353.wat", 3061 "--output", 3062 &temp.path().display().to_string(), 3063 ]) 3064 .env("WASMTIME_LOG", "trace") 3065 .env("RUST_BACKTRACE", "1") 3066 .output()?; 3067 if !output.status.success() { 3068 println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); 3069 println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 3070 panic!("wasmtime compilation failed when logs requested"); 3071 } 3072 Ok(()) 3073 } 3074 3075 #[test] 3076 fn big_table_in_pooling_allocator() -> Result<()> { 3077 // Works by default 3078 run_wasmtime(&["tests/all/cli_tests/big_table.wat"])?; 3079 3080 // Does not work by default in the pooling allocator, and the error message 3081 // should mention something about the pooling allocator. 3082 let output = run_wasmtime_for_output( 3083 &["-Opooling-allocator", "tests/all/cli_tests/big_table.wat"], 3084 None, 3085 )?; 3086 assert!(!output.status.success()); 3087 println!("{}", String::from_utf8_lossy(&output.stderr)); 3088 assert!(String::from_utf8_lossy(&output.stderr).contains("pooling allocator")); 3089 3090 // Does work with `-Wmax-table-elements` 3091 run_wasmtime(&[ 3092 "-Opooling-allocator", 3093 "-Wmax-table-elements=25000", 3094 "tests/all/cli_tests/big_table.wat", 3095 ])?; 3096 // Also works with `-Opooling-table-elements` 3097 run_wasmtime(&[ 3098 "-Opooling-allocator", 3099 "-Opooling-table-elements=25000", 3100 "tests/all/cli_tests/big_table.wat", 3101 ])?; 3102 Ok(()) 3103 } 3104 3105 fn wizen(args: &[&str], wat: &str) -> Result<Output> { 3106 let mut cmd = get_wasmtime_command()?; 3107 cmd.arg("wizer").args(args).arg("-"); 3108 cmd.stdin(Stdio::piped()) 3109 .stdout(Stdio::piped()) 3110 .stderr(Stdio::piped()); 3111 let mut child = cmd.spawn()?; 3112 let mut stdin = child.stdin.take().unwrap(); 3113 stdin.write_all(wat.as_bytes())?; 3114 drop(stdin); 3115 3116 let output = child.wait_with_output()?; 3117 if !output.status.success() { 3118 println!( 3119 "Failed to execute wasmtime wizer with: {cmd:?}\n{}", 3120 String::from_utf8_lossy(&output.stderr) 3121 ); 3122 } 3123 Ok(output) 3124 } 3125 3126 #[test] 3127 fn wizer_no_imports_by_default() -> Result<()> { 3128 let result = wizen( 3129 &[], 3130 r#"(module 3131 (func (export "wizer-initialize")) 3132 )"#, 3133 )?; 3134 assert!(result.status.success()); 3135 3136 let result = wizen( 3137 &[], 3138 r#"(module 3139 (import "foo" "bar" (func)) 3140 (func (export "wizer-initialize")) 3141 )"#, 3142 )?; 3143 assert!(!result.status.success()); 3144 3145 let result = wizen( 3146 &[], 3147 r#"(module 3148 (import "wasi_snapshot_preview1" "fd_write" (func (param i32 i32 i32 i32) (result i32))) 3149 (func (export "wizer-initialize")) 3150 )"#, 3151 )?; 3152 assert!(!result.status.success()); 3153 3154 let result = wizen( 3155 &["-Scli"], 3156 r#"(module 3157 (import "wasi_snapshot_preview1" "fd_write" (func (param i32 i32 i32 i32) (result i32))) 3158 (func (export "wizer-initialize")) 3159 )"#, 3160 )?; 3161 assert!(result.status.success()); 3162 3163 Ok(()) 3164 } 3165 3166 #[test] 3167 fn wizer_components() -> Result<()> { 3168 let result = wizen( 3169 &[], 3170 r#" 3171 (component 3172 (core module $a 3173 (global (mut i32) (i32.const 0)) 3174 (func (export "init") 3175 i32.const 100 3176 global.set 0) 3177 ) 3178 (core instance $a (instantiate $a)) 3179 (func (export "wizer-initialize") (canon lift (core func $a "init"))) 3180 ) 3181 "#, 3182 )?; 3183 assert!(result.status.success()); 3184 3185 let component_with_wasi = r#" 3186 (component 3187 (import "wasi:cli/[email protected]" (instance 3188 (export "get-arguments" (func (result (list string)))) 3189 )) 3190 (core module $a 3191 (global (mut i32) (i32.const 0)) 3192 (func (export "init") 3193 i32.const 100 3194 global.set 0) 3195 ) 3196 (core instance $a (instantiate $a)) 3197 (func (export "wizer-initialize") (canon lift (core func $a "init"))) 3198 ) 3199 "#; 3200 3201 let result = wizen(&[], component_with_wasi)?; 3202 assert!(!result.status.success()); 3203 let result = wizen(&["-Scli"], component_with_wasi)?; 3204 assert!(result.status.success()); 3205 3206 Ok(()) 3207 } 3208