#![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};

// 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<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)
}

/// Get the Wasmtime CLI as a [Command].
pub fn get_wasmtime_command() -> Result<Command> {
    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");

    Ok(cmd)
}

fn get_wasmtime_path() -> &'static str {
    env!("CARGO_BIN_EXE_wasmtime")
}

// 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<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)
}

// Very basic use case: compile binary wasm file and run specific function with arguments.
#[test]
fn run_wasmtime_simple() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
    run_wasmtime(&[
        "run",
        "--invoke",
        "simple",
        "-Ccache=n",
        wasm.path().to_str().unwrap(),
        "4",
    ])?;
    Ok(())
}

// Wasmtime shall fail when not enough arguments were provided.
#[test]
fn run_wasmtime_simple_fail_no_args() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
    assert!(
        run_wasmtime(&[
            "run",
            "-Ccache=n",
            "--invoke",
            "simple",
            wasm.path().to_str().unwrap(),
        ])
        .is_err(),
        "shall fail"
    );
    Ok(())
}

#[test]
fn run_coredump_smoketest() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/coredump_smoketest.wat")?;
    let coredump_file = NamedTempFile::new()?;
    let coredump_arg = format!("-Dcoredump={}", coredump_file.path().display());
    let err = run_wasmtime(&[
        "run",
        "--invoke",
        "a",
        "-Ccache=n",
        &coredump_arg,
        wasm.path().to_str().unwrap(),
    ])
    .unwrap_err();
    assert!(err.to_string().contains(&format!(
        "core dumped at {}",
        coredump_file.path().display()
    )));
    Ok(())
}

// Running simple wat
#[test]
fn run_wasmtime_simple_wat() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
    run_wasmtime(&[
        "run",
        "--invoke",
        "simple",
        "-Ccache=n",
        wasm.path().to_str().unwrap(),
        "4",
    ])?;
    assert_eq!(
        run_wasmtime(&[
            "run",
            "--invoke",
            "get_f32",
            "-Ccache=n",
            wasm.path().to_str().unwrap(),
        ])?,
        "100\n"
    );
    assert_eq!(
        run_wasmtime(&[
            "run",
            "--invoke",
            "get_f64",
            "-Ccache=n",
            wasm.path().to_str().unwrap(),
        ])?,
        "100\n"
    );
    Ok(())
}

// Running a wat that traps.
#[test]
fn run_wasmtime_unreachable_wat() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/unreachable.wat")?;
    let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?;

    assert_ne!(output.stderr, b"");
    assert_eq!(output.stdout, b"");

    assert_trap_code(&output.status);
    Ok(())
}

fn assert_trap_code(status: &ExitStatus) {
    let code = status
        .code()
        .expect("wasmtime process should exit normally");

    // Test for the specific error code Wasmtime uses to indicate a trap return.
    #[cfg(unix)]
    assert_eq!(code, 128 + libc::SIGABRT);
    #[cfg(windows)]
    assert_eq!(code, 3);
}

// Run a simple WASI hello world, snapshot0 edition.
#[test]
fn hello_wasi_snapshot0() -> Result<()> {
    for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
        let stdout = run_wasmtime(&[
            "-Ccache=n",
            preview2,
            "tests/all/cli_tests/hello_wasi_snapshot0.wat",
        ])?;
        assert_eq!(stdout, "Hello, world!\n");
    }
    Ok(())
}

// Run a simple WASI hello world, snapshot1 edition.
#[test]
fn hello_wasi_snapshot1() -> Result<()> {
    let stdout = run_wasmtime(&["-Ccache=n", "tests/all/cli_tests/hello_wasi_snapshot1.wat"])?;
    assert_eq!(stdout, "Hello, world!\n");
    Ok(())
}

#[test]
fn timeout_in_start() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/iloop-start.wat")?;
    let output = run_wasmtime_for_output(
        &[
            "run",
            "-Wtimeout=1ms",
            "-Ccache=n",
            wasm.path().to_str().unwrap(),
        ],
        None,
    )?;
    assert!(!output.status.success());
    assert_eq!(output.stdout, b"");
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        stderr.contains("wasm trap: interrupt"),
        "bad stderr: {stderr}"
    );
    Ok(())
}

#[test]
fn timeout_in_invoke() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/iloop-invoke.wat")?;
    let output = run_wasmtime_for_output(
        &[
            "run",
            "-Wtimeout=1ms",
            "-Ccache=n",
            wasm.path().to_str().unwrap(),
        ],
        None,
    )?;
    assert!(!output.status.success());
    assert_eq!(output.stdout, b"");
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        stderr.contains("wasm trap: interrupt"),
        "bad stderr: {stderr}"
    );
    Ok(())
}

// Exit with a valid non-zero exit code, snapshot0 edition.
#[test]
fn exit2_wasi_snapshot0() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot0.wat")?;

    for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
        let output = run_wasmtime_for_output(
            &["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
            None,
        )?;
        assert_eq!(output.status.code().unwrap(), 2);
    }
    Ok(())
}

