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