use std::process::Command;
fn cargo_bin() -> Command {
Command::new(env!("CARGO_BIN_EXE_iftoprs"))
}
#[test]
fn help_short_exits_zero() {
let out = cargo_bin().arg("-h").output().unwrap();
assert_eq!(out.status.code(), Some(0));
}
#[test]
fn help_long_exits_zero() {
let out = cargo_bin().arg("--help").output().unwrap();
assert_eq!(out.status.code(), Some(0));
}
#[test]
fn version_short_exits_zero() {
let out = cargo_bin().arg("-V").output().unwrap();
assert_eq!(out.status.code(), Some(0));
}
#[test]
fn list_interfaces_exits_zero() {
let out = cargo_bin().arg("--list-interfaces").output().unwrap();
assert_eq!(out.status.code(), Some(0));
}
#[test]
fn list_colors_exits_zero() {
let out = cargo_bin().arg("--list-colors").output().unwrap();
assert_eq!(out.status.code(), Some(0));
}
#[test]
fn completions_zsh_exits_zero() {
let out = cargo_bin().args(["--completions", "zsh"]).output().unwrap();
assert_eq!(out.status.code(), Some(0));
}
#[test]
fn invalid_flag_exits_two() {
let out = cargo_bin()
.arg("--this-flag-does-not-exist")
.output()
.unwrap();
assert_eq!(
out.status.code(),
Some(2),
"unknown flag should produce clap's standard exit code 2"
);
}
#[test]
fn invalid_completions_shell_exits_nonzero() {
let out = cargo_bin()
.args(["--completions", "no-such-shell"])
.output()
.unwrap();
assert!(
!out.status.success(),
"unknown completion shell must error, got exit {:?}",
out.status.code()
);
}
#[test]
fn help_writes_to_stdout_not_stderr() {
let out = cargo_bin().arg("-h").output().unwrap();
assert!(!out.stdout.is_empty(), "help body must hit stdout");
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
!stderr.contains("BANDWIDTH MONITOR"),
"help banner should be on stdout, not stderr (got stderr: {:?})",
&stderr.as_ref()[..stderr.len().min(200)]
);
}
#[test]
fn version_writes_to_stdout_not_stderr() {
let out = cargo_bin().arg("-V").output().unwrap();
assert!(!out.stdout.is_empty(), "version body must hit stdout");
}
#[test]
fn version_long_matches_cargo_pkg_version() {
let out = cargo_bin().arg("--version").output().unwrap();
let s = String::from_utf8_lossy(&out.stdout);
assert!(
s.contains(env!("CARGO_PKG_VERSION")),
"--version output must contain crate version {:?}, got {:?}",
env!("CARGO_PKG_VERSION"),
s,
);
}
#[test]
fn version_short_matches_cargo_pkg_version() {
let out = cargo_bin().arg("-V").output().unwrap();
let s = String::from_utf8_lossy(&out.stdout);
assert!(s.contains(env!("CARGO_PKG_VERSION")));
}
#[test]
fn every_long_flag_paired_with_h_exits_zero() {
let pairs: &[&[&str]] = &[
&["--bytes", "-h"],
&["--hide-ports", "-h"],
&["--json", "-h"],
&["--no-bars", "-h"],
&["--no-dns", "-h"],
&["--no-port-names", "-h"],
&["--no-processes", "-h"],
&["--interface", "lo", "-h"],
&["--filter", "port 80", "-h"],
&["--net-filter", "127.0.0.0/8", "-h"],
&["--config", "/tmp/iftoprs-nonexistent.conf", "-h"],
&["--completions", "zsh", "-h"],
];
for args in pairs {
let out = cargo_bin().args(*args).output().unwrap();
assert_eq!(
out.status.code(),
Some(0),
"{:?} + -h should exit 0, got {:?}",
args,
out.status,
);
}
}
#[test]
fn config_path_pointing_to_directory_handled_gracefully() {
let out = cargo_bin().args(["-c", "/tmp", "-h"]).output().unwrap();
assert!(
out.status.success(),
"-h must short-circuit even when -c points at a dir; got {:?}\nstderr: {:?}",
out.status,
String::from_utf8_lossy(&out.stderr),
);
}
fn scratch_path(suffix: &str) -> std::path::PathBuf {
use std::sync::atomic::{AtomicU64, Ordering};
static N: AtomicU64 = AtomicU64::new(0);
let id = N.fetch_add(1, Ordering::SeqCst);
std::env::temp_dir().join(format!(
"iftoprs_cli_extra_{}_{}_{}",
std::process::id(),
id,
suffix
))
}
#[test]
fn malformed_toml_config_errors_cleanly() {
let p = scratch_path("malformed.conf");
std::fs::write(&p, "this isn't valid TOML = [[[\n").expect("write");
let out = cargo_bin()
.args(["-c", p.to_str().unwrap()])
.output()
.unwrap();
let _ = std::fs::remove_file(&p);
assert!(
out.status.code().is_some(),
"process must exit with a code (no signal), got {:?}",
out.status,
);
}
#[test]
fn empty_toml_config_accepted() {
let p = scratch_path("empty.conf");
std::fs::write(&p, "").expect("write");
let out = cargo_bin()
.args(["-c", p.to_str().unwrap(), "-h"])
.output()
.unwrap();
let _ = std::fs::remove_file(&p);
assert_eq!(out.status.code(), Some(0));
}
fn collect_completions(shell: &str) -> String {
let out = cargo_bin().args(["--completions", shell]).output().unwrap();
assert!(out.status.success(), "--completions {shell} must succeed");
String::from_utf8_lossy(&out.stdout).to_string()
}
#[test]
fn completions_bash_mentions_all_long_flags() {
let bash = collect_completions("bash");
for flag in &[
"--bytes",
"--config",
"--hide-ports",
"--json",
"--no-bars",
"--no-dns",
"--no-port-names",
"--no-processes",
"--interface",
"--filter",
"--net-filter",
"--list-interfaces",
"--list-colors",
"--completions",
] {
assert!(
bash.contains(flag),
"bash completion missing flag {flag}; output prefix: {:?}",
&bash.as_str()[..bash.len().min(400)],
);
}
}
#[test]
fn completions_zsh_mentions_all_long_flags() {
let zsh = collect_completions("zsh");
for flag in &[
"--bytes",
"--config",
"--hide-ports",
"--json",
"--no-bars",
"--no-dns",
"--no-port-names",
"--no-processes",
"--interface",
"--filter",
"--net-filter",
"--list-interfaces",
"--list-colors",
"--completions",
] {
assert!(zsh.contains(flag), "zsh completion missing flag {flag}",);
}
}
#[test]
fn completions_fish_mentions_all_long_flags() {
let fish = collect_completions("fish");
for flag in &[
"bytes",
"config",
"hide-ports",
"json",
"no-bars",
"no-dns",
"no-port-names",
"no-processes",
"interface",
"filter",
"net-filter",
"list-interfaces",
"list-colors",
"completions",
] {
let needle = format!("-l {}", flag);
assert!(
fish.contains(&needle),
"fish completion missing flag '{flag}' (expected `{needle}`)",
);
}
}
#[test]
fn completions_bash_starts_with_complete_directive() {
let bash = collect_completions("bash");
assert!(
bash.contains("complete") || bash.contains("_iftoprs"),
"bash completion shape unexpected: {:?}",
&bash[..bash.len().min(200)],
);
}
#[test]
fn completions_zsh_starts_with_compdef() {
let zsh = collect_completions("zsh");
assert!(
zsh.contains("#compdef") || zsh.contains("compdef"),
"zsh completion missing compdef header: {:?}",
&zsh[..zsh.len().min(200)],
);
}
#[test]
fn list_colors_output_is_nonempty_string() {
let out = cargo_bin().arg("--list-colors").output().unwrap();
let s = String::from_utf8_lossy(&out.stdout);
assert!(
!s.trim().is_empty(),
"--list-colors must print SOMETHING; got empty stdout"
);
}
#[test]
fn list_interfaces_includes_loopback() {
let out = cargo_bin().arg("--list-interfaces").output().unwrap();
let s = String::from_utf8_lossy(&out.stdout);
assert!(
s.contains("lo"),
"--list-interfaces output should contain loopback: {:?}",
s,
);
}
#[test]
fn version_short_and_long_produce_identical_output() {
let short = cargo_bin().arg("-V").output().unwrap().stdout;
let long = cargo_bin().arg("--version").output().unwrap().stdout;
assert_eq!(
short, long,
"-V and --version should produce identical text"
);
}
#[test]
fn list_interfaces_then_list_colors_both_succeed() {
let a = cargo_bin().arg("--list-interfaces").output().unwrap();
let b = cargo_bin().arg("--list-colors").output().unwrap();
assert!(a.status.success() && b.status.success());
}