// Exit with a valid non-zero exit code, snapshot1 edition.
#[test]
fn exit2_wasi_snapshot1() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/exit2_wasi_snapshot1.wat")?;
    let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?;
    assert_eq!(output.status.code().unwrap(), 2);
    Ok(())
}

// Exit with a valid non-zero exit code, snapshot0 edition.
#[test]
fn exit125_wasi_snapshot0() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot0.wat")?;
    for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
        let output = run_wasmtime_for_output(
            &["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
            None,
        )?;
        dbg!(&output);
        assert_eq!(output.status.code().unwrap(), 125);
    }
    Ok(())
}

// Exit with a valid non-zero exit code, snapshot1 edition.
#[test]
fn exit125_wasi_snapshot1() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/exit125_wasi_snapshot1.wat")?;
    let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?;
    assert_eq!(output.status.code().unwrap(), 125);
    Ok(())
}

// Exit with an invalid non-zero exit code, snapshot0 edition.
#[test]
fn exit126_wasi_snapshot0() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot0.wat")?;

    for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
        let output = run_wasmtime_for_output(
            &["-Ccache=n", preview2, wasm.path().to_str().unwrap()],
            None,
        )?;
        assert_eq!(output.status.code().unwrap(), 1);
        assert!(output.stdout.is_empty());
        assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
    }
    Ok(())
}

// Exit with an invalid non-zero exit code, snapshot1 edition.
#[test]
fn exit126_wasi_snapshot1() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/exit126_wasi_snapshot1.wat")?;
    let output = run_wasmtime_for_output(&[wasm.path().to_str().unwrap(), "-Ccache=n"], None)?;
    assert_eq!(output.status.code().unwrap(), 1);
    assert!(output.stdout.is_empty());
    assert!(String::from_utf8_lossy(&output.stderr).contains("invalid exit status"));
    Ok(())
}

// Run a minimal command program.
#[test]
fn minimal_command() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?;
    let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?;
    assert_eq!(stdout, "");
    Ok(())
}

// Run a minimal reactor program.
#[test]
fn minimal_reactor() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?;
    let stdout = run_wasmtime(&["-Ccache=n", wasm.path().to_str().unwrap()])?;
    assert_eq!(stdout, "");
    Ok(())
}

// Attempt to call invoke on a command.
#[test]
fn command_invoke() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/minimal-command.wat")?;
    run_wasmtime(&[
        "run",
        "--invoke",
        "_start",
        "-Ccache=n",
        wasm.path().to_str().unwrap(),
    ])?;
    Ok(())
}

// Attempt to call invoke on a command.
#[test]
fn reactor_invoke() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/minimal-reactor.wat")?;
    run_wasmtime(&[
        "run",
        "--invoke",
        "_initialize",
        "-Ccache=n",
        wasm.path().to_str().unwrap(),
    ])?;
    Ok(())
}

// Run the greeter test, which runs a preloaded reactor and a command.
#[test]
fn greeter() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?;
    let stdout = run_wasmtime(&[
        "run",
        "-Ccache=n",
        "--preload",
        "reactor=tests/all/cli_tests/greeter_reactor.wat",
        wasm.path().to_str().unwrap(),
    ])?;
    assert_eq!(
        stdout,
        "Hello _initialize\nHello _start\nHello greet\nHello done\n"
    );
    Ok(())
}

// Run the greeter test, but this time preload a command.
#[test]
fn greeter_preload_command() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/greeter_reactor.wat")?;
    let stdout = run_wasmtime(&[
        "run",
        "-Ccache=n",
        "--preload",
        "reactor=tests/all/cli_tests/hello_wasi_snapshot1.wat",
        wasm.path().to_str().unwrap(),
    ])?;
    assert_eq!(stdout, "Hello _initialize\n");
    Ok(())
}

// Run the greeter test, which runs a preloaded reactor and a command.
#[test]
fn greeter_preload_callable_command() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/greeter_command.wat")?;
    let stdout = run_wasmtime(&[
        "run",
        "-Ccache=n",
        "--preload",
        "reactor=tests/all/cli_tests/greeter_callable_command.wat",
        wasm.path().to_str().unwrap(),
    ])?;
    assert_eq!(stdout, "Hello _start\nHello callable greet\nHello done\n");
    Ok(())
}

// Ensure successful WASI exit call with FPR saving frames on stack for Windows x64
// See https://github.com/bytecodealliance/wasmtime/issues/1967
#[test]
fn exit_with_saved_fprs() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/exit_with_saved_fprs.wat")?;
    let output = run_wasmtime_for_output(&["-Ccache=n", wasm.path().to_str().unwrap()], None)?;
    assert_eq!(output.status.code().unwrap(), 0);
    assert!(output.stdout.is_empty());
    Ok(())
}

