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