use std::fs;
use std::path::PathBuf;
use std::process::Command;
use std::sync::atomic::{AtomicU64, Ordering};
static TEST_COUNTER: AtomicU64 = AtomicU64::new(0);
fn bin() -> PathBuf {
let mut path = PathBuf::from(env!("CARGO_BIN_EXE_tp"));
if !path.exists() {
path = PathBuf::from(env!("CARGO_BIN_EXE_temprs"));
}
path
}
fn setup_clean_env() -> PathBuf {
let id = TEST_COUNTER.fetch_add(1, Ordering::SeqCst);
let dir = std::env::temp_dir().join(format!(
"temprs_expire_invalid_{}_{}",
std::process::id(),
id
));
if dir.exists() {
let _ = fs::remove_dir_all(&dir);
}
dir
}
fn run_tp(dir: &PathBuf, args: &[&str]) -> std::process::Output {
Command::new(bin())
.env("TEMPRS_DIR", dir)
.args(args)
.stdin(std::process::Stdio::null())
.output()
.expect("failed to execute tp")
}
fn run_tp_stdin(dir: &PathBuf, args: &[&str], input: &str) -> std::process::Output {
use std::io::Write;
let mut child = Command::new(bin())
.env("TEMPRS_DIR", dir)
.args(args)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.expect("failed to spawn tp");
child
.stdin
.as_mut()
.unwrap()
.write_all(input.as_bytes())
.unwrap();
child.wait_with_output().unwrap()
}
fn count(dir: &PathBuf) -> usize {
let out = run_tp(dir, &["--count"]);
String::from_utf8_lossy(&out.stdout)
.split_whitespace()
.find_map(|tok| tok.parse().ok())
.unwrap_or(usize::MAX)
}
fn tick() {
std::thread::sleep(std::time::Duration::from_millis(50));
}
#[test]
fn expire_equals_negative_must_not_silently_clear_stack() {
let dir = setup_clean_env();
let push = run_tp_stdin(&dir, &[], "keepme");
assert!(push.status.success(), "seed push must succeed");
tick();
assert_eq!(count(&dir), 1, "precondition: one item on stack");
let out = run_tp(&dir, &["--expire=-1"]);
let stack_after = count(&dir);
assert_eq!(
stack_after,
1,
"--expire=-1 must not silently purge files; \
stack went from 1 to {} (exit={:?}, stderr={:?})",
stack_after,
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn expire_nan_must_not_silently_clear_stack() {
let dir = setup_clean_env();
let push = run_tp_stdin(&dir, &[], "keepme");
assert!(push.status.success(), "seed push must succeed");
tick();
assert_eq!(count(&dir), 1, "precondition: one item on stack");
let out = run_tp(&dir, &["--expire", "NaN"]);
let stack_after = count(&dir);
assert_eq!(
stack_after,
1,
"--expire NaN must reject NaN (or be a no-op), \
not silently purge; stack went from 1 to {} (exit={:?}, stderr={:?})",
stack_after,
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
}
#[test]
fn clear_all_refuses_when_temprs_dir_basename_does_not_contain_temprs() {
let dir = setup_clean_env();
let unsafe_dir = std::env::temp_dir().join(format!(
"not_a_safe_name_{}_{}",
std::process::id(),
TEST_COUNTER.fetch_add(1, Ordering::SeqCst)
));
fs::create_dir_all(&unsafe_dir).unwrap();
let sentinel = unsafe_dir.join("DO_NOT_DELETE.txt");
fs::write(&sentinel, b"important user data").unwrap();
let out = run_tp(&unsafe_dir, &["-c"]);
assert!(
sentinel.exists(),
"tp -c must refuse to remove a TEMPRS_DIR whose basename does not contain `temprs`; \
sentinel was wiped (exit={:?}, stderr={:?})",
out.status.code(),
String::from_utf8_lossy(&out.stderr)
);
let _ = fs::remove_dir_all(&unsafe_dir);
let _ = dir;
}