#[test]
fn run_cwasm() -> Result<()> {
    let td = TempDir::new()?;
    let cwasm = td.path().join("foo.cwasm");
    let stdout = run_wasmtime(&[
        "compile",
        "tests/all/cli_tests/simple.wat",
        "-o",
        cwasm.to_str().unwrap(),
    ])?;
    assert_eq!(stdout, "");
    let stdout = run_wasmtime(&["run", "--allow-precompiled", cwasm.to_str().unwrap()])?;
    assert_eq!(stdout, "");
    Ok(())
}

#[cfg(unix)]
#[test]
fn hello_wasi_snapshot0_from_stdin() -> Result<()> {
    // Run a simple WASI hello world, snapshot0 edition.
    // The module is piped from standard input.
    let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot0.wat")?;
    for preview2 in ["-Spreview2=n", "-Spreview2=y"] {
        let stdout = {
            let path = wasm.path();
            let args: &[&str] = &["-Ccache=n", preview2, "-"];
            let output = run_wasmtime_for_output(args, Some(path))?;
            if !output.status.success() {
                bail!(
                    "Failed to execute wasmtime with: {:?}\n{}",
                    args,
                    String::from_utf8_lossy(&output.stderr)
                );
            }
            Ok::<_, anyhow::Error>(String::from_utf8(output.stdout).unwrap())
        }?;
        assert_eq!(stdout, "Hello, world!\n");
    }
    Ok(())
}

#[test]
fn specify_env() -> Result<()> {
    // By default no env is inherited
    let output = get_wasmtime_command()?
        .args(&["run", "tests/all/cli_tests/print_env.wat"])
        .env("THIS_WILL_NOT", "show up in the output")
        .output()?;
    assert!(output.status.success());
    assert_eq!(String::from_utf8_lossy(&output.stdout), "");

    // Specify a single env var
    let output = get_wasmtime_command()?
        .args(&[
            "run",
            "--env",
            "FOO=bar",
            "tests/all/cli_tests/print_env.wat",
        ])
        .output()?;
    assert!(output.status.success());
    assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n");

    // Inherit a single env var
    let output = get_wasmtime_command()?
        .args(&["run", "--env", "FOO", "tests/all/cli_tests/print_env.wat"])
        .env("FOO", "bar")
        .output()?;
    assert!(output.status.success());
    assert_eq!(String::from_utf8_lossy(&output.stdout), "FOO=bar\n");

    // Inherit a nonexistent env var
    let output = get_wasmtime_command()?
        .args(&[
            "run",
            "--env",
            "SURELY_THIS_ENV_VAR_DOES_NOT_EXIST_ANYWHERE_RIGHT",
            "tests/all/cli_tests/print_env.wat",
        ])
        .output()?;
    assert!(output.status.success());

    // 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}");

    Ok(())
}

#[cfg(unix)]
#[test]
fn run_cwasm_from_stdin() -> Result<()> {
    use std::process::Stdio;

    let td = TempDir::new()?;
    let cwasm = td.path().join("foo.cwasm");
    let stdout = run_wasmtime(&[
        "compile",
        "tests/all/cli_tests/simple.wat",
        "-o",
        cwasm.to_str().unwrap(),
    ])?;
    assert_eq!(stdout, "");

    // If stdin is literally the file itself then that should work
    let args: &[&str] = &["run", "--allow-precompiled", "-"];
    let output = get_wasmtime_command()?
        .args(args)
        .stdin(File::open(&cwasm)?)
        .output()?;
    assert!(output.status.success(), "a file as stdin should work");

    // If stdin is a pipe, that should also work
    let input = std::fs::read(&cwasm)?;
    let mut child = get_wasmtime_command()?
        .args(args)
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()?;
    let mut stdin = child.stdin.take().unwrap();
    let t = std::thread::spawn(move || {
        let _ = stdin.write_all(&input);
    });
    let output = child.wait_with_output()?;
    assert!(output.status.success());
    t.join().unwrap();
    Ok(())
}

#[cfg(feature = "wasi-threads")]
#[test]
fn run_threads() -> Result<()> {
    // Only run threaded tests on platforms that support threads. Also skip
    // these tests with ASAN as it, rightfully, complains about a memory leak.
    // The memory leak at this time is that child threads aren't joined with the
    // main thread, meaning that allocations done on child threads are indeed
    // leaked.
    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<()> {
    // Skip this test on platforms that don't support threads.
    if crate::threads::engine().is_none() {
        return Ok(());
    }
    // We expect to be able to run Wasm modules that do not have correct
    // wasi-thread entry points or imported shared memory as long as no threads
    // are spawned.
    let wasm = build_wasm("tests/all/cli_tests/simple.wat")?;
    let stdout = run_wasmtime(&[
        "run",
        "-Wthreads",
        "-Sthreads",
        "-Ccache=n",
        "--invoke",
        "simple",
        wasm.path().to_str().unwrap(),
        "4",
    ])?;
    assert_eq!(stdout, "4\n");
    Ok(())
}

#[test]
fn wasm_flags() -> Result<()> {
    // Any argument after the wasm module should be interpreted as for the
    // command itself
    let stdout = run_wasmtime(&[
        "run",
        "--",
        "tests/all/cli_tests/print-arguments.wat",
        "--argument",
        "-for",
        "the",
        "command",
    ])?;
    assert_eq!(
        stdout,
        "\
            print-arguments.wat\n\
            --argument\n\
            -for\n\
            the\n\
            command\n\
        "
    );
    let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "-"])?;
    assert_eq!(
        stdout,
        "\
            print-arguments.wat\n\
            -\n\
        "
    );
    let stdout = run_wasmtime(&["run", "--", "tests/all/cli_tests/print-arguments.wat", "--"])?;
    assert_eq!(
        stdout,
        "\
            print-arguments.wat\n\
            --\n\
        "
    );
    let stdout = run_wasmtime(&[
        "run",
        "--",
        "tests/all/cli_tests/print-arguments.wat",
        "--",
        "--",
        "-a",
        "b",
    ])?;
    assert_eq!(
        stdout,
        "\
            print-arguments.wat\n\
            --\n\
            --\n\
            -a\n\
            b\n\
        "
    );
    Ok(())
}

