#![cfg(not(miri))]
use anyhow::{Result, bail};
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::process::{Command, ExitStatus, Output, Stdio};
use tempfile::{NamedTempFile, TempDir};
pub fn run_wasmtime_for_output(args: &[&str], stdin: Option<&Path>) -> Result<Output> {
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)
}
pub fn get_wasmtime_command() -> Result<Command> {
let mut cmd = wasmtime_test_util::command(get_wasmtime_path());
cmd.env_remove("WASMTIME_NEW_CLI");
Ok(cmd)
}
fn get_wasmtime_path() -> &'static str {
env!("CARGO_BIN_EXE_wasmtime")
}
pub fn run_wasmtime(args: &[&str]) -> Result<String> {
let output = run_wasmtime_for_output(args, None)?;
if !output.status.success() {
bail!(
"Failed to execute wasmtime with: {:?}\nstatus: {}\n{}",
args,
output.status,
String::from_utf8_lossy(&output.stderr)
);
}
Ok(String::from_utf8(output.stdout).unwrap())
}
fn build_wasm(wat_path: impl AsRef<Path>) -> Result<NamedTempFile> {
let mut wasm_file = NamedTempFile::new()?;
let wasm = wat::parse_file(wat_path)?;
wasm_file.write(&wasm)?;
Ok(wasm_file)
}
#[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(())
}
#[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(())
}
#[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(())
}
#[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_trap_code(&output.status);
Ok(())
}
fn assert_trap_code(status: &ExitStatus) {
let code = status
.code()
.expect("wasmtime process should exit normally");
#[cfg(unix)]
assert_eq!(code, 128 + libc::SIGABRT);
#[cfg(windows)]
assert_eq!(code, 3);
}
#[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(())
}
#[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(())
}
#[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(())
}
#[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(())
}
#[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);
assert_eq!(output.status.code().unwrap(), 125);
}
Ok(())
}
#[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)?;
assert_eq!(output.status.code().unwrap(), 125);
Ok(())
}
#[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(())
}
#[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(())
}
#[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(())
}
#[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(())
}
#[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(())
}
#[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(())
}
#[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(())
}
#[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(())
}
#[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(())
}
#[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<()> {
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<()> {
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), "");
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");
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");
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());
let output = get_wasmtime_command()?
.args(&["run", "-Sinherit-env", "tests/all/cli_tests/print_env.wat"])
.env("FOO", "bar")
.output()?;
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("FOO=bar"), "bad output: {stdout}");
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, "");
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");
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<()> {
if crate::threads::engine().is_none() || cfg!(asan) {
return Ok(());
}
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<()> {
if crate::threads::engine().is_none() {
return Ok(());
}
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<()> {
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<()> {
let output = get_wasmtime_command()?
.current_dir("tests/all/cli_tests")
.arg("run")
.output()?;
assert!(!output.status.success());
let output = get_wasmtime_command()?
.current_dir("tests/all/cli_tests")
.arg("--")
.arg("run")
.output()?;
assert!(output.status.success(), "expected success got {output:#?}");
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("-Wcomponent-model=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}"
);
let output = get_wasmtime_command()?
.arg("-Ccache=n")
.arg("-Wcomponent-model=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(())
}
#[test]
#[cfg_attr(not(feature = "component-model"), ignore)]
fn component_enabled_by_default() -> Result<()> {
let path = "tests/all/cli_tests/component-basic.wat";
let wasm = build_wasm(path)?;
let output = get_wasmtime_command()?
.arg("-Ccache=n")
.arg(wasm.path())
.output()?;
assert!(output.status.success());
let output = get_wasmtime_command()?
.arg("-Ccache=n")
.arg(path)
.output()?;
assert!(output.status.success());
Ok(())
}
#[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_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]
#[cfg(not(target_arch = "s390x"))]
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);
let expected = if cfg!(target_pointer_width = "32") {
"overflow calculating new table size"
} else {
"forcing trap when growing table to 4294967296 elements"
};
assert!(stderr.contains(expected), "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)
};
let output = cmd()?.output()?;
assert!(output.status.success());
assert_eq!(String::from_utf8_lossy(&output.stdout), "0\n");
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"));
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::<usize>()
.unwrap();
assert!(read < n + 1000, "test read too much {read}");
Ok(written)
};
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 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(())
}
#[test]
fn mpk_without_pooling() -> Result<()> {
let output = get_wasmtime_command()?
.args(&[
"run",
"-O",
"memory-protection-keys=y",
"--invoke",
"echo_f32",
"tests/all/cli_tests/simple.wat",
"1.0",
])
.env("WASMTIME_NEW_CLI", "1")
.output()?;
assert!(!output.status.success());
Ok(())
}
#[test]
fn increase_stack_size() -> Result<()> {
run_wasmtime(&[
"run",
"--invoke",
"simple",
&format!("-Wmax-wasm-stack={}", 5 << 20),
"-Ccache=n",
"tests/all/cli_tests/simple.wat",
"4",
])?;
Ok(())
}
mod test_programs {
use super::{get_wasmtime_command, run_wasmtime};
use anyhow::{Context, Result, bail};
use http_body_util::BodyExt;
use hyper::header::HeaderValue;
use std::io::{BufRead, BufReader, Read, Write};
use std::iter;
use std::net::SocketAddr;
use std::process::{Child, Command, Stdio};
use test_programs_artifacts::*;
use tokio::net::TcpStream;
macro_rules! assert_test_exists {
($name:ident) => {
#[expect(unused_imports, reason = "just here to assert the test is here")]
use self::$name as _;
};
}
foreach_cli!(assert_test_exists);
#[test]
fn cli_hello_stdout() -> Result<()> {
run_wasmtime(&["run", "-Wcomponent-model", CLI_HELLO_STDOUT_COMPONENT])?;
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_empty() -> Result<()> {
let mut child = get_wasmtime_command()?
.args(&["run", "-Wcomponent-model", CLI_STDIN_EMPTY_COMPONENT])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::piped())
.spawn()?;
child
.stdin
.take()
.unwrap()
.write_all(b"not to be read")
.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_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_with_code() -> Result<()> {
let output = get_wasmtime_command()?
.args(&[
"run",
"-Wcomponent-model",
"-Scli-exit-with-code",
CLI_EXIT_WITH_CODE_COMPONENT,
])
.output()?;
assert!(!output.status.success());
assert_eq!(output.status.code(), Some(42));
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",
"-Scli,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]
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",
"-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",
"-Sinherit-network,udp=no",
CLI_NO_UDP_COMPONENT,
],
None,
)?;
println!("{}", String::from_utf8_lossy(&output.stderr));
assert!(output.status.success());
Ok(())
}
#[test]
fn cli_no_ip_name_lookup() -> Result<()> {
let output = super::run_wasmtime_for_output(
&[
"-Wcomponent-model",
"-Sinherit-network,allow-ip-name-lookup=no",
CLI_NO_IP_NAME_LOOKUP_COMPONENT,
],
None,
)?;
println!("{}", String::from_utf8_lossy(&output.stderr));
assert!(output.status.success());
Ok(())
}
#[test]
fn cli_sleep() -> Result<()> {
run_wasmtime(&["run", CLI_SLEEP])?;
run_wasmtime(&["run", CLI_SLEEP_COMPONENT])?;
Ok(())
}
#[test]
fn cli_sleep_forever() -> Result<()> {
for timeout in [
"-Wtimeout=1ns",
"-Wtimeout=250ms",
] {
let e = run_wasmtime(&["run", timeout, CLI_SLEEP_FOREVER]).unwrap_err();
let e = e.to_string();
println!("Got error: {e}");
assert!(e.contains("interrupt"));
let e = run_wasmtime(&["run", timeout, CLI_SLEEP_FOREVER_COMPONENT]).unwrap_err();
let e = e.to_string();
println!("Got error: {e}");
assert!(e.contains("interrupt"));
}
Ok(())
}
struct WasmtimeServe {
child: Option<Child>,
addr: SocketAddr,
shutdown_addr: SocketAddr,
}
impl WasmtimeServe {
fn new(wasm: &str, configure: impl FnOnce(&mut Command)) -> Result<WasmtimeServe> {
let mut cmd = super::get_wasmtime_command()?;
cmd.arg("serve").arg("--addr=127.0.0.1:0").arg(wasm);
configure(&mut cmd);
Self::spawn(&mut cmd)
}
fn spawn(cmd: &mut Command) -> Result<WasmtimeServe> {
cmd.arg("--shutdown-addr=127.0.0.1:0");
cmd.stdin(Stdio::null());
cmd.stdout(Stdio::piped());
cmd.stderr(Stdio::piped());
let mut child = cmd.spawn()?;
let mut line = String::new();
let mut reader = BufReader::new(child.stderr.take().unwrap());
let mut read_addr_from_line = |prefix: &str| -> Result<SocketAddr> {
reader.read_line(&mut line)?;
if !line.starts_with(prefix) {
bail!("input line `{line}` didn't start with `{prefix}`");
}
match line.find("127.0.0.1").and_then(|addr_start| {
let addr = &line[addr_start..];
let addr_end = addr.find("/")?;
addr[..addr_end].parse().ok()
}) {
Some(addr) => {
line.truncate(0);
Ok(addr)
}
None => bail!("failed to address from: {line}"),
}
};
let shutdown_addr = read_addr_from_line("Listening for shutdown");
let addr = read_addr_from_line("Serving HTTP on");
let (shutdown_addr, addr) = match (shutdown_addr, addr) {
(Ok(a), Ok(b)) => (a, b),
(Err(a), _) | (_, Err(a)) => {
child.kill()?;
child.wait()?;
reader.read_to_string(&mut line)?;
return Err(a.context(line));
}
};
assert!(reader.buffer().is_empty());
child.stderr = Some(reader.into_inner());
Ok(WasmtimeServe {
child: Some(child),
addr,
shutdown_addr,
})
}
fn finish(mut self) -> Result<(String, String)> {
let mut child = self.child.take().unwrap();
if child.try_wait()?.is_none() {
std::net::TcpStream::connect(&self.shutdown_addr)
.context("failed to initiate graceful shutdown")?;
}
let output = child.wait_with_output()?;
if !output.status.success() {
bail!("child failed {output:?}");
}
Ok((
String::from_utf8_lossy(&output.stdout).into_owned(),
String::from_utf8_lossy(&output.stderr).into_owned(),
))
}
async fn send_request(&self, req: http::Request<String>) -> Result<http::Response<String>> {
let (mut send, conn_task) = self.start_requests().await?;
let response = send
.send_request(req)
.await
.context("error sending request")?;
drop(send);
let (parts, body) = response.into_parts();
let body = body.collect().await.context("failed to read body")?;
assert!(body.trailers().is_none());
let body = std::str::from_utf8(&body.to_bytes())?.to_string();
conn_task.await??;
Ok(http::Response::from_parts(parts, body))
}
async fn start_requests(
&self,
) -> Result<(
hyper::client::conn::http1::SendRequest<String>,
tokio::task::JoinHandle<hyper::Result<()>>,
)> {
let tcp = TcpStream::connect(&self.addr)
.await
.context("failed to connect")?;
let tcp = wasmtime_wasi_http::io::TokioIo::new(tcp);
let (send, conn) = hyper::client::conn::http1::handshake(tcp)
.await
.context("failed http handshake")?;
Ok((send, tokio::task::spawn(conn)))
}
}
impl Drop for WasmtimeServe {
fn drop(&mut self) {
let mut child = match self.child.take() {
Some(child) => child,
None => return,
};
if child.kill().is_err() {
return;
}
let output = match child.wait_with_output() {
Ok(output) => output,
Err(_) => return,
};
println!("server status: {}", output.status);
if !output.stdout.is_empty() {
println!(
"server stdout:\n{}",
String::from_utf8_lossy(&output.stdout)
);
}
if !output.stderr.is_empty() {
println!(
"server stderr:\n{}",
String::from_utf8_lossy(&output.stderr)
);
}
}
}
#[tokio::test]
async fn cli_serve_echo_env() -> Result<()> {
let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| {
cmd.arg("--env=FOO=bar");
cmd.arg("--env=BAR");
cmd.arg("-Scli");
cmd.env_remove("BAR");
})?;
let foo_env = server
.send_request(
hyper::Request::builder()
.uri("http://localhost/")
.header("env", "FOO")
.body(String::new())
.context("failed to make request")?,
)
.await?;
assert!(foo_env.status().is_success());
assert!(foo_env.body().is_empty());
let headers = foo_env.headers();
assert_eq!(headers.get("env"), Some(&HeaderValue::from_static("bar")));
let bar_env = server
.send_request(
hyper::Request::builder()
.uri("http://localhost/")
.header("env", "BAR")
.body(String::new())
.context("failed to make request")?,
)
.await?;
assert!(bar_env.status().is_success());
assert!(bar_env.body().is_empty());
let headers = bar_env.headers();
assert_eq!(headers.get("env"), None);
server.finish()?;
Ok(())
}
#[tokio::test]
async fn cli_serve_outgoing_body_config() -> Result<()> {
let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| {
cmd.arg("-Scli");
cmd.arg("-Shttp-outgoing-body-buffer-chunks=2");
cmd.arg("-Shttp-outgoing-body-chunk-size=1024");
})?;
let resp = server
.send_request(
hyper::Request::builder()
.uri("http://localhost/")
.header("env", "FOO")
.body(String::new())
.context("failed to make request")?,
)
.await?;
assert!(resp.status().is_success());
server.finish()?;
Ok(())
}
#[tokio::test]
#[ignore]
async fn cli_serve_respect_pooling_options() -> Result<()> {
let server = WasmtimeServe::new(CLI_SERVE_ECHO_ENV_COMPONENT, |cmd| {
cmd.arg("-Opooling-total-memories=0").arg("-Scli");
})?;
let result = server
.send_request(
hyper::Request::builder()
.uri("http://localhost/")
.header("env", "FOO")
.body(String::new())
.context("failed to make request")?,
)
.await;
assert!(result.is_err());
let (_, stderr) = server.finish()?;
assert!(
stderr.contains("maximum concurrent memory limit of 0 reached"),
"bad stderr: {stderr}",
);
Ok(())
}
#[test]
fn cli_large_env() -> Result<()> {
for wasm in [CLI_LARGE_ENV, CLI_LARGE_ENV_COMPONENT] {
println!("run {wasm:?}");
let mut cmd = get_wasmtime_command()?;
cmd.arg("run").arg("-Sinherit-env").arg(wasm);
let debug_cmd = format!("{cmd:?}");
for i in 0..512 {
let var = format!("KEY{i}");
let val = (0..1024).map(|_| 'x').collect::<String>();
cmd.env(&var, &val);
}
let output = cmd.output()?;
if !output.status.success() {
bail!(
"Failed to execute wasmtime with: {debug_cmd}\n{}",
String::from_utf8_lossy(&output.stderr)
);
}
}
Ok(())
}
#[tokio::test]
async fn cli_serve_only_one_process_allowed() -> Result<()> {
let wasm = CLI_SERVE_ECHO_ENV_COMPONENT;
let server = WasmtimeServe::new(wasm, |cmd| {
cmd.arg("-Scli");
})?;
let err = WasmtimeServe::spawn(
super::get_wasmtime_command()?
.arg("serve")
.arg("-Scli")
.arg(format!("--addr={}", server.addr))
.arg(wasm),
)
.err()
.expect("server spawn should have failed but it succeeded");
drop(server);
let err = format!("{err:?}");
println!("{err}");
assert!(err.contains("os error"));
Ok(())
}
#[tokio::test]
async fn cli_serve_quick_rebind_allowed() -> Result<()> {
let wasm = CLI_SERVE_ECHO_ENV_COMPONENT;
let server = WasmtimeServe::new(wasm, |cmd| {
cmd.arg("-Scli");
})?;
let addr = server.addr;
let (mut send, conn_task) = server.start_requests().await?;
let _ = send
.send_request(
hyper::Request::builder()
.uri("http://localhost/")
.header("env", "FOO")
.body(String::new())
.context("failed to make request")?,
)
.await;
drop(server);
drop(send);
let _ = conn_task.await;
let _server2 = WasmtimeServe::spawn(
super::get_wasmtime_command()?
.arg("serve")
.arg("-Scli")
.arg(format!("--addr={addr}"))
.arg(wasm),
)?;
Ok(())
}
#[tokio::test]
async fn cli_serve_with_print() -> Result<()> {
let server = WasmtimeServe::new(CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| {
cmd.arg("-Scli");
})?;
for _ in 0..2 {
let resp = server
.send_request(
hyper::Request::builder()
.uri("http://localhost/")
.body(String::new())
.context("failed to make request")?,
)
.await?;
assert!(resp.status().is_success());
}
let (out, err) = server.finish()?;
assert_eq!(
out,
"\
stdout [0] :: this is half a print to stdout
stdout [0] :: \n\
stdout [0] :: after empty
stdout [1] :: this is half a print to stdout
stdout [1] :: \n\
stdout [1] :: after empty
"
);
assert!(
err.contains(
"\
stderr [0] :: this is half a print to stderr
stderr [0] :: \n\
stderr [0] :: after empty
stderr [0] :: start a print 1234
stderr [1] :: this is half a print to stderr
stderr [1] :: \n\
stderr [1] :: after empty
stderr [1] :: start a print 1234
"
),
"bad stderr: {err}"
);
Ok(())
}
#[tokio::test]
async fn cli_serve_with_print_no_prefix() -> Result<()> {
let server = WasmtimeServe::new(CLI_SERVE_WITH_PRINT_COMPONENT, |cmd| {
cmd.arg("-Scli");
cmd.arg("--no-logging-prefix");
})?;
for _ in 0..2 {
let resp = server
.send_request(
hyper::Request::builder()
.uri("http://localhost/")
.body(String::new())
.context("failed to make request")?,
)
.await?;
assert!(resp.status().is_success());
}
let (out, err) = server.finish()?;
assert_eq!(
out,
"\
this is half a print to stdout
\n\
after empty
this is half a print to stdout
\n\
after empty
"
);
assert!(
err.contains(
"\
this is half a print to stderr
\n\
after empty
start a print 1234
this is half a print to stderr
\n\
after empty
start a print 1234
"
),
"bad stderr {err}",
);
Ok(())
}
#[tokio::test]
async fn cli_serve_authority_and_scheme() -> Result<()> {
let server = WasmtimeServe::new(CLI_SERVE_AUTHORITY_AND_SCHEME_COMPONENT, |cmd| {
cmd.arg("-Scli");
})?;
let resp = server
.send_request(
hyper::Request::builder()
.uri("/")
.header("Host", "localhost")
.body(String::new())
.context("failed to make request")?,
)
.await?;
assert!(resp.status().is_success());
let resp = server
.send_request(
hyper::Request::builder()
.method("CONNECT")
.uri("http://localhost/")
.body(String::new())
.context("failed to make request")?,
)
.await?;
assert!(resp.status().is_success());
Ok(())
}
#[test]
fn cli_argv0() -> Result<()> {
run_wasmtime(&["run", "--argv0=a", CLI_ARGV0, "a"])?;
run_wasmtime(&["run", "--argv0=b", CLI_ARGV0_COMPONENT, "b"])?;
run_wasmtime(&["run", "--argv0=foo.wasm", CLI_ARGV0, "foo.wasm"])?;
Ok(())
}
#[tokio::test]
async fn cli_serve_config() -> Result<()> {
let server = WasmtimeServe::new(CLI_SERVE_CONFIG_COMPONENT, |cmd| {
cmd.arg("-Scli");
cmd.arg("-Sconfig");
cmd.arg("-Sconfig-var=hello=world");
})?;
let resp = server
.send_request(
hyper::Request::builder()
.uri("http://localhost/")
.body(String::new())
.context("failed to make request")?,
)
.await?;
assert!(resp.status().is_success());
assert_eq!(resp.body(), "world");
Ok(())
}
#[test]
fn cli_config() -> Result<()> {
run_wasmtime(&[
"run",
"-Sconfig",
"-Sconfig-var=hello=world",
CONFIG_GET_COMPONENT,
])?;
Ok(())
}
#[tokio::test]
async fn cli_serve_keyvalue() -> Result<()> {
let server = WasmtimeServe::new(CLI_SERVE_KEYVALUE_COMPONENT, |cmd| {
cmd.arg("-Scli");
cmd.arg("-Skeyvalue");
cmd.arg("-Skeyvalue-in-memory-data=hello=world");
})?;
let resp = server
.send_request(
hyper::Request::builder()
.uri("http://localhost/")
.body(String::new())
.context("failed to make request")?,
)
.await?;
assert!(resp.status().is_success());
assert_eq!(resp.body(), "world");
Ok(())
}
#[test]
fn cli_keyvalue() -> Result<()> {
run_wasmtime(&[
"run",
"-Skeyvalue",
"-Skeyvalue-in-memory-data=atomics_key=5",
KEYVALUE_MAIN_COMPONENT,
])?;
Ok(())
}
#[test]
fn cli_multiple_preopens() -> Result<()> {
run_wasmtime(&[
"run",
"--dir=/::/a",
"--dir=/::/b",
"--dir=/::/c",
CLI_MULTIPLE_PREOPENS_COMPONENT,
])?;
Ok(())
}
async fn cli_serve_guest_never_invoked_set(wasm: &str) -> Result<()> {
let server = WasmtimeServe::new(wasm, |cmd| {
cmd.arg("-Scli");
})?;
for _ in 0..2 {
let res = server
.send_request(
hyper::Request::builder()
.uri("http://localhost/")
.body(String::new())
.context("failed to make request")?,
)
.await
.expect("got response from wasmtime");
assert_eq!(res.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
}
let (stdout, stderr) = server.finish()?;
println!("stdout: {stdout}");
println!("stderr: {stderr}");
assert!(stderr.contains("guest never invoked `response-outparam::set` method"));
assert!(!stderr.contains("panicked"));
Ok(())
}
#[tokio::test]
async fn cli_serve_return_before_set() -> Result<()> {
cli_serve_guest_never_invoked_set(CLI_SERVE_RETURN_BEFORE_SET_COMPONENT).await
}
#[tokio::test]
async fn cli_serve_trap_before_set() -> Result<()> {
cli_serve_guest_never_invoked_set(CLI_SERVE_TRAP_BEFORE_SET_COMPONENT).await
}
#[test]
fn cli_p3_hello_stdout() -> Result<()> {
let output = run_wasmtime(&[
"run",
"-Wcomponent-model-async",
"-Sp3",
CLI_P3_HELLO_STDOUT_COMPONENT,
]);
if cfg!(feature = "component-model-async") {
let output = output?;
assert_eq!(output, "hello, world\n");
} else {
assert!(output.is_err());
}
Ok(())
}
mod invoke {
use super::*;
#[test]
fn cli_hello_stdout() -> Result<()> {
println!("{CLI_HELLO_STDOUT_COMPONENT}");
let output = run_wasmtime(&[
"run",
"-Wcomponent-model",
"--invoke",
"run()",
CLI_HELLO_STDOUT_COMPONENT,
])?;
assert_eq!(output, "hello, world\nok\n");
Ok(())
}
}
#[test]
#[cfg_attr(not(feature = "component-model-async"), ignore)]
fn cli_invoke_async() -> Result<()> {
let output = run_wasmtime(&[
"run",
"-Wcomponent-model-async",
"--invoke",
"echo(\"hello?\")",
CLI_INVOKE_ASYNC_COMPONENT,
])?;
assert_eq!(output, "\"hello?\"\n");
Ok(())
}
fn run_much_stdout(component: &str, extra_flags: &[&str]) -> Result<()> {
let total_write_size = 1 << 19;
let expected = iter::repeat('a').take(total_write_size).collect::<String>();
for i in 0..15 {
let string = iter::repeat('a').take(1 << i).collect::<String>();
let times = (total_write_size >> i).to_string();
println!("writing {} bytes {times} times", string.len());
let mut args = Vec::new();
args.push("run");
args.extend_from_slice(extra_flags);
args.push(component);
args.push(&string);
args.push(×);
let output = run_wasmtime(&args)?;
println!(
"expected {} bytes, got {} bytes",
expected.len(),
output.len()
);
assert!(output == expected);
}
Ok(())
}
#[test]
fn cli_p1_much_stdout() -> Result<()> {
run_much_stdout(CLI_P1_MUCH_STDOUT_COMPONENT, &[])
}
#[test]
fn cli_p2_much_stdout() -> Result<()> {
run_much_stdout(CLI_P2_MUCH_STDOUT_COMPONENT, &[])
}
#[test]
#[cfg_attr(not(feature = "component-model-async"), ignore)]
fn cli_p3_much_stdout() -> Result<()> {
run_much_stdout(
CLI_P3_MUCH_STDOUT_COMPONENT,
&["-Wcomponent-model-async", "-Sp3"],
)
}
}
#[test]
fn settings_command() -> Result<()> {
if cranelift_native::builder().is_err() {
return Ok(());
}
let output = run_wasmtime(&["settings"])?;
assert!(output.contains("Cranelift settings for target"));
Ok(())
}
#[cfg(target_arch = "x86_64")]
#[test]
fn profile_with_vtune() -> Result<()> {
if !is_vtune_available() {
println!("> `vtune` is not available on the system path; skipping test");
return Ok(());
}
let mut bin = Command::new("vtune");
bin.args(&[
"-verbose",
"-collect",
"hotspots",
"-user-data-dir",
&std::env::temp_dir().to_string_lossy(),
get_wasmtime_path(),
"--profile=vtune",
"tests/all/cli_tests/simple.wat",
]);
println!("> executing: {bin:?}");
let output = bin.output()?;
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
println!("> stdout:\n{stdout}");
assert!(stdout.contains("CPU Time"));
println!("> stderr:\n{stderr}");
assert!(!stderr.contains("Error"));
Ok(())
}
#[cfg(target_arch = "x86_64")]
fn is_vtune_available() -> bool {
Command::new("vtune").arg("-version").output().is_ok()
}
#[test]
fn unreachable_without_wasi() -> Result<()> {
let output = run_wasmtime_for_output(
&[
"-Scli=n",
"-Ccache=n",
"tests/all/cli_tests/unreachable.wat",
],
None,
)?;
assert_ne!(output.stderr, b"");
assert_eq!(output.stdout, b"");
assert_trap_code(&output.status);
Ok(())
}
#[test]
fn config_cli_flag() -> Result<()> {
let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts();
cfg.write_all(
br#"
[optimize]
opt-level = 2
regalloc-algorithm = "single-pass"
signals-based-traps = false
[codegen]
collector = "null"
[debug]
debug-info = true
[wasm]
max-wasm-stack = 65536
[wasi]
cli = true
"#,
)?;
let output = run_wasmtime(&[
"run",
"--config",
cfg_path.to_str().unwrap(),
"--invoke",
"get_f64",
wasm.path().to_str().unwrap(),
])?;
assert_eq!(output, "100\n");
let output = run_wasmtime(&[
"run",
"--config",
cfg_path.to_str().unwrap(),
"--invoke",
"get_f64",
"-W",
"max-wasm-stack=0",
wasm.path().to_str().unwrap(),
]);
assert!(
output
.as_ref()
.unwrap_err()
.to_string()
.contains("max_wasm_stack size cannot be zero"),
"'{output:?}' did not contain expected error message",
);
let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts();
cfg.write_all(
br#"
[optimize]
this-key-does-not-exist = true
"#,
)?;
let output = run_wasmtime(&[
"run",
"--config",
cfg_path.to_str().unwrap(),
wasm.path().to_str().unwrap(),
]);
assert!(
output
.as_ref()
.unwrap_err()
.to_string()
.contains("unknown field `this-key-does-not-exist`"),
"'{output:?}' did not contain expected error message"
);
let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts();
cfg.write_all(
br#"
[invalid_table]
"#,
)?;
let output = run_wasmtime(&[
"run",
"--config",
cfg_path.to_str().unwrap(),
wasm.path().to_str().unwrap(),
]);
assert!(
output
.as_ref()
.unwrap_err()
.to_string()
.contains("unknown field `invalid_table`, expected one of `optimize`, `codegen`, `debug`, `wasm`, `wasi`"),
"'{output:?}' did not contain expected error message",
);
Ok(())
}
#[test]
fn invalid_subcommand() -> Result<()> {
let output = run_wasmtime_for_output(&["invalid-subcommand"], None)?;
dbg!(&output);
assert!(!output.status.success());
assert!(String::from_utf8_lossy(&output.stderr).contains("invalid-subcommand"));
Ok(())
}
#[test]
fn numeric_args() -> Result<()> {
let wasm = build_wasm("tests/all/cli_tests/numeric_args.wat")?;
let output = run_wasmtime_for_output(
&[
"run",
"--invoke",
"i32_test",
wasm.path().to_str().unwrap(),
"42",
],
None,
)?;
assert_eq!(output.status.success(), true);
assert_eq!(output.stdout, b"42\n");
let output = run_wasmtime_for_output(
&[
"run",
"--invoke",
"i32_test",
wasm.path().to_str().unwrap(),
"0x2A",
],
None,
)?;
assert_eq!(output.status.success(), true);
assert_eq!(output.stdout, b"42\n");
let output = run_wasmtime_for_output(
&[
"run",
"--invoke",
"i32_test",
wasm.path().to_str().unwrap(),
"0X2a",
],
None,
)?;
assert_eq!(output.status.success(), true);
assert_eq!(output.stdout, b"42\n");
let output = run_wasmtime_for_output(
&[
"run",
"--invoke",
"i32_test",
wasm.path().to_str().unwrap(),
"ff",
],
None,
)?;
assert!(!output.status.success());
let output = run_wasmtime_for_output(
&[
"run",
"--invoke",
"i64_test",
wasm.path().to_str().unwrap(),
"42",
],
None,
)?;
assert_eq!(output.status.success(), true);
assert_eq!(output.stdout, b"42\n");
let output = run_wasmtime_for_output(
&[
"run",
"--invoke",
"i64_test",
wasm.path().to_str().unwrap(),
"0x2A",
],
None,
)?;
assert_eq!(output.status.success(), true);
assert_eq!(output.stdout, b"42\n");
Ok(())
}
#[test]
fn compilation_logs() -> Result<()> {
let temp = tempfile::NamedTempFile::new()?;
let output = get_wasmtime_command()?
.args(&[
"compile",
"-Wgc",
"tests/all/cli_tests/issue-10353.wat",
"--output",
&temp.path().display().to_string(),
])
.env("WASMTIME_LOG", "trace")
.env("RUST_BACKTRACE", "1")
.output()?;
if !output.status.success() {
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
panic!("wasmtime compilation failed when logs requested");
}
Ok(())
}
#[test]
fn big_table_in_pooling_allocator() -> Result<()> {
run_wasmtime(&["tests/all/cli_tests/big_table.wat"])?;
let output = run_wasmtime_for_output(
&["-Opooling-allocator", "tests/all/cli_tests/big_table.wat"],
None,
)?;
assert!(!output.status.success());
println!("{}", String::from_utf8_lossy(&output.stderr));
assert!(String::from_utf8_lossy(&output.stderr).contains("pooling allocator"));
run_wasmtime(&[
"-Opooling-allocator",
"-Wmax-table-elements=25000",
"tests/all/cli_tests/big_table.wat",
])?;
run_wasmtime(&[
"-Opooling-allocator",
"-Opooling-table-elements=25000",
"tests/all/cli_tests/big_table.wat",
])?;
Ok(())
}