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