#[test]
fn name_same_as_builtin_command() -> Result<()> {
    // a bare subcommand shouldn't run successfully
    let output = get_wasmtime_command()?
        .current_dir("tests/all/cli_tests")
        .arg("run")
        .output()?;
    assert!(!output.status.success());

    // a `--` prefix should let everything else get interpreted as a wasm
    // module and arguments, even if the module has a name like `run`
    let output = get_wasmtime_command()?
        .current_dir("tests/all/cli_tests")
        .arg("--")
        .arg("run")
        .output()?;
    assert!(output.status.success(), "expected success got {output:#?}");

    // Passing options before the subcommand should work and doesn't require
    // `--` to disambiguate
    let output = get_wasmtime_command()?
        .current_dir("tests/all/cli_tests")
        .arg("-Ccache=n")
        .arg("run")
        .output()?;
    assert!(output.status.success(), "expected success got {output:#?}");
    Ok(())
}

#[test]
#[cfg(unix)]
fn run_just_stdin_argument() -> Result<()> {
    let output = get_wasmtime_command()?
        .arg("-")
        .stdin(File::open("tests/all/cli_tests/simple.wat")?)
        .output()?;
    assert!(output.status.success());
    Ok(())
}

#[test]
fn wasm_flags_without_subcommand() -> Result<()> {
    let output = get_wasmtime_command()?
        .current_dir("tests/all/cli_tests/")
        .arg("print-arguments.wat")
        .arg("-foo")
        .arg("bar")
        .output()?;
    assert!(output.status.success());
    assert_eq!(
        String::from_utf8_lossy(&output.stdout),
        "\
            print-arguments.wat\n\
            -foo\n\
            bar\n\
        "
    );
    Ok(())
}

#[test]
fn wasi_misaligned_pointer() -> Result<()> {
    let output = get_wasmtime_command()?
        .arg("./tests/all/cli_tests/wasi_misaligned_pointer.wat")
        .output()?;
    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        stderr.contains("Pointer not aligned"),
        "bad stderr: {stderr}",
    );
    Ok(())
}

#[test]
#[cfg_attr(not(feature = "component-model"), ignore)]
fn hello_with_preview2() -> Result<()> {
    let wasm = build_wasm("tests/all/cli_tests/hello_wasi_snapshot1.wat")?;
    let stdout = run_wasmtime(&["-Ccache=n", "-Spreview2", wasm.path().to_str().unwrap()])?;
    assert_eq!(stdout, "Hello, world!\n");
    Ok(())
}

#[test]
#[cfg_attr(not(feature = "component-model"), ignore)]
fn component_missing_feature() -> Result<()> {
    let path = "tests/all/cli_tests/empty-component.wat";
    let wasm = build_wasm(path)?;
    let output = get_wasmtime_command()?
        .arg("-Ccache=n")
        .arg("-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}"
    );

    // also tests with raw *.wat input
    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());

    // also tests with raw *.wat input
    let output = get_wasmtime_command()?
        .arg("-Ccache=n")
        .arg(path)
        .output()?;
    assert!(output.status.success());

    Ok(())
}

// If the text format is invalid then the filename should be mentioned in the
// error message.
#[test]
fn bad_text_syntax() -> Result<()> {
    let output = get_wasmtime_command()?
        .arg("-Ccache=n")
        .arg("tests/all/cli_tests/bad-syntax.wat")
        .output()?;
    assert!(!output.status.success());
    let stderr = String::from_utf8_lossy(&output.stderr);
    assert!(
        stderr.contains("--> tests/all/cli_tests/bad-syntax.wat"),
        "bad stderr: {stderr}"
    );
    Ok(())
}

