#![cfg(not(miri))] use anyhow::{bail, Result}; use std::fs::File; use std::io::Write; use std::path::Path; use std::process::{Command, Output, Stdio}; use tempfile::{NamedTempFile, TempDir}; // Run the wasmtime CLI with the provided args and return the `Output`. // If the `stdin` is `Some`, opens the file and redirects to the child's stdin. pub fn run_wasmtime_for_output(args: &[&str], stdin: Option<&Path>) -> Result { let mut cmd = get_wasmtime_command()?; cmd.args(args); if let Some(file) = stdin { cmd.stdin(File::open(file)?); } cmd.output().map_err(Into::into) } /// Get the Wasmtime CLI as a [Command]. pub fn get_wasmtime_command() -> Result { // Figure out the Wasmtime binary from the current executable. let runner = std::env::vars() .filter(|(k, _v)| k.starts_with("CARGO_TARGET") && k.ends_with("RUNNER")) .next(); let mut me = std::env::current_exe()?; me.pop(); // chop off the file name me.pop(); // chop off `deps` me.push("wasmtime"); // If we're running tests with a "runner" then we might be doing something // like cross-emulation, so spin up the emulator rather than the tests // itself, which may not be natively executable. let mut cmd = if let Some((_, runner)) = runner { let mut parts = runner.split_whitespace(); let mut cmd = Command::new(parts.next().unwrap()); for arg in parts { cmd.arg(arg); } cmd.arg(&me); cmd } else { Command::new(&me) }; // Ignore this if it's specified in the environment to allow tests to run in // "default mode" by default. cmd.env_remove("WASMTIME_NEW_CLI"); Ok(cmd) } // Run the wasmtime CLI with the provided args and, if it succeeds, return // the standard output in a `String`. fn run_wasmtime(args: &[&str]) -> Result { let output = run_wasmtime_for_output(args, None)?; if !output.status.success() { bail!( "Failed to execute wasmtime with: {:?}\n{}", args, String::from_utf8_lossy(&output.stderr) ); } Ok(String::from_utf8(output.stdout).unwrap()) } fn build_wasm(wat_path: impl AsRef) -> Result { let mut wasm_file = NamedTempFile::new()?; let wasm = wat::parse_file(wat_path)?; wasm_file.write(&wasm)?; Ok(wasm_file) } // Very basic use case: compile binary wasm file and run specific function with arguments. #[test] fn run_wasmtime_simple() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; run_wasmtime(&[ "run", "--invoke", "simple", "-Ccache=n", wasm.path().to_str().unwrap(), "4", ])?; Ok(()) } // Wasmtime shall fail when not enough arguments were provided. #[test] fn run_wasmtime_simple_fail_no_args() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; assert!( run_wasmtime(&[ "run", "-Ccache=n", "--invoke", "simple", wasm.path().to_str().unwrap(), ]) .is_err(), "shall fail" ); Ok(()) } #[test] fn run_coredump_smoketest() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/coredump_smoketest.wat")?; let coredump_file = NamedTempFile::new()?; let coredump_arg = format!("-Dcoredump={}", coredump_file.path().display()); let err = run_wasmtime(&[ "run", "--invoke", "a", "-Ccache=n", &coredump_arg, wasm.path().to_str().unwrap(), ]) .unwrap_err(); assert!(err.to_string().contains(&format!( "core dumped at {}", coredump_file.path().display() ))); Ok(()) } // Running simple wat #[test] fn run_wasmtime_simple_wat() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; run_wasmtime(&[ "run", "--invoke", "simple", "-Ccache=n", wasm.path().to_str().unwrap(), "4", ])?; assert_eq!( run_wasmtime(&[ "run", "--invoke", "get_f32", "-Ccache=n", wasm.path().to_str().unwrap(), ])?, "100\n" ); assert_eq!( run_wasmtime(&[ "run", "--invoke", "get_f64", "-Ccache=n", wasm.path().to_str().unwrap(), ])?, "100\n" ); Ok(()) } // Running a wat that traps. #[test] fn run_wasmtime_unreachable_wat() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/unreachable.wat")?; let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?; assert_ne!(output.stderr, b""); assert_eq!(output.stdout, b""); assert!(!output.status.success()); let code = output .status .code() .expect("wasmtime process should exit normally"); // Test for the specific error code Wasmtime uses to indicate a trap return. #[cfg(unix)] assert_eq!(code, 128 + libc::SIGABRT); #[cfg(windows)] assert_eq!(code, 3); Ok(()) } // Run a simple WASI hello world, snapshot0 edition. #[test] fn hello_wasi_snapshot0() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?; for preview2 in ["-Spreview2=n", "-Spreview2=y"] { let stdout = run_wasmtime(&["-Ccache=n", preview2, wasm.path().to_str().unwrap()])?; assert_eq!(stdout, "Hello, world!\n"); } Ok(()) } // Run a simple WASI hello world, snapshot1 edition. #[test] fn hello_wasi_snapshot1() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?; let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?; assert_eq!(stdout, "Hello, world!\n"); Ok(()) } #[test] fn timeout_in_start() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/iloop-start.wat")?; let output = run_wasmtime_for_output( &[ "run", "-Wtimeout=1ms", "-Ccache=n", wasm.path().to_str().unwrap(), ], None, )?; assert!(!output.status.success()); assert_eq!(output.stdout, b""); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("wasm trap: interrupt"), "bad stderr: {}", stderr ); Ok(()) } #[test] fn timeout_in_invoke() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/iloop-invoke.wat")?; let output = run_wasmtime_for_output( &[ "run", "-Wtimeout=1ms", "-Ccache=n", wasm.path().to_str().unwrap(), ], None, )?; assert!(!output.status.success()); assert_eq!(output.stdout, b""); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("wasm trap: interrupt"), "bad stderr: {}", stderr ); Ok(()) } // Exit with a valid non-zero exit code, snapshot0 edition. #[test] fn exit2_wasi_snapshot0() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot0.wat")?; for preview2 in ["-Spreview2=n", "-Spreview2=y"] { let output = run_wasmtime_for_output( &["-Ccache=n", preview2, wasm.path().to_str().unwrap()], None, )?; assert_eq!(output.status.code().unwrap(), 2); } Ok(()) } // Exit with a valid non-zero exit code, snapshot1 edition. #[test] fn exit2_wasi_snapshot1() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot1.wat")?; let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?; assert_eq!(output.status.code().unwrap(), 2); Ok(()) } // Exit with a valid non-zero exit code, snapshot0 edition. #[test] fn exit125_wasi_snapshot0() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot0.wat")?; for preview2 in ["-Spreview2=n", "-Spreview2=y"] { let output = run_wasmtime_for_output( &["-Ccache=n", preview2, wasm.path().to_str().unwrap()], None, )?; dbg!(&output); if cfg!(windows) { assert_eq!(output.status.code().unwrap(), 1); } else { assert_eq!(output.status.code().unwrap(), 125); } } Ok(()) } // Exit with a valid non-zero exit code, snapshot1 edition. #[test] fn exit125_wasi_snapshot1() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot1.wat")?; let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?; if cfg!(windows) { assert_eq!(output.status.code().unwrap(), 1); } else { assert_eq!(output.status.code().unwrap(), 125); } Ok(()) } // Exit with an invalid non-zero exit code, snapshot0 edition. #[test] fn exit126_wasi_snapshot0() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot0.wat")?; for preview2 in ["-Spreview2=n", "-Spreview2=y"] { let output = run_wasmtime_for_output( &["-Ccache=n", preview2, wasm.path().to_str().unwrap()], None, )?; assert_eq!(output.status.code().unwrap(), 1); assert!(output.stdout.is_empty()); assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status")); } Ok(()) } // Exit with an invalid non-zero exit code, snapshot1 edition. #[test] fn exit126_wasi_snapshot1() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot1.wat")?; let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?; assert_eq!(output.status.code().unwrap(), 1); assert!(output.stdout.is_empty()); assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status")); Ok(()) } // Run a minimal command program. #[test] fn minimal_command() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?; let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?; assert_eq!(stdout, ""); Ok(()) } // Run a minimal reactor program. #[test] fn minimal_reactor() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?; let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?; assert_eq!(stdout, ""); Ok(()) } // Attempt to call invoke on a command. #[test] fn command_invoke() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?; run_wasmtime(&[ "run", "--invoke", "_start", "-Ccache=n", wasm.path().to_str().unwrap(), ])?; Ok(()) } // Attempt to call invoke on a command. #[test] fn reactor_invoke() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?; run_wasmtime(&[ "run", "--invoke", "_initialize", "-Ccache=n", wasm.path().to_str().unwrap(), ])?; Ok(()) } // Run the greeter test, which runs a preloaded reactor and a command. #[test] fn greeter() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?; let stdout = run_wasmtime(&[ "run", "-Ccache=n", "--preload", "reactor=tests/all/cli_tests/greeter_reactor.wat", wasm.path().to_str().unwrap(), ])?; assert_eq!( stdout, "Hello _initialize\nHello _start\nHello greet\nHello done\n" ); Ok(()) } // Run the greeter test, but this time preload a command. #[test] fn greeter_preload_command() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/greeter_reactor.wat")?; let stdout = run_wasmtime(&[ "run", "-Ccache=n", "--preload", "reactor=tests/all/cli_tests/hello_wasi_snapshot1.wat", wasm.path().to_str().unwrap(), ])?; assert_eq!(stdout, "Hello _initialize\n"); Ok(()) } // Run the greeter test, which runs a preloaded reactor and a command. #[test] fn greeter_preload_callable_command() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?; let stdout = run_wasmtime(&[ "run", "-Ccache=n", "--preload", "reactor=tests/all/cli_tests/greeter_callable_command.wat", wasm.path().to_str().unwrap(), ])?; assert_eq!(stdout, "Hello _start\nHello callable greet\nHello done\n"); Ok(()) } // Ensure successful WASI exit call with FPR saving frames on stack for Windows x64 // See https://github.com/bytecodealliance/wasmtime/issues/1967 #[test] fn exit_with_saved_fprs() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/exit_with_saved_fprs.wat")?; let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?; assert_eq!(output.status.code().unwrap(), 0); assert!(output.stdout.is_empty()); Ok(()) } #[test] fn run_cwasm() -> Result<()> { let td = TempDir::new()?; let cwasm = td.path().join("foo.cwasm"); let stdout = run_wasmtime(&[ "compile", "tests/all/cli_tests/simple.wat", "-o", cwasm.to_str().unwrap(), ])?; assert_eq!(stdout, ""); let stdout = run_wasmtime(&["run", "--allow-precompiled", cwasm.to_str().unwrap()])?; assert_eq!(stdout, ""); Ok(()) } #[cfg(unix)] #[test] fn hello_wasi_snapshot0_from_stdin() -> Result<()> { // Run a simple WASI hello world, snapshot0 edition. // The module is piped from standard input. let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?; for preview2 in ["-Spreview2=n", "-Spreview2=y"] { let stdout = { let path = wasm.path(); let args: &[&str] = &["-Ccache=n", preview2, "-"]; let output = run_wasmtime_for_output(args, Some(path))?; if !output.status.success() { bail!( "Failed to execute wasmtime with: {:?}\n{}", args, String::from_utf8_lossy(&output.stderr) ); } Ok::<_, anyhow::Error>(String::from_utf8(output.stdout).unwrap()) }?; assert_eq!(stdout, "Hello, world!\n"); } Ok(()) } #[test] fn specify_env() -> Result<()> { // By default no env is inherited let output = get_wasmtime_command()? .args(&["run", "tests/all/cli_tests/print_env.wat"]) .env("THIS_WILL_NOT", "show up in the output") .output()?; assert!(output.status.success()); assert_eq!(String::from_utf8_lossy(&output.stdout), ""); // Specify a single env var let output = get_wasmtime_command()? .args(&[ "run", "--env", "FOO=bar", "tests/all/cli_tests/print_env.wat", ]) .output()?; assert!(output.status.success()); assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n"); // Inherit a single env var let output = get_wasmtime_command()? .args(&["run", "--env", "FOO", "tests/all/cli_tests/print_env.wat"]) .env("FOO", "bar") .output()?; assert!(output.status.success()); assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n"); // Inherit a nonexistent env var let output = get_wasmtime_command()? .args(&[ "run", "--env", "SURELY_THIS_ENV_VAR_DOES_NOT_EXIST_ANYWHERE_RIGHT", "tests/all/cli_tests/print_env.wat", ]) .output()?; assert!(!output.status.success()); Ok(()) } #[cfg(unix)] #[test] fn run_cwasm_from_stdin() -> Result<()> { use std::process::Stdio; let td = TempDir::new()?; let cwasm = td.path().join("foo.cwasm"); let stdout = run_wasmtime(&[ "compile", "tests/all/cli_tests/simple.wat", "-o", cwasm.to_str().unwrap(), ])?; assert_eq!(stdout, ""); // If stdin is literally the file itself then that should work let args: &[&str] = &["run", "--allow-precompiled", "-"]; let output = get_wasmtime_command()? .args(args) .stdin(File::open(&cwasm)?) .output()?; assert!(output.status.success(), "a file as stdin should work"); // If stdin is a pipe, that should also work let input = std::fs::read(&cwasm)?; let mut child = get_wasmtime_command()? .args(args) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()?; let mut stdin = child.stdin.take().unwrap(); let t = std::thread::spawn(move || { let _ = stdin.write_all(&input); }); let output = child.wait_with_output()?; assert!(output.status.success()); t.join().unwrap(); Ok(()) } #[cfg(feature = "wasi-threads")] #[test] fn run_threads() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/threads.wat")?; let stdout = run_wasmtime(&[ "run", "-Wthreads", "-Sthreads", "-Ccache=n", wasm.path().to_str().unwrap(), ])?; assert!( stdout == "Called _start\n\ Running wasi_thread_start\n\ Running wasi_thread_start\n\ Running wasi_thread_start\n\ Done\n" ); Ok(()) } #[cfg(feature = "wasi-threads")] #[test] fn run_simple_with_wasi_threads() -> Result<()> { // We expect to be able to run Wasm modules that do not have correct // wasi-thread entry points or imported shared memory as long as no threads // are spawned. let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; let stdout = run_wasmtime(&[ "run", "-Wthreads", "-Sthreads", "-Ccache=n", "--invoke", "simple", wasm.path().to_str().unwrap(), "4", ])?; assert_eq!(stdout, "4\n"); Ok(()) } #[test] fn wasm_flags() -> Result<()> { // Any argument after the wasm module should be interpreted as for the // command itself let stdout = run_wasmtime(&[ "run", "--", "tests/all/cli_tests/print-arguments.wat", "--argument", "-for", "the", "command", ])?; assert_eq!( stdout, "\ print-arguments.wat\n\ --argument\n\ -for\n\ the\n\ command\n\ " ); let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "-"])?; assert_eq!( stdout, "\ print-arguments.wat\n\ -\n\ " ); let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "--"])?; assert_eq!( stdout, "\ print-arguments.wat\n\ --\n\ " ); let stdout = run_wasmtime(&[ "run", "--", "tests/all/cli_tests/print-arguments.wat", "--", "--", "-a", "b", ])?; assert_eq!( stdout, "\ print-arguments.wat\n\ --\n\ --\n\ -a\n\ b\n\ " ); Ok(()) } #[test] fn name_same_as_builtin_command() -> Result<()> { // a bare subcommand shouldn't run successfully let output = get_wasmtime_command()? .current_dir("tests/all/cli_tests") .arg("run") .output()?; assert!(!output.status.success()); // a `--` prefix should let everything else get interpreted as a wasm // module and arguments, even if the module has a name like `run` let output = get_wasmtime_command()? .current_dir("tests/all/cli_tests") .arg("--") .arg("run") .output()?; assert!(output.status.success(), "expected success got {output:#?}"); // Passing options before the subcommand should work and doesn't require // `--` to disambiguate let output = get_wasmtime_command()? .current_dir("tests/all/cli_tests") .arg("-Ccache=n") .arg("run") .output()?; assert!(output.status.success(), "expected success got {output:#?}"); Ok(()) } #[test] #[cfg(unix)] fn run_just_stdin_argument() -> Result<()> { let output = get_wasmtime_command()? .arg("-") .stdin(File::open("tests/all/cli_tests/simple.wat")?) .output()?; assert!(output.status.success()); Ok(()) } #[test] fn wasm_flags_without_subcommand() -> Result<()> { let output = get_wasmtime_command()? .current_dir("tests/all/cli_tests/") .arg("print-arguments.wat") .arg("-foo") .arg("bar") .output()?; assert!(output.status.success()); assert_eq!( String::from_utf8_lossy(&output.stdout), "\ print-arguments.wat\n\ -foo\n\ bar\n\ " ); Ok(()) } #[test] fn wasi_misaligned_pointer() -> Result<()> { let output = get_wasmtime_command()? .arg("./tests/all/cli_tests/wasi_misaligned_pointer.wat") .output()?; assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("Pointer not aligned"), "bad stderr: {stderr}", ); Ok(()) } #[test] #[cfg_attr(not(feature = "component-model"), ignore)] fn hello_with_preview2() -> Result<()> { let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?; let stdout = run_wasmtime(&["-Ccache=n", "-Spreview2", wasm.path().to_str().unwrap()])?; assert_eq!(stdout, "Hello, world!\n"); Ok(()) } #[test] #[cfg_attr(not(feature = "component-model"), ignore)] fn component_missing_feature() -> Result<()> { let path = "tests/all/cli_tests/empty-component.wat"; let wasm = build_wasm(path)?; let output = get_wasmtime_command()? .arg("-Ccache=n") .arg(wasm.path()) .output()?; assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("cannot execute a component without `--wasm component-model`"), "bad stderr: {stderr}" ); // also tests with raw *.wat input let output = get_wasmtime_command()? .arg("-Ccache=n") .arg(path) .output()?; assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("cannot execute a component without `--wasm component-model`"), "bad stderr: {stderr}" ); Ok(()) } // If the text format is invalid then the filename should be mentioned in the // error message. #[test] fn bad_text_syntax() -> Result<()> { let output = get_wasmtime_command()? .arg("-Ccache=n") .arg("tests/all/cli_tests/bad-syntax.wat") .output()?; assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("--> tests/all/cli_tests/bad-syntax.wat"), "bad stderr: {stderr}" ); Ok(()) } #[test] #[cfg_attr(not(feature = "component-model"), ignore)] fn run_basic_component() -> Result<()> { let path = "tests/all/cli_tests/component-basic.wat"; let wasm = build_wasm(path)?; // Run both the `*.wasm` binary and the text format run_wasmtime(&[ "-Ccache=n", "-Wcomponent-model", wasm.path().to_str().unwrap(), ])?; run_wasmtime(&["-Ccache=n", "-Wcomponent-model", path])?; Ok(()) } #[test] #[cfg_attr(not(feature = "component-model"), ignore)] fn run_precompiled_component() -> Result<()> { let td = TempDir::new()?; let cwasm = td.path().join("component-basic.cwasm"); let stdout = run_wasmtime(&[ "compile", "tests/all/cli_tests/component-basic.wat", "-o", cwasm.to_str().unwrap(), "-Wcomponent-model", ])?; assert_eq!(stdout, ""); let stdout = run_wasmtime(&[ "run", "-Wcomponent-model", "--allow-precompiled", cwasm.to_str().unwrap(), ])?; assert_eq!(stdout, ""); Ok(()) } #[test] fn memory_growth_failure() -> Result<()> { let output = get_wasmtime_command()? .args(&[ "run", "-Wmemory64", "-Wtrap-on-grow-failure", "tests/all/cli_tests/memory-grow-failure.wat", ]) .output()?; assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("forcing a memory growth failure to be a trap"), "bad stderr: {stderr}" ); Ok(()) } #[test] fn table_growth_failure() -> Result<()> { let output = get_wasmtime_command()? .args(&[ "run", "-Wtrap-on-grow-failure", "tests/all/cli_tests/table-grow-failure.wat", ]) .output()?; assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("forcing trap when growing table"), "bad stderr: {stderr}" ); Ok(()) } #[test] fn table_growth_failure2() -> Result<()> { let output = get_wasmtime_command()? .args(&[ "run", "-Wtrap-on-grow-failure", "tests/all/cli_tests/table-grow-failure2.wat", ]) .output()?; assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("forcing a table growth failure to be a trap"), "bad stderr: {stderr}" ); Ok(()) } #[test] fn option_group_help() -> Result<()> { run_wasmtime(&["run", "-Whelp"])?; run_wasmtime(&["run", "-O", "help"])?; run_wasmtime(&["run", "--codegen", "help"])?; run_wasmtime(&["run", "--debug=help"])?; run_wasmtime(&["run", "-Shelp"])?; run_wasmtime(&["run", "-Whelp-long"])?; Ok(()) } #[test] fn option_group_comma_separated() -> Result<()> { run_wasmtime(&[ "run", "-Wrelaxed-simd,simd", "tests/all/cli_tests/simple.wat", ])?; Ok(()) } #[test] fn option_group_boolean_parsing() -> Result<()> { run_wasmtime(&["run", "-Wrelaxed-simd", "tests/all/cli_tests/simple.wat"])?; run_wasmtime(&["run", "-Wrelaxed-simd=n", "tests/all/cli_tests/simple.wat"])?; run_wasmtime(&["run", "-Wrelaxed-simd=y", "tests/all/cli_tests/simple.wat"])?; run_wasmtime(&["run", "-Wrelaxed-simd=no", "tests/all/cli_tests/simple.wat"])?; run_wasmtime(&[ "run", "-Wrelaxed-simd=yes", "tests/all/cli_tests/simple.wat", ])?; run_wasmtime(&[ "run", "-Wrelaxed-simd=true", "tests/all/cli_tests/simple.wat", ])?; run_wasmtime(&[ "run", "-Wrelaxed-simd=false", "tests/all/cli_tests/simple.wat", ])?; Ok(()) } #[test] fn preview2_stdin() -> Result<()> { let test = "tests/all/cli_tests/count-stdin.wat"; let cmd = || -> Result<_> { let mut cmd = get_wasmtime_command()?; cmd.arg("--invoke=count").arg("-Spreview2").arg(test); Ok(cmd) }; // read empty pipe is ok let output = cmd()?.output()?; assert!(output.status.success()); assert_eq!(String::from_utf8_lossy(&output.stdout), "0\n"); // read itself is ok let file = File::open(test)?; let size = file.metadata()?.len(); let output = cmd()?.stdin(File::open(test)?).output()?; assert!(output.status.success()); assert_eq!(String::from_utf8_lossy(&output.stdout), format!("{size}\n")); // read piped input ok is ok let mut child = cmd()? .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()?; let mut stdin = child.stdin.take().unwrap(); std::thread::spawn(move || { stdin.write_all(b"hello").unwrap(); }); let output = child.wait_with_output()?; assert!(output.status.success()); assert_eq!(String::from_utf8_lossy(&output.stdout), "5\n"); let count_up_to = |n: usize| -> Result<_> { let mut child = get_wasmtime_command()? .arg("--invoke=count-up-to") .arg("-Spreview2") .arg(test) .arg(n.to_string()) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()?; let mut stdin = child.stdin.take().unwrap(); let t = std::thread::spawn(move || { let mut written = 0; let bytes = [0; 64 * 1024]; loop { written += match stdin.write(&bytes) { Ok(n) => n, Err(_) => break written, }; } }); let output = child.wait_with_output()?; assert!(output.status.success()); let written = t.join().unwrap(); let read = String::from_utf8_lossy(&output.stdout) .trim() .parse::() .unwrap(); // The test reads in 1000 byte chunks so make sure that it doesn't read // more than 1000 bytes than requested. assert!(read < n + 1000, "test read too much {read}"); Ok(written) }; // wasmtime shouldn't eat information that the guest never actually tried to // read. // // NB: this may be a bit flaky. Exactly how much we wrote in the above // helper thread depends on how much the OS buffers for us. For now give // some some slop and assume that OSes are unlikely to buffer more than // that. let slop = 256 * 1024; for amt in [0, 100, 100_000] { let written = count_up_to(amt)?; assert!(written < slop + amt, "wrote too much {written}"); } Ok(()) } #[test] fn old_cli_warn_if_ambiguous_flags() -> Result<()> { // This is accepted in the old CLI parser and the new but it's interpreted // differently so a warning should be printed. let output = get_wasmtime_command()? .args(&["tests/all/cli_tests/simple.wat", "--invoke", "get_f32"]) .output()?; assert_eq!(String::from_utf8_lossy(&output.stdout), "100\n"); assert_eq!( String::from_utf8_lossy(&output.stderr), "\ warning: this CLI invocation of Wasmtime will be parsed differently in future Wasmtime versions -- see this online issue for more information: https://github.com/bytecodealliance/wasmtime/issues/7384 Wasmtime will now execute with the old (<= Wasmtime 13) CLI parsing, however this behavior can also be temporarily configured with an environment variable: - WASMTIME_NEW_CLI=0 to indicate old semantics are desired and silence this warning, or - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and use the latest behavior warning: using `--invoke` with a function that returns values is experimental and may break in the future " ); // Test disabling the warning let output = get_wasmtime_command()? .args(&["tests/all/cli_tests/simple.wat", "--invoke", "get_f32"]) .env("WASMTIME_NEW_CLI", "0") .output()?; assert_eq!(String::from_utf8_lossy(&output.stdout), "100\n"); assert_eq!( String::from_utf8_lossy(&output.stderr), "\ warning: using `--invoke` with a function that returns values is experimental and may break in the future " ); // Test forcing the new behavior where nothing happens because the file is // invoked with `--invoke` as its own argument. let output = get_wasmtime_command()? .args(&["tests/all/cli_tests/simple.wat", "--invoke", "get_f32"]) .env("WASMTIME_NEW_CLI", "1") .output()?; assert_eq!(String::from_utf8_lossy(&output.stdout), ""); assert_eq!(String::from_utf8_lossy(&output.stderr), ""); // This is unambiguous let output = get_wasmtime_command()? .args(&["--invoke", "get_f32", "tests/all/cli_tests/simple.wat"]) .output()?; assert_eq!(String::from_utf8_lossy(&output.stdout), "100\n"); assert_eq!( String::from_utf8_lossy(&output.stderr), "\ warning: using `--invoke` with a function that returns values is experimental and may break in the future " ); // This fails to parse in the old but succeeds in the new, so it should run // under the new semantics with no warning. let output = get_wasmtime_command()? .args(&["run", "tests/all/cli_tests/print-arguments.wat", "--arg"]) .output()?; assert_eq!( String::from_utf8_lossy(&output.stdout), "print-arguments.wat\n--arg\n" ); assert_eq!(String::from_utf8_lossy(&output.stderr), ""); // Old behavior can be forced however let output = get_wasmtime_command()? .args(&["run", "tests/all/cli_tests/print-arguments.wat", "--arg"]) .env("WASMTIME_NEW_CLI", "0") .output()?; assert!(!output.status.success()); // This works in both the old and the new, so no warnings let output = get_wasmtime_command()? .args(&["run", "tests/all/cli_tests/print-arguments.wat", "arg"]) .output()?; assert_eq!( String::from_utf8_lossy(&output.stdout), "print-arguments.wat\narg\n" ); assert_eq!(String::from_utf8_lossy(&output.stderr), ""); // This works in both the old and the new, so no warnings let output = get_wasmtime_command()? .args(&[ "run", "--", "tests/all/cli_tests/print-arguments.wat", "--arg", ]) .output()?; assert_eq!( String::from_utf8_lossy(&output.stdout), "print-arguments.wat\n--arg\n" ); assert_eq!(String::from_utf8_lossy(&output.stderr), ""); // Old flags still work, but with a warning let output = get_wasmtime_command()? .args(&[ "run", "--max-wasm-stack", "1000000", "tests/all/cli_tests/print-arguments.wat", ]) .output()?; assert_eq!( String::from_utf8_lossy(&output.stdout), "print-arguments.wat\n" ); assert_eq!( String::from_utf8_lossy(&output.stderr), "\ warning: this CLI invocation of Wasmtime is going to break in the future -- for more information see this issue online: https://github.com/bytecodealliance/wasmtime/issues/7384 Wasmtime will now execute with the old (<= Wasmtime 13) CLI parsing, however this behavior can also be temporarily configured with an environment variable: - WASMTIME_NEW_CLI=0 to indicate old semantics are desired and silence this warning, or - WASMTIME_NEW_CLI=1 to indicate new semantics are desired and see the error " ); // Old flags warning is suppressible. let output = get_wasmtime_command()? .args(&[ "run", "--max-wasm-stack", "1000000", "tests/all/cli_tests/print-arguments.wat", ]) .env("WASMTIME_NEW_CLI", "0") .output()?; assert_eq!( String::from_utf8_lossy(&output.stdout), "print-arguments.wat\n" ); assert_eq!(String::from_utf8_lossy(&output.stderr), ""); // the `--dir` flag prints no warning when used with `::` let dir = tempfile::tempdir()?; std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?; let output = get_wasmtime_command()? .args(&[ "run", &format!("--dir={}::/", dir.path().to_str().unwrap()), test_programs_artifacts::CLI_FILE_READ, ]) .output()?; assert_eq!(String::from_utf8_lossy(&output.stdout), ""); assert_eq!(String::from_utf8_lossy(&output.stderr), ""); Ok(()) } #[test] fn float_args() -> Result<()> { let result = run_wasmtime(&[ "--invoke", "echo_f32", "tests/all/cli_tests/simple.wat", "1.0", ])?; assert_eq!(result, "1\n"); let result = run_wasmtime(&[ "--invoke", "echo_f64", "tests/all/cli_tests/simple.wat", "1.1", ])?; assert_eq!(result, "1.1\n"); Ok(()) } mod test_programs { use super::{get_wasmtime_command, run_wasmtime}; use anyhow::Result; use std::io::{Read, Write}; use std::process::Stdio; use test_programs_artifacts::*; macro_rules! assert_test_exists { ($name:ident) => { #[allow(unused_imports)] use self::$name as _; }; } foreach_cli!(assert_test_exists); #[test] fn cli_hello_stdout() -> Result<()> { run_wasmtime(&[ "run", "-Wcomponent-model", CLI_HELLO_STDOUT_COMPONENT, "gussie", "sparky", "willa", ])?; Ok(()) } #[test] fn cli_args() -> Result<()> { run_wasmtime(&[ "run", "-Wcomponent-model", CLI_ARGS_COMPONENT, "hello", "this", "", "is an argument", "with 🚩 emoji", ])?; Ok(()) } #[test] fn cli_stdin() -> Result<()> { let mut child = get_wasmtime_command()? .args(&["run", "-Wcomponent-model", CLI_STDIN_COMPONENT]) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .stdin(Stdio::piped()) .spawn()?; child .stdin .take() .unwrap() .write_all(b"So rested he by the Tumtum tree") .unwrap(); let output = child.wait_with_output()?; println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); assert!(output.status.success()); Ok(()) } #[test] fn cli_splice_stdin() -> Result<()> { let mut child = get_wasmtime_command()? .args(&["run", "-Wcomponent-model", CLI_SPLICE_STDIN_COMPONENT]) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .stdin(Stdio::piped()) .spawn()?; let msg = "So rested he by the Tumtum tree"; child .stdin .take() .unwrap() .write_all(msg.as_bytes()) .unwrap(); let output = child.wait_with_output()?; assert!(output.status.success()); let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); if !stderr.is_empty() { eprintln!("{stderr}"); } assert_eq!( format!( "before splice\n{msg}\ncompleted splicing {} bytes\n", msg.as_bytes().len() ), stdout ); Ok(()) } #[test] fn cli_env() -> Result<()> { run_wasmtime(&[ "run", "-Wcomponent-model", "--env=frabjous=day", "--env=callooh=callay", CLI_ENV_COMPONENT, ])?; Ok(()) } #[test] fn cli_file_read() -> Result<()> { let dir = tempfile::tempdir()?; std::fs::write(dir.path().join("bar.txt"), b"And stood awhile in thought")?; run_wasmtime(&[ "run", "-Wcomponent-model", &format!("--dir={}::/", dir.path().to_str().unwrap()), CLI_FILE_READ_COMPONENT, ])?; Ok(()) } #[test] fn cli_file_append() -> Result<()> { let dir = tempfile::tempdir()?; std::fs::File::create(dir.path().join("bar.txt"))? .write_all(b"'Twas brillig, and the slithy toves.\n")?; run_wasmtime(&[ "run", "-Wcomponent-model", &format!("--dir={}::/", dir.path().to_str().unwrap()), CLI_FILE_APPEND_COMPONENT, ])?; let contents = std::fs::read(dir.path().join("bar.txt"))?; assert_eq!( std::str::from_utf8(&contents).unwrap(), "'Twas brillig, and the slithy toves.\n\ Did gyre and gimble in the wabe;\n\ All mimsy were the borogoves,\n\ And the mome raths outgrabe.\n" ); Ok(()) } #[test] fn cli_file_dir_sync() -> Result<()> { let dir = tempfile::tempdir()?; std::fs::File::create(dir.path().join("bar.txt"))? .write_all(b"'Twas brillig, and the slithy toves.\n")?; run_wasmtime(&[ "run", "-Wcomponent-model", &format!("--dir={}::/", dir.path().to_str().unwrap()), CLI_FILE_DIR_SYNC_COMPONENT, ])?; Ok(()) } #[test] fn cli_exit_success() -> Result<()> { run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_SUCCESS_COMPONENT])?; Ok(()) } #[test] fn cli_exit_default() -> Result<()> { run_wasmtime(&["run", "-Wcomponent-model", CLI_EXIT_DEFAULT_COMPONENT])?; Ok(()) } #[test] fn cli_exit_failure() -> Result<()> { let output = get_wasmtime_command()? .args(&["run", "-Wcomponent-model", CLI_EXIT_FAILURE_COMPONENT]) .output()?; assert!(!output.status.success()); assert_eq!(output.status.code(), Some(1)); Ok(()) } #[test] fn cli_exit_panic() -> Result<()> { let output = get_wasmtime_command()? .args(&["run", "-Wcomponent-model", CLI_EXIT_PANIC_COMPONENT]) .output()?; assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!(stderr.contains("Curiouser and curiouser!")); Ok(()) } #[test] fn cli_directory_list() -> Result<()> { let dir = tempfile::tempdir()?; std::fs::File::create(dir.path().join("foo.txt"))?; std::fs::File::create(dir.path().join("bar.txt"))?; std::fs::File::create(dir.path().join("baz.txt"))?; std::fs::create_dir(dir.path().join("sub"))?; std::fs::File::create(dir.path().join("sub").join("wow.txt"))?; std::fs::File::create(dir.path().join("sub").join("yay.txt"))?; run_wasmtime(&[ "run", "-Wcomponent-model", &format!("--dir={}::/", dir.path().to_str().unwrap()), CLI_DIRECTORY_LIST_COMPONENT, ])?; Ok(()) } #[test] fn cli_default_clocks() -> Result<()> { run_wasmtime(&["run", "-Wcomponent-model", CLI_DEFAULT_CLOCKS_COMPONENT])?; Ok(()) } #[test] fn cli_export_cabi_realloc() -> Result<()> { run_wasmtime(&[ "run", "-Wcomponent-model", CLI_EXPORT_CABI_REALLOC_COMPONENT, ])?; Ok(()) } #[test] fn run_wasi_http_component() -> Result<()> { let output = super::run_wasmtime_for_output( &[ "-Ccache=no", "-Wcomponent-model", "-Scommon,http,preview2", HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT, ], None, )?; println!("{}", String::from_utf8_lossy(&output.stderr)); let stdout = String::from_utf8_lossy(&output.stdout); println!("{}", stdout); assert!(stdout.starts_with("Called _start\n")); assert!(stdout.ends_with("Done\n")); assert!(output.status.success()); Ok(()) } // Test to ensure that prints in the guest aren't buffered on the host by // accident. The test here will print something without a newline and then // wait for input on stdin, and the test here is to ensure that the // character shows up here even as the guest is waiting on input via stdin. #[test] fn cli_stdio_write_flushes() -> Result<()> { fn run(args: &[&str]) -> Result<()> { println!("running {args:?}"); let mut child = get_wasmtime_command()? .args(args) .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; let mut stdout = child.stdout.take().unwrap(); let mut buf = [0; 10]; match stdout.read(&mut buf) { Ok(2) => assert_eq!(&buf[..2], b"> "), e => panic!("unexpected read result {e:?}"), } drop(stdout); drop(child.stdin.take().unwrap()); let status = child.wait()?; assert!(status.success()); Ok(()) } run(&["run", "-Spreview2=n", CLI_STDIO_WRITE_FLUSHES])?; run(&["run", "-Spreview2=y", CLI_STDIO_WRITE_FLUSHES])?; run(&[ "run", "-Wcomponent-model", CLI_STDIO_WRITE_FLUSHES_COMPONENT, ])?; Ok(()) } #[test] fn cli_no_tcp() -> Result<()> { let output = super::run_wasmtime_for_output( &[ "-Wcomponent-model", // Turn on network but turn off TCP "-Sinherit-network,tcp=no", CLI_NO_TCP_COMPONENT, ], None, )?; println!("{}", String::from_utf8_lossy(&output.stderr)); assert!(output.status.success()); Ok(()) } #[test] fn cli_no_udp() -> Result<()> { let output = super::run_wasmtime_for_output( &[ "-Wcomponent-model", // Turn on network but turn off UDP "-Sinherit-network,udp=no", CLI_NO_UDP_COMPONENT, ], None, )?; println!("{}", String::from_utf8_lossy(&output.stderr)); assert!(output.status.success()); Ok(()) } }