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 } 1534 1535 impl WasmtimeServe { 1536 /// Creates a new server which will serve the wasm component pointed to 1537 /// by `wasm`. 1538 /// 1539 /// A `configure` callback is provided to specify how `wasmtime serve` 1540 /// will be invoked and configure arguments such as headers. 1541 fn new(wasm: &str, configure: impl FnOnce(&mut Command)) -> Result<WasmtimeServe> { 1542 // Spawn `wasmtime serve` on port 0 which will randomly assign it a 1543 // port. 1544 let mut cmd = super::get_wasmtime_command()?; 1545 cmd.arg("serve").arg("--addr=127.0.0.1:0").arg(wasm); 1546 configure(&mut cmd); 1547 Self::spawn(&mut cmd) 1548 } 1549 1550 fn spawn(cmd: &mut Command) -> Result<WasmtimeServe> { 1551 cmd.stdin(Stdio::null()); 1552 cmd.stdout(Stdio::piped()); 1553 cmd.stderr(Stdio::piped()); 1554 let mut child = cmd.spawn()?; 1555 1556 // Read the first line of stderr which will say which address it's 1557 // listening on. 1558 // 1559 // NB: this intentionally discards any extra buffered data in the 1560 // `BufReader` once the newline is found. The server shouldn't print 1561 // anything interesting other than the address so once we get a line 1562 // all remaining output is left to be captured by future requests 1563 // send to the server. 1564 let mut line = String::new(); 1565 let mut reader = BufReader::new(child.stderr.take().unwrap()); 1566 reader.read_line(&mut line)?; 1567 1568 match line.find("127.0.0.1").and_then(|addr_start| { 1569 let addr = &line[addr_start..]; 1570 let addr_end = addr.find("/")?; 1571 addr[..addr_end].parse().ok() 1572 }) { 1573 Some(addr) => { 1574 assert!(reader.buffer().is_empty()); 1575 child.stderr = Some(reader.into_inner()); 1576 Ok(WasmtimeServe { 1577 child: Some(child), 1578 addr, 1579 }) 1580 } 1581 None => { 1582 child.kill()?; 1583 child.wait()?; 1584 reader.read_to_string(&mut line)?; 1585 bail!("failed to start child: {line}") 1586 } 1587 } 1588 } 1589 1590 /// Completes this server gracefully by printing the output on failure. 1591 fn finish(mut self) -> Result<(String, String)> { 1592 let mut child = self.child.take().unwrap(); 1593 1594 // If the child process has already exited then collect the output 1595 // and test if it succeeded. Otherwise it's still running so kill it 1596 // and then reap it. Assume that if it's still running then the test 1597 // has otherwise passed so no need to print the output. 1598 let known_failure = if child.try_wait()?.is_some() { 1599 false 1600 } else { 1601 child.kill()?; 1602 true 1603 }; 1604 let output = child.wait_with_output()?; 1605 if !known_failure && !output.status.success() { 1606 bail!("child failed {output:?}"); 1607 } 1608 1609 Ok(( 1610 String::from_utf8_lossy(&output.stdout).into_owned(), 1611 String::from_utf8_lossy(&output.stderr).into_owned(), 1612 )) 1613 } 1614 1615 /// Send a request to this server and wait for the response. 1616 async fn send_request(&self, req: http::Request<String>) -> Result<http::Response<String>> { 1617 let (mut send, conn_task) = self.start_requests().await?; 1618 1619 let response = send 1620 .send_request(req) 1621 .await 1622 .context("error sending request")?; 1623 drop(send); 1624 let (parts, body) = response.into_parts(); 1625 1626 let body = body.collect().await.context("failed to read body")?; 1627 assert!(body.trailers().is_none()); 1628 let body = std::str::from_utf8(&body.to_bytes())?.to_string(); 1629 1630 conn_task.await??; 1631 1632 Ok(http::Response::from_parts(parts, body)) 1633 } 1634 1635 async fn start_requests( 1636 &self, 1637 ) -> Result<( 1638 hyper::client::conn::http1::SendRequest<String>, 1639 tokio::task::JoinHandle<hyper::Result<()>>, 1640 )> { 1641 let tcp = TcpStream::connect(&self.addr) 1642 .await 1643 .context("failed to connect")?; 1644 let tcp = wasmtime_wasi_http::io::TokioIo::new(tcp); 1645 let (send, conn) = hyper::client::conn::http1::handshake(tcp) 1646 .await 1647 .context("failed http handshake")?; 1648 Ok((send, tokio::task::spawn(conn))) 1649 } 1650 } 1651 1652 // Don't leave child processes running by accident so kill the child process 1653 // if our server goes away. 1654 impl Drop for WasmtimeServe { 1655 fn drop(&mut self) { 1656 let mut child = match self.child.take() { 1657 Some(child) => child, 1658 None => return, 1659 }; 1660 if child.kill().is_err() { 1661 return; 1662 } 1663 let output = match child.wait_with_output() { 1664 Ok(output) => output, 1665 Err(_) => return, 1666 }; 1667 1668 println!("server status: {}", output.status); 1669 if !output.stdout.is_empty() { 1670 println!( 1671 "server stdout:\n{}", 1672 String::from_utf8_lossy(&output.stdout) 1673 ); 1674 } 1675 if !output.stderr.is_empty() { 1676 println!( 1677 "server stderr:\n{}", 1678 String::from_utf8_lossy(&output.stderr) 1679 ); 1680 } 1681 } 1682 } 1683 1684 #[tokio::test] 1685 async fn cli_serve_echo_env() -> Result<()> { 1686 let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| { 1687 cmd.arg("--env=FOO=bar"); 1688 cmd.arg("--env=BAR"); 1689 cmd.arg("-Scli"); 1690 cmd.env_remove("BAR"); 1691 })?; 1692 1693 let foo_env = server 1694 .send_request( 1695 hyper::Request::builder() 1696 .uri("http://localhost/") 1697 .header("env", "FOO") 1698 .body(String::new()) 1699 .context("failed to make request")?, 1700 ) 1701 .await?; 1702 1703 assert!(foo_env.status().is_success()); 1704 assert!(foo_env.body().is_empty()); 1705 let headers = foo_env.headers(); 1706 assert_eq!(headers.get("env"), Some(&HeaderValue::from_static("bar"))); 1707 1708 let bar_env = server 1709 .send_request( 1710 hyper::Request::builder() 1711 .uri("http://localhost/") 1712 .header("env", "BAR") 1713 .body(String::new()) 1714 .context("failed to make request")?, 1715 ) 1716 .await?; 1717 1718 assert!(bar_env.status().is_success()); 1719 assert!(bar_env.body().is_empty()); 1720 let headers = bar_env.headers(); 1721 assert_eq!(headers.get("env"), None); 1722 1723 server.finish()?; 1724 Ok(()) 1725 } 1726 1727 #[tokio::test] 1728 async fn cli_serve_outgoing_body_config() -> Result<()> { 1729 let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| { 1730 cmd.arg("-Scli"); 1731 cmd.arg("-Shttp-outgoing-body-buffer-chunks=2"); 1732 cmd.arg("-Shttp-outgoing-body-chunk-size=1024"); 1733 })?; 1734 1735 let resp = server 1736 .send_request( 1737 hyper::Request::builder() 1738 .uri("http://localhost/") 1739 .header("env", "FOO") 1740 .body(String::new()) 1741 .context("failed to make request")?, 1742 ) 1743 .await?; 1744 1745 assert!(resp.status().is_success()); 1746 1747 server.finish()?; 1748 Ok(()) 1749 } 1750 1751 #[tokio::test] 1752 #[ignore] // TODO: printing stderr in the child and killing the child at the 1753 // end of this test race so the stderr may be present or not. Need 1754 // to implement a more graceful shutdown routine for `wasmtime 1755 // serve`. 1756 async fn cli_serve_respect_pooling_options() -> Result<()> { 1757 let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| { 1758 cmd.arg("-Opooling-total-memories=0").arg("-Scli"); 1759 })?; 1760 1761 let result = server 1762 .send_request( 1763 hyper::Request::builder() 1764 .uri("http://localhost/") 1765 .header("env", "FOO") 1766 .body(String::new()) 1767 .context("failed to make request")?, 1768 ) 1769 .await; 1770 assert!(result.is_err()); 1771 let (_, stderr) = server.finish()?; 1772 assert!( 1773 stderr.contains("maximum concurrent memory limit of 0 reached"), 1774 "bad stderr: {stderr}", 1775 ); 1776 Ok(()) 1777 } 1778 1779 #[test] 1780 fn cli_large_env() -> Result<()> { 1781 for wasm in [CLI_LARGE_ENV, CLI_LARGE_ENV_COMPONENT] { 1782 println!("run {wasm:?}"); 1783 let mut cmd = get_wasmtime_command()?; 1784 cmd.arg("run").arg("-Sinherit-env").arg(wasm); 1785 1786 let debug_cmd = format!("{cmd:?}"); 1787 for i in 0..512 { 1788 let var = format!("KEY{i}"); 1789 let val = (0..1024).map(|_| 'x').collect::<String>(); 1790 cmd.env(&var, &val); 1791 } 1792 let output = cmd.output()?; 1793 if !output.status.success() { 1794 bail!( 1795 "Failed to execute wasmtime with: {debug_cmd}\n{}", 1796 String::from_utf8_lossy(&output.stderr) 1797 ); 1798 } 1799 } 1800 Ok(()) 1801 } 1802 1803 #[tokio::test] 1804 async fn cli_serve_only_one_process_allowed() -> Result<()> { 1805 let wasm = CLI_SERVE_ECHO_ENV_COMPONENT; 1806 let server = WasmtimeServe::new(wasm, |cmd| { 1807 cmd.arg("-Scli"); 1808 })?; 1809 1810 let err = WasmtimeServe::spawn( 1811 super::get_wasmtime_command()? 1812 .arg("serve") 1813 .arg("-Scli") 1814 .arg(format!("--addr={}", server.addr)) 1815 .arg(wasm), 1816 ) 1817 .err() 1818 .expect("server spawn should have failed but it succeeded"); 1819 drop(server); 1820 1821 let err = format!("{err:?}"); 1822 println!("{err}"); 1823 assert!(err.contains("os error")); 1824 Ok(()) 1825 } 1826 1827 // Technically this test is a little racy. This binds port 0 to acquire a 1828 // random port, issues a single request to this port, but then kills this 1829 // server while the request is still processing. The port is then rebound 1830 // in the next process while it technically could be stolen by another 1831 // process. 1832 #[tokio::test] 1833 async fn cli_serve_quick_rebind_allowed() -> Result<()> { 1834 let wasm = CLI_SERVE_ECHO_ENV_COMPONENT; 1835 let server = WasmtimeServe::new(wasm, |cmd| { 1836 cmd.arg("-Scli"); 1837 })?; 1838 let addr = server.addr; 1839 1840 // Start up a `send` and `conn_task` which represents a connection to 1841 // this server. 1842 let (mut send, conn_task) = server.start_requests().await?; 1843 let _ = send 1844 .send_request( 1845 hyper::Request::builder() 1846 .uri("http://localhost/") 1847 .header("env", "FOO") 1848 .body(String::new()) 1849 .context("failed to make request")?, 1850 ) 1851 .await; 1852 1853 // ... once a response has been received (or at least the status 1854 // code/headers) then kill the server. THis is done while `conn_task` 1855 // and `send` are still alive so we're guaranteed that the other side 1856 // got a request (we got a response) and our connection is still open. 1857 // 1858 // This forces the address/port into the `TIME_WAIT` state. The rebind 1859 // below in the next process will fail if `SO_REUSEADDR` isn't set. 1860 drop(server); 1861 drop(send); 1862 let _ = conn_task.await; 1863 1864 // If this is successfully bound then we'll create `WasmtimeServe` 1865 // which reads off the first line of output to know which address was 1866 // bound. 1867 let _server2 = WasmtimeServe::spawn( 1868 super::get_wasmtime_command()? 1869 .arg("serve") 1870 .arg("-Scli") 1871 .arg(format!("--addr={addr}")) 1872 .arg(wasm), 1873 )?; 1874 1875 Ok(()) 1876 } 1877 1878 #[tokio::test] 1879 async fn cli_serve_with_print() -> Result<()> { 1880 let server = WasmtimeServe::new(CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| { 1881 cmd.arg("-Scli"); 1882 })?; 1883 1884 for _ in 0..2 { 1885 let resp = server 1886 .send_request( 1887 hyper::Request::builder() 1888 .uri("http://localhost/") 1889 .body(String::new()) 1890 .context("failed to make request")?, 1891 ) 1892 .await?; 1893 assert!(resp.status().is_success()); 1894 } 1895 1896 let (out, err) = server.finish()?; 1897 assert_eq!( 1898 out, 1899 "\ 1900 stdout [0] :: this is half a print to stdout 1901 stdout [0] :: \n\ 1902 stdout [0] :: after empty 1903 stdout [1] :: this is half a print to stdout 1904 stdout [1] :: \n\ 1905 stdout [1] :: after empty 1906 " 1907 ); 1908 assert_eq!( 1909 err, 1910 "\ 1911 stderr [0] :: this is half a print to stderr 1912 stderr [0] :: \n\ 1913 stderr [0] :: after empty 1914 stderr [1] :: this is half a print to stderr 1915 stderr [1] :: \n\ 1916 stderr [1] :: after empty 1917 " 1918 ); 1919 1920 Ok(()) 1921 } 1922 1923 #[tokio::test] 1924 async fn cli_serve_with_print_no_prefix() -> Result<()> { 1925 let server = WasmtimeServe::new(CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| { 1926 cmd.arg("-Scli"); 1927 cmd.arg("--no-logging-prefix"); 1928 })?; 1929 1930 for _ in 0..2 { 1931 let resp = server 1932 .send_request( 1933 hyper::Request::builder() 1934 .uri("http://localhost/") 1935 .body(String::new()) 1936 .context("failed to make request")?, 1937 ) 1938 .await?; 1939 assert!(resp.status().is_success()); 1940 } 1941 1942 let (out, err) = server.finish()?; 1943 assert_eq!( 1944 out, 1945 "\ 1946 this is half a print to stdout 1947 \n\ 1948 after empty 1949 this is half a print to stdout 1950 \n\ 1951 after empty 1952 " 1953 ); 1954 assert_eq!( 1955 err, 1956 "\ 1957 this is half a print to stderr 1958 \n\ 1959 after empty 1960 this is half a print to stderr 1961 \n\ 1962 after empty 1963 " 1964 ); 1965 1966 Ok(()) 1967 } 1968 1969 #[tokio::test] 1970 async fn cli_serve_authority_and_scheme() -> Result<()> { 1971 let server = WasmtimeServe::new(CLI_SERVE_AUTHORITY_AND_SCHEME_COMPONENT, |cmd| { 1972 cmd.arg("-Scli"); 1973 })?; 1974 1975 let resp = server 1976 .send_request( 1977 hyper::Request::builder() 1978 .uri("/") 1979 .header("Host", "localhost") 1980 .body(String::new()) 1981 .context("failed to make request")?, 1982 ) 1983 .await?; 1984 assert!(resp.status().is_success()); 1985 1986 let resp = server 1987 .send_request( 1988 hyper::Request::builder() 1989 .method("CONNECT") 1990 .uri("http://localhost/") 1991 .body(String::new()) 1992 .context("failed to make request")?, 1993 ) 1994 .await?; 1995 assert!(resp.status().is_success()); 1996 1997 Ok(()) 1998 } 1999 2000 #[test] 2001 fn cli_argv0() -> Result<()> { 2002 run_wasmtime(&["run", "--argv0=a", CLI_ARGV0, "a"])?; 2003 run_wasmtime(&["run", "--argv0=b", CLI_ARGV0_COMPONENT, "b"])?; 2004 run_wasmtime(&["run", "--argv0=foo.wasm", CLI_ARGV0, "foo.wasm"])?; 2005 Ok(()) 2006 } 2007 2008 #[tokio::test] 2009 async fn cli_serve_config() -> Result<()> { 2010 let server = WasmtimeServe::new(CLI_SERVE_CONFIG_COMPONENT, |cmd| { 2011 cmd.arg("-Scli"); 2012 cmd.arg("-Sconfig"); 2013 cmd.arg("-Sconfig-var=hello=world"); 2014 })?; 2015 2016 let resp = server 2017 .send_request( 2018 hyper::Request::builder() 2019 .uri("http://localhost/") 2020 .body(String::new()) 2021 .context("failed to make request")?, 2022 ) 2023 .await?; 2024 2025 assert!(resp.status().is_success()); 2026 assert_eq!(resp.body(), "world"); 2027 Ok(()) 2028 } 2029 2030 #[test] 2031 fn cli_config() -> Result<()> { 2032 run_wasmtime(&[ 2033 "run", 2034 "-Sconfig", 2035 "-Sconfig-var=hello=world", 2036 CONFIG_GET_COMPONENT, 2037 ])?; 2038 Ok(()) 2039 } 2040 2041 #[tokio::test] 2042 async fn cli_serve_keyvalue() -> Result<()> { 2043 let server = WasmtimeServe::new(CLI_SERVE_KEYVALUE_COMPONENT, |cmd| { 2044 cmd.arg("-Scli"); 2045 cmd.arg("-Skeyvalue"); 2046 cmd.arg("-Skeyvalue-in-memory-data=hello=world"); 2047 })?; 2048 2049 let resp = server 2050 .send_request( 2051 hyper::Request::builder() 2052 .uri("http://localhost/") 2053 .body(String::new()) 2054 .context("failed to make request")?, 2055 ) 2056 .await?; 2057 2058 assert!(resp.status().is_success()); 2059 assert_eq!(resp.body(), "world"); 2060 Ok(()) 2061 } 2062 2063 #[test] 2064 fn cli_keyvalue() -> Result<()> { 2065 run_wasmtime(&[ 2066 "run", 2067 "-Skeyvalue", 2068 "-Skeyvalue-in-memory-data=atomics_key=5", 2069 KEYVALUE_MAIN_COMPONENT, 2070 ])?; 2071 Ok(()) 2072 } 2073 2074 #[test] 2075 fn cli_multiple_preopens() -> Result<()> { 2076 run_wasmtime(&[ 2077 "run", 2078 "--dir=/::/a", 2079 "--dir=/::/b", 2080 "--dir=/::/c", 2081 CLI_MULTIPLE_PREOPENS_COMPONENT, 2082 ])?; 2083 Ok(()) 2084 } 2085 } 2086 2087 #[test] 2088 fn settings_command() -> Result<()> { 2089 // Skip this test on platforms that Cranelift doesn't support. 2090 if cranelift_native::builder().is_err() { 2091 return Ok(()); 2092 } 2093 let output = run_wasmtime(&["settings"])?; 2094 assert!(output.contains("Cranelift settings for target")); 2095 Ok(()) 2096 } 2097 2098 #[cfg(target_arch = "x86_64")] 2099 #[test] 2100 fn profile_with_vtune() -> Result<()> { 2101 if !is_vtune_available() { 2102 println!("> `vtune` is not available on the system path; skipping test"); 2103 return Ok(()); 2104 } 2105 2106 let mut bin = Command::new("vtune"); 2107 bin.args(&[ 2108 // Configure VTune... 2109 "-verbose", 2110 "-collect", 2111 "hotspots", 2112 "-user-data-dir", 2113 &std::env::temp_dir().to_string_lossy(), 2114 // ...then run Wasmtime with profiling enabled: 2115 &get_wasmtime_path()?.to_string_lossy(), 2116 "--profile=vtune", 2117 "tests/all/cli_tests/simple.wat", 2118 ]); 2119 2120 println!("> executing: {bin:?}"); 2121 let output = bin.output()?; 2122 2123 assert!(output.status.success()); 2124 let stdout = String::from_utf8_lossy(&output.stdout); 2125 let stderr = String::from_utf8_lossy(&output.stderr); 2126 println!("> stdout:\n{stdout}"); 2127 assert!(stdout.contains("CPU Time")); 2128 println!("> stderr:\n{stderr}"); 2129 assert!(!stderr.contains("Error")); 2130 Ok(()) 2131 } 2132 2133 #[cfg(target_arch = "x86_64")] 2134 fn is_vtune_available() -> bool { 2135 Command::new("vtune").arg("-version").output().is_ok() 2136 } 2137 2138 #[test] 2139 fn unreachable_without_wasi() -> Result<()> { 2140 let output = run_wasmtime_for_output( 2141 &[ 2142 "-Scli=n", 2143 "-Ccache=n", 2144 "tests/all/cli_tests/unreachable.wat", 2145 ], 2146 None, 2147 )?; 2148 2149 assert_ne!(output.stderr, b""); 2150 assert_eq!(output.stdout, b""); 2151 assert_trap_code(&output.status); 2152 Ok(()) 2153 } 2154 2155 #[test] 2156 fn config_cli_flag() -> Result<()> { 2157 let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; 2158 2159 // Test some valid TOML values 2160 let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts(); 2161 cfg.write_all( 2162 br#" 2163 [optimize] 2164 opt_level = 2 2165 regalloc_algorithm = "single-pass" 2166 signals_based_traps = false 2167 2168 [codegen] 2169 collector = "null" 2170 2171 [debug] 2172 debug_info = true 2173 2174 [wasm] 2175 max_wasm_stack = 65536 2176 2177 [wasi] 2178 cli = true 2179 "#, 2180 )?; 2181 let output = run_wasmtime(&[ 2182 "run", 2183 "--config", 2184 cfg_path.to_str().unwrap(), 2185 "--invoke", 2186 "get_f64", 2187 wasm.path().to_str().unwrap(), 2188 ])?; 2189 assert_eq!(output, "100\n"); 2190 2191 // Make sure CLI flags overrides TOML values 2192 let output = run_wasmtime(&[ 2193 "run", 2194 "--config", 2195 cfg_path.to_str().unwrap(), 2196 "--invoke", 2197 "get_f64", 2198 "-W", 2199 "max-wasm-stack=0", // should override TOML value 65536 specified above and execution should fail 2200 wasm.path().to_str().unwrap(), 2201 ]); 2202 assert!( 2203 output 2204 .as_ref() 2205 .unwrap_err() 2206 .to_string() 2207 .contains("max_wasm_stack size cannot be zero"), 2208 "'{output:?}' did not contain expected error message", 2209 ); 2210 2211 // Test invalid TOML key 2212 let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts(); 2213 cfg.write_all( 2214 br#" 2215 [optimize] 2216 this_key_does_not_exist = true 2217 "#, 2218 )?; 2219 let output = run_wasmtime(&[ 2220 "run", 2221 "--config", 2222 cfg_path.to_str().unwrap(), 2223 wasm.path().to_str().unwrap(), 2224 ]); 2225 assert!( 2226 output 2227 .as_ref() 2228 .unwrap_err() 2229 .to_string() 2230 .contains("unknown field `this_key_does_not_exist`"), 2231 "'{output:?}' did not contain expected error message" 2232 ); 2233 2234 // Test invalid TOML table 2235 let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts(); 2236 cfg.write_all( 2237 br#" 2238 [invalid_table] 2239 "#, 2240 )?; 2241 let output = run_wasmtime(&[ 2242 "run", 2243 "--config", 2244 cfg_path.to_str().unwrap(), 2245 wasm.path().to_str().unwrap(), 2246 ]); 2247 assert!( 2248 output 2249 .as_ref() 2250 .unwrap_err() 2251 .to_string() 2252 .contains("unknown field `invalid_table`, expected one of `optimize`, `codegen`, `debug`, `wasm`, `wasi`"), 2253 "'{output:?}' did not contain expected error message", 2254 ); 2255 2256 Ok(()) 2257 } 2258