#[test]
#[cfg_attr(not(feature = "component-model"), ignore)]
fn run_basic_component() -> Result<()> {
    let path = "tests/all/cli_tests/component-basic.wat";
    let wasm = build_wasm(path)?;

    // Run both the `*.wasm` binary and the text format
    run_wasmtime(&[
        "-Ccache=n",
        "-Wcomponent-model",
        wasm.path().to_str().unwrap(),
    ])?;
    run_wasmtime(&["-Ccache=n", "-Wcomponent-model", path])?;

    Ok(())
}

#[test]
#[cfg_attr(not(feature = "component-model"), ignore)]
fn run_precompiled_component() -> Result<()> {
    let td = TempDir::new()?;
    let cwasm = td.path().join("component-basic.cwasm");
    let stdout = run_wasmtime(&[
        "compile",
        "tests/all/cli_tests/component-basic.wat",
        "-o",
        cwasm.to_str().unwrap(),
        "-Wcomponent-model",
    ])?;
    assert_eq!(stdout, "");
    let stdout = run_wasmtime(&[
        "run",
        "-Wcomponent-model",
        "--allow-precompiled",
        cwasm.to_str().unwrap(),
    ])?;
    assert_eq!(stdout, "");

    Ok(())
}

// Disable test on s390x because the large allocation may actually succeed;
// the whole 64-bit address space is available on this platform.
#[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)
    };

    // read empty pipe is ok
    let output = cmd()?.output()?;
    assert!(output.status.success());
    assert_eq!(String::from_utf8_lossy(&output.stdout), "0\n");

    // read itself is ok
    let file = File::open(test)?;
    let size = file.metadata()?.len();
    let output = cmd()?.stdin(File::open(test)?).output()?;
    assert!(output.status.success());
    assert_eq!(String::from_utf8_lossy(&output.stdout), format!("{size}\n"));

    // read piped input ok is ok
    let mut child = cmd()?
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .spawn()?;
    let mut stdin = child.stdin.take().unwrap();
    std::thread::spawn(move || {
        stdin.write_all(b"hello").unwrap();
    });
    let output = child.wait_with_output()?;
    assert!(output.status.success());
    assert_eq!(String::from_utf8_lossy(&output.stdout), "5\n");

    let count_up_to = |n: usize| -> Result<_> {
        let mut child = get_wasmtime_command()?
            .arg("--invoke=count-up-to")
            .arg("-Spreview2")
            .arg(test)
            .arg(n.to_string())
            .stdin(Stdio::piped())
            .stdout(Stdio::piped())
            .stderr(Stdio::piped())
            .spawn()?;
        let mut stdin = child.stdin.take().unwrap();
        let t = std::thread::spawn(move || {
            let mut written = 0;
            let bytes = [0; 64 * 1024];
            loop {
                written += match stdin.write(&bytes) {
                    Ok(n) => n,
                    Err(_) => break written,
                };
            }
        });
        let output = child.wait_with_output()?;
        assert!(output.status.success());
        let written = t.join().unwrap();
        let read = String::from_utf8_lossy(&output.stdout)
            .trim()
            .parse::<usize>()
            .unwrap();
        // The test reads in 1000 byte chunks so make sure that it doesn't read
        // more than 1000 bytes than requested.
        assert!(read < n + 1000, "test read too much {read}");
        Ok(written)
    };

    // wasmtime shouldn't eat information that the guest never actually tried to
    // read.
    //
    // NB: this may be a bit flaky. Exactly how much we wrote in the above
    // helper thread depends on how much the OS buffers for us. For now give
    // some some slop and assume that OSes are unlikely to buffer more than
    // that.
    let slop = 256 * 1024;
    for amt in [0, 100, 100_000] {
        let written = count_up_to(amt)?;
        assert!(written < slop + amt, "wrote too much {written}");
    }
    Ok(())
}

#[test]
fn 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(())
}

