use std::fs;
use std::path::{Path, PathBuf};
use assert_cmd::Command;
fn snapdir_bin() -> &'static str {
env!("CARGO_BIN_EXE_snapdir")
}
fn temp_tree(tag: &str) -> PathBuf {
let mut dir = std::env::temp_dir();
let unique = format!(
"snapdir-cli-list-{tag}-{}-{:?}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
);
dir.push(unique);
fs::create_dir_all(&dir).expect("create temp tree");
dir
}
fn build_tree(root: &Path) {
fs::write(root.join("alpha.txt"), b"a").unwrap();
fs::write(root.join("beta.txt"), b"b").unwrap();
fs::write(root.join("gamma.txt"), b"g").unwrap();
fs::write(root.join("keep.txt"), b"k").unwrap();
}
fn manifest_stdout(root: &Path, args: &[&str]) -> String {
let mut cmd = Command::new(snapdir_bin());
cmd.arg("manifest");
cmd.args(args);
cmd.arg(root.to_string_lossy().into_owned());
let out = cmd.output().expect("run snapdir manifest");
assert!(
out.status.success(),
"snapdir manifest failed: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8(out.stdout).expect("utf8 stdout")
}
fn assert_present(stdout: &str, present: &[&str], absent: &[&str]) {
for name in present {
assert!(
stdout.contains(&format!("./{name}")),
"expected {name} present in:\n{stdout}"
);
}
for name in absent {
assert!(
!stdout.contains(&format!("./{name}")),
"expected {name} absent in:\n{stdout}"
);
}
}
#[test]
fn list_options_single_exclude_unchanged() {
let root = temp_tree("single");
build_tree(&root);
let out = manifest_stdout(&root, &["--exclude", "alpha"]);
assert_present(&out, &["beta.txt", "gamma.txt", "keep.txt"], &["alpha.txt"]);
fs::remove_dir_all(&root).ok();
}
#[test]
fn list_options_repeated_exclude() {
let root = temp_tree("repeated");
build_tree(&root);
let out = manifest_stdout(&root, &["--exclude", "alpha", "--exclude", "beta"]);
assert_present(&out, &["gamma.txt", "keep.txt"], &["alpha.txt", "beta.txt"]);
fs::remove_dir_all(&root).ok();
}
#[test]
fn list_options_comma_delimited() {
let root = temp_tree("comma");
build_tree(&root);
let out = manifest_stdout(&root, &["--exclude", "alpha,beta"]);
assert_present(&out, &["gamma.txt", "keep.txt"], &["alpha.txt", "beta.txt"]);
fs::remove_dir_all(&root).ok();
}
#[test]
fn list_options_mixed_comma_and_repeated() {
let root = temp_tree("mixed");
build_tree(&root);
let out = manifest_stdout(&root, &["--exclude", "alpha,beta", "--exclude", "gamma"]);
assert_present(&out, &["keep.txt"], &["alpha.txt", "beta.txt", "gamma.txt"]);
fs::remove_dir_all(&root).ok();
}
#[test]
fn list_options_repeated_and_comma_match_same_set() {
let root_a = temp_tree("eq-a");
let root_b = temp_tree("eq-b");
build_tree(&root_a);
build_tree(&root_b);
let comma = manifest_stdout(&root_a, &["--exclude", "alpha,gamma"]);
let repeated = manifest_stdout(&root_b, &["--exclude", "alpha", "--exclude", "gamma"]);
assert_present(
&comma,
&["beta.txt", "keep.txt"],
&["alpha.txt", "gamma.txt"],
);
assert_present(
&repeated,
&["beta.txt", "keep.txt"],
&["alpha.txt", "gamma.txt"],
);
fs::remove_dir_all(&root_a).ok();
fs::remove_dir_all(&root_b).ok();
}
#[test]
fn list_options_macro_combined_with_literal() {
let root = temp_tree("macro");
build_tree(&root);
fs::create_dir(root.join("node_modules")).unwrap();
fs::write(root.join("node_modules").join("pkg.txt"), b"p").unwrap();
let out = manifest_stdout(&root, &["--exclude", "%common%", "--exclude", "alpha"]);
assert_present(&out, &["beta.txt", "gamma.txt", "keep.txt"], &["alpha.txt"]);
assert!(
!out.contains("node_modules"),
"node_modules excluded by %common% in:\n{out}"
);
fs::remove_dir_all(&root).ok();
}
#[test]
fn list_options_subcommand_overrides_global_exclude() {
let root = temp_tree("precedence");
build_tree(&root);
let mut cmd = Command::new(snapdir_bin());
cmd.arg("--exclude").arg("alpha"); cmd.arg("manifest");
cmd.arg("--exclude").arg("gamma"); cmd.arg(root.to_string_lossy().into_owned());
let out = cmd.output().expect("run snapdir manifest");
assert!(out.status.success());
let stdout = String::from_utf8(out.stdout).unwrap();
assert_present(
&stdout,
&["alpha.txt", "beta.txt", "keep.txt"],
&["gamma.txt"],
);
fs::remove_dir_all(&root).ok();
}