diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index ca34b9306b03..5d8e45214ac5 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -47,6 +47,7 @@ fn init_file_per_thread_logger(prefix: &'static str) { } wasmtime_option_group! { + #[env = "OPTIMIZE"] pub struct OptimizeOptions { /// Optimization level of generated code (0-2, s; default: 2) #[serde(default)] @@ -234,6 +235,7 @@ wasmtime_option_group! { } wasmtime_option_group! { + #[env = "CODEGEN"] pub struct CodegenOptions { /// Either `cranelift` or `winch`. /// @@ -296,6 +298,7 @@ wasmtime_option_group! { } wasmtime_option_group! { + #[env = "DEBUG"] pub struct DebugOptions { /// Enable generation of DWARF debug information in compiled code. pub debug_info: Option, @@ -337,6 +340,7 @@ wasmtime_option_group! { } wasmtime_option_group! { + #[env = "WASM"] pub struct WasmOptions { /// Enable canonicalization of all NaN values. pub nan_canonicalization: Option, @@ -487,6 +491,7 @@ wasmtime_option_group! { } wasmtime_option_group! { + #[env = "WASI"] pub struct WasiOptions { /// Enable support for WASI CLI APIs, including filesystems, sockets, clocks, and random. pub cli: Option, @@ -587,6 +592,7 @@ wasmtime_option_group! { } wasmtime_option_group! { + #[env = "RECORD"] pub struct RecordOptions { /// Filename for the recorded execution trace (or empty string to skip writing a file). pub path: Option, @@ -801,12 +807,12 @@ impl CommonOptions { ); } } - self.opts.configure_with(&self.opts_raw); - self.codegen.configure_with(&self.codegen_raw); - self.debug.configure_with(&self.debug_raw); - self.wasm.configure_with(&self.wasm_raw); - self.wasi.configure_with(&self.wasi_raw); - self.record.configure_with(&self.record_raw); + self.opts.configure_with(&self.opts_raw)?; + self.codegen.configure_with(&self.codegen_raw)?; + self.debug.configure_with(&self.debug_raw)?; + self.wasm.configure_with(&self.wasm_raw)?; + self.wasi.configure_with(&self.wasi_raw)?; + self.record.configure_with(&self.record_raw)?; Ok(()) } diff --git a/crates/cli-flags/src/opt.rs b/crates/cli-flags/src/opt.rs index 16914356223d..54ffd4256d83 100644 --- a/crates/cli-flags/src/opt.rs +++ b/crates/cli-flags/src/opt.rs @@ -17,6 +17,7 @@ use std::num::NonZeroU32; use std::path::PathBuf; use std::str::FromStr; use std::time::Duration; +use wasmtime::error::Context; use wasmtime::{Result, bail, format_err}; /// Characters which can be safely ignored while parsing numeric options to wasmtime @@ -25,6 +26,7 @@ const IGNORED_NUMBER_CHARS: [char; 1] = ['_']; #[macro_export] macro_rules! wasmtime_option_group { ( + #[env = $env:tt] $(#[$attr:meta])* pub struct $opts:ident { $( @@ -74,6 +76,7 @@ macro_rules! wasmtime_option_group { } impl $crate::opt::WasmtimeOption for $option { + const ENV_PREFIX: &'static str = concat!("WASMTIME_", $env); const OPTIONS: &'static [$crate::opt::OptionDesc<$option>] = &[ $( $crate::opt::OptionDesc { @@ -126,18 +129,24 @@ macro_rules! wasmtime_option_group { } impl $opts { - fn configure_with(&mut self, opts: &[$crate::opt::CommaSeparated<$option>]) { - for opt in opts.iter().flat_map(|o| o.0.iter()) { - match opt { - $( - $option::$opt(val) => { - $crate::opt::OptionContainer::push(&mut self.$opt, val.clone()); - } - )+ - $( - $option::$prefixed(key, val) => self.$prefixed.push((key.clone(), val.clone())), - )? - } + fn configure_with(&mut self, opts: &[$crate::opt::CommaSeparated<$option>]) -> Result<()> { + let env_opts = <$option as $crate::opt::WasmtimeOption>::parse_env()?; + for opt in env_opts.iter().chain(opts.iter().flat_map(|o| o.0.iter())) { + self.configure(opt); + } + Ok(()) + } + + fn configure(&mut self, opt: &$option) { + match opt { + $( + $option::$opt(val) => { + $crate::opt::OptionContainer::push(&mut self.$opt, val.clone()); + } + )+ + $( + $option::$prefixed(key, val) => self.$prefixed.push((key.clone(), val.clone())), + )? } } @@ -251,6 +260,62 @@ where std::process::exit(0); } + T::parse_csv(&val).map(CommaSeparated).map_err(|e| { + Error::raw( + ErrorKind::InvalidValue, + format!("failed to parse -{arg_short} / --{arg_long} option: {e:?}\n"), + ) + }) + } +} + +/// Helper trait used by `CommaSeparated` which contains a list of all options +/// supported by the option group. +pub trait WasmtimeOption: Sized + Send + Sync + Clone + 'static { + const OPTIONS: &'static [OptionDesc]; + const ENV_PREFIX: &'static str; + + /// Parse all environment variables that relate to this option, returning + /// all parsed variables as a list. + /// + /// Returns an error if any environment variable has invalid syntax and/or + /// failed to parse. + fn parse_env() -> Result> { + let mut ret = Vec::new(); + if let Some(val) = std::env::var_os(Self::ENV_PREFIX) { + let val = match val.to_str() { + Some(s) => s, + None => bail!("env var `{}` is not valid UTF-8", Self::ENV_PREFIX), + }; + ret.extend( + Self::parse_csv(&val) + .with_context(|| format!("failed to parse env var `{}`", Self::ENV_PREFIX))?, + ); + } + + for option in Self::OPTIONS { + let key = match &option.name { + OptName::Name(s) => format!("{}_{}", Self::ENV_PREFIX, s.to_ascii_uppercase()), + OptName::Prefix(_) => continue, + }; + let val = match std::env::var_os(&key) { + Some(val) => val, + None => continue, + }; + let val = match val.to_str() { + Some(s) => s, + None => bail!("env var `{key}` is not valid UTF-8"), + }; + ret.push( + (option.parse)(&key, Some(val)) + .with_context(|| format!("failed to parse env var `{key}`"))?, + ); + } + Ok(ret) + } + + /// Parses `val` as a comma-separated list of values for `Self::OPTIONS`. + fn parse_csv(val: &str) -> Result> { let mut result = Vec::new(); for val in val.split(',') { // Split `k=v` into `k` and `v` where `v` is optional @@ -259,7 +324,7 @@ where let key_val = iter.next(); // Find `key` within `T::OPTIONS` - let option = options + let option = Self::OPTIONS .iter() .filter_map(|d| match d.name { OptName::Name(s) => { @@ -275,34 +340,18 @@ where let (desc, key) = match option { Some(pair) => pair, - None => { - let err = Error::raw( - ErrorKind::InvalidValue, - format!("unknown -{arg_short} / --{arg_long} option: {key}\n"), - ); - return Err(err.with_cmd(cmd)); - } + None => bail!("unknown option: {key}\n"), }; - result.push((desc.parse)(&key, key_val).map_err(|e| { - Error::raw( - ErrorKind::InvalidValue, - format!("failed to parse -{arg_short} option `{val}`: {e:?}\n"), - ) - .with_cmd(cmd) - })?) + result.push( + (desc.parse)(&key, key_val) + .with_context(|| format!("failed to parse option `{val}`"))?, + ); } - - Ok(CommaSeparated(result)) + Ok(result) } } -/// Helper trait used by `CommaSeparated` which contains a list of all options -/// supported by the option group. -pub trait WasmtimeOption: Sized + Send + Sync + Clone + 'static { - const OPTIONS: &'static [OptionDesc]; -} - pub struct OptionDesc { pub name: OptName, pub docs: &'static str, diff --git a/docs/cli-options.md b/docs/cli-options.md index f55895d3e102..4f5bd665d8de 100644 --- a/docs/cli-options.md +++ b/docs/cli-options.md @@ -344,6 +344,7 @@ For example, adding `--optimize opt-level=0` to a `wasmtime compile` subcommand will turn off most optimizations for the generated code. ## CLI options using TOML file + Most key-value options that can be provided using the `--optimize`, `--codegen`, `--debug`, `--wasm`, and `--wasi` flags can also be provided using a TOML file using the `--config ` cli flag, by putting the key-value inside a TOML @@ -368,3 +369,48 @@ key-value pairs as you want in the TOML file. Options on the CLI take precedent over options specified in a configuration file, meaning they're allowed to shadow configuration values in a TOML configuration file. + +## CLI options using environment variables + +Shared CLI options are additionally supported when passed in environment +variables. Each option can be individually specified as an environment variable +or as a group of options. For example + +```console +wasmtime compile --optimize opt-level=0 +``` + +is equivalent to + +```console +export WASMTIME_OPTIMIZE_OPT_LEVEL=0 +wasmtime compile +``` + +is equivalent to + +```console +export WASMTIME_OPTIMIZE=opt-level=0 +wasmtime compile +``` + +Specific environment variables override more general ones, so for example an +optimization level of 1 is used in this case: + +```console +export WASMTIME_OPTIMIZE=opt-level=0 +export WASMTIME_OPTIMIZE_OPT_LEVEL=1 +wasmtime compile +``` + +Additionally command-line parameters override what's specified in environment +variables, additionally using the optimization level of 1 in this case: + +```console +export WASMTIME_OPTIMIZE_OPT_LEVEL=0 +wasmtime compile --optimize opt-level=1 +``` + +Environment variables names are "WASMTIME_" followed by the group name, such as +"OPTIMIZE", followed by specific options. The syntax for option values in +environment variables is the same for those specified in arguments. diff --git a/tests/all/cli_tests.rs b/tests/all/cli_tests.rs index 9c2b8e9b208e..ce850f444ae7 100644 --- a/tests/all/cli_tests.rs +++ b/tests/all/cli_tests.rs @@ -7,24 +7,15 @@ use std::process::{Command, ExitStatus, Output, Stdio}; use tempfile::{NamedTempFile, TempDir}; use wasmtime::{Result, bail}; -// 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 { +pub fn wasmtime(args: &[&str]) -> 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) + Ok(cmd) } /// Get the Wasmtime CLI as a [Command]. pub fn get_wasmtime_command() -> Result { - let mut cmd = wasmtime_test_util::command(get_wasmtime_path()); - - // 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"); + let cmd = wasmtime_test_util::command(get_wasmtime_path()); Ok(cmd) } @@ -36,11 +27,16 @@ fn get_wasmtime_path() -> &'static str { // Run the wasmtime CLI with the provided args and, if it succeeds, return // the standard output in a `String`. pub fn run_wasmtime(args: &[&str]) -> Result { - let output = run_wasmtime_for_output(args, None)?; + run_cmd(&mut wasmtime(args)?) +} + +// Run the wasmtime CLI with the provided args and, if it succeeds, return +// the standard output in a `String`. +pub fn run_cmd(cmd: &mut Command) -> Result { + let output = cmd.output()?; if !output.status.success() { bail!( - "Failed to execute wasmtime with: {:?}\nstatus: {}\n{}", - args, + "Failed to execute {cmd:?}\nstatus: {}\n{}", output.status, String::from_utf8_lossy(&output.stderr) ); @@ -58,13 +54,12 @@ fn build_wasm(wat_path: impl AsRef) -> Result { // 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(), + "tests/all/cli_tests/simple.wat", "4", ])?; Ok(()) @@ -73,14 +68,13 @@ fn run_wasmtime_simple() -> Result<()> { // 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(), + "tests/all/cli_tests/simple.wat" ]) .is_err(), "shall fail" @@ -90,7 +84,6 @@ fn run_wasmtime_simple_fail_no_args() -> Result<()> { #[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(&[ @@ -99,7 +92,7 @@ fn run_coredump_smoketest() -> Result<()> { "a", "-Ccache=n", &coredump_arg, - wasm.path().to_str().unwrap(), + "tests/all/cli_tests/coredump_smoketest.wat", ]) .unwrap_err(); assert!(err.to_string().contains(&format!( @@ -112,13 +105,12 @@ fn run_coredump_smoketest() -> Result<()> { // 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(), + "tests/all/cli_tests/simple.wat", "4", ])?; assert_eq!( @@ -127,7 +119,7 @@ fn run_wasmtime_simple_wat() -> Result<()> { "--invoke", "get_f32", "-Ccache=n", - wasm.path().to_str().unwrap(), + "tests/all/cli_tests/simple.wat", ])?, "100\n" ); @@ -137,7 +129,7 @@ fn run_wasmtime_simple_wat() -> Result<()> { "--invoke", "get_f64", "-Ccache=n", - wasm.path().to_str().unwrap(), + "tests/all/cli_tests/simple.wat", ])?, "100\n" ); @@ -147,8 +139,7 @@ fn run_wasmtime_simple_wat() -> Result<()> { // 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)?; + let output = wasmtime(&["-Ccache=n", "tests/all/cli_tests/unreachable.wat"])?.output()?; assert_ne!(output.stderr, b""); assert_eq!(output.stdout, b""); @@ -187,17 +178,13 @@ fn hello_wasi_snapshot1() -> Result<()> { #[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()); + let output = wasmtime(&[ + "run", + "-Wtimeout=1ms", + "-Ccache=n", + "tests/all/cli_tests/iloop-start.wat", + ])? + .output()?; assert_eq!(output.stdout, b""); let stderr = String::from_utf8_lossy(&output.stderr); assert!( @@ -209,16 +196,13 @@ fn timeout_in_start() -> Result<()> { #[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, - )?; + let output = wasmtime(&[ + "run", + "-Wtimeout=1ms", + "-Ccache=n", + "tests/all/cli_tests/iloop-invoke.wat", + ])? + .output()?; assert!(!output.status.success()); assert_eq!(output.stdout, b""); let stderr = String::from_utf8_lossy(&output.stderr); @@ -232,9 +216,8 @@ fn timeout_in_invoke() -> Result<()> { // 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")?; - - let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?; + let output = + wasmtime(&["-Ccache=n", "tests/all/cli_tests/exit2_wasi_snapshot0.wat"])?.output()?; assert_eq!(output.status.code().unwrap(), 2); Ok(()) } @@ -242,8 +225,8 @@ fn exit2_wasi_snapshot0() -> Result<()> { // 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)?; + let output = + wasmtime(&["-Ccache=n", "tests/all/cli_tests/exit2_wasi_snapshot1.wat"])?.output()?; assert_eq!(output.status.code().unwrap(), 2); Ok(()) } @@ -251,8 +234,11 @@ fn exit2_wasi_snapshot1() -> Result<()> { // 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")?; - let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?; + let output = wasmtime(&[ + "-Ccache=n", + "tests/all/cli_tests/exit125_wasi_snapshot0.wat", + ])? + .output()?; dbg!(&output); assert_eq!(output.status.code().unwrap(), 125); Ok(()) @@ -261,8 +247,11 @@ fn exit125_wasi_snapshot0() -> Result<()> { // 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)?; + let output = wasmtime(&[ + "-Ccache=n", + "tests/all/cli_tests/exit125_wasi_snapshot1.wat", + ])? + .output()?; assert_eq!(output.status.code().unwrap(), 125); Ok(()) } @@ -270,9 +259,11 @@ fn exit125_wasi_snapshot1() -> Result<()> { // 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")?; - - let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?; + let output = wasmtime(&[ + "-Ccache=n", + "tests/all/cli_tests/exit126_wasi_snapshot0.wat", + ])? + .output()?; assert_eq!(output.status.code().unwrap(), 1); assert!(output.stdout.is_empty()); assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status")); @@ -282,8 +273,11 @@ fn exit126_wasi_snapshot0() -> Result<()> { // 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)?; + let output = wasmtime(&[ + "-Ccache=n", + "tests/all/cli_tests/exit126_wasi_snapshot1.wat", + ])? + .output()?; assert_eq!(output.status.code().unwrap(), 1); assert!(output.stdout.is_empty()); assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status")); @@ -293,8 +287,7 @@ fn exit126_wasi_snapshot1() -> Result<()> { // 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()])?; + let stdout = run_wasmtime(&["-Ccache=n", "tests/all/cli_tests/minimal-command.wat"])?; assert_eq!(stdout, ""); Ok(()) } @@ -302,8 +295,7 @@ fn minimal_command() -> Result<()> { // 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()])?; + let stdout = run_wasmtime(&["-Ccache=n", "tests/all/cli_tests/minimal-reactor.wat"])?; assert_eq!(stdout, ""); Ok(()) } @@ -311,13 +303,12 @@ fn minimal_reactor() -> Result<()> { // 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(), + "tests/all/cli_tests/minimal-command.wat", ])?; Ok(()) } @@ -325,13 +316,12 @@ fn command_invoke() -> Result<()> { // 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(), + "tests/all/cli_tests/minimal-reactor.wat", ])?; Ok(()) } @@ -339,13 +329,12 @@ fn reactor_invoke() -> Result<()> { // 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(), + "tests/all/cli_tests/greeter_command.wat", ])?; assert_eq!( stdout, @@ -357,13 +346,12 @@ fn greeter() -> Result<()> { // 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(), + "tests/all/cli_tests/greeter_reactor.wat", ])?; assert_eq!(stdout, "Hello _initialize\n"); Ok(()) @@ -372,13 +360,12 @@ fn greeter_preload_command() -> Result<()> { // 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(), + "tests/all/cli_tests/greeter_command.wat", ])?; assert_eq!(stdout, "Hello _start\nHello callable greet\nHello done\n"); Ok(()) @@ -388,10 +375,8 @@ fn greeter_preload_callable_command() -> Result<()> { // 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()); + let stdout = run_wasmtime(&["-Ccache=n", "tests/all/cli_tests/exit_with_saved_fprs.wat"])?; + assert!(stdout.is_empty()); Ok(()) } @@ -414,22 +399,10 @@ fn run_cwasm() -> Result<()> { #[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")?; - let stdout = { - let path = wasm.path(); - let args: &[&str] = &["-Ccache=n", "-"]; - 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::<_, wasmtime::Error>(String::from_utf8(output.stdout).unwrap()) - }?; + let stdout = run_cmd( + wasmtime(&["-Ccache=n", "-"])? + .stdin(File::open("tests/all/cli_tests/hello_wasi_snapshot0.wat")?), + )?; assert_eq!(stdout, "Hello, world!\n"); Ok(()) } @@ -437,52 +410,40 @@ fn hello_wasi_snapshot0_from_stdin() -> Result<()> { #[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), ""); + let output = run_cmd( + wasmtime(&["run", "tests/all/cli_tests/print_env.wat"])? + .env("THIS_WILL_NOT", "show up in the output"), + )?; + assert_eq!(output, ""); // 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"); + let output = run_wasmtime(&[ + "run", + "--env", + "FOO=bar", + "tests/all/cli_tests/print_env.wat", + ])?; + assert_eq!(output, "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"); + let output = run_cmd( + wasmtime(&["run", "--env", "FOO", "tests/all/cli_tests/print_env.wat"])?.env("FOO", "bar"), + )?; + assert_eq!(output, "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()); + run_wasmtime(&[ + "run", + "--env", + "SURELY_THIS_ENV_VAR_DOES_NOT_EXIST_ANYWHERE_RIGHT", + "tests/all/cli_tests/print_env.wat", + ])?; // Inherit all env vars - 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}"); + let output = run_cmd( + wasmtime(&["run", "-Sinherit-env", "tests/all/cli_tests/print_env.wat"])?.env("FOO", "bar"), + )?; + assert!(output.contains("FOO=bar"), "bad output: {output}"); Ok(()) } @@ -504,16 +465,11 @@ fn run_cwasm_from_stdin() -> Result<()> { // 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"); + run_cmd(wasmtime(args)?.stdin(File::open(&cwasm)?))?; // If stdin is a pipe, that should also work let input = std::fs::read(&cwasm)?; - let mut child = get_wasmtime_command()? - .args(args) + let mut child = wasmtime(args)? .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -600,46 +556,28 @@ fn name_same_as_builtin_command() -> Result<()> { // 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:#?}"); + run_cmd(wasmtime(&["--", "run"])?.current_dir("tests/all/cli_tests"))?; // 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:#?}"); + run_cmd(wasmtime(&["-Ccache=n", "run"])?.current_dir("tests/all/cli_tests"))?; 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()); + run_cmd(wasmtime(&["-"])?.stdin(File::open("tests/all/cli_tests/simple.wat")?))?; 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()); + let output = run_cmd( + wasmtime(&["print-arguments.wat", "-foo", "bar"])?.current_dir("tests/all/cli_tests/"), + )?; assert_eq!( - String::from_utf8_lossy(&output.stdout), + output, "\ print-arguments.wat\n\ -foo\n\ @@ -666,8 +604,11 @@ fn wasi_misaligned_pointer() -> Result<()> { #[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()])?; + let stdout = run_wasmtime(&[ + "-Ccache=n", + "-Spreview2", + "tests/all/cli_tests/hello_wasi_snapshot1.wat", + ])?; assert_eq!(stdout, "Hello, world!\n"); Ok(()) } @@ -710,18 +651,10 @@ fn component_missing_feature() -> Result<()> { 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()); + run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?; // also tests with raw *.wat input - let output = get_wasmtime_command()? - .arg("-Ccache=n") - .arg(path) - .output()?; - assert!(output.status.success()); + run_wasmtime(&["-Ccache=n", path])?; Ok(()) } @@ -729,7 +662,6 @@ fn component_enabled_by_default() -> Result<()> { #[test] fn component_invoke_multiple_run_exports() -> Result<()> { let path = "tests/all/cli_tests/component-multiple-runs.wat"; - let wasm = build_wasm(path)?; // demonstrate run --invoke can give a useful error message when // there are multiple interfaces that export the function name specified @@ -739,7 +671,7 @@ fn component_invoke_multiple_run_exports() -> Result<()> { .arg("-Ccache=n") .arg("--invoke") .arg("run()") - .arg(wasm.path()) + .arg(path) .output()?; assert!( !output.status.success(), @@ -749,27 +681,25 @@ fn component_invoke_multiple_run_exports() -> Result<()> { assert!(stderr.contains("Multiple instances contained funcs named `run`, retry with a more specific name: `wasi:cli/run.run@0.2.0`, `some:other/one.run`")); // test program cli run gives `ok`: - let output = get_wasmtime_command()? - .arg("run") - .arg("-Wcomponent-model") - .arg("-Ccache=n") - .arg("--invoke") - .arg("wasi:cli/run.run@0.2.0()") - .arg(wasm.path()) - .output()?; - let stdout = String::from_utf8_lossy(&output.stdout); + let stdout = run_wasmtime(&[ + "run", + "-Wcomponent-model", + "-Ccache=n", + "--invoke", + "wasi:cli/run.run@0.2.0()", + path, + ])?; assert_eq!(stdout, "ok\n"); // test program other run gives `err`: - let output = get_wasmtime_command()? - .arg("run") - .arg("-Wcomponent-model") - .arg("-Ccache=n") - .arg("--invoke") - .arg("some:other/one.run()") - .arg(wasm.path()) - .output()?; - let stdout = String::from_utf8_lossy(&output.stdout); + let stdout = run_wasmtime(&[ + "run", + "-Wcomponent-model", + "-Ccache=n", + "--invoke", + "some:other/one.run()", + path, + ])?; assert_eq!(stdout, "err\n"); Ok(()) @@ -948,16 +878,14 @@ fn preview2_stdin() -> Result<()> { }; // read empty pipe is ok - let output = cmd()?.output()?; - assert!(output.status.success()); - assert_eq!(String::from_utf8_lossy(&output.stdout), "0\n"); + let stdout = run_cmd(&mut cmd()?)?; + assert_eq!(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")); + let output = run_cmd(cmd()?.stdin(File::open(test)?))?; + assert_eq!(output, format!("{size}\n")); // read piped input ok is ok let mut child = cmd()? @@ -1056,7 +984,6 @@ fn mpk_without_pooling() -> Result<()> { "tests/all/cli_tests/simple.wat", "1.0", ]) - .env("WASMTIME_NEW_CLI", "1") .output()?; assert!(!output.status.success()); Ok(()) @@ -1425,21 +1352,15 @@ mod test_programs { #[test] fn run_wasi_http_component() -> Result<()> { - let output = super::run_wasmtime_for_output( - &[ - "-Ccache=no", - "-Wcomponent-model", - "-Scli,http,preview2", - P2_HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT, - ], - None, - )?; - println!("{}", String::from_utf8_lossy(&output.stderr)); - let stdout = String::from_utf8_lossy(&output.stdout); + let stdout = super::run_wasmtime(&[ + "-Ccache=no", + "-Wcomponent-model", + "-Scli,http,preview2", + P2_HTTP_OUTBOUND_REQUEST_RESPONSE_BUILD_COMPONENT, + ])?; println!("{stdout}"); assert!(stdout.starts_with("Called _start\n")); assert!(stdout.ends_with("Done\n")); - assert!(output.status.success()); Ok(()) } @@ -1480,49 +1401,34 @@ mod test_programs { #[test] fn p2_cli_no_tcp() -> Result<()> { - let output = super::run_wasmtime_for_output( - &[ - "-Wcomponent-model", - // Turn on network but turn off TCP - "-Sinherit-network,tcp=no", - P2_CLI_NO_TCP_COMPONENT, - ], - None, - )?; - println!("{}", String::from_utf8_lossy(&output.stderr)); - assert!(output.status.success()); + super::run_wasmtime(&[ + "-Wcomponent-model", + // Turn on network but turn off TCP + "-Sinherit-network,tcp=no", + P2_CLI_NO_TCP_COMPONENT, + ])?; Ok(()) } #[test] fn p2_cli_no_udp() -> Result<()> { - let output = super::run_wasmtime_for_output( - &[ - "-Wcomponent-model", - // Turn on network but turn off UDP - "-Sinherit-network,udp=no", - P2_CLI_NO_UDP_COMPONENT, - ], - None, - )?; - println!("{}", String::from_utf8_lossy(&output.stderr)); - assert!(output.status.success()); + super::run_wasmtime(&[ + "-Wcomponent-model", + // Turn on network but turn off UDP + "-Sinherit-network,udp=no", + P2_CLI_NO_UDP_COMPONENT, + ])?; Ok(()) } #[test] fn p2_cli_no_ip_name_lookup() -> Result<()> { - let output = super::run_wasmtime_for_output( - &[ - "-Wcomponent-model", - // Turn on network but ensure name lookup is disabled - "-Sinherit-network,allow-ip-name-lookup=no", - P2_CLI_NO_IP_NAME_LOOKUP_COMPONENT, - ], - None, - )?; - println!("{}", String::from_utf8_lossy(&output.stderr)); - assert!(output.status.success()); + super::run_wasmtime(&[ + "-Wcomponent-model", + // Turn on network but ensure name lookup is disabled + "-Sinherit-network,allow-ip-name-lookup=no", + P2_CLI_NO_IP_NAME_LOOKUP_COMPONENT, + ])?; Ok(()) } @@ -1915,22 +1821,14 @@ mod test_programs { fn p2_cli_large_env() -> Result<()> { for wasm in [P2_CLI_LARGE_ENV, P2_CLI_LARGE_ENV_COMPONENT] { println!("run {wasm:?}"); - let mut cmd = get_wasmtime_command()?; - cmd.arg("run").arg("-Sinherit-env").arg(wasm); + let mut cmd = super::wasmtime(&["run", "-Sinherit-env", wasm])?; - let debug_cmd = format!("{cmd:?}"); for i in 0..512 { let var = format!("KEY{i}"); let val = (0..1024).map(|_| 'x').collect::(); 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) - ); - } + super::run_cmd(&mut cmd)?; } Ok(()) } @@ -3112,15 +3010,9 @@ fn profile_with_vtune() -> Result<()> { ]); println!("> executing: {bin:?}"); - let output = bin.output()?; + let stdout = run_cmd(&mut bin)?; - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); println!("> stdout:\n{stdout}"); - println!("> stderr:\n{stderr}"); - - assert!(output.status.success()); - assert!(!stderr.contains("Error")); assert!(stdout.contains("CPU Time")); Ok(()) } @@ -3135,22 +3027,14 @@ fn profile_guest() -> Result<()> { let tmpdir = std::env::temp_dir(); let dir = tmpdir.to_string_lossy(); - let output = run_wasmtime_for_output( - &[ - &format!("--profile=guest,{dir}/out.json"), - "--env", - "FOO=bar", - "tests/all/cli_tests/print_env.wat", - ], - None, - )?; + let stdout = run_wasmtime(&[ + &format!("--profile=guest,{dir}/out.json"), + "--env", + "FOO=bar", + "tests/all/cli_tests/print_env.wat", + ])?; - let stdout = String::from_utf8_lossy(&output.stdout); - let stderr = String::from_utf8_lossy(&output.stderr); println!("> stdout:\n{stdout}"); - println!("> stderr:\n{stderr}"); - assert!(output.status.success()); - assert!(!stderr.contains("Error")); let out_json = std::fs::read_to_string(format!("{dir}/out.json")).unwrap(); println!("> out.json:\n{out_json}"); Ok(()) @@ -3158,14 +3042,12 @@ fn profile_guest() -> Result<()> { #[test] fn unreachable_without_wasi() -> Result<()> { - let output = run_wasmtime_for_output( - &[ - "-Scli=n", - "-Ccache=n", - "tests/all/cli_tests/unreachable.wat", - ], - None, - )?; + let output = wasmtime(&[ + "-Scli=n", + "-Ccache=n", + "tests/all/cli_tests/unreachable.wat", + ])? + .output()?; assert_ne!(output.stderr, b""); assert_eq!(output.stdout, b""); @@ -3175,8 +3057,6 @@ fn unreachable_without_wasi() -> Result<()> { #[test] fn config_cli_flag() -> Result<()> { - let wasm = build_wasm("tests/all/cli_tests/simple.wat")?; - // Test some valid TOML values let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts(); cfg.write_all( @@ -3204,7 +3084,7 @@ fn config_cli_flag() -> Result<()> { cfg_path.to_str().unwrap(), "--invoke", "get_f64", - wasm.path().to_str().unwrap(), + "tests/all/cli_tests/simple.wat", ])?; assert_eq!(output, "100\n"); @@ -3217,7 +3097,7 @@ fn config_cli_flag() -> Result<()> { "get_f64", "-W", "max-wasm-stack=0", // should override TOML value 65536 specified above and execution should fail - wasm.path().to_str().unwrap(), + "tests/all/cli_tests/simple.wat", ]); assert!( output @@ -3240,7 +3120,7 @@ fn config_cli_flag() -> Result<()> { "run", "--config", cfg_path.to_str().unwrap(), - wasm.path().to_str().unwrap(), + "tests/all/cli_tests/simple.wat", ]); assert!( output @@ -3262,7 +3142,7 @@ fn config_cli_flag() -> Result<()> { "run", "--config", cfg_path.to_str().unwrap(), - wasm.path().to_str().unwrap(), + "tests/all/cli_tests/simple.wat", ]); assert!( output @@ -3278,7 +3158,7 @@ fn config_cli_flag() -> Result<()> { #[test] fn invalid_subcommand() -> Result<()> { - let output = run_wasmtime_for_output(&["invalid-subcommand"], None)?; + let output = wasmtime(&["invalid-subcommand"])?.output()?; dbg!(&output); assert!(!output.status.success()); assert!(String::from_utf8_lossy(&output.stderr).contains("invalid-subcommand")); @@ -3287,107 +3167,79 @@ fn invalid_subcommand() -> Result<()> { #[test] fn numeric_args() -> Result<()> { - let wasm = build_wasm("tests/all/cli_tests/numeric_args.wat")?; // Test decimal i32 - 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(&[ + "run", + "--invoke", + "i32_test", + "tests/all/cli_tests/numeric_args.wat", + "42", + ])?; + assert_eq!(output, "42\n"); // Test hexadecimal i32 with lowercase prefix - 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(&[ + "run", + "--invoke", + "i32_test", + "tests/all/cli_tests/numeric_args.wat", + "0x2A", + ])?; + assert_eq!(output, "42\n"); // Test hexadecimal i32 with uppercase prefix - 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(&[ + "run", + "--invoke", + "i32_test", + "tests/all/cli_tests/numeric_args.wat", + "0X2a", + ])?; + assert_eq!(output, "42\n"); // Test that non-prefixed hex strings are not interpreted as hex - let output = run_wasmtime_for_output( - &[ - "run", - "--invoke", - "i32_test", - wasm.path().to_str().unwrap(), - "ff", - ], - None, - )?; + let output = wasmtime(&[ + "run", + "--invoke", + "i32_test", + "tests/all/cli_tests/numeric_args.wat", + "ff", + ])? + .output()?; assert!(!output.status.success()); // Should fail as "ff" is not a valid decimal number // Test decimal i64 - 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(&[ + "run", + "--invoke", + "i64_test", + "tests/all/cli_tests/numeric_args.wat", + "42", + ])?; + assert_eq!(output, "42\n"); // Test hexadecimal i64 - 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"); + let output = run_wasmtime(&[ + "run", + "--invoke", + "i64_test", + "tests/all/cli_tests/numeric_args.wat", + "0x2A", + ])?; + assert_eq!(output, "42\n"); Ok(()) } #[test] fn compilation_logs() -> Result<()> { let temp = tempfile::NamedTempFile::new()?; - let output = get_wasmtime_command()? - .args(&[ + run_cmd( + wasmtime(&[ "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"); - } + .env("RUST_BACKTRACE", "1"), + )?; Ok(()) } @@ -3398,10 +3250,8 @@ fn big_table_in_pooling_allocator() -> Result<()> { // Does not work by default in the pooling allocator, and the error message // should mention something about the pooling allocator. - let output = run_wasmtime_for_output( - &["-Opooling-allocator", "tests/all/cli_tests/big_table.wat"], - None, - )?; + let output = + wasmtime(&["-Opooling-allocator", "tests/all/cli_tests/big_table.wat"])?.output()?; assert!(!output.status.success()); println!("{}", String::from_utf8_lossy(&output.stderr)); assert!(String::from_utf8_lossy(&output.stderr).contains("pooling allocator")); @@ -3607,19 +3457,16 @@ fn hot_blocks_fib() -> Result<()> { return Ok(()); } - let wasm = build_wasm("tests/all/cli_tests/fib.wat")?; - let output = run_wasmtime_for_output( - &[ - "hot-blocks", - "-Ccache=n", - "--event", - "instructions", - "--percent", - "90", - wasm.path().to_str().unwrap(), - ], - None, - )?; + let output = wasmtime(&[ + "hot-blocks", + "-Ccache=n", + "--event", + "instructions", + "--percent", + "90", + "tests/all/cli_tests/fib.wat", + ])? + .output()?; // The command should succeed. if !output.status.success() { @@ -3665,51 +3512,43 @@ fn non_utf8_raises_error() -> Result<()> { let invalid_utf8 = OsStr::from_bytes(b"\xFF"); // If everything is valid this should succeed... - { - let mut cmd = get_wasmtime_command()?; - cmd.arg("-Sinherit-env") - .arg("tests/all/cli_tests/simple.wat"); - let output = cmd.output()?; - assert!(output.status.success()); - } + run_wasmtime(&["-Sinherit-env", "tests/all/cli_tests/simple.wat"])?; // -Sinherit-env { - let mut cmd = get_wasmtime_command()?; - cmd.arg("-Sinherit-env") - .arg("tests/all/cli_tests/simple.wat"); - cmd.env("HI", invalid_utf8); - let output = cmd.output()?; + let output = wasmtime(&["-Sinherit-env", "tests/all/cli_tests/simple.wat"])? + .env("HI", invalid_utf8) + .output()?; if output.status.success() { bail!("should have failed: {output:?}") } } // --env=HI { - let mut cmd = get_wasmtime_command()?; - cmd.arg("--env=HI").arg("tests/all/cli_tests/simple.wat"); - cmd.env("HI", invalid_utf8); - let output = cmd.output()?; + let output = wasmtime(&["--env=HI", "tests/all/cli_tests/simple.wat"])? + .env("HI", invalid_utf8) + .output()?; if output.status.success() { bail!("should have failed: {output:?}") } } // --env=HI=$bad { - let mut cmd = get_wasmtime_command()?; let mut bad = OsString::from("--env=HI="); bad.push(invalid_utf8); - cmd.arg(&bad).arg("tests/all/cli_tests/simple.wat"); - let output = cmd.output()?; + let output = get_wasmtime_command()? + .arg(&bad) + .arg("tests/all/cli_tests/simple.wat") + .output()?; if output.status.success() { bail!("should have failed: {output:?}") } } // $bad (bare argument) { - let mut cmd = get_wasmtime_command()?; - cmd.arg("tests/all/cli_tests/simple.wat").arg(invalid_utf8); - let output = cmd.output()?; + let output = wasmtime(&["tests/all/cli_tests/simple.wat"])? + .arg(invalid_utf8) + .output()?; if output.status.success() { bail!("should have failed: {output:?}") } @@ -3734,3 +3573,82 @@ fn compile_empty_component_with_debug_info() -> Result<()> { assert_eq!(stdout, ""); Ok(()) } + +#[test] +fn environment_configuration() -> Result<()> { + let td = TempDir::new()?; + let cwasm = td.path().join("empty-component.cwasm"); + run_wasmtime(&[ + "compile", + "tests/all/cli_tests/empty_component.wat", + "-o", + cwasm.to_str().unwrap(), + ])?; + + // Pass option bag of arguments + let output = wasmtime(&[ + "compile", + "tests/all/cli_tests/empty_component.wat", + "-o", + cwasm.to_str().unwrap(), + ])? + .env("WASMTIME_WASM", "component-model=n") + .output()?; + assert!(!output.status.success()); + + // Pass specific argument + let output = wasmtime(&[ + "compile", + "tests/all/cli_tests/empty_component.wat", + "-o", + cwasm.to_str().unwrap(), + ])? + .env("WASMTIME_WASM_COMPONENT_MODEL", "n") + .output()?; + assert!(!output.status.success()); + + // Pass invalid argument + let output = wasmtime(&["tests/all/cli_tests/simple.wat"])? + .env("WASMTIME_WASM_COMPONENT_MODEL", "invalid") + .output()?; + assert!(!output.status.success()); + let output = wasmtime(&["tests/all/cli_tests/simple.wat"])? + .env("WASMTIME_WASM", "invalid") + .output()?; + assert!(!output.status.success()); + + // CLI overrides env vars + run_cmd( + wasmtime(&[ + "compile", + "-Wcomponent-model", + "tests/all/cli_tests/empty_component.wat", + "-o", + cwasm.to_str().unwrap(), + ])? + .env("WASMTIME_WASM_COMPONENT_MODEL", "n"), + )?; + run_cmd( + wasmtime(&[ + "compile", + "-Wcomponent-model", + "tests/all/cli_tests/empty_component.wat", + "-o", + cwasm.to_str().unwrap(), + ])? + .env("WASMTIME_WASM", "component-model=n"), + )?; + + // specific env var overrides general + run_cmd( + wasmtime(&[ + "compile", + "tests/all/cli_tests/empty_component.wat", + "-o", + cwasm.to_str().unwrap(), + ])? + .env("WASMTIME_WASM", "component-model=n") + .env("WASMTIME_WASM_COMPONENT_MODEL", "y"), + )?; + Ok(()) +}