// Very basic use case: compile binary wasm file and run specific function with arguments.
#[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, format_err};
    use http_body_util::BodyExt;
    use hyper::header::HeaderValue;
    use std::io::{BufRead, BufReader, Read, Write};
    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 to ensure that prints in the guest aren't buffered on the host by
    // accident. The test here will print something without a newline and then
    // wait for input on stdin, and the test here is to ensure that the
    // character shows up here even as the guest is waiting on input via stdin.
    #[test]
    fn cli_stdio_write_flushes() -> Result<()> {
        fn run(args: &[&str]) -> Result<()> {
            println!("running {args:?}");
            let mut child = get_wasmtime_command()?
                .args(args)
                .stdin(Stdio::piped())
                .stdout(Stdio::piped())
                .spawn()?;
            let mut stdout = child.stdout.take().unwrap();
            let mut buf = [0; 10];
            match stdout.read(&mut buf) {
                Ok(2) => assert_eq!(&buf[..2], b"> "),
                e => panic!("unexpected read result {e:?}"),
            }
            drop(stdout);
            drop(child.stdin.take().unwrap());
            let status = child.wait()?;
            assert!(status.success());
            Ok(())
        }

        run(&["run", "-Spreview2=n", CLI_STDIO_WRITE_FLUSHES])?;
        run(&["run", "-Spreview2=y", CLI_STDIO_WRITE_FLUSHES])?;
        run(&[
            "run",
            "-Wcomponent-model",
            CLI_STDIO_WRITE_FLUSHES_COMPONENT,
        ])?;
        Ok(())
    }

    #[test]
    fn cli_no_tcp() -> Result<()> {
        let output = super::run_wasmtime_for_output(
            &[
                "-Wcomponent-model",
                // Turn on network but turn off TCP
                "-Sinherit-network,tcp=no",
                CLI_NO_TCP_COMPONENT,
            ],
            None,
        )?;
        println!("{}", String::from_utf8_lossy(&output.stderr));
        assert!(output.status.success());
        Ok(())
    }

    #[test]
    fn cli_no_udp() -> Result<()> {
        let output = super::run_wasmtime_for_output(
            &[
                "-Wcomponent-model",
                // Turn on network but turn off UDP
                "-Sinherit-network,udp=no",
                CLI_NO_UDP_COMPONENT,
            ],
            None,
        )?;
        println!("{}", String::from_utf8_lossy(&output.stderr));
        assert!(output.status.success());
        Ok(())
    }

    #[test]
    fn 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",
                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 [
            // Tests still pass when we race with going to sleep.
            "-Wtimeout=1ns",
            // Tests pass when we wait till the Wasm has (likely) gone to sleep.
            "-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(())
    }

    /// Helper structure to manage an invocation of `wasmtime serve`
    struct WasmtimeServe {
        child: Option<Child>,
        addr: SocketAddr,
        shutdown_addr: SocketAddr,
    }

    impl WasmtimeServe {
        /// Creates a new server which will serve the wasm component pointed to
        /// by `wasm`.
        ///
        /// A `configure` callback is provided to specify how `wasmtime serve`
        /// will be invoked and configure arguments such as headers.
        fn new(wasm: &str, configure: impl FnOnce(&mut Command)) -> Result<WasmtimeServe> {
            // Spawn `wasmtime serve` on port 0 which will randomly assign it a
            // port.
            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()?;

            // Read the first few lines of stderr which will say which address
            // it's listening on. The first line is the shutdown line (with
            // `--shutdown-addr`) and the second is what `--addr` was bound to.
            // This is done to figure out what `:0` was bound to in the child
            // process.
            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),
                // If either failed kill the child and otherwise try to shepherd
                // along any contextual information we have.
                (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,
            })
        }

        /// Completes this server gracefully by printing the output on failure.
        fn finish(mut self) -> Result<(String, String)> {
            let mut child = self.child.take().unwrap();

            // If the child process has already exited, then great! Otherwise
            // the server is still running and it shouldn't be possible to exit
            // until a shutdown signal is sent, so do that here. Make a TCP
            // connection to the shutdown port which is used as a shutdown
            // signal.
            if child.try_wait()?.is_none() {
                std::net::TcpStream::connect(&self.shutdown_addr)
                    .context("failed to initiate graceful shutdown")?;
            }

            // Regardless of whether we just shut the server down or whether it
            // was already shut down (e.g. panicked or similar), wait for the
            // result here. The result should succeed (e.g. 0 exit status), and
            // if it did then the stdout/stderr are the caller's problem.
            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(),
            ))
        }

        /// Send a request to this server and wait for the response.
        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)))
        }
    }

    // Don't leave child processes running by accident so kill the child process
    // if our server goes away.
    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] // TODO: printing stderr in the child and killing the child at the
    // end of this test race so the stderr may be present or not. Need
    // to implement a more graceful shutdown routine for `wasmtime
    // serve`.
    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(())
    }

    // Technically this test is a little racy. This binds port 0 to acquire a
    // random port, issues a single request to this port, but then kills this
    // server while the request is still processing. The port is then rebound
    // in the next process while it technically could be stolen by another
    // process.
    #[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;

        // Start up a `send` and `conn_task` which represents a connection to
        // this server.
        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;

        // ... once a response has been received (or at least the status
        // code/headers) then kill the server. THis is done while `conn_task`
        // and `send` are still alive so we're guaranteed that the other side
        // got a request (we got a response) and our connection is still open.
        //
        // This forces the address/port into the `TIME_WAIT` state. The rebind
        // below in the next process will fail if `SO_REUSEADDR` isn't set.
        drop(server);
        drop(send);
        let _ = conn_task.await;

        // If this is successfully bound then we'll create `WasmtimeServe`
        // which reads off the first line of output to know which address was
        // bound.
        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_hello_stdout_component() -> Result<()> {
        println!("{CLI_HELLO_STDOUT_COMPONENT}");
        let output = run_wasmtime(&[
            "run",
            "-Wcomponent-model",
            "--invoke",
            "run()",
            CLI_HELLO_STDOUT_COMPONENT,
        ])?;
        // First this component prints "hello, world", then the invoke
        // result is printed as "ok".
        assert_eq!(output, "hello, world\nok\n");
        Ok(())
    }

    #[test]
    fn preview2_random_limits() -> Result<()> {
        println!("{PREVIEW2_RANDOM_COMPONENT}");

        // By default, p2_random.rs fits within the random limits - always
        // asks for 256 bytes.
        let output = run_wasmtime(&["run", PREVIEW2_RANDOM_COMPONENT])?;
        assert_eq!(output, "");

        // Lowering limit to 255 bytes should produce a trap in the first
        // call, which goes by way of the wasip1 adapter:
        let output = run_wasmtime(&["run", "-Smax-random-size=255", PREVIEW2_RANDOM_COMPONENT])
            .err()
            .ok_or_else(|| format_err!("execution with max-random-size=255 should trap"))?;
        let output = format!("{output:?}");
        assert!(
            output.contains("wasi::lib_generated::random_get"),
            "expected error stack frames to contain 'wasip1::lib_generated::random_get'. Got:\n{output}"
        );
        assert!(
            output.contains("requested len 256 exceeds limit 255"),
            "expected error stack frames to contain 'requested len 256 exceeds limit 255'. Got:\n{output}"
        );

        // Lowering limit to 255 bytes and setting environment variable for
        // the first call to only request 255 bytes should be OK, and then the
        // program produce a trap in the second call, which calls the wasip2
        // random import directly:
        let output = run_wasmtime(&[
            "run",
            "-Smax-random-size=255",
            "--env",
            "TEST_P1_RANDOM_LEN=255",
            PREVIEW2_RANDOM_COMPONENT,
        ])
        .err()
        .ok_or_else(|| format_err!("execution with max-random-size=255 should trap"))?;
        let output = format!("{output:?}");
        assert!(
            output.contains("wasi::random::random::get_random_bytes"),
            "expected error stack frames to contain 'wasi::random::random::get_random_bytes'. Got:\n{output}"
        );
        assert!(
            output.contains("requested len 256 exceeds limit 255"),
            "expected error stack frames to contain 'requested len 256 exceeds limit 255'. Got:\n{output}"
        );

        // Lowering limit to 255 bytes and setting environment variable for
        // the first and second calls to be at the limit should produce a trap
        // in the third call, which calls the wasip2 insecure random import:
        let output = run_wasmtime(&[
            "run",
            "-Smax-random-size=255",
            "--env",
            "TEST_P1_RANDOM_LEN=255",
            "--env",
            "TEST_P2_RANDOM_LEN=255",
            PREVIEW2_RANDOM_COMPONENT,
        ])
        .err()
        .ok_or_else(|| format_err!("execution with max-random-size=255 should trap"))?;
        let output = format!("{output:?}");
        assert!(
            output.contains("wasi::random::insecure::get_insecure_random_bytes"),
            "expected error stack frames to contain 'wasi::random::insecure::get_insecure_random_bytes'. Got:\n{output}"
        );
        assert!(
            output.contains("requested len 256 exceeds limit 255"),
            "expected error stack frames to contain 'requested len 256 exceeds limit 255'. Got:\n{output}"
        );

        // Lowering limit to 255 bytes and setting environment variable for
        // the first and second calls to be under the limit should trap in the
        // third call, which calls the wasip2 insecure random import:
        let output = run_wasmtime(&[
            "run",
            "-Smax-random-size=255",
            "--env",
            "TEST_P1_RANDOM_LEN=255",
            "--env",
            "TEST_P2_RANDOM_LEN=255",
            "--env",
            "TEST_P2_INSECURE_RANDOM_LEN=255",
            PREVIEW2_RANDOM_COMPONENT,
        ])
        .context("setting all calls to be equal to the limit should pass")?;
        assert_eq!(output, "");

        Ok(())
    }

    #[test]
    fn cli_many_resources() -> Result<()> {
        let err = run_wasmtime(&["run", CLI_MANY_RESOURCES_COMPONENT]).unwrap_err();
        assert!(
            err.to_string().contains("resource table has no free keys"),
            "bad error message: {err}"
        );
        Ok(())
    }

    #[test]
    fn cli_max_resources() -> Result<()> {
        let err =
            run_wasmtime(&["run", "-Smax-resources=50", CLI_MAX_RESOURCES_COMPONENT]).unwrap_err();
        assert!(
            err.to_string().contains("resource table has no free keys"),
            "bad error message: {err}"
        );
        run_wasmtime(&["run", "-Smax-resources=200", CLI_MAX_RESOURCES_COMPONENT])?;
        Ok(())
    }

    #[test]
    fn cli_p1_hostcall_fuel() -> Result<()> {
        let dir = tempfile::tempdir()?;
        run_wasmtime(&[
            "run",
            &format!("--dir={}::.", dir.path().to_str().unwrap()),
            "-Shostcall-fuel=1000",
            CLI_P1_HOSTCALL_FUEL,
        ])?;
        Ok(())
    }

    #[test]
    fn cli_p2_hostcall_fuel() -> Result<()> {
        enum Exit {
            Ok,
            NoFuel,
            TooManyZeroes,
            BufferTooLarge,
        }

        let dir = tempfile::tempdir()?;
        let file = dir.path().join("1mb");
        std::fs::write(&file, vec![0; 1024 * 1024])?;
        for (arg, exit) in [
            ("poll", Exit::NoFuel),
            ("read", Exit::Ok),
            ("write", Exit::NoFuel),
            ("mkdir", Exit::NoFuel),
            ("write-stream", Exit::NoFuel),
            ("write-stream-blocking", Exit::NoFuel),
            ("resolve", Exit::NoFuel),
            ("udp-send-many", Exit::NoFuel),
            ("udp-send-big", Exit::NoFuel),
            ("write-zeroes", Exit::TooManyZeroes),
            ("write-stream-buffer-too-large", Exit::BufferTooLarge),
            ("write-zeroes-buffer-too-large", Exit::BufferTooLarge),
            ("read-file-big", Exit::Ok),
            ("read-tcp-big", Exit::Ok),
        ] {
            println!("test: {arg}");
            let result = run_wasmtime(&[
                "run",
                "-Shostcall-fuel=5000",
                "-Sinherit-network",
                &format!("--dir={}::.", dir.path().to_str().unwrap()),
                CLI_P2_HOSTCALL_FUEL_COMPONENT,
                arg,
            ]);

            match exit {
                Exit::Ok => {
                    result.unwrap();
                }
                Exit::NoFuel => {
                    let err = result.unwrap_err();
                    assert!(
                        err.to_string()
                            .contains("fuel allocated for hostcalls has been exhausted"),
                        "bad error message: {err}"
                    );
                }
                Exit::TooManyZeroes => {
                    let err = result.unwrap_err();
                    assert!(
                        err.to_string()
                            .contains("cannot write more zeroes than `check_write` allows"),
                        "bad error message: {err}"
                    );
                }
                Exit::BufferTooLarge => {
                    let err = result.unwrap_err();
                    assert!(
                        err.to_string().contains("Buffer too large"),
                        "bad error message: {err}"
                    );
                }
            }
        }
        Ok(())
    }

    #[test]
    fn cli_http_headers() -> Result<()> {
        for test in ["append", "append-empty", "append-same", "append-same-empty"] {
            let err = run_wasmtime(&[
                "run",
                "-Shttp",
                "-Smax-http-fields-size=1048576",
                CLI_HTTP_HEADERS_COMPONENT,
                test,
            ])
            .unwrap_err();
            assert!(
                err.to_string()
                    .contains("Field size limit 1048576 exceeded")
                    || err.to_string().contains("max size reached"),
                "bad error message: {err:?}"
            );
        }

        // With an extremely large limit Wasmtime still shouldn't panic.
        let err = run_wasmtime(&[
            "run",
            "-Shttp",
            &format!("-Smax-http-fields-size={}", 1 << 30),
            CLI_HTTP_HEADERS_COMPONENT,
            "append",
        ])
        .unwrap_err();
        assert!(
            err.to_string().contains("max size reached"),
            "bad error message: {err:?}"
        );
        Ok(())
    }
}

