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 old_cli_warn_if_ambiguous_flags() -> Result<()> { 1063 // This is accepted in the old CLI parser and the new but it's interpreted 1064 // differently so a warning should be printed. 1065 let output = get_wasmtime_command()? 1066 .args(&["tests/all/cli_tests/simple.wat", "--invoke", "get_f32"]) 1067 .output()?; 1068 assert_eq!(String::from_utf8_lossy(&output.stdout), "100\n"); 1069 assert_eq!( 1070 String::from_utf8_lossy(&output.stderr), 1071 "\ 1072 warning: this CLI invocation of Wasmtime will be parsed differently in future 1073 Wasmtime versions -- see this online issue for more information: 1074 https://github.com/bytecodealliance/wasmtime/issues/7384 1075 1076 Wasmtime will now execute with the old (<= Wasmtime 13) CLI parsing, 1077 however this behavior can also be temporarily configured with an 1078 environment variable: 1079 1080 - WASMTIME_NEW_CLI=0 to indicate old semantics are desired and silence this warning, or 1081 - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and use the latest behavior 1082 warning: using `--invoke` with a function that returns values is experimental and may break in the future 1083 " 1084 ); 1085 1086 // Test disabling the warning 1087 let output = get_wasmtime_command()? 1088 .args(&["tests/all/cli_tests/simple.wat", "--invoke", "get_f32"]) 1089 .env("WASMTIME_NEW_CLI", "0") 1090 .output()?; 1091 assert_eq!(String::from_utf8_lossy(&output.stdout), "100\n"); 1092 assert_eq!( 1093 String::from_utf8_lossy(&output.stderr), 1094 "\ 1095 warning: using `--invoke` with a function that returns values is experimental and may break in the future 1096 " 1097 ); 1098 1099 // Test forcing the new behavior where nothing happens because the file is 1100 // invoked with `--invoke` as its own argument. 1101 let output = get_wasmtime_command()? 1102 .args(&["tests/all/cli_tests/simple.wat", "--invoke", "get_f32"]) 1103 .env("WASMTIME_NEW_CLI", "1") 1104 .output()?; 1105 assert_eq!(String::from_utf8_lossy(&output.stdout), ""); 1106 assert_eq!(String::from_utf8_lossy(&output.stderr), ""); 1107 1108 // This is unambiguous 1109 let output = get_wasmtime_command()? 1110 .args(&["--invoke", "get_f32", "tests/all/cli_tests/simple.wat"]) 1111 .output()?; 1112 assert_eq!(String::from_utf8_lossy(&output.stdout), "100\n"); 1113 assert_eq!( 1114 String::from_utf8_lossy(&output.stderr), 1115 "\ 1116 warning: using `--invoke` with a function that returns values is experimental and may break in the future 1117 " 1118 ); 1119 1120 // This fails to parse in the old but succeeds in the new, so it should run 1121 // under the new semantics with no warning. 1122 let output = get_wasmtime_command()? 1123 .args(&["run", "tests/all/cli_tests/print-arguments.wat", "--arg"]) 1124 .output()?; 1125 assert_eq!( 1126 String::from_utf8_lossy(&output.stdout), 1127 "print-arguments.wat\n--arg\n" 1128 ); 1129 assert_eq!(String::from_utf8_lossy(&output.stderr), ""); 1130 1131 // Old behavior can be forced however 1132 let output = get_wasmtime_command()? 1133 .args(&["run", "tests/all/cli_tests/print-arguments.wat", "--arg"]) 1134 .env("WASMTIME_NEW_CLI", "0") 1135 .output()?; 1136 assert!(!output.status.success()); 1137 1138 // This works in both the old and the new, so no warnings 1139 let output = get_wasmtime_command()? 1140 .args(&["run", "tests/all/cli_tests/print-arguments.wat", "arg"]) 1141 .output()?; 1142 assert_eq!( 1143 String::from_utf8_lossy(&output.stdout), 1144 "print-arguments.wat\narg\n" 1145 ); 1146 assert_eq!(String::from_utf8_lossy(&output.stderr), ""); 1147 1148 // This works in both the old and the new, so no warnings 1149 let output = get_wasmtime_command()? 1150 .args(&[ 1151 "run", 1152 "--", 1153 "tests/all/cli_tests/print-arguments.wat", 1154 "--arg", 1155 ]) 1156 .output()?; 1157 assert_eq!( 1158 String::from_utf8_lossy(&output.stdout), 1159 "print-arguments.wat\n--arg\n" 1160 ); 1161 assert_eq!(String::from_utf8_lossy(&output.stderr), ""); 1162 1163 // Old flags still work, but with a warning 1164 let output = get_wasmtime_command()? 1165 .args(&[ 1166 "run", 1167 "--max-wasm-stack", 1168 "1000000", 1169 "tests/all/cli_tests/print-arguments.wat", 1170 ]) 1171 .output()?; 1172 assert_eq!( 1173 String::from_utf8_lossy(&output.stdout), 1174 "print-arguments.wat\n" 1175 ); 1176 assert_eq!( 1177 String::from_utf8_lossy(&output.stderr), 1178 "\ 1179 warning: this CLI invocation of Wasmtime is going to break in the future -- for 1180 more information see this issue online: 1181 https://github.com/bytecodealliance/wasmtime/issues/7384 1182 1183 Wasmtime will now execute with the old (<= Wasmtime 13) CLI parsing, 1184 however this behavior can also be temporarily configured with an 1185 environment variable: 1186 1187 - WASMTIME_NEW_CLI=0 to indicate old semantics are desired and silence this warning, or 1188 - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and see the error 1189 " 1190 ); 1191 1192 // Old flags warning is suppressible. 1193 let output = get_wasmtime_command()? 1194 .args(&[ 1195 "run", 1196 "--max-wasm-stack", 1197 "1000000", 1198 "tests/all/cli_tests/print-arguments.wat", 1199 ]) 1200 .env("WASMTIME_NEW_CLI", "0") 1201 .output()?; 1202 assert_eq!( 1203 String::from_utf8_lossy(&output.stdout), 1204 "print-arguments.wat\n" 1205 ); 1206 assert_eq!(String::from_utf8_lossy(&output.stderr), ""); 1207 1208 // the `--dir` flag prints no warning when used with `::` 1209 let dir = tempfile::tempdir()?; 1210 std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?; 1211 let output = get_wasmtime_command()? 1212 .args(&[ 1213 "run", 1214 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1215 test_programs_artifacts::CLI_FILE_READ, 1216 ]) 1217 .output()?; 1218 assert_eq!(String::from_utf8_lossy(&output.stdout), ""); 1219 assert_eq!(String::from_utf8_lossy(&output.stderr), ""); 1220 1221 Ok(()) 1222 } 1223 1224 #[test] 1225 fn float_args() -> Result<()> { 1226 let result = run_wasmtime(&[ 1227 "--invoke", 1228 "echo_f32", 1229 "tests/all/cli_tests/simple.wat", 1230 "1.0", 1231 ])?; 1232 assert_eq!(result, "1\n"); 1233 let result = run_wasmtime(&[ 1234 "--invoke", 1235 "echo_f64", 1236 "tests/all/cli_tests/simple.wat", 1237 "1.1", 1238 ])?; 1239 assert_eq!(result, "1.1\n"); 1240 Ok(()) 1241 } 1242 1243 #[test] 1244 fn mpk_without_pooling() -> Result<()> { 1245 let output = get_wasmtime_command()? 1246 .args(&[ 1247 "run", 1248 "-O", 1249 "memory-protection-keys=y", 1250 "--invoke", 1251 "echo_f32", 1252 "tests/all/cli_tests/simple.wat", 1253 "1.0", 1254 ]) 1255 .env("WASMTIME_NEW_CLI", "1") 1256 .output()?; 1257 assert!(!output.status.success()); 1258 Ok(()) 1259 } 1260 1261 mod test_programs { 1262 use super::{get_wasmtime_command, run_wasmtime}; 1263 use anyhow::{bail, Context, Result}; 1264 use http_body_util::BodyExt; 1265 use hyper::header::HeaderValue; 1266 use std::io::{BufRead, BufReader, Read, Write}; 1267 use std::net::SocketAddr; 1268 use std::process::{Child, Command, Stdio}; 1269 use test_programs_artifacts::*; 1270 use tokio::net::TcpStream; 1271 1272 macro_rules! assert_test_exists { 1273 ($name:ident) => { 1274 #[allow(unused_imports)] 1275 use self::$name as _; 1276 }; 1277 } 1278 foreach_cli!(assert_test_exists); 1279 1280 #[test] 1281 fn cli_hello_stdout() -> Result<()> { 1282 run_wasmtime(&[ 1283 "run", 1284 "-Wcomponent-model", 1285 CLI_HELLO_STDOUT_COMPONENT, 1286 "gussie", 1287 "sparky", 1288 "willa", 1289 ])?; 1290 Ok(()) 1291 } 1292 1293 #[test] 1294 fn cli_args() -> Result<()> { 1295 run_wasmtime(&[ 1296 "run", 1297 "-Wcomponent-model", 1298 CLI_ARGS_COMPONENT, 1299 "hello", 1300 "this", 1301 "", 1302 "is an argument", 1303 "with emoji", 1304 ])?; 1305 Ok(()) 1306 } 1307 1308 #[test] 1309 fn cli_stdin() -> Result<()> { 1310 let mut child = get_wasmtime_command()? 1311 .args(&["run", "-Wcomponent-model", CLI_STDIN_COMPONENT]) 1312 .stdout(Stdio::piped()) 1313 .stderr(Stdio::piped()) 1314 .stdin(Stdio::piped()) 1315 .spawn()?; 1316 child 1317 .stdin 1318 .take() 1319 .unwrap() 1320 .write_all(b"So rested he by the Tumtum tree") 1321 .unwrap(); 1322 let output = child.wait_with_output()?; 1323 println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); 1324 println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 1325 assert!(output.status.success()); 1326 Ok(()) 1327 } 1328 1329 #[test] 1330 fn cli_splice_stdin() -> Result<()> { 1331 let mut child = get_wasmtime_command()? 1332 .args(&["run", "-Wcomponent-model", CLI_SPLICE_STDIN_COMPONENT]) 1333 .stdout(Stdio::piped()) 1334 .stderr(Stdio::piped()) 1335 .stdin(Stdio::piped()) 1336 .spawn()?; 1337 let msg = "So rested he by the Tumtum tree"; 1338 child 1339 .stdin 1340 .take() 1341 .unwrap() 1342 .write_all(msg.as_bytes()) 1343 .unwrap(); 1344 let output = child.wait_with_output()?; 1345 assert!(output.status.success()); 1346 let stdout = String::from_utf8_lossy(&output.stdout); 1347 let stderr = String::from_utf8_lossy(&output.stderr); 1348 if !stderr.is_empty() { 1349 eprintln!("{stderr}"); 1350 } 1351 1352 assert_eq!( 1353 format!( 1354 "before splice\n{msg}\ncompleted splicing {} bytes\n", 1355 msg.as_bytes().len() 1356 ), 1357 stdout 1358 ); 1359 Ok(()) 1360 } 1361 1362 #[test] 1363 fn cli_env() -> Result<()> { 1364 run_wasmtime(&[ 1365 "run", 1366 "-Wcomponent-model", 1367 "--env=frabjous=day", 1368 "--env=callooh=callay", 1369 CLI_ENV_COMPONENT, 1370 ])?; 1371 Ok(()) 1372 } 1373 1374 #[test] 1375 fn cli_file_read() -> Result<()> { 1376 let dir = tempfile::tempdir()?; 1377 1378 std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?; 1379 1380 run_wasmtime(&[ 1381 "run", 1382 "-Wcomponent-model", 1383 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1384 CLI_FILE_READ_COMPONENT, 1385 ])?; 1386 Ok(()) 1387 } 1388 1389 #[test] 1390 fn cli_file_append() -> Result<()> { 1391 let dir = tempfile::tempdir()?; 1392 1393 std::fs::File::create(dir.path().join("bar.txt"))? 1394 .write_all(b"'Twas brillig, and the slithy toves.\n")?; 1395 1396 run_wasmtime(&[ 1397 "run", 1398 "-Wcomponent-model", 1399 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1400 CLI_FILE_APPEND_COMPONENT, 1401 ])?; 1402 1403 let contents = std::fs::read(dir.path().join("bar.txt"))?; 1404 assert_eq!( 1405 std::str::from_utf8(&contents).unwrap(), 1406 "'Twas brillig, and the slithy toves.\n\ 1407 Did gyre and gimble in the wabe;\n\ 1408 All mimsy were the borogoves,\n\ 1409 And the mome raths outgrabe.\n" 1410 ); 1411 Ok(()) 1412 } 1413 1414 #[test] 1415 fn cli_file_dir_sync() -> Result<()> { 1416 let dir = tempfile::tempdir()?; 1417 1418 std::fs::File::create(dir.path().join("bar.txt"))? 1419 .write_all(b"'Twas brillig, and the slithy toves.\n")?; 1420 1421 run_wasmtime(&[ 1422 "run", 1423 "-Wcomponent-model", 1424 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1425 CLI_FILE_DIR_SYNC_COMPONENT, 1426 ])?; 1427 1428 Ok(()) 1429 } 1430 1431 #[test] 1432 fn cli_exit_success() -> Result<()> { 1433 run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_SUCCESS_COMPONENT])?; 1434 Ok(()) 1435 } 1436 1437 #[test] 1438 fn cli_exit_default() -> Result<()> { 1439 run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_DEFAULT_COMPONENT])?; 1440 Ok(()) 1441 } 1442 1443 #[test] 1444 fn cli_exit_failure() -> Result<()> { 1445 let output = get_wasmtime_command()? 1446 .args(&["run", "-Wcomponent-model", CLI_EXIT_FAILURE_COMPONENT]) 1447 .output()?; 1448 assert!(!output.status.success()); 1449 assert_eq!(output.status.code(), Some(1)); 1450 Ok(()) 1451 } 1452 1453 #[test] 1454 fn cli_exit_panic() -> Result<()> { 1455 let output = get_wasmtime_command()? 1456 .args(&["run", "-Wcomponent-model", CLI_EXIT_PANIC_COMPONENT]) 1457 .output()?; 1458 assert!(!output.status.success()); 1459 let stderr = String::from_utf8_lossy(&output.stderr); 1460 assert!(stderr.contains("Curiouser and curiouser!")); 1461 Ok(()) 1462 } 1463 1464 #[test] 1465 fn cli_directory_list() -> Result<()> { 1466 let dir = tempfile::tempdir()?; 1467 1468 std::fs::File::create(dir.path().join("foo.txt"))?; 1469 std::fs::File::create(dir.path().join("bar.txt"))?; 1470 std::fs::File::create(dir.path().join("baz.txt"))?; 1471 std::fs::create_dir(dir.path().join("sub"))?; 1472 std::fs::File::create(dir.path().join("sub").join("wow.txt"))?; 1473 std::fs::File::create(dir.path().join("sub").join("yay.txt"))?; 1474 1475 run_wasmtime(&[ 1476 "run", 1477 "-Wcomponent-model", 1478 &format!("--dir={}::/", dir.path().to_str().unwrap()), 1479 CLI_DIRECTORY_LIST_COMPONENT, 1480 ])?; 1481 Ok(()) 1482 } 1483 1484 #[test] 1485 fn cli_default_clocks() -> Result<()> { 1486 run_wasmtime(&["run", "-Wcomponent-model", CLI_DEFAULT_CLOCKS_COMPONENT])?; 1487 Ok(()) 1488 } 1489 1490 #[test] 1491 fn cli_export_cabi_realloc() -> Result<()> { 1492 run_wasmtime(&[ 1493 "run", 1494 "-Wcomponent-model", 1495 CLI_EXPORT_CABI_REALLOC_COMPONENT, 1496 ])?; 1497 Ok(()) 1498 } 1499 1500 #[test] 1501 fn run_wasi_http_component() -> Result<()> { 1502 let output = super::run_wasmtime_for_output( 1503 &[ 1504 "-Ccache=no", 1505 "-Wcomponent-model", 1506 "-Scli,http,preview2", 1507 HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT, 1508 ], 1509 None, 1510 )?; 1511 println!("{}", String::from_utf8_lossy(&output.stderr)); 1512 let stdout = String::from_utf8_lossy(&output.stdout); 1513 println!("{}", stdout); 1514 assert!(stdout.starts_with("Called _start\n")); 1515 assert!(stdout.ends_with("Done\n")); 1516 assert!(output.status.success()); 1517 Ok(()) 1518 } 1519 1520 // Test to ensure that prints in the guest aren't buffered on the host by 1521 // accident. The test here will print something without a newline and then 1522 // wait for input on stdin, and the test here is to ensure that the 1523 // character shows up here even as the guest is waiting on input via stdin. 1524 #[test] 1525 fn cli_stdio_write_flushes() -> Result<()> { 1526 fn run(args: &[&str]) -> Result<()> { 1527 println!("running {args:?}"); 1528 let mut child = get_wasmtime_command()? 1529 .args(args) 1530 .stdin(Stdio::piped()) 1531 .stdout(Stdio::piped()) 1532 .spawn()?; 1533 let mut stdout = child.stdout.take().unwrap(); 1534 let mut buf = [0; 10]; 1535 match stdout.read(&mut buf) { 1536 Ok(2) => assert_eq!(&buf[..2], b"> "), 1537 e => panic!("unexpected read result {e:?}"), 1538 } 1539 drop(stdout); 1540 drop(child.stdin.take().unwrap()); 1541 let status = child.wait()?; 1542 assert!(status.success()); 1543 Ok(()) 1544 } 1545 1546 run(&["run", "-Spreview2=n", CLI_STDIO_WRITE_FLUSHES])?; 1547 run(&["run", "-Spreview2=y", CLI_STDIO_WRITE_FLUSHES])?; 1548 run(&[ 1549 "run", 1550 "-Wcomponent-model", 1551 CLI_STDIO_WRITE_FLUSHES_COMPONENT, 1552 ])?; 1553 Ok(()) 1554 } 1555 1556 #[test] 1557 fn cli_no_tcp() -> Result<()> { 1558 let output = super::run_wasmtime_for_output( 1559 &[ 1560 "-Wcomponent-model", 1561 // Turn on network but turn off TCP 1562 "-Sinherit-network,tcp=no", 1563 CLI_NO_TCP_COMPONENT, 1564 ], 1565 None, 1566 )?; 1567 println!("{}", String::from_utf8_lossy(&output.stderr)); 1568 assert!(output.status.success()); 1569 Ok(()) 1570 } 1571 1572 #[test] 1573 fn cli_no_udp() -> Result<()> { 1574 let output = super::run_wasmtime_for_output( 1575 &[ 1576 "-Wcomponent-model", 1577 // Turn on network but turn off UDP 1578 "-Sinherit-network,udp=no", 1579 CLI_NO_UDP_COMPONENT, 1580 ], 1581 None, 1582 )?; 1583 println!("{}", String::from_utf8_lossy(&output.stderr)); 1584 assert!(output.status.success()); 1585 Ok(()) 1586 } 1587 1588 #[test] 1589 fn cli_no_ip_name_lookup() -> Result<()> { 1590 let output = super::run_wasmtime_for_output( 1591 &[ 1592 "-Wcomponent-model", 1593 // Turn on network but ensure name lookup is disabled 1594 "-Sinherit-network,allow-ip-name-lookup=no", 1595 CLI_NO_IP_NAME_LOOKUP_COMPONENT, 1596 ], 1597 None, 1598 )?; 1599 println!("{}", String::from_utf8_lossy(&output.stderr)); 1600 assert!(output.status.success()); 1601 Ok(()) 1602 } 1603 1604 #[test] 1605 fn cli_sleep() -> Result<()> { 1606 run_wasmtime(&["run", CLI_SLEEP])?; 1607 run_wasmtime(&["run", CLI_SLEEP_COMPONENT])?; 1608 Ok(()) 1609 } 1610 1611 /// Helper structure to manage an invocation of `wasmtime serve` 1612 struct WasmtimeServe { 1613 child: Option<Child>, 1614 addr: SocketAddr, 1615 } 1616 1617 impl WasmtimeServe { 1618 /// Creates a new server which will serve the wasm component pointed to 1619 /// by `wasm`. 1620 /// 1621 /// A `configure` callback is provided to specify how `wasmtime serve` 1622 /// will be invoked and configure arguments such as headers. 1623 fn new(wasm: &str, configure: impl FnOnce(&mut Command)) -> Result<WasmtimeServe> { 1624 // Spawn `wasmtime serve` on port 0 which will randomly assign it a 1625 // port. 1626 let mut cmd = super::get_wasmtime_command()?; 1627 cmd.stdin(Stdio::null()); 1628 cmd.stdout(Stdio::piped()); 1629 cmd.stderr(Stdio::piped()); 1630 cmd.arg("serve").arg("--addr=127.0.0.1:0").arg(wasm); 1631 configure(&mut cmd); 1632 let mut child = cmd.spawn()?; 1633 1634 // Read the first line of stderr which will say which address it's 1635 // listening on. 1636 // 1637 // NB: this intentionally discards any extra buffered data in the 1638 // `BufReader` once the newline is found. The server shouldn't print 1639 // anything interesting other than the address so once we get a line 1640 // all remaining output is left to be captured by future requests 1641 // send to the server. 1642 let mut line = String::new(); 1643 BufReader::new(child.stderr.as_mut().unwrap()).read_line(&mut line)?; 1644 let addr_start = line.find("127.0.0.1").unwrap(); 1645 let addr = &line[addr_start..]; 1646 let addr_end = addr.find("/").unwrap(); 1647 let addr = &addr[..addr_end]; 1648 Ok(WasmtimeServe { 1649 child: Some(child), 1650 addr: addr.parse().unwrap(), 1651 }) 1652 } 1653 1654 /// Completes this server gracefully by printing the output on failure. 1655 fn finish(mut self) -> Result<()> { 1656 let mut child = self.child.take().unwrap(); 1657 1658 // If the child process has already exited then collect the output 1659 // and test if it succeeded. Otherwise it's still running so kill it 1660 // and then reap it. Assume that if it's still running then the test 1661 // has otherwise passed so no need to print the output. 1662 if child.try_wait()?.is_some() { 1663 let output = child.wait_with_output()?; 1664 if output.status.success() { 1665 return Ok(()); 1666 } 1667 bail!("child failed {output:?}"); 1668 } else { 1669 child.kill()?; 1670 child.wait_with_output()?; 1671 } 1672 Ok(()) 1673 } 1674 1675 /// Send a request to this server and wait for the response. 1676 async fn send_request(&self, req: http::Request<String>) -> Result<http::Response<String>> { 1677 let tcp = TcpStream::connect(&self.addr) 1678 .await 1679 .context("failed to connect")?; 1680 let tcp = wasmtime_wasi_http::io::TokioIo::new(tcp); 1681 let (mut send, conn) = hyper::client::conn::http1::handshake(tcp) 1682 .await 1683 .context("failed http handshake")?; 1684 1685 let conn_task = tokio::task::spawn(conn); 1686 1687 let response = send 1688 .send_request(req) 1689 .await 1690 .context("error sending request")?; 1691 drop(send); 1692 let (parts, body) = response.into_parts(); 1693 1694 let body = body.collect().await.context("failed to read body")?; 1695 assert!(body.trailers().is_none()); 1696 let body = std::str::from_utf8(&body.to_bytes())?.to_string(); 1697 1698 conn_task.await??; 1699 1700 Ok(http::Response::from_parts(parts, body)) 1701 } 1702 } 1703 1704 // Don't leave child processes running by accident so kill the child process 1705 // if our server goes away. 1706 impl Drop for WasmtimeServe { 1707 fn drop(&mut self) { 1708 let mut child = match self.child.take() { 1709 Some(child) => child, 1710 None => return, 1711 }; 1712 if child.kill().is_err() { 1713 return; 1714 } 1715 let output = match child.wait_with_output() { 1716 Ok(output) => output, 1717 Err(_) => return, 1718 }; 1719 1720 println!("server status: {}", output.status); 1721 if !output.stdout.is_empty() { 1722 println!( 1723 "server stdout:\n{}", 1724 String::from_utf8_lossy(&output.stdout) 1725 ); 1726 } 1727 if !output.stderr.is_empty() { 1728 println!( 1729 "server stderr:\n{}", 1730 String::from_utf8_lossy(&output.stderr) 1731 ); 1732 } 1733 } 1734 } 1735 1736 #[tokio::test] 1737 async fn cli_serve_echo_env() -> Result<()> { 1738 let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| { 1739 cmd.arg("--env=FOO=bar"); 1740 cmd.arg("--env=BAR"); 1741 cmd.arg("-Scli"); 1742 cmd.env_remove("BAR"); 1743 })?; 1744 1745 let foo_env = server 1746 .send_request( 1747 hyper::Request::builder() 1748 .uri("http://localhost/") 1749 .header("env", "FOO") 1750 .body(String::new()) 1751 .context("failed to make request")?, 1752 ) 1753 .await?; 1754 1755 assert!(foo_env.status().is_success()); 1756 assert!(foo_env.body().is_empty()); 1757 let headers = foo_env.headers(); 1758 assert_eq!(headers.get("env"), Some(&HeaderValue::from_static("bar"))); 1759 1760 let bar_env = server 1761 .send_request( 1762 hyper::Request::builder() 1763 .uri("http://localhost/") 1764 .header("env", "BAR") 1765 .body(String::new()) 1766 .context("failed to make request")?, 1767 ) 1768 .await?; 1769 1770 assert!(bar_env.status().is_success()); 1771 assert!(bar_env.body().is_empty()); 1772 let headers = bar_env.headers(); 1773 assert_eq!(headers.get("env"), None); 1774 1775 server.finish()?; 1776 Ok(()) 1777 } 1778 } 1779 1780 #[test] 1781 fn settings_command() -> Result<()> { 1782 let output = run_wasmtime(&["settings"])?; 1783 assert!(output.contains("Cranelift settings for target")); 1784 Ok(()) 1785 } 1786