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