#[cfg(not(target_arch = "wasm32"))]
mod os_default {
use perl_subprocess_runtime::OsSubprocessRuntime;
#[cfg(not(windows))]
use perl_subprocess_runtime::SubprocessRuntime;
#[test]
fn os_runtime_default_impl_delegates_to_new() -> Result<(), Box<dyn std::error::Error>> {
let _runtime = OsSubprocessRuntime::default();
Ok(())
}
#[cfg(not(windows))]
#[test]
fn os_runtime_default_runs_command_successfully() -> Result<(), Box<dyn std::error::Error>> {
let runtime = OsSubprocessRuntime::default();
let output = runtime.run_command("true", &[], None)?;
assert!(output.success());
Ok(())
}
}
#[cfg(not(target_arch = "wasm32"))]
mod input_validation {
use perl_subprocess_runtime::{OsSubprocessRuntime, SubprocessError, SubprocessRuntime};
fn run_command_error(
program: &str,
args: &[&str],
) -> Result<SubprocessError, Box<dyn std::error::Error>> {
let runtime = OsSubprocessRuntime::new();
match runtime.run_command(program, args, None) {
Ok(output) => Err(format!(
"command must fail validation before spawning; got status {}",
output.status_code
)
.into()),
Err(err) => Ok(err),
}
}
#[test]
fn rejects_empty_string_program_name() -> Result<(), Box<dyn std::error::Error>> {
let err = run_command_error("", &[])?;
assert!(
err.message.contains("must not be empty"),
"unexpected error message: {}",
err.message
);
Ok(())
}
#[test]
fn rejects_single_space_program_name() -> Result<(), Box<dyn std::error::Error>> {
let err = run_command_error(" ", &[])?;
assert!(err.message.contains("must not be empty"), "unexpected: {}", err.message);
Ok(())
}
#[test]
fn rejects_tab_only_program_name() -> Result<(), Box<dyn std::error::Error>> {
let err = run_command_error("\t", &[])?;
assert!(err.message.contains("must not be empty"), "unexpected: {}", err.message);
Ok(())
}
#[test]
fn rejects_newline_only_program_name() -> Result<(), Box<dyn std::error::Error>> {
let err = run_command_error("\n", &[])?;
assert!(err.message.contains("must not be empty"), "unexpected: {}", err.message);
Ok(())
}
#[test]
fn rejects_mixed_whitespace_program_name() -> Result<(), Box<dyn std::error::Error>> {
let err = run_command_error(" \t \r\n ", &[])?;
assert!(err.message.contains("must not be empty"), "unexpected: {}", err.message);
Ok(())
}
#[test]
fn rejects_nul_at_start_of_program_name() -> Result<(), Box<dyn std::error::Error>> {
let err = run_command_error("\0perl", &[])?;
assert!(err.message.contains("NUL"), "unexpected: {}", err.message);
Ok(())
}
#[test]
fn rejects_nul_at_end_of_program_name() -> Result<(), Box<dyn std::error::Error>> {
let err = run_command_error("perl\0", &[])?;
assert!(err.message.contains("NUL"), "unexpected: {}", err.message);
Ok(())
}
#[test]
fn rejects_nul_in_middle_of_program_name() -> Result<(), Box<dyn std::error::Error>> {
let err = run_command_error("per\0l", &[])?;
assert!(err.message.contains("NUL"), "unexpected: {}", err.message);
Ok(())
}
#[test]
fn rejects_nul_in_single_arg() -> Result<(), Box<dyn std::error::Error>> {
let err = run_command_error("perl", &["-e\0"])?;
assert!(err.message.contains("NUL"), "unexpected: {}", err.message);
Ok(())
}
#[test]
fn rejects_nul_in_second_arg() -> Result<(), Box<dyn std::error::Error>> {
let err = run_command_error("perl", &["-e", "print\0"])?;
assert!(err.message.contains("NUL"), "unexpected: {}", err.message);
Ok(())
}
#[test]
fn rejects_nul_in_third_arg() -> Result<(), Box<dyn std::error::Error>> {
let err = run_command_error("perl", &["-I", "lib", "scri\0pt.pl"])?;
assert!(err.message.contains("NUL"), "unexpected: {}", err.message);
Ok(())
}
#[test]
fn whitespace_program_checked_before_nul_in_arg() -> Result<(), Box<dyn std::error::Error>> {
let err = run_command_error(" ", &["arg\0"])?;
assert!(
err.message.contains("must not be empty"),
"expected empty-program error, got: {}",
err.message
);
Ok(())
}
#[test]
fn nul_in_program_checked_before_nul_in_arg() -> Result<(), Box<dyn std::error::Error>> {
let err = run_command_error("per\0l", &["arg\0"])?;
assert!(
err.message.contains("program name must not contain NUL"),
"expected NUL-in-program error, got: {}",
err.message
);
Ok(())
}
#[cfg(not(windows))]
#[test]
fn valid_program_empty_args_passes_validation() -> Result<(), Box<dyn std::error::Error>> {
let runtime = OsSubprocessRuntime::new();
let output = runtime.run_command("true", &[], None)?;
assert!(output.success());
Ok(())
}
#[cfg(not(windows))]
#[test]
fn valid_program_with_args_passes_validation() -> Result<(), Box<dyn std::error::Error>> {
let runtime = OsSubprocessRuntime::new();
let output = runtime.run_command("echo", &["hello", "world"], None)?;
assert!(output.success());
assert!(output.stdout_lossy().contains("hello"));
Ok(())
}
}
#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
mod timeout_constructor {
use perl_subprocess_runtime::{OsSubprocessRuntime, SubprocessRuntime};
#[test]
fn with_timeout_one_second_succeeds_for_instant_command()
-> Result<(), Box<dyn std::error::Error>> {
let runtime = OsSubprocessRuntime::with_timeout(1);
let output = runtime.run_command("true", &[], None)?;
assert!(output.success());
Ok(())
}
#[test]
fn with_timeout_propagates_output() -> Result<(), Box<dyn std::error::Error>> {
let runtime = OsSubprocessRuntime::with_timeout(5);
let output = runtime.run_command("echo", &["timeout-test"], None)?;
assert!(output.success());
assert!(output.stdout_lossy().trim() == "timeout-test");
Ok(())
}
#[test]
fn with_timeout_and_stdin() -> Result<(), Box<dyn std::error::Error>> {
let runtime = OsSubprocessRuntime::with_timeout(5);
let output = runtime.run_command("cat", &[], Some(b"hello"))?;
assert!(output.success());
assert_eq!(output.stdout_lossy(), "hello");
Ok(())
}
#[test]
fn with_timeout_tight_deadline_still_succeeds() -> Result<(), Box<dyn std::error::Error>> {
let runtime = OsSubprocessRuntime::with_timeout(1);
let output = runtime.run_command("echo", &["quick"], None)?;
assert!(output.success());
Ok(())
}
}