#[test]
fn settings_command() -> Result<()> {
    // Skip this test on platforms that Cranelift doesn't support.
    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(&[
        // Configure VTune...
        "-verbose",
        "-collect",
        "hotspots",
        "-user-data-dir",
        &std::env::temp_dir().to_string_lossy(),
        // ...then run Wasmtime with profiling enabled:
        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")?;

    // Test some valid TOML values
    let (mut cfg, cfg_path) = tempfile::NamedTempFile::new()?.into_parts();
    cfg.write_all(
        br#"
        [optimize]
        opt-level = 2
        signals-based-traps = false

        [codegen]
        collector = "null"

        [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");

    // Make sure CLI flags overrides TOML values
    let output = run_wasmtime(&[
        "run",
        "--config",
        cfg_path.to_str().unwrap(),
        "--invoke",
        "get_f64",
        "-W",
        "max-wasm-stack=0", // should override TOML value 65536 specified above and execution should fail
        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",
    );

    // Test invalid TOML key
    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"
    );

    // Test invalid TOML table
    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")?;
    // 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");
    // 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");
    // 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");
    // 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,
    )?;
    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");
    // 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");
    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<()> {
    // Works by default
    run_wasmtime(&["tests/all/cli_tests/big_table.wat"])?;

    // 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,
    )?;
    assert!(!output.status.success());
    println!("{}", String::from_utf8_lossy(&output.stderr));
    assert!(String::from_utf8_lossy(&output.stderr).contains("pooling allocator"));

    // Does work with `-Wmax-table-elements`
    run_wasmtime(&[
        "-Opooling-allocator",
        "-Wmax-table-elements=25000",
        "tests/all/cli_tests/big_table.wat",
    ])?;
    // Also works with `-Opooling-table-elements`
    run_wasmtime(&[
        "-Opooling-allocator",
        "-Opooling-table-elements=25000",
        "tests/all/cli_tests/big_table.wat",
    ])?;
    Ok(())
}
