diff --git a/Cargo.lock b/Cargo.lock index 3f14cdd5c7b..13063f9911c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3121,8 +3121,11 @@ checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" dependencies = [ "anstyle", "difflib", + "float-cmp", "itertools", + "normalize-line-endings", "predicates-core", + "regex", ] [[package]] @@ -5632,6 +5635,7 @@ name = "wasmer-cli" version = "4.0.0-beta.3" dependencies = [ "anyhow", + "assert_cmd 2.0.11", "async-trait", "atty", "bytes", @@ -5640,7 +5644,6 @@ dependencies = [ "cfg-if 1.0.0", "chrono", "clap 4.3.4", - "clap-verbosity-flag", "colored 2.0.0", "dialoguer", "dirs", @@ -5654,6 +5657,7 @@ dependencies = [ "object 0.30.4", "once_cell", "pathdiff", + "predicates 3.0.3", "prettytable-rs", "regex", "reqwest", diff --git a/lib/cli/Cargo.toml b/lib/cli/Cargo.toml index 061f52667da..9c5e00c8556 100644 --- a/lib/cli/Cargo.toml +++ b/lib/cli/Cargo.toml @@ -91,7 +91,6 @@ object = "0.30.0" wasm-coredump-builder = { version = "0.1.11", optional = true } tracing = { version = "0.1" } tracing-subscriber = { version = "0.3", features = [ "env-filter", "fmt" ] } -clap-verbosity-flag = "2" async-trait = "0.1.68" tokio = { version = "1.28.1", features = ["macros", "rt-multi-thread"] } once_cell = "1.17.1" @@ -126,84 +125,33 @@ unix_mode = "0.1.3" [features] # Don't add the compiler features in default, please add them on the Makefile # since we might want to autoconfigure them depending on the availability on the host. -default = [ - "sys", - "wat", - "wast", - "compiler", - "wasmer-artifact-create", - "static-artifact-create", -] +default = ["sys", "wat", "wast", "compiler", "wasmer-artifact-create", "static-artifact-create"] backend = [] -coredump = [ - "wasm-coredump-builder", -] -sys = [ - "compiler", - "wasmer-vm", -] -jsc = [ - "backend", - "wasmer/jsc", - "wasmer/std" -] +coredump = ["wasm-coredump-builder"] +sys = ["compiler", "wasmer-vm"] +jsc = ["backend", "wasmer/jsc", "wasmer/std"] wast = ["wasmer-wast"] -host-net = [ "virtual-net/host-net" ] +host-net = ["virtual-net/host-net"] wat = ["wasmer/wat"] -compiler = [ - "backend", - "wasmer/compiler", - "wasmer-compiler/translator", - "wasmer-compiler/compiler" -] -wasmer-artifact-create = ["compiler", - "wasmer/wasmer-artifact-load", - "wasmer/wasmer-artifact-create", - "wasmer-compiler/wasmer-artifact-load", - "wasmer-compiler/wasmer-artifact-create", - "wasmer-object", - ] -static-artifact-create = ["compiler", - "wasmer/static-artifact-load", - "wasmer/static-artifact-create", - "wasmer-compiler/static-artifact-load", - "wasmer-compiler/static-artifact-create", - "wasmer-object", - ] -wasmer-artifact-load = ["compiler", - "wasmer/wasmer-artifact-load", - "wasmer-compiler/wasmer-artifact-load", - ] -static-artifact-load = ["compiler", - "wasmer/static-artifact-load", - "wasmer-compiler/static-artifact-load", - ] -experimental-io-devices = [ - "wasmer-wasix-experimental-io-devices", -] -singlepass = [ - "wasmer-compiler-singlepass", - "compiler", -] -cranelift = [ - "wasmer-compiler-cranelift", - "compiler", -] -llvm = [ - "wasmer-compiler-llvm", - "compiler", -] +compiler = ["backend", "wasmer/compiler", "wasmer-compiler/translator", "wasmer-compiler/compiler"] +wasmer-artifact-create = ["compiler", "wasmer/wasmer-artifact-load", "wasmer/wasmer-artifact-create", "wasmer-compiler/wasmer-artifact-load", "wasmer-compiler/wasmer-artifact-create", "wasmer-object"] +static-artifact-create = ["compiler", "wasmer/static-artifact-load", "wasmer/static-artifact-create", "wasmer-compiler/static-artifact-load", "wasmer-compiler/static-artifact-create", "wasmer-object"] +wasmer-artifact-load = ["compiler", "wasmer/wasmer-artifact-load", "wasmer-compiler/wasmer-artifact-load"] +static-artifact-load = ["compiler", "wasmer/static-artifact-load", "wasmer-compiler/static-artifact-load"] +experimental-io-devices = ["wasmer-wasix-experimental-io-devices"] +singlepass = ["wasmer-compiler-singlepass", "compiler"] +cranelift = ["wasmer-compiler-cranelift", "compiler"] +llvm = ["wasmer-compiler-llvm", "compiler"] disable-all-logging = ["wasmer-wasix/disable-all-logging", "log/release_max_level_off"] headless = [] headless-minimal = ["headless", "disable-all-logging"] # Optional -enable-serde = [ - "wasmer/enable-serde", - "wasmer-vm/enable-serde", - "wasmer-compiler/enable-serde", - "wasmer-wasix/enable-serde", -] +enable-serde = ["wasmer/enable-serde", "wasmer-vm/enable-serde", "wasmer-compiler/enable-serde", "wasmer-wasix/enable-serde"] + +[dev-dependencies] +assert_cmd = "2.0.11" +predicates = "3.0.3" [target.'cfg(target_os = "windows")'.dependencies] colored = "2.0.0" diff --git a/lib/cli/src/cli.rs b/lib/cli/src/cli.rs index 2c88d6fbeff..134cf3cbf08 100644 --- a/lib/cli/src/cli.rs +++ b/lib/cli/src/cli.rs @@ -14,29 +14,96 @@ use crate::commands::{ #[cfg(feature = "static-artifact-create")] use crate::commands::{CreateObj, GenCHeader}; use crate::error::PrettyError; -use clap::{error::ErrorKind, CommandFactory, Parser}; +use clap::{CommandFactory, Parser}; + +/// The main function for the Wasmer CLI tool. +pub fn wasmer_main() { + // We allow windows to print properly colors + #[cfg(windows)] + colored::control::set_virtual_terminal(true).unwrap(); + + PrettyError::report(wasmer_main_inner()) +} + +fn wasmer_main_inner() -> Result<(), anyhow::Error> { + if is_binfmt_interpreter() { + Run::from_binfmt_args().execute(); + } + + match Args::try_parse() { + Ok(args) => { + args.output.initialize_logging(); + args.execute() + } + Err(e) => { + let might_be_wasmer_run = matches!( + e.kind(), + clap::error::ErrorKind::InvalidSubcommand | clap::error::ErrorKind::UnknownArgument + ); + + if might_be_wasmer_run { + if let Ok(run) = Run::try_parse() { + // Try to parse the command using the `wasmer some/package` + // shorthand. Note that this has discoverability issues + // because it's not shown as part of the main argument + // parser's help, but that's fine. + crate::logging::Output::default().initialize_logging(); + run.execute(); + } + } + + e.exit(); + } + } +} + +/// Command-line arguments for the Wasmer CLI. +#[derive(Parser, Debug)] +#[clap(author, version)] +#[clap(disable_version_flag = true)] // handled manually +#[cfg_attr(feature = "headless", clap( + name = "wasmer-headless", + about = concat!("wasmer-headless ", env!("CARGO_PKG_VERSION")), +))] +#[cfg_attr(not(feature = "headless"), clap( + name = "wasmer", + about = concat!("wasmer ", env!("CARGO_PKG_VERSION")), +))] +pub struct Args { + /// Print version info and exit. + #[clap(short = 'V', long)] + version: bool, + #[clap(flatten)] + output: crate::logging::Output, + #[clap(subcommand)] + cmd: Option, +} + +impl Args { + fn execute(self) -> Result<(), anyhow::Error> { + let Args { + cmd, + version, + output, + } = self; + + if version { + return print_version(output.is_verbose()); + } + + if let Some(cmd) = cmd { + cmd.execute() + } else { + Args::command().print_long_help()?; + // Note: clap uses an exit code of 2 when CLI parsing fails + std::process::exit(2); + } + } +} #[derive(Parser, Debug)] -#[cfg_attr( - not(feature = "headless"), - clap( - name = "wasmer", - about = concat!("wasmer ", env!("CARGO_PKG_VERSION")), - version, - author - ) -)] -#[cfg_attr( - feature = "headless", - clap( - name = "wasmer-headless", - about = concat!("wasmer ", env!("CARGO_PKG_VERSION")), - version, - author - ) -)] /// The options for the wasmer Command Line Interface -enum WasmerCLIOptions { +enum Cmd { /// Login into a wasmer.io-like registry Login(Login), @@ -175,7 +242,7 @@ enum WasmerCLIOptions { Namespace(wasmer_deploy_cli::cmd::namespace::CmdNamespace), } -impl WasmerCLIOptions { +impl Cmd { fn execute(self) -> Result<(), anyhow::Error> { use wasmer_deploy_cli::cmd::CliCommand; @@ -213,111 +280,49 @@ impl WasmerCLIOptions { } } -/// The main function for the Wasmer CLI tool. -pub fn wasmer_main() { - // We allow windows to print properly colors - #[cfg(windows)] - colored::control::set_virtual_terminal(true).unwrap(); - - PrettyError::report(wasmer_main_inner()) -} - -fn wasmer_main_inner() -> Result<(), anyhow::Error> { - // We try to run wasmer with the normal arguments. - // Eg. `wasmer ` - // In case that fails, we fallback trying the Run subcommand directly. - // Eg. `wasmer myfile.wasm --dir=.` - // - // In case we've been run as wasmer-binfmt-interpreter myfile.wasm args, - // we assume that we're registered via binfmt_misc - let args = std::env::args().collect::>(); - let binpath = args.get(0).map(|s| s.as_ref()).unwrap_or(""); - - let firstarg = args.get(1).map(|s| s.as_str()); - let secondarg = args.get(2).map(|s| s.as_str()); - - match (firstarg, secondarg) { - (None, _) | (Some("help"), _) | (Some("--help"), _) => { - return print_help(true); - } - (Some("-h"), _) => { - return print_help(false); - } - (Some("-vV"), _) - | (Some("version"), Some("--verbose")) - | (Some("--version"), Some("--verbose")) => { - return print_version(true); - } - - (Some("-v"), _) | (Some("-V"), _) | (Some("version"), _) | (Some("--version"), _) => { - return print_version(false); +fn is_binfmt_interpreter() -> bool { + cfg_if::cfg_if! { + if #[cfg(target_os = "linux")] { + // Note: we'll be invoked by the kernel as Binfmt::FILENAME + let binary_path = match std::env::args_os().next() { + Some(path) => std::path::PathBuf::from(path), + None => return false, + }; + binary_path.file_name().and_then(|f| f.to_str()) == Some(Binfmt::FILENAME) + } else { + false } - _ => {} - } - - let command = args.get(1); - let options = if cfg!(target_os = "linux") && binpath.ends_with("wasmer-binfmt-interpreter") { - WasmerCLIOptions::Run(Run::from_binfmt_args()) - } else { - match command.unwrap_or(&String::new()).as_ref() { - "add" | "app" | "apps" | "binfmt" | "cache" | "compile" | "config" | "create-obj" - | "create-exe" | "deploy" | "help" | "gen-c-header" | "inspect" | "init" | "login" - | "namespace" | "namespaces" | "publish" | "run" | "run-unstable" | "self-update" - | "validate" | "wast" | "ssh" | "" => WasmerCLIOptions::parse(), - _ => { - WasmerCLIOptions::try_parse_from(args.iter()).unwrap_or_else(|e| { - match e.kind() { - // This fixes a issue that: - // 1. Shows the version twice when doing `wasmer -V` - // 2. Shows the run help (instead of normal help) when doing `wasmer --help` - ErrorKind::DisplayVersion | ErrorKind::DisplayHelp => e.exit(), - _ => WasmerCLIOptions::Run(Run::parse()), - } - }) - } - } - }; - - options.execute() -} - -fn print_help(verbose: bool) -> Result<(), anyhow::Error> { - let mut cmd = WasmerCLIOptions::command(); - if verbose { - let _ = cmd.print_long_help(); - } else { - let _ = cmd.print_help(); } - Ok(()) } -#[allow(unused_mut, clippy::vec_init_then_push)] fn print_version(verbose: bool) -> Result<(), anyhow::Error> { if !verbose { println!("wasmer {}", env!("CARGO_PKG_VERSION")); - } else { - println!( - "wasmer {} ({} {})", - env!("CARGO_PKG_VERSION"), - env!("WASMER_BUILD_GIT_HASH_SHORT"), - env!("WASMER_BUILD_DATE") - ); - println!("binary: {}", env!("CARGO_PKG_NAME")); - println!("commit-hash: {}", env!("WASMER_BUILD_GIT_HASH")); - println!("commit-date: {}", env!("WASMER_BUILD_DATE")); - println!("host: {}", target_lexicon::HOST); - println!("compiler: {}", { - let mut s = Vec::<&'static str>::new(); - - #[cfg(feature = "singlepass")] - s.push("singlepass"); - #[cfg(feature = "cranelift")] - s.push("cranelift"); - #[cfg(feature = "llvm")] - s.push("llvm"); - - s.join(",") - }); + return Ok(()); + } + + println!( + "wasmer {} ({} {})", + env!("CARGO_PKG_VERSION"), + env!("WASMER_BUILD_GIT_HASH_SHORT"), + env!("WASMER_BUILD_DATE") + ); + println!("binary: {}", env!("CARGO_PKG_NAME")); + println!("commit-hash: {}", env!("WASMER_BUILD_GIT_HASH")); + println!("commit-date: {}", env!("WASMER_BUILD_DATE")); + println!("host: {}", target_lexicon::HOST); + + let mut compilers = Vec::<&'static str>::new(); + if cfg!(feature = "singlepass") { + compilers.push("singlepass"); } + if cfg!(feature = "cranelift") { + compilers.push("cranelift"); + } + if cfg!(feature = "llvm") { + compilers.push("llvm"); + } + println!("compiler: {}", compilers.join(",")); + Ok(()) } diff --git a/lib/cli/src/commands/binfmt.rs b/lib/cli/src/commands/binfmt.rs index 3c636fa7678..a4b7a3c6b61 100644 --- a/lib/cli/src/commands/binfmt.rs +++ b/lib/cli/src/commands/binfmt.rs @@ -53,6 +53,9 @@ fn seccheck(path: &Path) -> Result<()> { } impl Binfmt { + /// The filename used to register the wasmer CLI as a binfmt interpreter. + pub const FILENAME: &str = "wasmer-binfmt-interpreter"; + /// execute [Binfmt] pub fn execute(&self) -> Result<()> { if !self.binfmt_misc.exists() { @@ -66,7 +69,7 @@ impl Binfmt { let bin_path_orig: PathBuf = env::current_exe() .and_then(|p| p.canonicalize()) .context("Cannot get path to wasmer executable")?; - let bin_path = temp_dir.path().join("wasmer-binfmt-interpreter"); + let bin_path = temp_dir.path().join(Binfmt::FILENAME); fs::copy(bin_path_orig, &bin_path).context("Copy wasmer binary to temp folder")?; let bin_path = fs::canonicalize(&bin_path).with_context(|| { format!( diff --git a/lib/cli/src/commands/run.rs b/lib/cli/src/commands/run.rs index 85505b18da1..f2dea753a1d 100644 --- a/lib/cli/src/commands/run.rs +++ b/lib/cli/src/commands/run.rs @@ -16,7 +16,6 @@ use std::{ use anyhow::{Context, Error}; use clap::Parser; -use clap_verbosity_flag::WarnLevel; use once_cell::sync::Lazy; use sha2::{Digest, Sha256}; use tempfile::NamedTempFile; @@ -58,8 +57,6 @@ static WASMER_HOME: Lazy = Lazy::new(|| { /// The unstable `wasmer run` subcommand. #[derive(Debug, Parser)] pub struct Run { - #[clap(flatten)] - verbosity: clap_verbosity_flag::Verbosity, /// The Wasmer home directory. #[clap(long = "wasmer-dir", env = "WASMER_DIR", default_value = WASMER_HOME.as_os_str())] wasmer_dir: PathBuf, @@ -92,7 +89,6 @@ impl Run { } fn execute_inner(&self) -> Result<(), Error> { - crate::logging::set_up_logging(self.verbosity.log_level_filter()); let runtime = tokio::runtime::Builder::new_multi_thread() .enable_all() .build()?; @@ -360,7 +356,6 @@ impl Run { }; let store = StoreOptions::default(); Ok(Run { - verbosity: clap_verbosity_flag::Verbosity::new(0, 0), wasmer_dir: WASMER_HOME.clone(), store, wasi: Wasi::for_binfmt_interpreter()?, diff --git a/lib/cli/src/logging.rs b/lib/cli/src/logging.rs index 54ce76c6275..de30b7f5452 100644 --- a/lib/cli/src/logging.rs +++ b/lib/cli/src/logging.rs @@ -1,48 +1,96 @@ //! Logging functions for the debug feature. -use tracing_subscriber::{ - filter::Directive, fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, -}; - -/// Subroutine to instantiate the loggers -pub fn set_up_logging(level: log::LevelFilter) { - let fmt_layer = fmt::layer() - .with_target(true) - .with_span_events(fmt::format::FmtSpan::CLOSE) - .with_ansi(should_emit_colors()) - .with_thread_ids(true) - .with_writer(std::io::stderr) - .compact(); - - let filter_layer = EnvFilter::builder() - .with_default_directive(log_directive(level)) - .from_env_lossy(); - - tracing_subscriber::registry() - .with(filter_layer) - .with(fmt_layer) - .init(); -} +use tracing::level_filters::LevelFilter; +use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; + +const WHITELISTED_LOG_TARGETS: &[&str] = &["wasmer", "wasmer_wasix", "virtual_fs"]; -/// Check whether we should emit ANSI escape codes for log formatting. -/// -/// The `tracing-subscriber` crate doesn't have native support for -/// "--color=always|never|auto", so we implement a poor man's version. -/// -/// For more, see https://github.com/tokio-rs/tracing/issues/2388 -fn should_emit_colors() -> bool { - isatty::stderr_isatty() && std::env::var_os("NO_COLOR").is_none() +/// Control the output generated by the CLI. +#[derive(Debug, Default, Clone, PartialEq, clap::Parser)] +pub struct Output { + /// Generate verbose output (repeat for more verbosity) + #[clap(short, long, action = clap::ArgAction::Count, global = true, conflicts_with = "quiet")] + pub verbose: u8, + /// Do not print progress messages. + #[clap(short, long, global = true, conflicts_with = "verbose")] + pub quiet: bool, + /// When to display colored output. + #[clap(long, default_value_t = clap::ColorChoice::Auto, global = true)] + pub color: clap::ColorChoice, } -fn log_directive(level: log::LevelFilter) -> Directive { - let tracing_level = match level { - log::LevelFilter::Off => tracing::level_filters::LevelFilter::OFF, - log::LevelFilter::Error => tracing::level_filters::LevelFilter::ERROR, - log::LevelFilter::Warn => tracing::level_filters::LevelFilter::WARN, - log::LevelFilter::Info => tracing::level_filters::LevelFilter::INFO, - log::LevelFilter::Debug => tracing::level_filters::LevelFilter::DEBUG, - log::LevelFilter::Trace => tracing::level_filters::LevelFilter::TRACE, - }; - - tracing_level.into() +impl Output { + /// Has the `--verbose` flag been set? + pub fn is_verbose(&self) -> bool { + self.verbose > 0 + } + + /// Initialize logging based on the `$RUST_LOG` environment variable and + /// command-line flags. + pub fn initialize_logging(&self) { + let fmt_layer = fmt::layer() + .with_target(true) + .with_span_events(fmt::format::FmtSpan::CLOSE) + .with_ansi(self.should_emit_colors()) + .with_thread_ids(true) + .with_writer(std::io::stderr) + .compact(); + + let filter_layer = self.log_filter(); + + tracing_subscriber::registry() + .with(filter_layer) + .with(fmt_layer) + .init(); + } + + fn log_filter(&self) -> EnvFilter { + let default_filters = [ + LevelFilter::OFF, + LevelFilter::WARN, + LevelFilter::INFO, + LevelFilter::DEBUG, + ]; + + // First, we set up the default log level. + let default_level = default_filters + .get(self.verbose as usize) + .copied() + .unwrap_or(LevelFilter::TRACE); + let mut filter = EnvFilter::builder() + .with_default_directive(default_level.into()) + .from_env_lossy(); + + // Next we add level-specific directives, where verbosity=0 means don't + // override anything. Note that these are shifted one level up so we'll + // get something like RUST_LOG="warn,wasmer_wasix=info" + let specific_filters = [LevelFilter::WARN, LevelFilter::INFO, LevelFilter::DEBUG]; + if self.verbose > 0 { + let level = specific_filters + .get(self.verbose as usize) + .copied() + .unwrap_or(LevelFilter::TRACE); + + for target in WHITELISTED_LOG_TARGETS { + let directive = format!("{target}={level}").parse().unwrap(); + filter = filter.add_directive(directive); + } + } + + filter + } + + /// Check whether we should emit ANSI escape codes for log formatting. + /// + /// The `tracing-subscriber` crate doesn't have native support for + /// "--color=always|never|auto", so we implement a poor man's version. + /// + /// For more, see https://github.com/tokio-rs/tracing/issues/2388 + fn should_emit_colors(&self) -> bool { + match self.color { + clap::ColorChoice::Auto => isatty::stderr_isatty(), + clap::ColorChoice::Always => true, + clap::ColorChoice::Never => false, + } + } } diff --git a/lib/cli/tests/version.rs b/lib/cli/tests/version.rs new file mode 100644 index 00000000000..1dcb7e83079 --- /dev/null +++ b/lib/cli/tests/version.rs @@ -0,0 +1,68 @@ +use assert_cmd::Command; + +const WASMER_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[test] +fn short_version_string() { + let version_number = format!("wasmer {WASMER_VERSION}"); + + Command::cargo_bin("wasmer") + .unwrap() + .arg("--version") + .assert() + .success() + .stdout(predicates::str::contains(&version_number)); + + Command::cargo_bin("wasmer") + .unwrap() + .arg("-V") + .assert() + .success() + .stdout(predicates::str::contains(&version_number)); +} + +#[test] +fn long_version_string() { + let long_version_number = format!( + "wasmer {} ({} {})", + env!("CARGO_PKG_VERSION"), + env!("WASMER_BUILD_GIT_HASH_SHORT"), + env!("WASMER_BUILD_DATE") + ); + + Command::cargo_bin("wasmer") + .unwrap() + .arg("--version") + .arg("--verbose") + .assert() + .success() + .stdout(predicates::str::contains(&long_version_number)) + .stdout(predicates::str::contains("binary:")); + + Command::cargo_bin("wasmer") + .unwrap() + .arg("-Vv") + .assert() + .success() + .stdout(predicates::str::contains(&long_version_number)) + .stdout(predicates::str::contains("binary:")); +} + +#[test] +fn help_text_contains_version() { + let version_number = format!("wasmer {WASMER_VERSION}"); + + Command::cargo_bin("wasmer") + .unwrap() + .arg("-h") + .assert() + .success() + .stdout(predicates::str::contains(&version_number)); + + Command::cargo_bin("wasmer") + .unwrap() + .arg("--help") + .assert() + .success() + .stdout(predicates::str::contains(&version_number)); +} diff --git a/tests/integration/cli/tests/version.rs b/tests/integration/cli/tests/version.rs deleted file mode 100644 index f304749d732..00000000000 --- a/tests/integration/cli/tests/version.rs +++ /dev/null @@ -1,64 +0,0 @@ -use anyhow::bail; -use std::process::Command; -use wasmer_integration_tests_cli::get_wasmer_path; - -const WASMER_VERSION: &str = env!("CARGO_PKG_VERSION"); - -#[test] -fn version_string_is_correct() -> anyhow::Result<()> { - let expected_version_output = format!("wasmer {}\n", WASMER_VERSION); - let wasmer_path = get_wasmer_path(); - - let outputs = [ - Command::new(&wasmer_path).arg("--version").output()?, - Command::new(&wasmer_path).arg("-V").output()?, - ]; - - for output in &outputs { - if !output.status.success() { - bail!( - "version failed with: stdout: {}\n\nstderr: {}", - std::str::from_utf8(&output.stdout) - .expect("stdout is not utf8! need to handle arbitrary bytes"), - std::str::from_utf8(&output.stderr) - .expect("stderr is not utf8! need to handle arbitrary bytes") - ); - } - - let stdout_output = std::str::from_utf8(&output.stdout).unwrap(); - assert_eq!(stdout_output, &expected_version_output); - } - - Ok(()) -} - -#[test] -fn help_text_contains_version() -> anyhow::Result<()> { - let expected_version_output = format!("wasmer {}", WASMER_VERSION); - let wasmer_path = get_wasmer_path(); - - let outputs = [ - Command::new(&wasmer_path).arg("--help").output()?, - Command::new(&wasmer_path).arg("-h").output()?, - ]; - - for output in &outputs { - if !output.status.success() { - bail!( - "version failed with: stdout: {}\n\nstderr: {}", - std::str::from_utf8(&output.stdout) - .expect("stdout is not utf8! need to handle arbitrary bytes"), - std::str::from_utf8(&output.stderr) - .expect("stderr is not utf8! need to handle arbitrary bytes") - ); - } - - let stdout_output = std::str::from_utf8(&output.stdout).unwrap(); - assert_eq!( - stdout_output.lines().next().unwrap(), - &expected_version_output - ); - } - - Ok(()) -}