use std::fs;
use std::process::Command;
fn run_retch(args: &[&str]) -> (String, String, bool) {
let bin_path = env!("CARGO_BIN_EXE_retch");
let output = Command::new(bin_path)
.args(args)
.output()
.expect("Failed to execute retch binary");
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
(stdout, stderr, output.status.success())
}
#[test]
fn test_cli_help() {
let (stdout, stderr, success) = run_retch(&["--help"]);
assert!(success);
assert!(stderr.is_empty());
assert!(stdout.contains("Usage:"));
assert!(stdout.contains("Options:"));
assert!(stdout.contains("--theme"));
assert!(stdout.contains("--logo"));
assert!(stdout.contains("-s, --short"));
assert!(stdout.contains("-l, --long"));
assert!(stdout.contains("-f, --full"));
}
#[test]
fn test_cli_mode_missing_arg() {
let (_, stderr, success) = run_retch(&["--mode"]);
assert!(!success);
assert!(stderr.contains("error: a value is required for '--mode <MODE>' but none was supplied"));
}
#[test]
fn test_cli_version() {
let (stdout, stderr, success) = run_retch(&["--version"]);
assert!(success);
assert!(stderr.is_empty());
let current_version = env!("CARGO_PKG_VERSION");
assert!(stdout.contains(current_version));
}
#[test]
fn test_cli_list_themes() {
let (stdout, stderr, success) = run_retch(&["--list-themes"]);
assert!(success);
assert!(stderr.is_empty());
assert!(stdout.contains("neutral"));
assert!(stdout.contains("dark"));
assert!(stdout.contains("light"));
assert!(stdout.contains("catppuccin-mocha"));
}
#[test]
fn test_cli_print_logos() {
let (stdout, stderr, success) = run_retch(&["--print-logos"]);
assert!(success);
assert!(stderr.is_empty());
assert!(stdout.contains("Arch"));
assert!(stdout.contains("Debian"));
assert!(stdout.contains("Fedora"));
assert!(stdout.contains("MX Linux"));
assert!(stdout.contains("Linux Mint"));
assert!(stdout.contains("Kali Linux"));
assert!(stdout.contains("Zorin OS"));
assert!(stdout.contains("Garuda Linux"));
assert!(stdout.contains("macOS"));
assert!(stdout.contains("Windows"));
assert!(stdout.contains("Tux"));
}
#[test]
fn test_cli_generate_config() {
let (stdout, stderr, success) = run_retch(&["--generate-config"]);
assert!(success);
assert!(stderr.is_empty());
assert!(stdout.contains("# Theme to use"));
assert!(stdout.contains("# show_logo = true"));
}
#[test]
fn test_cli_write_config_temp() {
let temp_dir = std::env::temp_dir();
let temp_file = temp_dir.join(format!("retch_test_config_{}.toml", std::process::id()));
if temp_file.exists() {
let _ = fs::remove_file(&temp_file);
}
let path_str = temp_file.to_str().unwrap();
let (stdout, stderr, success) = run_retch(&["--write-config", path_str]);
assert!(success);
assert!(stderr.is_empty());
assert!(stdout.contains("Wrote default config to"));
assert!(temp_file.exists());
let content = fs::read_to_string(&temp_file).unwrap();
assert!(content.contains("# Theme to use"));
let _ = fs::remove_file(&temp_file);
}
#[test]
fn test_cli_fields_terminal_size() {
let (stdout, stderr, success) = run_retch(&["--fields", "terminal-size"]);
assert!(success, "stderr: {}", stderr);
assert!(stderr.is_empty());
assert!(
stdout.contains("Terminal Size") || stdout.trim().is_empty(),
"unexpected output: {}",
stdout
);
}
#[test]
fn test_cli_fields_dns() {
let (stdout, stderr, success) = run_retch(&["--fields", "dns"]);
assert!(success, "stderr: {}", stderr);
assert!(stderr.is_empty());
assert!(
stdout.contains("DNS"),
"expected 'DNS' in output: {}",
stdout
);
}
#[test]
fn test_cli_fields_wm() {
let (stdout, stderr, success) = run_retch(&["--fields", "wm"]);
assert!(success, "stderr: {}", stderr);
assert!(stderr.is_empty());
assert!(
stdout.contains("WM") || stdout.trim().is_empty(),
"unexpected output: {}",
stdout
);
}
#[test]
fn test_cli_full_mode() {
let (stdout, stderr, success) = run_retch(&["--full", "--no-logo"]);
assert!(success, "stderr: {}", stderr);
assert!(stderr.is_empty());
assert!(
stdout.contains("OS"),
"expected 'OS' in --full output: {}",
stdout
);
}
#[test]
fn test_cli_piped_output_no_graphical_logo() {
let (stdout, stderr, success) = run_retch(&[]);
assert!(success, "stderr: {}", stderr);
assert!(
!stdout.contains("\x1b_G"),
"Kitty escape sequence found in piped output"
);
assert!(
!stdout.contains("\x1b]1337"),
"iTerm2 escape sequence found in piped output"
);
}
#[test]
fn test_bench_compiles() {
let output = Command::new("cargo")
.args(["bench", "--no-run"])
.output()
.expect("Failed to run cargo bench --no-run");
assert!(
output.status.success(),
"Benchmark harness failed to compile: {}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_docs_cover_all_registry_fields() {
let manifest = env!("CARGO_MANIFEST_DIR");
let readme = fs::read_to_string(format!("{manifest}/README.md")).expect("read README.md");
let man = fs::read_to_string(format!("{manifest}/docs/retch.1.md")).expect("read retch.1.md");
for key in retch_cli::fields::all_keys() {
assert!(
readme.contains(&format!("\"{key}\"")),
"README.md is missing field key {key:?} — add it to the fields array"
);
assert!(
man.contains(&format!("`{key}`")),
"docs/retch.1.md is missing field key {key:?} — document it under FIELDS"
);
}
}
#[test]
fn test_generated_config_covers_all_registry_fields() {
let (stdout, _stderr, success) = run_retch(&["--generate-config"]);
assert!(success);
for key in retch_cli::fields::all_keys() {
assert!(
stdout.contains(&format!("\"{key}\"")),
"--generate-config output missing field key: {key}"
);
}
}
fn strip_ansi(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut chars = s.chars();
while let Some(c) = chars.next() {
if c != '\x1b' {
out.push(c);
continue;
}
if chars.next() != Some('[') {
continue;
}
for f in chars.by_ref() {
if ('\u{40}'..='\u{7E}').contains(&f) {
break;
}
}
}
out
}
fn run_retch_no_color(args: &[&str], no_color: Option<&str>) -> String {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_retch"));
cmd.args(args);
match no_color {
Some(v) => cmd.env("NO_COLOR", v),
None => cmd.env_remove("NO_COLOR"),
};
let output = cmd.output().expect("Failed to execute retch binary");
assert!(output.status.success(), "retch {args:?} failed");
String::from_utf8_lossy(&output.stdout).into_owned()
}
#[test]
fn test_color_flag_and_no_color() {
let args = |color: &'static str| vec!["--fields", "os", "--no-logo", color];
let has_esc = |s: &str| s.contains('\x1b');
let forced = run_retch_no_color(&args("--color=always"), None);
assert!(has_esc(&forced), "--color=always must colour a pipe");
assert!(
forced.contains("OS"),
"control produced no OS line: {forced:?}"
);
let piped = run_retch_no_color(&["--fields", "os", "--no-logo"], None);
assert!(
!has_esc(&piped),
"piped output is plain by default: {piped:?}"
);
assert!(
piped.contains("OS:"),
"plain output lost its content: {piped:?}"
);
assert_eq!(piped, strip_ansi(&forced));
assert!(!has_esc(&run_retch_no_color(&args("--color=never"), None)));
assert!(!has_esc(&run_retch_no_color(
&args("--color=auto"),
Some("1")
)));
assert!(has_esc(&run_retch_no_color(
&args("--color=always"),
Some("1")
)));
}
#[test]
fn test_help_lists_color_flag() {
let (stdout, _, success) = run_retch(&["--help"]);
assert!(success);
assert!(stdout.contains("--color <WHEN>"), "{stdout}");
}
#[test]
fn test_host_is_listed_first() {
for args in [
["--short", "--no-logo"].as_slice(),
["--no-logo"].as_slice(),
] {
let (stdout, _, success) = run_retch(args);
assert!(success, "retch {args:?} did not exit successfully");
let plain = strip_ansi(&stdout);
let first = plain
.lines()
.find(|l| !l.trim().is_empty())
.unwrap_or_else(|| panic!("retch {args:?} produced no output lines"));
assert!(
first.starts_with("Host:"),
"expected `Host` to be the first field for {args:?}, got: {first:?}"
);
let host = plain.find("\nHost:").expect("Host line missing");
let os = plain.find("\nOS:").expect("OS line missing");
assert!(
host < os,
"expected Host before OS for {args:?} (host at {host}, os at {os})"
);
}
}