1 #![cfg(not(miri))] 2 3 use anyhow::{bail, Result}; 4 use std::fs::File; 5 use std::io::Write; 6 use std::path::{Path, PathBuf}; 7 use std::process::{Command, ExitStatus, Output, Stdio}; 8 use tempfile::{NamedTempFile, TempDir}; 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 // Figure out the Wasmtime binary from the current executable. 24 let bin = get_wasmtime_path()?; 25 let runner = std::env::vars() 26 .filter(|(k, _v)| k.starts_with("CARGO_TARGET") && k.ends_with("RUNNER")) 27 .next(); 28 29 // If we're running tests with a "runner" then we might be doing something 30 // like cross-emulation, so spin up the emulator rather than the tests 31 // itself, which may not be natively executable. 32 let mut cmd = if let Some((_, runner)) = runner { 33 let mut parts = runner.split_whitespace(); 34 let mut cmd = Command::new(parts.next().unwrap()); 35 for arg in parts { 36 cmd.arg(arg); 37 } 38 cmd.arg(&bin); 39 cmd 40 } else { 41 Command::new(&bin) 42 }; 43 44 // Ignore this if it's specified in the environment to allow tests to run in 45 // "default mode" by default. 46 cmd.env_remove("WASMTIME_NEW_CLI"); 47 48 Ok(cmd) 49 } 50 51 fn get_wasmtime_path() -> Result<PathBuf> { 52 let mut path = std::env::current_exe()?; 53 path.pop(); // chop off the file name 54 path.pop(); // chop off `deps` 55 path.push("wasmtime"); 56 Ok(path) 57 } 58 59 // Run the wasmtime CLI with the provided args and, if it succeeds, return 60 // the standard output in a `String`. 61 pub fn run_wasmtime(args: &[&str]) -> Result<String> { 62 let output = run_wasmtime_for_output(args, None)?; 63 if !output.status.success() { 64 bail!( 65 "Failed to execute wasmtime with: {:?}\n{}", 66 args, 67 String::from_utf8_lossy(&output.stderr) 68 ); 69 } 70 Ok(String::from_utf8(output.stdout).unwrap()) 71 } 72 73 fn build_wasm(wat_path: impl AsRef<Path>) -> Result<NamedTempFile> { 74 let mut wasm_file = NamedTempFile::new()?; 75 let wasm = wat::parse_file(wat_path)?; 76 wasm_file.write(&wasm)?; 77 Ok(wasm_file) 78 } 79 80 // Very basic use case: compile binary wasm file and run specific function with arguments. 81 #[test] 82 fn run_wasmtime_simple() -> Result<()> { 83 let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; 84 run_wasmtime(&[ 85 "run", 86 "--invoke", 87 "simple", 88 "-Ccache=n", 89 wasm.path().to_str().unwrap(), 90 "4", 91 ])?; 92 Ok(()) 93 } 94 95 // Wasmtime shall fail when not enough arguments were provided. 96 #[test] 97 fn run_wasmtime_simple_fail_no_args() -> Result<()> { 98 let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; 99 assert!( 100 run_wasmtime(&[ 101 "run", 102 "-Ccache=n", 103 "--invoke", 104 "simple", 105 wasm.path().to_str().unwrap(), 106 ]) 107 .is_err(), 108 "shall fail" 109 ); 110 Ok(()) 111 } 112 113 #[test] 114 fn run_coredump_smoketest() -> Result<()> { 115 let wasm = build_wasm("tests/all/cli_tests/coredump_smoketest.wat")?; 116 let coredump_file = NamedTempFile::new()?; 117 let coredump_arg = format!("-Dcoredump={}", coredump_file.path().display()); 118 let err = run_wasmtime(&[ 119 "run", 120 "--invoke", 121 "a", 122 "-Ccache=n", 123 &coredump_arg, 124 wasm.path().to_str().unwrap(), 125 ]) 126 .unwrap_err(); 127 assert!(err.to_string().contains(&format!( 128 "core dumped at {}", 129 coredump_file.path().display() 130 ))); 131 Ok(()) 132 } 133 134 // Running simple wat 135 #[test] 136 fn run_wasmtime_simple_wat() -> Result<()> { 137 let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; 138 run_wasmtime(&[ 139 "run", 140 "--invoke", 141 "simple", 142 "-Ccache=n", 143 wasm.path().to_str().unwrap(), 144 "4", 145 ])?; 146 assert_eq!( 147 run_wasmtime(&[ 148 "run", 149 "--invoke", 150 "get_f32", 151 "-Ccache=n", 152 wasm.path().to_str().unwrap(), 153 ])?, 154 "100\n" 155 ); 156 assert_eq!( 157 run_wasmtime(&[ 158 "run", 159 "--invoke", 160 "get_f64", 161 "-Ccache=n", 162 wasm.path().to_str().unwrap(), 163 ])?, 164 "100\n" 165 ); 166 Ok(()) 167 } 168 169 // Running a wat that traps. 170 #[test] 171 fn run_wasmtime_unreachable_wat() -> Result<()> { 172 let wasm = build_wasm("tests/all/cli_tests/unreachable.wat")?; 173 let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?; 174 175 assert_ne!(output.stderr, b""); 176 assert_eq!(output.stdout, b""); 177 178 assert_trap_code(&output.status); 179 Ok(()) 180 } 181 182 fn assert_trap_code(status: &ExitStatus) { 183 let code = status 184 .code() 185 .expect("wasmtime process should exit normally"); 186 187 // Test for the specific error code Wasmtime uses to indicate a trap return. 188 #[cfg(unix)] 189 assert_eq!(code, 128 + libc::SIGABRT); 190 #[cfg(windows)] 191 assert_eq!(code, 3); 192 } 193 194 // Run a simple WASI hello world, snapshot0 edition. 195 #[test] 196 fn hello_wasi_snapshot0() -> Result<()> { 197 let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?; 198 for preview2 in ["-Spreview2=n", "-Spreview2=y"] { 199 let stdout = run_wasmtime(&["-Ccache=n", preview2, wasm.path().to_str().unwrap()])?; 200 assert_eq!(stdout, "Hello, world!\n"); 201 } 202 Ok(()) 203 } 204 205 // Run a simple WASI hello world, snapshot1 edition. 206 #[test] 207 fn hello_wasi_snapshot1() -> Result<()> { 208 let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?; 209 let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?; 210 assert_eq!(stdout, "Hello, world!\n"); 211 Ok(()) 212 } 213 214 #[test] 215 fn timeout_in_start() -> Result<()> { 216 let wasm = build_wasm("tests/all/cli_tests/iloop-start.wat")?; 217 let output = run_wasmtime_for_output( 218 &[ 219 "run", 220 "-Wtimeout=1ms", 221 "-Ccache=n", 222 wasm.path().to_str().unwrap(), 223 ], 224 None, 225 )?; 226 assert!(!output.status.success()); 227 assert_eq!(output.stdout, b""); 228 let stderr = String::from_utf8_lossy(&output.stderr); 229 assert!( 230 stderr.contains("wasm trap: interrupt"), 231 "bad stderr: {stderr}" 232 ); 233 Ok(()) 234 } 235 236 #[test] 237 fn timeout_in_invoke() -> Result<()> { 238 let wasm = build_wasm("tests/all/cli_tests/iloop-invoke.wat")?; 239 let output = run_wasmtime_for_output( 240 &[ 241 "run", 242 "-Wtimeout=1ms", 243 "-Ccache=n", 244 wasm.path().to_str().unwrap(), 245 ], 246 None, 247 )?; 248 assert!(!output.status.success()); 249 assert_eq!(output.stdout, b""); 250 let stderr = String::from_utf8_lossy(&output.stderr); 251 assert!( 252 stderr.contains("wasm trap: interrupt"), 253 "bad stderr: {stderr}" 254 ); 255 Ok(()) 256 } 257 258 // Exit with a valid non-zero exit code, snapshot0 edition. 259 #[test] 260 fn exit2_wasi_snapshot0() -> Result<()> { 261 let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot0.wat")?; 262 263 for preview2 in ["-Spreview2=n", "-Spreview2=y"] { 264 let output = run_wasmtime_for_output( 265 &["-Ccache=n", preview2, wasm.path().to_str().unwrap()], 266 None, 267 )?; 268 assert_eq!(output.status.code().unwrap(), 2); 269 } 270 Ok(()) 271 } 272 273 // Exit with a valid non-zero exit code, snapshot1 edition. 274 #[test] 275 fn exit2_wasi_snapshot1() -> Result<()> { 276 let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot1.wat")?; 277 let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?; 278 assert_eq!(output.status.code().unwrap(), 2); 279 Ok(()) 280 } 281 282 // Exit with a valid non-zero exit code, snapshot0 edition. 283 #[test] 284 fn exit125_wasi_snapshot0() -> Result<()> { 285 let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot0.wat")?; 286 for preview2 in ["-Spreview2=n", "-Spreview2=y"] { 287 let output = run_wasmtime_for_output( 288 &["-Ccache=n", preview2, wasm.path().to_str().unwrap()], 289 None, 290 )?; 291 dbg!(&output); 292 assert_eq!(output.status.code().unwrap(), 125); 293 } 294 Ok(()) 295 } 296 297 // Exit with a valid non-zero exit code, snapshot1 edition. 298 #[test] 299 fn exit125_wasi_snapshot1() -> Result<()> { 300 let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot1.wat")?; 301 let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?; 302 assert_eq!(output.status.code().unwrap(), 125); 303 Ok(()) 304 } 305 306 // Exit with an invalid non-zero exit code, snapshot0 edition. 307 #[test] 308 fn exit126_wasi_snapshot0() -> Result<()> { 309 let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot0.wat")?; 310 311 for preview2 in ["-Spreview2=n", "-Spreview2=y"] { 312 let output = run_wasmtime_for_output( 313 &["-Ccache=n", preview2, wasm.path().to_str().unwrap()], 314 None, 315 )?; 316 assert_eq!(output.status.code().unwrap(), 1); 317 assert!(output.stdout.is_empty()); 318 assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status")); 319 } 320 Ok(()) 321 } 322 323 // Exit with an invalid non-zero exit code, snapshot1 edition. 324 #[test] 325 fn exit126_wasi_snapshot1() -> Result<()> { 326 let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot1.wat")?; 327 let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?; 328 assert_eq!(output.status.code().unwrap(), 1); 329 assert!(output.stdout.is_empty()); 330 assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status")); 331 Ok(()) 332 } 333 334 // Run a minimal command program. 335 #[test] 336 fn minimal_command() -> Result<()> { 337 let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?; 338 let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?; 339 assert_eq!(stdout, ""); 340 Ok(()) 341 } 342 343 // Run a minimal reactor program. 344 #[test] 345 fn minimal_reactor() -> Result<()> { 346 let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?; 347 let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?; 348 assert_eq!(stdout, ""); 349 Ok(()) 350 } 351 352 // Attempt to call invoke on a command. 353 #[test] 354 fn command_invoke() -> Result<()> { 355 let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?; 356 run_wasmtime(&[ 357 "run", 358 "--invoke", 359 "_start", 360 "-Ccache=n", 361 wasm.path().to_str().unwrap(), 362 ])?; 363 Ok(()) 364 } 365 366 // Attempt to call invoke on a command. 367 #[test] 368 fn reactor_invoke() -> Result<()> { 369 let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?; 370 run_wasmtime(&[ 371 "run", 372 "--invoke", 373 "_initialize", 374 "-Ccache=n", 375 wasm.path().to_str().unwrap(), 376 ])?; 377 Ok(()) 378 } 379 380 // Run the greeter test, which runs a preloaded reactor and a command. 381 #[test] 382 fn greeter() -> Result<()> { 383 let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?; 384 let stdout = run_wasmtime(&[ 385 "run", 386 "-Ccache=n", 387 "--preload", 388 "reactor=tests/all/cli_tests/greeter_reactor.wat", 389 wasm.path().to_str().unwrap(), 390 ])?; 391 assert_eq!( 392 stdout, 393 "Hello _initialize\nHello _start\nHello greet\nHello done\n" 394 ); 395 Ok(()) 396 } 397 398 // Run the greeter test, but this time preload a command. 399 #[test] 400 fn greeter_preload_command() -> Result<()> { 401 let wasm = build_wasm("tests/all/cli_tests/greeter_reactor.wat")?; 402 let stdout = run_wasmtime(&[ 403 "run", 404 "-Ccache=n", 405 "--preload", 406 "reactor=tests/all/cli_tests/hello_wasi_snapshot1.wat", 407 wasm.path().to_str().unwrap(), 408 ])?; 409 assert_eq!(stdout, "Hello _initialize\n"); 410 Ok(()) 411 } 412 413 // Run the greeter test, which runs a preloaded reactor and a command. 414 #[test] 415 fn greeter_preload_callable_command() -> Result<()> { 416 let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?; 417 let stdout = run_wasmtime(&[ 418 "run", 419 "-Ccache=n", 420 "--preload", 421 "reactor=tests/all/cli_tests/greeter_callable_command.wat", 422 wasm.path().to_str().unwrap(), 423 ])?; 424 assert_eq!(stdout, "Hello _start\nHello callable greet\nHello done\n"); 425 Ok(()) 426 } 427 428 // Ensure successful WASI exit call with FPR saving frames on stack for Windows x64 429 // See https://github.com/bytecodealliance/wasmtime/issues/1967 430 #[test] 431 fn exit_with_saved_fprs() -> Result<()> { 432 let wasm = build_wasm("tests/all/cli_tests/exit_with_saved_fprs.wat")?; 433 let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?; 434 assert_eq!(output.status.code().unwrap(), 0); 435 assert!(output.stdout.is_empty()); 436 Ok(()) 437 } 438 439 #[test] 440 fn run_cwasm() -> Result<()> { 441 let td = TempDir::new()?; 442 let cwasm = td.path().join("foo.cwasm"); 443 let stdout = run_wasmtime(&[ 444 "compile", 445 "tests/all/cli_tests/simple.wat", 446 "-o", 447 cwasm.to_str().unwrap(), 448 ])?; 449 assert_eq!(stdout, ""); 450 let stdout = run_wasmtime(&["run", "--allow-precompiled", cwasm.to_str().unwrap()])?; 451 assert_eq!(stdout, ""); 452 Ok(()) 453 } 454 455 #[cfg(unix)] 456 #[test] 457 fn hello_wasi_snapshot0_from_stdin() -> Result<()> { 458 // Run a simple WASI hello world, snapshot0 edition. 459 // The module is piped from standard input. 460 let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?; 461 for preview2 in ["-Spreview2=n", "-Spreview2=y"] { 462 let stdout = { 463 let path = wasm.path(); 464 let args: &[&str] = &["-Ccache=n", preview2, "-"]; 465 let output = run_wasmtime_for_output(args, Some(path))?; 466 if !output.status.success() { 467 bail!( 468 "Failed to execute wasmtime with: {:?}\n{}", 469 args, 470 String::from_utf8_lossy(&output.stderr) 471 ); 472 } 473 Ok::<_, anyhow::Error>(String::from_utf8(output.stdout).unwrap()) 474 }?; 475 assert_eq!(stdout, "Hello, world!\n"); 476 } 477 Ok(()) 478 } 479 480 #[test] 481 fn specify_env() -> Result<()> { 482 // By default no env is inherited 483 let output = get_wasmtime_command()? 484 .args(&["run", "tests/all/cli_tests/print_env.wat"]) 485 .env("THIS_WILL_NOT", "show up in the output") 486 .output()?; 487 assert!(output.status.success()); 488 assert_eq!(String::from_utf8_lossy(&output.stdout), ""); 489 490 // Specify a single env var 491 let output = get_wasmtime_command()? 492 .args(&[ 493 "run", 494 "--env", 495 "FOO=bar", 496 "tests/all/cli_tests/print_env.wat", 497 ]) 498 .output()?; 499 assert!(output.status.success()); 500 assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n"); 501 502 // Inherit a single env var 503 let output = get_wasmtime_command()? 504 .args(&["run", "--env", "FOO", "tests/all/cli_tests/print_env.wat"]) 505 .env("FOO", "bar") 506 .output()?; 507 assert!(output.status.success()); 508 assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n"); 509 510 // Inherit a nonexistent env var 511 let output = get_wasmtime_command()? 512 .args(&[ 513 "run", 514 "--env", 515 "SURELY_THIS_ENV_VAR_DOES_NOT_EXIST_ANYWHERE_RIGHT", 516 "tests/all/cli_tests/print_env.wat", 517 ]) 518 .output()?; 519 assert!(output.status.success()); 520 521 // Inherit all env vars 522 let output = get_wasmtime_command()? 523 .args(&["run", "-Sinherit-env", "tests/all/cli_tests/print_env.wat"]) 524 .env("FOO", "bar") 525 .output()?; 526 assert!(output.status.success()); 527 let stdout = String::from_utf8_lossy(&output.stdout); 528 assert!(stdout.contains("FOO=bar"), "bad output: {stdout}"); 529 530 Ok(()) 531 } 532 533 #[cfg(unix)] 534 #[test] 535 fn run_cwasm_from_stdin() -> Result<()> { 536 use std::process::Stdio; 537 538 let td = TempDir::new()?; 539 let cwasm = td.path().join("foo.cwasm"); 540 let stdout = run_wasmtime(&[ 541 "compile", 542 "tests/all/cli_tests/simple.wat", 543 "-o", 544 cwasm.to_str().unwrap(), 545 ])?; 546 assert_eq!(stdout, ""); 547 548 // If stdin is literally the file itself then that should work 549 let args: &[&str] = &["run", "--allow-precompiled", "-"]; 550 let output = get_wasmtime_command()? 551 .args(args) 552 .stdin(File::open(&cwasm)?) 553 .output()?; 554 assert!(output.status.success(), "a file as stdin should work"); 555 556 // If stdin is a pipe, that should also work 557 let input = std::fs::read(&cwasm)?; 558 let mut child = get_wasmtime_command()? 559 .args(args) 560 .stdin(Stdio::piped()) 561 .stdout(Stdio::piped()) 562 .stderr(Stdio::piped()) 563 .spawn()?; 564 let mut stdin = child.stdin.take().unwrap(); 565 let t = std::thread::spawn(move || { 566 let _ = stdin.write_all(&input); 567 }); 568 let output = child.wait_with_output()?; 569 assert!(output.status.success()); 570 t.join().unwrap(); 571 Ok(()) 572 } 573 574 #[cfg(feature = "wasi-threads")] 575 #[test] 576 fn run_threads() -> Result<()> { 577 let wasm = build_wasm("tests/all/cli_tests/threads.wat")?; 578 let stdout = run_wasmtime(&[ 579 "run", 580 "-Wthreads", 581 "-Sthreads", 582 "-Ccache=n", 583 wasm.path().to_str().unwrap(), 584 ])?; 585 586 assert!( 587 stdout 588 == "Called _start\n\ 589 Running wasi_thread_start\n\ 590 Running wasi_thread_start\n\ 591 Running wasi_thread_start\n\ 592 Done\n" 593 ); 594 Ok(()) 595 } 596 597 #[cfg(feature = "wasi-threads")] 598 #[test] 599 fn run_simple_with_wasi_threads() -> Result<()> { 600 // We expect to be able to run Wasm modules that do not have correct 601 // wasi-thread entry points or imported shared memory as long as no threads 602 // are spawned. 603 let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; 604 let stdout = run_wasmtime(&[ 605 "run", 606 "-Wthreads", 607 "-Sthreads", 608 "-Ccache=n", 609 "--invoke", 610 "simple", 611 wasm.path().to_str().unwrap(), 612 "4", 613 ])?; 614 assert_eq!(stdout, "4\n"); 615 Ok(()) 616 } 617 618 #[test] 619 fn wasm_flags() -> Result<()> { 620 // Any argument after the wasm module should be interpreted as for the 621 // command itself 622 let stdout = run_wasmtime(&[ 623 "run", 624 "--", 625 "tests/all/cli_tests/print-arguments.wat", 626 "--argument", 627 "-for", 628 "the", 629 "command", 630 ])?; 631 assert_eq!( 632 stdout, 633 "\ 634 print-arguments.wat\n\ 635 --argument\n\ 636 -for\n\ 637 the\n\ 638 command\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(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "--"])?; 650 assert_eq!( 651 stdout, 652 "\ 653 print-arguments.wat\n\ 654 --\n\ 655 " 656 ); 657 let stdout = run_wasmtime(&[ 658 "run", 659 "--", 660 "tests/all/cli_tests/print-arguments.wat", 661 "--", 662 "--", 663 "-a", 664 "b", 665 ])?; 666 assert_eq!( 667 stdout, 668 "\ 669 print-arguments.wat\n\ 670 --\n\ 671 --\n\ 672 -a\n\ 673 b\n\ 674 " 675 ); 676 Ok(()) 677 } 678 679 #[test] 680 fn name_same_as_builtin_command() -> Result<()> { 681 // a bare subcommand shouldn't run successfully 682 let output = get_wasmtime_command()? 683 .current_dir("tests/all/cli_tests") 684 .arg("run") 685 .output()?; 686 assert!(!output.status.success()); 687 688 // a `--` prefix should let everything else get interpreted as a wasm 689 // module and arguments, even if the module has a name like `run` 690 let output = get_wasmtime_command()? 691 .current_dir("tests/all/cli_tests") 692 .arg("--") 693 .arg("run") 694 .output()?; 695 assert!(output.status.success(), "expected success got {output:#?}"); 696 697 // Passing options before the subcommand should work and doesn't require 698 // `--` to disambiguate 699 let output = get_wasmtime_command()? 700 .current_dir("tests/all/cli_tests") 701 .arg("-Ccache=n") 702 .arg("run") 703 .output()?; 704 assert!(output.status.success(), "expected success got {output:#?}"); 705 Ok(()) 706 } 707 708 #[test] 709 #[cfg(unix)] 710 fn run_just_stdin_argument() -> Result<()> { 711 let output = get_wasmtime_command()? 712 .arg("-") 713 .stdin(File::open("tests/all/cli_tests/simple.wat")?) 714 .output()?; 715 assert!(output.status.success()); 716 Ok(()) 717 } 718 719 #[test] 720 fn wasm_flags_without_subcommand() -> Result<()> { 721 let output = get_wasmtime_command()? 722 .current_dir("tests/all/cli_tests/") 723 .arg("print-arguments.wat") 724 .arg("-foo") 725 .arg("bar") 726 .output()?; 727 assert!(output.status.success()); 728 assert_eq!( 729 String::from_utf8_lossy(&output.stdout), 730 "\ 731 print-arguments.wat\n\ 732 -foo\n\ 733 bar\n\ 734 " 735 ); 736 Ok(()) 737 } 738 739 #[test] 740 fn wasi_misaligned_pointer() -> Result<()> { 741 let output = get_wasmtime_command()? 742 .arg("./tests/all/cli_tests/wasi_misaligned_pointer.wat") 743 .output()?; 744 assert!(!output.status.success()); 745 let stderr = String::from_utf8_lossy(&output.stderr); 746 assert!( 747 stderr.contains("Pointer not aligned"), 748 "bad stderr: {stderr}", 749 ); 750 Ok(()) 751 } 752 753 #[test] 754 #[cfg_attr(not(feature = "component-model"), ignore)] 755 fn hello_with_preview2() -> Result<()> { 756 let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?; 757 let stdout = run_wasmtime(&["-Ccache=n", "-Spreview2", wasm.path().to_str().unwrap()])?; 758 assert_eq!(stdout, "Hello, world!\n"); 759 Ok(()) 760 } 761 762 #[test] 763 #[cfg_attr(not(feature = "component-model"), ignore)] 764 fn component_missing_feature() -> Result<()> { 765 let path = "tests/all/cli_tests/empty-component.wat"; 766 let wasm = build_wasm(path)?; 767 let output = get_wasmtime_command()? 768 .arg("-Ccache=n") 769 .arg("-Wcomponent-model=n") 770 .arg(wasm.path()) 771 .output()?; 772 assert!(!output.status.success()); 773 let stderr = String::from_utf8_lossy(&output.stderr); 774 assert!( 775 stderr.contains("cannot execute a component without `--wasm component-model`"), 776 "bad stderr: {stderr}" 777 ); 778 779 // also tests with raw *.wat input 780 let output = get_wasmtime_command()? 781 .arg("-Ccache=n") 782 .arg("-Wcomponent-model=n") 783 .arg(path) 784 .output()?; 785 assert!(!output.status.success()); 786 let stderr = String::from_utf8_lossy(&output.stderr); 787 assert!( 788 stderr.contains("cannot execute a component without `--wasm component-model`"), 789 "bad stderr: {stderr}" 790 ); 791 792 Ok(()) 793 } 794 795 #[test] 796 #[cfg_attr(not(feature = "component-model"), ignore)] 797 fn component_enabled_by_default() -> Result<()> { 798 let path = "tests/all/cli_tests/component-basic.wat"; 799 let wasm = build_wasm(path)?; 800 let output = get_wasmtime_command()? 801 .arg("-Ccache=n") 802 .arg(wasm.path()) 803 .output()?; 804 assert!(output.status.success()); 805 806 // also tests with raw *.wat input 807 let output = get_wasmtime_command()? 808 .arg("-Ccache=n") 809 .arg(path) 810 .output()?; 811 assert!(output.status.success()); 812 813 Ok(()) 814 } 815 816 // If the text format is invalid then the filename should be mentioned in the 817 // error message. 818 #[test] 819 fn bad_text_syntax() -> Result<()> { 820 let output = get_wasmtime_command()? 821 .arg("-Ccache=n") 822 .arg("tests/all/cli_tests/bad-syntax.wat") 823 .output()?; 824 assert!(!output.status.success()); 825 let stderr = String::from_utf8_lossy(&output.stderr); 826 assert!( 827 stderr.contains("--> tests/all/cli_tests/bad-syntax.wat"), 828 "bad stderr: {stderr}" 829 ); 830 Ok(()) 831 } 832 833 #[test] 834 #[cfg_attr(not(feature = "component-model"), ignore)] 835 fn run_basic_component() -> Result<()> { 836 let path = "tests/all/cli_tests/component-basic.wat"; 837 let wasm = build_wasm(path)?; 838 839 // Run both the `*.wasm` binary and the text format 840 run_wasmtime(&[ 841 "-Ccache=n", 842 "-Wcomponent-model", 843 wasm.path().to_str().unwrap(), 844 ])?; 845 run_wasmtime(&["-Ccache=n", "-Wcomponent-model", path])?; 846 847 Ok(()) 848 } 849 850 #[test] 851 #[cfg_attr(not(feature = "component-model"), ignore)] 852 fn run_precompiled_component() -> Result<()> { 853 let td = TempDir::new()?; 854 let cwasm = td.path().join("component-basic.cwasm"); 855 let stdout = run_wasmtime(&[ 856 "compile", 857 "tests/all/cli_tests/component-basic.wat", 858 "-o", 859 cwasm.to_str().unwrap(), 860 "-Wcomponent-model", 861 ])?; 862 assert_eq!(stdout, ""); 863 let stdout = run_wasmtime(&[ 864 "run", 865 "-Wcomponent-model", 866 "--allow-precompiled", 867 cwasm.to_str().unwrap(), 868 ])?; 869 assert_eq!(stdout, ""); 870 871 Ok(()) 872 } 873 874 #[test] 875 fn memory_growth_failure() -> Result<()> { 876 let output = get_wasmtime_command()? 877 .args(&[ 878 "run", 879 "-Wmemory64", 880 "-Wtrap-on-grow-failure", 881 "tests/all/cli_tests/memory-grow-failure.wat", 882 ]) 883 .output()?; 884 assert!(!output.status.success()); 885 let stderr = String::from_utf8_lossy(&output.stderr); 886 assert!( 887 stderr.contains("forcing a memory growth failure to be a trap"), 888 "bad stderr: {stderr}" 889 ); 890 Ok(()) 891 } 892 893 #[test] 894 fn table_growth_failure() -> Result<()> { 895 let output = get_wasmtime_command()? 896 .args(&[ 897 "run", 898 "-Wtrap-on-grow-failure", 899 "tests/all/cli_tests/table-grow-failure.wat", 900 ]) 901 .output()?; 902 assert!(!output.status.success()); 903 let stderr = String::from_utf8_lossy(&output.stderr); 904 assert!( 905 stderr.contains("forcing trap when growing table"), 906 "bad stderr: {stderr}" 907 ); 908 Ok(()) 909 } 910 911 #[test] 912 fn table_growth_failure2() -> Result<()> { 913 let output = get_wasmtime_command()? 914 .args(&[ 915 "run", 916 "-Wtrap-on-grow-failure", 917 "tests/all/cli_tests/table-grow-failure2.wat", 918 ]) 919 .output()?; 920 assert!(!output.status.success()); 921 let stderr = String::from_utf8_lossy(&output.stderr); 922 assert!( 923 stderr.contains("forcing trap when growing table to 4294967296 elements"), 924 "bad stderr: {stderr}" 925 ); 926 Ok(()) 927 } 928 929 #[test] 930 fn option_group_help() -> Result<()> { 931 run_wasmtime(&["run", "-Whelp"])?; 932 run_wasmtime(&["run", "-O", "help"])?; 933 run_wasmtime(&["run", "--codegen", "help"])?; 934 run_wasmtime(&["run", "--debug=help"])?; 935 run_wasmtime(&["run", "-Shelp"])?; 936 run_wasmtime(&["run", "-Whelp-long"])?; 937 Ok(()) 938 } 939 940 #[test] 941 fn option_group_comma_separated() -> Result<()> { 942 run_wasmtime(&[ 943 "run", 944 "-Wrelaxed-simd,simd", 945 "tests/all/cli_tests/simple.wat", 946 ])?; 947 Ok(()) 948 } 949 950 #[test] 951 fn option_group_boolean_parsing() -> Result<()> { 952 run_wasmtime(&["run", "-Wrelaxed-simd", "tests/all/cli_tests/simple.wat"])?; 953 run_wasmtime(&["run", "-Wrelaxed-simd=n", "tests/all/cli_tests/simple.wat"])?; 954 run_wasmtime(&["run", "-Wrelaxed-simd=y", "tests/all/cli_tests/simple.wat"])?; 955 run_wasmtime(&["run", "-Wrelaxed-simd=no", "tests/all/cli_tests/simple.wat"])?; 956 run_wasmtime(&[ 957 "run", 958 "-Wrelaxed-simd=yes", 959 "tests/all/cli_tests/simple.wat", 960 ])?; 961 run_wasmtime(&[ 962 "run", 963 "-Wrelaxed-simd=true", 964 "tests/all/cli_tests/simple.wat", 965 ])?; 966 run_wasmtime(&[ 967 "run", 968 "-Wrelaxed-simd=false", 969 "tests/all/cli_tests/simple.wat", 970 ])?; 971 Ok(()) 972 } 973 974 #[test] 975 fn preview2_stdin() -> Result<()> { 976 let test = "tests/all/cli_tests/count-stdin.wat"; 977 let cmd = || -> Result<_> { 978 let mut cmd = get_wasmtime_command()?; 979 cmd.arg("--invoke=count").arg("-Spreview2").arg(test); 980 Ok(cmd) 981 }; 982 983 // read empty pipe is ok 984 let output = cmd()?.output()?; 985 assert!(output.status.success()); 986 assert_eq!(String::from_utf8_lossy(&output.stdout), "0\n"); 987 988 // read itself is ok 989 let file = File::open(test)?; 990 let size = file.metadata()?.len(); 991 let output = cmd()?.stdin(File::open(test)?).output()?; 992 assert!(output.status.success()); 993 assert_eq!(String::from_utf8_lossy(&output.stdout), format!("{size}\n")); 994 995 // read piped input ok is ok 996 let mut child = cmd()? 997 .stdin(Stdio::piped()) 998 .stdout(Stdio::piped()) 999 .stderr(Stdio::piped()) 1000 .spawn()?; 1001 let mut stdin = child.stdin.take().unwrap(); 1002 std::thread::spawn(move || { 1003 stdin.write_all(b"hello").unwrap(); 1004 }); 1005 let output = child.wait_with_output()?; 1006 assert!(output.status.success()); 1007 assert_eq!(String::from_utf8_lossy(&output.stdout), "5\n"); 1008 1009 let count_up_to = |n: usize| -> Result<_> { 1010 let mut child = get_wasmtime_command()? 1011 .arg("--invoke=count-up-to") 1012 .arg("-Spreview2") 1013 .arg(test) 1014 .arg(n.to_string()) 1015 .stdin(Stdio::piped()) 1016 .stdout(Stdio::piped()) 1017 .stderr(Stdio::piped()) 1018 .spawn()?; 1019 let mut stdin = child.stdin.take().unwrap(); 1020 let t = std::thread::spawn(move || { 1021 let mut written = 0; 1022 let bytes = [0; 64 * 1024]; 1023 loop { 1024 written += match stdin.write(&bytes) { 1025 Ok(n) => n, 1026 Err(_) => break written, 1027 }; 1028 } 1029 }); 1030 let output = child.wait_with_output()?; 1031 assert!(output.status.success()); 1032 let written = t.join().unwrap(); 1033 let read = String::from_utf8_lossy(&output.stdout) 1034 .trim() 1035 .parse::<usize>() 1036 .unwrap(); 1037 // The test reads in 1000 byte chunks so make sure that it doesn't read 1038 // more than 1000 bytes than requested. 1039 assert!(read < n + 1000, "test read too much {read}"); 1040 Ok(written) 1041 }; 1042 1043 // wasmtime shouldn't eat information that the guest never actually tried to 1044 // read. 1045 // 1046 // NB: this may be a bit flaky. Exactly how much we wrote in the above 1047 // helper thread depends on how much the OS buffers for us. For now give 1048 // some some slop and assume that OSes are unlikely to buffer more than 1049 // that. 1050 let slop = 256 * 1024; 1051 for amt in [0, 100, 100_000] { 1052 let written = count_up_to(amt)?; 1053 assert!(written < slop + amt, "wrote too much {written}"); 1054 } 1055 Ok(()) 1056 } 1057 1058 #[test] 1059 fn float_args() -> Result<()> { 1060 let result = run_wasmtime(&[ 1061 "--invoke", 1062 "echo_f32", 1063 "tests/all/cli_tests/simple.wat", 1064 "1.0", 1065 ])?; 1066 assert_eq!(result, "1\n"); 1067 let result = run_wasmtime(&[ 1068 "--invoke", 1069 "echo_f64", 1070 "tests/all/cli_tests/simple.wat", 1071 "1.1", 1072 ])?; 1073 assert_eq!(result, "1.1\n"); 1074 Ok(()) 1075 } 1076 1077 #[test] 1078 fn mpk_without_pooling() -> Result<()> { 1079 let output = get_wasmtime_command()? 1080 .args(&[ 1081 "run", 1082 "-O", 1083 "memory-protection-keys=y", 1084 "--invoke", 1085 "echo_f32", 1086 "tests/all/cli_tests/simple.wat", 1087 "1.0", 1088 ]) 1089 .env("WASMTIME_NEW_CLI", "1") 1090 .output()?; 1091 assert!(!output.status.success()); 1092 Ok(()) 1093 } 1094 1095 // Very basic use case: compile binary wasm file and run specific function with arguments. 1096 #[test] 1097 fn increase_stack_size() -> Result<()> { 1098 run_wasmtime(&[ 1099 "run", 1100 "--invoke", 1101 "simple", 1102 &format!("-Wmax-wasm-stack={}", 5 << 20), 1103 "-Ccache=n", 1104 "tests/all/cli_tests/simple.wat", 1105 "4", 1106 ])?; 1107 Ok(()) 1108 } 1109 1110 mod test_programs { 1111 use super::{get_wasmtime_command, run_wasmtime}; 1112 use anyhow::{bail, Context, Result}; 1113 use http_body_util::BodyExt; 1114 use hyper::header::HeaderValue; 1115 use std::io::{BufRead, BufReader, Read, Write}; 1116 use std::net::SocketAddr; 1117 use std::process::{Child, Command, Stdio}; 1118 use test_programs_artifacts::*; 1119 use tokio::net::TcpStream; 1120 1121 macro_rules! assert_test_exists { 1122 ($name:ident) => { 1123 #[allow(unused_imports)] 1124 use self::$name as _; 1125 }; 1126 } 1127 foreach_cli!(assert_test_exists); 1128 1129 #[test] 1130 fn cli_hello_stdout() -> Result<()> { 1131 run_wasmtime(&[ 1132 "run", 1133 "-Wcomponent-model", 1134 CLI_HELLO_STDOUT_COMPONENT, 1135 "gussie", 1136 "sparky", 1137 "willa", 1138 ])?; 1139 Ok(()) 1140 } 1141 1142 #[test] 1143 fn cli_args() -> Result<()> { 1144 run_wasmtime(&[ 1145 "run", 1146 "-Wcomponent-model", 1147 CLI_ARGS_COMPONENT, 1148 "hello", 1149 "this", 1150 "", 1151 "is an argument", 1152 "with emoji", 1153 ])?; 1154 Ok(()) 1155 } 1156 1157 #[test] 1158 fn cli_stdin_empty() -> Result<()> { 1159 let mut child = get_wasmtime_command()? 1160 .args(&["run", "-Wcomponent-model", CLI_STDIN_EMPTY_COMPONENT]) 1161 .stdout(Stdio::piped()) 1162 .stderr(Stdio::piped()) 1163 .stdin(Stdio::piped()) 1164 .spawn()?; 1165 child 1166 .stdin 1167 .take() 1168 .unwrap() 1169 .write_all(b"not to be read") 1170 .unwrap(); 1171 let output = child.wait_with_output()?; 1172 println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); 1173 println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 1174 assert!(output.status.success()); 1175 Ok(()) 1176 } 1177 1178 #[test] 1179 fn cli_stdin() -> Result<()> { 1180 let mut child = get_wasmtime_command()? 1181 .args(&["run", "-Wcomponent-model", CLI_STDIN_COMPONENT]) 1182 .stdout(Stdio::piped()) 1183 .stderr(Stdio::piped()) 1184 .stdin(Stdio::piped()) 1185 .spawn()?; 1186 child 1187 .stdin 1188 .take() 1189 .unwrap() 1190 .write_all(b"So rested he by the Tumtum tree") 1191 .unwrap(); 1192 let output = child.wait_with_output()?; 1193 println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); 1194 println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 1195 assert!(output.status.success()); 1196 Ok(()) 1197 } 1198 1199 #[test] 1200 fn cli_splice_stdin() -> Result<()> { 1201 let mut child = get_wasmtime_command()? 1202 .args(&["run", "-Wcomponent-model", CLI_SPLICE_STDIN_COMPONENT]) 1203 .stdout(Stdio::piped()) 1204 .stderr(Stdio::piped()) 1205 .stdin(Stdio::piped()) 1206 .spawn()?; 1207 let msg = "So rested he by the Tumtum tree"; 1208 child 1209 .stdin 1210 .take() 1211 .unwrap() 1212 .write_all(msg.as_bytes()) 1213 .unwrap(); 1214 let output = child.wait_with_output()?; 1215 assert!(output.status.success()); 1216 let stdout = String::from_utf8_lossy(&output.stdout); 1217 let stderr = String::from_utf8_lossy(&output.stderr); 1218 if !stderr.is_empty() { 1219 eprintln!("{stderr}"); 1220 } 1221 1222 assert_eq!( 1223 format!( 1224 "before splice\n{msg}\ncompleted splicing {} bytes\n", 1225 msg.as_bytes().len() 1226 ), 1227 stdout 1228 ); 1229 Ok(()) 1230 } 1231 1232 #[test] 1233 fn cli_env() -> Result<()> { 1234 run_wasmtime(&[ 1235 "run", 1236 "-Wcomponent-model", 1237 "--env=frabjous=day", 1238 "--env=callooh=callay", 1239 CLI_ENV_COMPONENT, 1240 ])?; 1241 Ok(()) 1242 } 1243 1244 #[test] 1245 fn cli_file_read() -> Result<()> { 1246 let dir = tempfile::tempdir()?; 1247 1248 std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?; 1249 1250 run_wasmtime(&[ 1251 "run", 1252 "-Wcomponent-model", 1253 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1254 CLI_FILE_READ_COMPONENT, 1255 ])?; 1256 Ok(()) 1257 } 1258 1259 #[test] 1260 fn cli_file_append() -> Result<()> { 1261 let dir = tempfile::tempdir()?; 1262 1263 std::fs::File::create(dir.path().join("bar.txt"))? 1264 .write_all(b"'Twas brillig, and the slithy toves.\n")?; 1265 1266 run_wasmtime(&[ 1267 "run", 1268 "-Wcomponent-model", 1269 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1270 CLI_FILE_APPEND_COMPONENT, 1271 ])?; 1272 1273 let contents = std::fs::read(dir.path().join("bar.txt"))?; 1274 assert_eq!( 1275 std::str::from_utf8(&contents).unwrap(), 1276 "'Twas brillig, and the slithy toves.\n\ 1277 Did gyre and gimble in the wabe;\n\ 1278 All mimsy were the borogoves,\n\ 1279 And the mome raths outgrabe.\n" 1280 ); 1281 Ok(()) 1282 } 1283 1284 #[test] 1285 fn cli_file_dir_sync() -> Result<()> { 1286 let dir = tempfile::tempdir()?; 1287 1288 std::fs::File::create(dir.path().join("bar.txt"))? 1289 .write_all(b"'Twas brillig, and the slithy toves.\n")?; 1290 1291 run_wasmtime(&[ 1292 "run", 1293 "-Wcomponent-model", 1294 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1295 CLI_FILE_DIR_SYNC_COMPONENT, 1296 ])?; 1297 1298 Ok(()) 1299 } 1300 1301 #[test] 1302 fn cli_exit_success() -> Result<()> { 1303 run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_SUCCESS_COMPONENT])?; 1304 Ok(()) 1305 } 1306 1307 #[test] 1308 fn cli_exit_default() -> Result<()> { 1309 run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_DEFAULT_COMPONENT])?; 1310 Ok(()) 1311 } 1312 1313 #[test] 1314 fn cli_exit_failure() -> Result<()> { 1315 let output = get_wasmtime_command()? 1316 .args(&["run", "-Wcomponent-model", CLI_EXIT_FAILURE_COMPONENT]) 1317 .output()?; 1318 assert!(!output.status.success()); 1319 assert_eq!(output.status.code(), Some(1)); 1320 Ok(()) 1321 } 1322 1323 #[test] 1324 fn cli_exit_with_code() -> Result<()> { 1325 let output = get_wasmtime_command()? 1326 .args(&[ 1327 "run", 1328 "-Wcomponent-model", 1329 "-Scli-exit-with-code", 1330 CLI_EXIT_WITH_CODE_COMPONENT, 1331 ]) 1332 .output()?; 1333 assert!(!output.status.success()); 1334 assert_eq!(output.status.code(), Some(42)); 1335 Ok(()) 1336 } 1337 1338 #[test] 1339 fn cli_exit_panic() -> Result<()> { 1340 let output = get_wasmtime_command()? 1341 .args(&["run", "-Wcomponent-model", CLI_EXIT_PANIC_COMPONENT]) 1342 .output()?; 1343 assert!(!output.status.success()); 1344 let stderr = String::from_utf8_lossy(&output.stderr); 1345 assert!(stderr.contains("Curiouser and curiouser!")); 1346 Ok(()) 1347 } 1348 1349 #[test] 1350 fn cli_directory_list() -> Result<()> { 1351 let dir = tempfile::tempdir()?; 1352 1353 std::fs::File::create(dir.path().join("foo.txt"))?; 1354 std::fs::File::create(dir.path().join("bar.txt"))?; 1355 std::fs::File::create(dir.path().join("baz.txt"))?; 1356 std::fs::create_dir(dir.path().join("sub"))?; 1357 std::fs::File::create(dir.path().join("sub").join("wow.txt"))?; 1358 std::fs::File::create(dir.path().join("sub").join("yay.txt"))?; 1359 1360 run_wasmtime(&[ 1361 "run", 1362 "-Wcomponent-model", 1363 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1364 CLI_DIRECTORY_LIST_COMPONENT, 1365 ])?; 1366 Ok(()) 1367 } 1368 1369 #[test] 1370 fn cli_default_clocks() -> Result<()> { 1371 run_wasmtime(&["run", "-Wcomponent-model", CLI_DEFAULT_CLOCKS_COMPONENT])?; 1372 Ok(()) 1373 } 1374 1375 #[test] 1376 fn cli_export_cabi_realloc() -> Result<()> { 1377 run_wasmtime(&[ 1378 "run", 1379 "-Wcomponent-model", 1380 CLI_EXPORT_CABI_REALLOC_COMPONENT, 1381 ])?; 1382 Ok(()) 1383 } 1384 1385 #[test] 1386 fn run_wasi_http_component() -> Result<()> { 1387 let output = super::run_wasmtime_for_output( 1388 &[ 1389 "-Ccache=no", 1390 "-Wcomponent-model", 1391 "-Scli,http,preview2", 1392 HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT, 1393 ], 1394 None, 1395 )?; 1396 println!("{}", String::from_utf8_lossy(&output.stderr)); 1397 let stdout = String::from_utf8_lossy(&output.stdout); 1398 println!("{stdout}"); 1399 assert!(stdout.starts_with("Called _start\n")); 1400 assert!(stdout.ends_with("Done\n")); 1401 assert!(output.status.success()); 1402 Ok(()) 1403 } 1404 1405 // Test to ensure that prints in the guest aren't buffered on the host by 1406 // accident. The test here will print something without a newline and then 1407 // wait for input on stdin, and the test here is to ensure that the 1408 // character shows up here even as the guest is waiting on input via stdin. 1409 #[test] 1410 fn cli_stdio_write_flushes() -> Result<()> { 1411 fn run(args: &[&str]) -> Result<()> { 1412 println!("running {args:?}"); 1413 let mut child = get_wasmtime_command()? 1414 .args(args) 1415 .stdin(Stdio::piped()) 1416 .stdout(Stdio::piped()) 1417 .spawn()?; 1418 let mut stdout = child.stdout.take().unwrap(); 1419 let mut buf = [0; 10]; 1420 match stdout.read(&mut buf) { 1421 Ok(2) => assert_eq!(&buf[..2], b"> "), 1422 e => panic!("unexpected read result {e:?}"), 1423 } 1424 drop(stdout); 1425 drop(child.stdin.take().unwrap()); 1426 let status = child.wait()?; 1427 assert!(status.success()); 1428 Ok(()) 1429 } 1430 1431 run(&["run", "-Spreview2=n", CLI_STDIO_WRITE_FLUSHES])?; 1432 run(&["run", "-Spreview2=y", CLI_STDIO_WRITE_FLUSHES])?; 1433 run(&[ 1434 "run", 1435 "-Wcomponent-model", 1436 CLI_STDIO_WRITE_FLUSHES_COMPONENT, 1437 ])?; 1438 Ok(()) 1439 } 1440 1441 #[test] 1442 fn cli_no_tcp() -> Result<()> { 1443 let output = super::run_wasmtime_for_output( 1444 &[ 1445 "-Wcomponent-model", 1446 // Turn on network but turn off TCP 1447 "-Sinherit-network,tcp=no", 1448 CLI_NO_TCP_COMPONENT, 1449 ], 1450 None, 1451 )?; 1452 println!("{}", String::from_utf8_lossy(&output.stderr)); 1453 assert!(output.status.success()); 1454 Ok(()) 1455 } 1456 1457 #[test] 1458 fn cli_no_udp() -> Result<()> { 1459 let output = super::run_wasmtime_for_output( 1460 &[ 1461 "-Wcomponent-model", 1462 // Turn on network but turn off UDP 1463 "-Sinherit-network,udp=no", 1464 CLI_NO_UDP_COMPONENT, 1465 ], 1466 None, 1467 )?; 1468 println!("{}", String::from_utf8_lossy(&output.stderr)); 1469 assert!(output.status.success()); 1470 Ok(()) 1471 } 1472 1473 #[test] 1474 fn cli_no_ip_name_lookup() -> Result<()> { 1475 let output = super::run_wasmtime_for_output( 1476 &[ 1477 "-Wcomponent-model", 1478 // Turn on network but ensure name lookup is disabled 1479 "-Sinherit-network,allow-ip-name-lookup=no", 1480 CLI_NO_IP_NAME_LOOKUP_COMPONENT, 1481 ], 1482 None, 1483 )?; 1484 println!("{}", String::from_utf8_lossy(&output.stderr)); 1485 assert!(output.status.success()); 1486 Ok(()) 1487 } 1488 1489 #[test] 1490 fn cli_sleep() -> Result<()> { 1491 run_wasmtime(&["run", CLI_SLEEP])?; 1492 run_wasmtime(&["run", CLI_SLEEP_COMPONENT])?; 1493 Ok(()) 1494 } 1495 1496 #[test] 1497 fn cli_sleep_forever() -> Result<()> { 1498 for timeout in [ 1499 // Tests still pass when we race with going to sleep. 1500 "-Wtimeout=1ns", 1501 // Tests pass when we wait till the Wasm has (likely) gone to sleep. 1502 "-Wtimeout=250ms", 1503 ] { 1504 let e = run_wasmtime(&["run", timeout, CLI_SLEEP_FOREVER]).unwrap_err(); 1505 let e = e.to_string(); 1506 println!("Got error: {e}"); 1507 assert!(e.contains("interrupt")); 1508 1509 let e = run_wasmtime(&["run", timeout, CLI_SLEEP_FOREVER_COMPONENT]).unwrap_err(); 1510 let e = e.to_string(); 1511 println!("Got error: {e}"); 1512 assert!(e.contains("interrupt")); 1513 } 1514 1515 Ok(()) 1516 } 1517 1518 /// Helper structure to manage an invocation of `wasmtime serve` 1519 struct WasmtimeServe { 1520 child: Option<Child>, 1521 addr: SocketAddr, 1522 } 1523 1524 impl WasmtimeServe { 1525 /// Creates a new server which will serve the wasm component pointed to 1526 /// by `wasm`. 1527 /// 1528 /// A `configure` callback is provided to specify how `wasmtime serve` 1529 /// will be invoked and configure arguments such as headers. 1530 fn new(wasm: &str, configure: impl FnOnce(&mut Command)) -> Result<WasmtimeServe> { 1531 // Spawn `wasmtime serve` on port 0 which will randomly assign it a 1532 // port. 1533 let mut cmd = super::get_wasmtime_command()?; 1534 cmd.arg("serve").arg("--addr=127.0.0.1:0").arg(wasm); 1535 configure(&mut cmd); 1536 Self::spawn(&mut cmd) 1537 } 1538 1539 fn spawn(cmd: &mut Command) -> Result<WasmtimeServe> { 1540 cmd.stdin(Stdio::null()); 1541 cmd.stdout(Stdio::piped()); 1542 cmd.stderr(Stdio::piped()); 1543 let mut child = cmd.spawn()?; 1544 1545 // Read the first line of stderr which will say which address it's 1546 // listening on. 1547 // 1548 // NB: this intentionally discards any extra buffered data in the 1549 // `BufReader` once the newline is found. The server shouldn't print 1550 // anything interesting other than the address so once we get a line 1551 // all remaining output is left to be captured by future requests 1552 // send to the server. 1553 let mut line = String::new(); 1554 let mut reader = BufReader::new(child.stderr.take().unwrap()); 1555 reader.read_line(&mut line)?; 1556 1557 match line.find("127.0.0.1").and_then(|addr_start| { 1558 let addr = &line[addr_start..]; 1559 let addr_end = addr.find("/")?; 1560 addr[..addr_end].parse().ok() 1561 }) { 1562 Some(addr) => { 1563 assert!(reader.buffer().is_empty()); 1564 child.stderr = Some(reader.into_inner()); 1565 Ok(WasmtimeServe { 1566 child: Some(child), 1567 addr, 1568 }) 1569 } 1570 None => { 1571 child.kill()?; 1572 child.wait()?; 1573 reader.read_to_string(&mut line)?; 1574 bail!("failed to start child: {line}") 1575 } 1576 } 1577 } 1578 1579 /// Completes this server gracefully by printing the output on failure. 1580 fn finish(mut self) -> Result<(String, String)> { 1581 let mut child = self.child.take().unwrap(); 1582 1583 // If the child process has already exited then collect the output 1584 // and test if it succeeded. Otherwise it's still running so kill it 1585 // and then reap it. Assume that if it's still running then the test 1586 // has otherwise passed so no need to print the output. 1587 let known_failure = if child.try_wait()?.is_some() { 1588 false 1589 } else { 1590 child.kill()?; 1591 true 1592 }; 1593 let output = child.wait_with_output()?; 1594 if !known_failure && !output.status.success() { 1595 bail!("child failed {output:?}"); 1596 } 1597 1598 Ok(( 1599 String::from_utf8_lossy(&output.stdout).into_owned(), 1600 String::from_utf8_lossy(&output.stderr).into_owned(), 1601 )) 1602 } 1603 1604 /// Send a request to this server and wait for the response. 1605 async fn send_request(&self, req: http::Request<String>) -> Result<http::Response<String>> { 1606 let (mut send, conn_task) = self.start_requests().await?; 1607 1608 let response = send 1609 .send_request(req) 1610 .await 1611 .context("error sending request")?; 1612 drop(send); 1613 let (parts, body) = response.into_parts(); 1614 1615 let body = body.collect().await.context("failed to read body")?; 1616 assert!(body.trailers().is_none()); 1617 let body = std::str::from_utf8(&body.to_bytes())?.to_string(); 1618 1619 conn_task.await??; 1620 1621 Ok(http::Response::from_parts(parts, body)) 1622 } 1623 1624 async fn start_requests( 1625 &self, 1626 ) -> Result<( 1627 hyper::client::conn::http1::SendRequest<String>, 1628 tokio::task::JoinHandle<hyper::Result<()>>, 1629 )> { 1630 let tcp = TcpStream::connect(&self.addr) 1631 .await 1632 .context("failed to connect")?; 1633 let tcp = wasmtime_wasi_http::io::TokioIo::new(tcp); 1634 let (send, conn) = hyper::client::conn::http1::handshake(tcp) 1635 .await 1636 .context("failed http handshake")?; 1637 Ok((send, tokio::task::spawn(conn))) 1638 } 1639 } 1640 1641 // Don't leave child processes running by accident so kill the child process 1642 // if our server goes away. 1643 impl Drop for WasmtimeServe { 1644 fn drop(&mut self) { 1645 let mut child = match self.child.take() { 1646 Some(child) => child, 1647 None => return, 1648 }; 1649 if child.kill().is_err() { 1650 return; 1651 } 1652 let output = match child.wait_with_output() { 1653 Ok(output) => output, 1654 Err(_) => return, 1655 }; 1656 1657 println!("server status: {}", output.status); 1658 if !output.stdout.is_empty() { 1659 println!( 1660 "server stdout:\n{}", 1661 String::from_utf8_lossy(&output.stdout) 1662 ); 1663 } 1664 if !output.stderr.is_empty() { 1665 println!( 1666 "server stderr:\n{}", 1667 String::from_utf8_lossy(&output.stderr) 1668 ); 1669 } 1670 } 1671 } 1672 1673 #[tokio::test] 1674 async fn cli_serve_echo_env() -> Result<()> { 1675 let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| { 1676 cmd.arg("--env=FOO=bar"); 1677 cmd.arg("--env=BAR"); 1678 cmd.arg("-Scli"); 1679 cmd.env_remove("BAR"); 1680 })?; 1681 1682 let foo_env = server 1683 .send_request( 1684 hyper::Request::builder() 1685 .uri("http://localhost/") 1686 .header("env", "FOO") 1687 .body(String::new()) 1688 .context("failed to make request")?, 1689 ) 1690 .await?; 1691 1692 assert!(foo_env.status().is_success()); 1693 assert!(foo_env.body().is_empty()); 1694 let headers = foo_env.headers(); 1695 assert_eq!(headers.get("env"), Some(&HeaderValue::from_static("bar"))); 1696 1697 let bar_env = server 1698 .send_request( 1699 hyper::Request::builder() 1700 .uri("http://localhost/") 1701 .header("env", "BAR") 1702 .body(String::new()) 1703 .context("failed to make request")?, 1704 ) 1705 .await?; 1706 1707 assert!(bar_env.status().is_success()); 1708 assert!(bar_env.body().is_empty()); 1709 let headers = bar_env.headers(); 1710 assert_eq!(headers.get("env"), None); 1711 1712 server.finish()?; 1713 Ok(()) 1714 } 1715 1716 #[tokio::test] 1717 #[ignore] // TODO: printing stderr in the child and killing the child at the 1718 // end of this test race so the stderr may be present or not. Need 1719 // to implement a more graceful shutdown routine for `wasmtime 1720 // serve`. 1721 async fn cli_serve_respect_pooling_options() -> Result<()> { 1722 let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| { 1723 cmd.arg("-Opooling-total-memories=0").arg("-Scli"); 1724 })?; 1725 1726 let result = server 1727 .send_request( 1728 hyper::Request::builder() 1729 .uri("http://localhost/") 1730 .header("env", "FOO") 1731 .body(String::new()) 1732 .context("failed to make request")?, 1733 ) 1734 .await; 1735 assert!(result.is_err()); 1736 let (_, stderr) = server.finish()?; 1737 assert!( 1738 stderr.contains("maximum concurrent memory limit of 0 reached"), 1739 "bad stderr: {stderr}", 1740 ); 1741 Ok(()) 1742 } 1743 1744 #[test] 1745 fn cli_large_env() -> Result<()> { 1746 for wasm in [CLI_LARGE_ENV, CLI_LARGE_ENV_COMPONENT] { 1747 println!("run {wasm:?}"); 1748 let mut cmd = get_wasmtime_command()?; 1749 cmd.arg("run").arg("-Sinherit-env").arg(wasm); 1750 1751 let debug_cmd = format!("{cmd:?}"); 1752 for i in 0..512 { 1753 let var = format!("KEY{i}"); 1754 let val = (0..1024).map(|_| 'x').collect::<String>(); 1755 cmd.env(&var, &val); 1756 } 1757 let output = cmd.output()?; 1758 if !output.status.success() { 1759 bail!( 1760 "Failed to execute wasmtime with: {debug_cmd}\n{}", 1761 String::from_utf8_lossy(&output.stderr) 1762 ); 1763 } 1764 } 1765 Ok(()) 1766 } 1767 1768 #[tokio::test] 1769 async fn cli_serve_only_one_process_allowed() -> Result<()> { 1770 let wasm = CLI_SERVE_ECHO_ENV_COMPONENT; 1771 let server = WasmtimeServe::new(wasm, |cmd| { 1772 cmd.arg("-Scli"); 1773 })?; 1774 1775 let err = WasmtimeServe::spawn( 1776 super::get_wasmtime_command()? 1777 .arg("serve") 1778 .arg("-Scli") 1779 .arg(format!("--addr={}", server.addr)) 1780 .arg(wasm), 1781 ) 1782 .err() 1783 .expect("server spawn should have failed but it succeeded"); 1784 drop(server); 1785 1786 let err = format!("{err:?}"); 1787 println!("{err}"); 1788 assert!(err.contains("os error")); 1789 Ok(()) 1790 } 1791 1792 // Technically this test is a little racy. This binds port 0 to acquire a 1793 // random port, issues a single request to this port, but then kills this 1794 // server while the request is still processing. The port is then rebound 1795 // in the next process while it technically could be stolen by another 1796 // process. 1797 #[tokio::test] 1798 async fn cli_serve_quick_rebind_allowed() -> Result<()> { 1799 let wasm = CLI_SERVE_ECHO_ENV_COMPONENT; 1800 let server = WasmtimeServe::new(wasm, |cmd| { 1801 cmd.arg("-Scli"); 1802 })?; 1803 let addr = server.addr; 1804 1805 // Start up a `send` and `conn_task` which represents a connection to 1806 // this server. 1807 let (mut send, conn_task) = server.start_requests().await?; 1808 let _ = send 1809 .send_request( 1810 hyper::Request::builder() 1811 .uri("http://localhost/") 1812 .header("env", "FOO") 1813 .body(String::new()) 1814 .context("failed to make request")?, 1815 ) 1816 .await; 1817 1818 // ... once a response has been received (or at least the status 1819 // code/headers) then kill the server. THis is done while `conn_task` 1820 // and `send` are still alive so we're guaranteed that the other side 1821 // got a request (we got a response) and our connection is still open. 1822 // 1823 // This forces the address/port into the `TIME_WAIT` state. The rebind 1824 // below in the next process will fail if `SO_REUSEADDR` isn't set. 1825 drop(server); 1826 drop(send); 1827 let _ = conn_task.await; 1828 1829 // If this is successfully bound then we'll create `WasmtimeServe` 1830 // which reads off the first line of output to know which address was 1831 // bound. 1832 let _server2 = WasmtimeServe::spawn( 1833 super::get_wasmtime_command()? 1834 .arg("serve") 1835 .arg("-Scli") 1836 .arg(format!("--addr={addr}")) 1837 .arg(wasm), 1838 )?; 1839 1840 Ok(()) 1841 } 1842 1843 #[tokio::test] 1844 async fn cli_serve_with_print() -> Result<()> { 1845 let server = WasmtimeServe::new(CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| { 1846 cmd.arg("-Scli"); 1847 })?; 1848 1849 for _ in 0..2 { 1850 let resp = server 1851 .send_request( 1852 hyper::Request::builder() 1853 .uri("http://localhost/") 1854 .body(String::new()) 1855 .context("failed to make request")?, 1856 ) 1857 .await?; 1858 assert!(resp.status().is_success()); 1859 } 1860 1861 let (out, err) = server.finish()?; 1862 assert_eq!( 1863 out, 1864 "\ 1865 stdout [0] :: this is half a print to stdout 1866 stdout [0] :: \n\ 1867 stdout [0] :: after empty 1868 stdout [1] :: this is half a print to stdout 1869 stdout [1] :: \n\ 1870 stdout [1] :: after empty 1871 " 1872 ); 1873 assert_eq!( 1874 err, 1875 "\ 1876 stderr [0] :: this is half a print to stderr 1877 stderr [0] :: \n\ 1878 stderr [0] :: after empty 1879 stderr [1] :: this is half a print to stderr 1880 stderr [1] :: \n\ 1881 stderr [1] :: after empty 1882 " 1883 ); 1884 1885 Ok(()) 1886 } 1887 1888 #[tokio::test] 1889 async fn cli_serve_authority_and_scheme() -> Result<()> { 1890 let server = WasmtimeServe::new(CLI_SERVE_AUTHORITY_AND_SCHEME_COMPONENT, |cmd| { 1891 cmd.arg("-Scli"); 1892 })?; 1893 1894 let resp = server 1895 .send_request( 1896 hyper::Request::builder() 1897 .uri("/") 1898 .header("Host", "localhost") 1899 .body(String::new()) 1900 .context("failed to make request")?, 1901 ) 1902 .await?; 1903 assert!(resp.status().is_success()); 1904 1905 let resp = server 1906 .send_request( 1907 hyper::Request::builder() 1908 .method("CONNECT") 1909 .uri("http://localhost/") 1910 .body(String::new()) 1911 .context("failed to make request")?, 1912 ) 1913 .await?; 1914 assert!(resp.status().is_success()); 1915 1916 Ok(()) 1917 } 1918 1919 #[test] 1920 fn cli_argv0() -> Result<()> { 1921 run_wasmtime(&["run", "--argv0=a", CLI_ARGV0, "a"])?; 1922 run_wasmtime(&["run", "--argv0=b", CLI_ARGV0_COMPONENT, "b"])?; 1923 run_wasmtime(&["run", "--argv0=foo.wasm", CLI_ARGV0, "foo.wasm"])?; 1924 Ok(()) 1925 } 1926 1927 #[tokio::test] 1928 async fn cli_serve_config() -> Result<()> { 1929 let server = WasmtimeServe::new(CLI_SERVE_CONFIG_COMPONENT, |cmd| { 1930 cmd.arg("-Scli"); 1931 cmd.arg("-Sconfig"); 1932 cmd.arg("-Sconfig-var=hello=world"); 1933 })?; 1934 1935 let resp = server 1936 .send_request( 1937 hyper::Request::builder() 1938 .uri("http://localhost/") 1939 .body(String::new()) 1940 .context("failed to make request")?, 1941 ) 1942 .await?; 1943 1944 assert!(resp.status().is_success()); 1945 assert_eq!(resp.body(), "world"); 1946 Ok(()) 1947 } 1948 1949 #[test] 1950 fn cli_config() -> Result<()> { 1951 run_wasmtime(&[ 1952 "run", 1953 "-Sconfig", 1954 "-Sconfig-var=hello=world", 1955 CONFIG_GET_COMPONENT, 1956 ])?; 1957 Ok(()) 1958 } 1959 1960 #[tokio::test] 1961 async fn cli_serve_keyvalue() -> Result<()> { 1962 let server = WasmtimeServe::new(CLI_SERVE_KEYVALUE_COMPONENT, |cmd| { 1963 cmd.arg("-Scli"); 1964 cmd.arg("-Skeyvalue"); 1965 cmd.arg("-Skeyvalue-in-memory-data=hello=world"); 1966 })?; 1967 1968 let resp = server 1969 .send_request( 1970 hyper::Request::builder() 1971 .uri("http://localhost/") 1972 .body(String::new()) 1973 .context("failed to make request")?, 1974 ) 1975 .await?; 1976 1977 assert!(resp.status().is_success()); 1978 assert_eq!(resp.body(), "world"); 1979 Ok(()) 1980 } 1981 1982 #[test] 1983 fn cli_keyvalue() -> Result<()> { 1984 run_wasmtime(&[ 1985 "run", 1986 "-Skeyvalue", 1987 "-Skeyvalue-in-memory-data=atomics_key=5", 1988 KEYVALUE_MAIN_COMPONENT, 1989 ])?; 1990 Ok(()) 1991 } 1992 } 1993 1994 #[test] 1995 fn settings_command() -> Result<()> { 1996 let output = run_wasmtime(&["settings"])?; 1997 assert!(output.contains("Cranelift settings for target")); 1998 Ok(()) 1999 } 2000 2001 #[cfg(target_arch = "x86_64")] 2002 #[test] 2003 fn profile_with_vtune() -> Result<()> { 2004 if !is_vtune_available() { 2005 println!("> `vtune` is not available on the system path; skipping test"); 2006 return Ok(()); 2007 } 2008 2009 let mut bin = Command::new("vtune"); 2010 bin.args(&[ 2011 // Configure VTune... 2012 "-verbose", 2013 "-collect", 2014 "hotspots", 2015 "-user-data-dir", 2016 &std::env::temp_dir().to_string_lossy(), 2017 // ...then run Wasmtime with profiling enabled: 2018 &get_wasmtime_path()?.to_string_lossy(), 2019 "--profile=vtune", 2020 "tests/all/cli_tests/simple.wat", 2021 ]); 2022 2023 println!("> executing: {bin:?}"); 2024 let output = bin.output()?; 2025 2026 assert!(output.status.success()); 2027 let stdout = String::from_utf8_lossy(&output.stdout); 2028 let stderr = String::from_utf8_lossy(&output.stderr); 2029 println!("> stdout:\n{stdout}"); 2030 assert!(stdout.contains("CPU Time")); 2031 println!("> stderr:\n{stderr}"); 2032 assert!(!stderr.contains("Error")); 2033 Ok(()) 2034 } 2035 2036 #[cfg(target_arch = "x86_64")] 2037 fn is_vtune_available() -> bool { 2038 Command::new("vtune").arg("-version").output().is_ok() 2039 } 2040 2041 #[test] 2042 fn unreachable_without_wasi() -> Result<()> { 2043 let output = run_wasmtime_for_output( 2044 &[ 2045 "-Scli=n", 2046 "-Ccache=n", 2047 "tests/all/cli_tests/unreachable.wat", 2048 ], 2049 None, 2050 )?; 2051 2052 assert_ne!(output.stderr, b""); 2053 assert_eq!(output.stdout, b""); 2054 assert_trap_code(&output.status); 2055 Ok(()) 2056 } 2057