#![allow(clippy::items_after_statements)]
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use assert_cmd::prelude::*;
use assert_fs::prelude::*;
use assert_fs::TempDir;
const SANDBOX_ID: &str = "ce2b0312911e5da75719a3fb3a23922252583a0a1b5c2044cea48ce0dcab8399";
const SANDBOX_FILE_COUNT: u64 = 2089;
fn snapdir(cache: &Path) -> Command {
let mut cmd = Command::cargo_bin("snapdir").expect("snapdir binary built");
cmd.env("SNAPDIR_CACHE_DIR", cache);
cmd.env_remove("NO_COLOR");
cmd.env("TERM", "xterm-256color");
cmd
}
fn sandbox_tree() -> Option<PathBuf> {
let mut dir = std::env::current_dir().ok()?;
loop {
let candidate = dir.join(".gatesmith/evidence/dx-sandbox/tree");
if candidate.join("README.md").is_file() {
return Some(candidate);
}
if !dir.pop() {
return None;
}
}
}
fn count_regular_files(root: &Path) -> u64 {
fn rec(dir: &Path, n: &mut u64) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let Ok(meta) = entry.path().symlink_metadata() else {
continue;
};
let ft = meta.file_type();
if ft.is_dir() {
rec(&entry.path(), n);
} else if ft.is_file() {
*n += 1;
}
}
}
let mut n = 0;
rec(root, &mut n);
n
}
fn build_many_file_tree(dir: &TempDir) -> u64 {
let n = 600u64;
for i in 0..n {
let sub = i % 12;
dir.child(format!("d{sub:02}/f{i:05}.bin"))
.write_str(&"payload-block-".repeat(48))
.unwrap();
}
dir.child("edge/empty_a.bin").write_str("").unwrap();
dir.child("edge/empty_b.bin").write_str("").unwrap();
n + 2
}
fn run_ok(cache: &Path, args: &[&str]) -> Output {
let out = snapdir(cache).args(args).output().expect("run snapdir");
assert!(
out.status.success(),
"snapdir {args:?} failed ({:?})\nstderr: {}",
out.status.code(),
String::from_utf8_lossy(&out.stderr),
);
out
}
fn stdout_str(out: &Output) -> String {
String::from_utf8(out.stdout.clone())
.unwrap()
.trim_end()
.to_owned()
}
fn has_ansi(bytes: &[u8]) -> bool {
bytes.contains(&0x1b)
}
fn has_cr(bytes: &[u8]) -> bool {
bytes.contains(&b'\r')
}
fn assert_is_id(id: &str, label: &str) {
assert_eq!(id.len(), 64, "{label}: stdout must be a bare id: {id:?}");
assert!(
id.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()),
"{label}: id must be lowercase hex: {id:?}"
);
}
fn strip_ansi(s: &str) -> String {
let bytes = s.as_bytes();
let mut out = String::with_capacity(s.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i] == 0x1b {
i += 1;
if i < bytes.len() && bytes[i] == b'[' {
i += 1;
while i < bytes.len() && !(0x40..=0x7e).contains(&bytes[i]) {
i += 1;
}
if i < bytes.len() {
i += 1; }
}
} else {
out.push(bytes[i] as char);
i += 1;
}
}
out
}
fn frames(raw: &[u8]) -> Vec<String> {
let text = String::from_utf8_lossy(raw);
text.split(['\r', '\n'])
.map(strip_ansi)
.map(|f| f.trim().to_owned())
.filter(|f| !f.is_empty())
.collect()
}
fn numbers_in(s: &str) -> Vec<u64> {
let mut out = Vec::new();
let mut cur = String::new();
for ch in s.chars() {
if ch.is_ascii_digit() {
cur.push(ch);
} else if !cur.is_empty() {
if let Ok(v) = cur.parse::<u64>() {
out.push(v);
}
cur.clear();
}
}
if !cur.is_empty() {
if let Ok(v) = cur.parse::<u64>() {
out.push(v);
}
}
out
}
fn fraction_denominators(frame: &str) -> Vec<u64> {
let bytes = frame.as_bytes();
let mut out = Vec::new();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'/' {
let mut j = i + 1;
while j < bytes.len() && bytes[j] == b' ' {
j += 1;
}
let start = j;
while j < bytes.len() && bytes[j].is_ascii_digit() {
j += 1;
}
let mut k = i;
if k > 0 && bytes[k - 1] == b' ' {
k -= 1;
}
let left_is_digit = k > 0 && bytes[k - 1].is_ascii_digit();
if left_is_digit && j > start {
if let Ok(v) = frame[start..j].parse::<u64>() {
out.push(v);
}
}
i = j.max(i + 1);
} else {
i += 1;
}
}
out
}
fn fraction_pairs(frame: &str) -> Vec<(u64, u64)> {
let bytes = frame.as_bytes();
let mut out = Vec::new();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'/' {
let mut l = i;
if l > 0 && bytes[l - 1] == b' ' {
l -= 1;
}
let num_end = l;
while l > 0 && bytes[l - 1].is_ascii_digit() {
l -= 1;
}
let num_start = l;
let mut r = i + 1;
while r < bytes.len() && bytes[r] == b' ' {
r += 1;
}
let den_start = r;
while r < bytes.len() && bytes[r].is_ascii_digit() {
r += 1;
}
if num_end > num_start && r > den_start {
if let (Ok(n), Ok(d)) = (
frame[num_start..num_end].parse::<u64>(),
frame[den_start..r].parse::<u64>(),
) {
out.push((n, d));
}
}
i = r.max(i + 1);
} else {
i += 1;
}
}
out
}
fn files_fraction_pairs(frame: &str) -> Vec<(u64, u64)> {
let mut out = Vec::new();
for (num, den) in fraction_pairs(frame) {
let needle = format!("{num}/{den}");
if let Some(pos) = frame.find(&needle) {
let after = &frame[pos + needle.len()..];
let after = after.trim_start();
if after.starts_with("files") {
out.push((num, den));
}
}
}
out
}
fn percents(frame: &str) -> Vec<u64> {
let bytes = frame.as_bytes();
let mut out = Vec::new();
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'%' {
let mut l = i;
while l > 0 && bytes[l - 1].is_ascii_digit() {
l -= 1;
}
if l < i {
if let Ok(v) = frame[l..i].parse::<u64>() {
out.push(v);
}
}
}
i += 1;
}
out
}
#[cfg(unix)]
#[allow(clippy::borrow_as_ptr, clippy::too_many_lines)]
fn run_under_pty(cache: &Path, args: &[&str]) -> Result<(Vec<u8>, Output), String> {
use std::io::Read;
use std::os::unix::io::FromRawFd;
use std::os::unix::process::CommandExt;
use std::time::{Duration, Instant};
let mut master: libc::c_int = -1;
let mut slave: libc::c_int = -1;
let rc = unsafe {
libc::openpty(
&mut master,
&mut slave,
std::ptr::null_mut(),
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
if rc != 0 {
return Err(format!(
"openpty failed: {}",
std::io::Error::last_os_error()
));
}
let mut child = {
let mut cmd = snapdir(cache);
cmd.args(args);
cmd.env("TERM", "xterm");
cmd.env("SNAPDIR_PTY_TEST", "1");
cmd.stdout(Stdio::piped());
cmd.stdin(Stdio::null());
let slave_for_child = slave;
unsafe {
cmd.pre_exec(move || {
if libc::dup2(slave_for_child, 2) == -1 {
return Err(std::io::Error::last_os_error());
}
Ok(())
});
}
cmd.spawn().map_err(|e| {
unsafe {
libc::close(master);
libc::close(slave);
}
format!("spawn under pty: {e}")
})?
};
unsafe {
libc::close(slave);
}
let mut master_file = unsafe { std::fs::File::from_raw_fd(master) };
set_nonblocking(master).map_err(|e| format!("set master nonblocking: {e}"))?;
let mut pty_bytes: Vec<u8> = Vec::new();
let deadline = Instant::now() + Duration::from_mins(1);
let mut buf = [0u8; 8192];
loop {
match master_file.read(&mut buf) {
Ok(0) => break,
Ok(n) => pty_bytes.extend_from_slice(&buf[..n]),
Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
if Instant::now() >= deadline {
let _ = child.kill();
return Err("timeout reading pty master".into());
}
std::thread::sleep(Duration::from_millis(5));
}
Err(e) if e.raw_os_error() == Some(libc::EIO) => break,
Err(e) => return Err(format!("read pty master: {e}")),
}
if Instant::now() >= deadline {
let _ = child.kill();
return Err("timeout draining pty master".into());
}
}
let out = child
.wait_with_output()
.map_err(|e| format!("wait for child: {e}"))?;
Ok((pty_bytes, out))
}
#[cfg(not(unix))]
fn run_under_pty(_cache: &Path, _args: &[&str]) -> Result<(Vec<u8>, Output), String> {
Err("pty harness is unix-only".into())
}
#[cfg(unix)]
fn set_nonblocking(fd: libc::c_int) -> std::io::Result<()> {
let flags = unsafe { libc::fcntl(fd, libc::F_GETFL) };
if flags == -1 {
return Err(std::io::Error::last_os_error());
}
if unsafe { libc::fcntl(fd, libc::F_SETFL, flags | libc::O_NONBLOCK) } == -1 {
return Err(std::io::Error::last_os_error());
}
Ok(())
}
fn pty_enabled() -> bool {
std::env::var_os("SNAPDIR_PTY_TEST").is_some()
}
fn skip_unless_pty(test: &str) -> bool {
if pty_enabled() {
return false;
}
eprintln!(
"{test}: SKIP (set SNAPDIR_PTY_TEST=1 to run the live pty progress tests). \
Determinism/cleanliness clauses 5 & 7 still run headless."
);
true
}
fn prepare_tree() -> (TempDir, PathBuf, u64, Option<TempDir>) {
let cache = TempDir::new().unwrap();
if let Some(tree) = sandbox_tree() {
(cache, tree, SANDBOX_FILE_COUNT, None)
} else {
let dir = TempDir::new().unwrap();
let n = build_many_file_tree(&dir);
let path = dir.path().to_path_buf();
(cache, path, n, Some(dir))
}
}
#[test]
fn dx_progress_denominator_is_files_not_bytes() {
if skip_unless_pty("dx_progress_denominator_is_files_not_bytes") {
return;
}
let (cache, tree, file_count, _keep) = prepare_tree();
let tree_str = tree.to_string_lossy().into_owned();
let (pty, out) = match run_under_pty(cache.path(), &["id", &tree_str]) {
Ok(v) => v,
Err(reason) => {
eprintln!("dx_progress_denominator_is_files_not_bytes: SKIP ({reason})");
return;
}
};
assert!(
out.status.success(),
"id under pty must succeed; stderr(pty): {}",
String::from_utf8_lossy(&pty)
);
let fs = frames(&pty);
assert!(
!fs.is_empty(),
"progress must render at least one frame on a pty; captured {} bytes",
pty.len()
);
const FILE_CEIL: u64 = 100_000;
const BYTES_FLOOR: u64 = 1_000_000;
let mut denoms: Vec<u64> = Vec::new();
for f in &fs {
denoms.extend(fraction_denominators(f));
}
let plausible_total = denoms.iter().copied().find(|&d| {
d > 0 && d < FILE_CEIL && d >= file_count / 4 && d <= file_count.saturating_mul(4)
});
assert!(
plausible_total.is_some(),
"no frame showed a FILE-COUNT denominator near {file_count}; \
denominators seen = {denoms:?}; frames = {fs:?}"
);
let byteish: Vec<u64> = denoms
.iter()
.copied()
.filter(|&d| d >= BYTES_FLOOR)
.collect();
assert!(
byteish.is_empty(),
"progress denominator must be FILES, not BYTES — saw byte-sized totals \
{byteish:?} (>= {BYTES_FLOOR}); frames = {fs:?}"
);
for f in &fs {
let lower = f.to_lowercase();
if lower.contains("file") {
for n in numbers_in(f) {
assert!(
n < BYTES_FLOOR,
"a 'files'-labelled frame shows a byte-sized count {n}: {f:?}"
);
}
}
}
}
#[test]
fn dx_progress_determinate_and_advancing() {
if skip_unless_pty("dx_progress_determinate_and_advancing") {
return;
}
let (cache, tree, _file_count, _keep) = prepare_tree();
let tree_str = tree.to_string_lossy().into_owned();
let (pty, out) = match run_under_pty(cache.path(), &["id", &tree_str]) {
Ok(v) => v,
Err(reason) => {
eprintln!("dx_progress_determinate_and_advancing: SKIP ({reason})");
return;
}
};
assert!(out.status.success(), "id under pty must succeed");
let fs = frames(&pty);
assert!(!fs.is_empty(), "progress must render frames on a pty");
let positive_pct = fs
.iter()
.flat_map(|f| percents(f))
.any(|p| p > 0 && p <= 100);
let advancing_fraction = fs
.iter()
.flat_map(|f| fraction_pairs(f))
.any(|(done, total)| total > 0 && done > 0 && done <= total);
assert!(
positive_pct || advancing_fraction,
"progress must be DETERMINATE and advancing — no frame showed a positive \
percentage or a 0<done<=total fraction (the 0%-frozen bug). frames = {fs:?}"
);
let all_pcts: Vec<u64> = fs.iter().flat_map(|f| percents(f)).collect();
if !all_pcts.is_empty() {
assert!(
all_pcts.iter().any(|&p| p > 0),
"percentage rendered but stayed frozen at 0% throughout: {all_pcts:?}"
);
}
}
#[test]
fn dx_progress_discovery_phase_is_visible() {
if skip_unless_pty("dx_progress_discovery_phase_is_visible") {
return;
}
let (cache, tree, _file_count, _keep) = prepare_tree();
let tree_str = tree.to_string_lossy().into_owned();
let (pty, out) = match run_under_pty(cache.path(), &["id", &tree_str]) {
Ok(v) => v,
Err(reason) => {
eprintln!("dx_progress_discovery_phase_is_visible: SKIP ({reason})");
return;
}
};
assert!(out.status.success(), "id under pty must succeed");
let fs = frames(&pty);
assert!(!fs.is_empty(), "progress must render frames on a pty");
const PHASE_WORDS: &[&str] = &[
"discover",
"enumerat",
"scan",
"walk",
"index",
"count",
"finding",
"find files",
];
let phase_word = fs.iter().any(|f| {
let l = f.to_lowercase();
PHASE_WORDS.iter().any(|w| l.contains(w))
});
let counts: Vec<u64> = fs
.iter()
.map(|f| numbers_in(f).into_iter().max().unwrap_or(0))
.collect();
let growing = counts.windows(2).any(|w| w[1] > w[0]);
assert!(
phase_word || growing,
"no VISIBLE discovery phase — no discovery-ish phase word and no growing \
enumeration count before hashing. frames = {fs:?}"
);
}
#[test]
fn dx_progress_no_progress_and_quiet_suppress_all() {
if skip_unless_pty("dx_progress_no_progress_and_quiet_suppress_all") {
return;
}
let (cache, tree, _file_count, _keep) = prepare_tree();
let tree_str = tree.to_string_lossy().into_owned();
for flag in ["--no-progress", "--quiet"] {
let (pty, out) = match run_under_pty(cache.path(), &["id", flag, &tree_str]) {
Ok(v) => v,
Err(reason) => {
eprintln!("dx_progress_no_progress_and_quiet_suppress_all: SKIP ({reason})");
return;
}
};
assert!(out.status.success(), "id {flag} under pty must succeed");
assert!(
!has_ansi(&pty),
"id {flag}: progress suppressed => no ANSI on the pty stderr; got {:?}",
String::from_utf8_lossy(&pty)
);
assert!(
!has_cr(&pty),
"id {flag}: progress suppressed => no CR redraw on the pty stderr; got {:?}",
String::from_utf8_lossy(&pty)
);
let id = String::from_utf8_lossy(&out.stdout).trim_end().to_owned();
assert_is_id(&id, &format!("id {flag} stdout"));
}
}
#[test]
fn dx_progress_id_stdout_byte_identical_keystone() {
let (cache, tree, _file_count, _keep) = prepare_tree();
let tree_str = tree.to_string_lossy().into_owned();
let baseline = run_ok(cache.path(), &["id", "--no-progress", &tree_str]);
let baseline_stdout = baseline.stdout.clone();
let baseline_id = stdout_str(&baseline);
assert_is_id(&baseline_id, "id --no-progress");
if sandbox_tree().is_some() {
assert_eq!(
baseline_id, SANDBOX_ID,
"sandbox id must equal the frozen keystone id"
);
}
let plain_on = run_ok(cache.path(), &["id", &tree_str]);
assert_eq!(
plain_on.stdout, baseline_stdout,
"id stdout must be byte-identical with vs without --no-progress (piped)"
);
if !pty_enabled() {
eprintln!(
"dx_progress_id_stdout_byte_identical_keystone: pty leg SKIPPED \
(set SNAPDIR_PTY_TEST=1); piped byte-identity leg ran."
);
return;
}
let (_pty, out) = match run_under_pty(cache.path(), &["id", &tree_str]) {
Ok(v) => v,
Err(reason) => {
eprintln!("dx_progress_id_stdout_byte_identical_keystone: pty leg SKIP ({reason})");
return;
}
};
assert!(out.status.success(), "id under pty must succeed");
assert_eq!(
out.stdout, baseline_stdout,
"id stdout under a TTY stderr must be byte-identical to the --no-progress baseline"
);
assert!(
!has_ansi(&out.stdout) && !has_cr(&out.stdout),
"stdout must never carry a progress fragment (ANSI/CR) even while the pty renders"
);
}
#[test]
fn dx_progress_manifest_stdout_byte_identical_keystone() {
let (cache, tree, _file_count, _keep) = prepare_tree();
let tree_str = tree.to_string_lossy().into_owned();
let baseline = run_ok(cache.path(), &["manifest", "--no-progress", &tree_str]);
let baseline_stdout = baseline.stdout.clone();
assert!(
!baseline_stdout.is_empty(),
"manifest --no-progress must print a manifest"
);
let plain_on = run_ok(cache.path(), &["manifest", &tree_str]);
assert_eq!(
plain_on.stdout, baseline_stdout,
"manifest stdout must be byte-identical with vs without --no-progress (piped)"
);
if !pty_enabled() {
eprintln!(
"dx_progress_manifest_stdout_byte_identical_keystone: pty leg SKIPPED \
(set SNAPDIR_PTY_TEST=1); piped byte-identity leg ran."
);
return;
}
let (_pty, out) = match run_under_pty(cache.path(), &["manifest", &tree_str]) {
Ok(v) => v,
Err(reason) => {
eprintln!(
"dx_progress_manifest_stdout_byte_identical_keystone: pty leg SKIP ({reason})"
);
return;
}
};
assert!(out.status.success(), "manifest under pty must succeed");
assert_eq!(
out.stdout, baseline_stdout,
"manifest stdout under a TTY stderr must be byte-identical to the --no-progress baseline"
);
assert!(
!has_ansi(&out.stdout) && !has_cr(&out.stdout),
"manifest stdout must never carry a progress fragment even while the pty renders"
);
}
#[test]
fn dx_progress_empty_dir_no_divide_by_zero() {
let cache = TempDir::new().unwrap();
let empty = TempDir::new().unwrap();
let empty_str = empty.path().to_string_lossy().into_owned();
let out = run_ok(cache.path(), &["id", &empty_str]);
let id = stdout_str(&out);
assert_is_id(&id, "id empty-dir");
let zeros = TempDir::new().unwrap();
zeros.child("a").write_str("").unwrap();
zeros.child("b").write_str("").unwrap();
zeros.child("sub/c").write_str("").unwrap();
let zeros_str = zeros.path().to_string_lossy().into_owned();
let zout = run_ok(cache.path(), &["id", &zeros_str]);
assert_is_id(&stdout_str(&zout), "id zero-byte-files dir");
if !pty_enabled() {
eprintln!(
"dx_progress_empty_dir_no_divide_by_zero: pty leg SKIPPED (set SNAPDIR_PTY_TEST=1); \
piped exit/id legs ran."
);
return;
}
for dir in [&empty_str, &zeros_str] {
let (pty, out) = match run_under_pty(cache.path(), &["id", dir]) {
Ok(v) => v,
Err(reason) => {
eprintln!("dx_progress_empty_dir_no_divide_by_zero: pty leg SKIP ({reason})");
return;
}
};
assert!(
out.status.success(),
"id {dir} under pty must exit 0 (no panic/divide-by-zero); stderr(pty): {}",
String::from_utf8_lossy(&pty)
);
let stderr = String::from_utf8_lossy(&pty).to_lowercase();
assert!(
!stderr.contains("panic"),
"id {dir} under pty must not panic; stderr(pty): {stderr}"
);
assert_is_id(
String::from_utf8_lossy(&out.stdout).trim_end(),
&format!("id {dir} under pty"),
);
}
}
#[test]
fn dx_progress_stdout_clean_under_live_render() {
if skip_unless_pty("dx_progress_stdout_clean_under_live_render") {
return;
}
let (cache, tree, _file_count, _keep) = prepare_tree();
let tree_str = tree.to_string_lossy().into_owned();
let (pty, out) = match run_under_pty(cache.path(), &["id", &tree_str]) {
Ok(v) => v,
Err(reason) => {
eprintln!("dx_progress_stdout_clean_under_live_render: SKIP ({reason})");
return;
}
};
assert!(out.status.success(), "id under pty must succeed");
let id = String::from_utf8_lossy(&out.stdout).trim_end().to_owned();
assert_is_id(&id, "id stdout under pty");
assert_eq!(
out.stdout,
format!("{id}\n").into_bytes(),
"id stdout under a live render must be EXACTLY the id + one newline (no progress fragment)"
);
assert!(
!has_ansi(&out.stdout) && !has_cr(&out.stdout),
"id stdout must carry no ANSI/CR even though stderr(pty) does ({} pty bytes)",
pty.len()
);
let baseline = run_ok(cache.path(), &["manifest", "--no-progress", &tree_str]);
let (_pty2, mout) = match run_under_pty(cache.path(), &["manifest", &tree_str]) {
Ok(v) => v,
Err(reason) => {
eprintln!("dx_progress_stdout_clean_under_live_render (manifest): SKIP ({reason})");
return;
}
};
assert!(mout.status.success(), "manifest under pty must succeed");
assert!(
!has_ansi(&mout.stdout) && !has_cr(&mout.stdout),
"manifest stdout must carry no ANSI/CR under a live render"
);
assert_eq!(
mout.stdout, baseline.stdout,
"manifest stdout under a live render must equal the --no-progress manifest byte-for-byte"
);
}
#[test]
fn dx_progress_denominator_equals_exact_file_count() {
if skip_unless_pty("dx_progress_denominator_equals_exact_file_count") {
return;
}
let (cache, tree, file_count, _keep) = prepare_tree();
let tree_str = tree.to_string_lossy().into_owned();
let counted = count_regular_files(&tree);
assert_eq!(
counted, file_count,
"fixture file-count bookkeeping disagrees: walked-from-helper={file_count}, \
counted-on-disk={counted}"
);
let (pty, out) = match run_under_pty(cache.path(), &["id", &tree_str]) {
Ok(v) => v,
Err(reason) => {
eprintln!("dx_progress_denominator_equals_exact_file_count: SKIP ({reason})");
return;
}
};
assert!(out.status.success(), "id under pty must succeed");
let fs = frames(&pty);
assert!(!fs.is_empty(), "progress must render frames on a pty");
let mut hash_denoms: Vec<u64> = Vec::new();
for f in &fs {
for (_done, total) in files_fraction_pairs(f) {
hash_denoms.push(total);
}
}
assert!(
!hash_denoms.is_empty(),
"no determinate hash fraction rendered; frames = {fs:?}"
);
assert!(
hash_denoms.iter().all(|&d| d == counted),
"every determinate denominator must equal the EXACT file count {counted}; \
saw denominators {hash_denoms:?}; frames = {fs:?}"
);
}
#[test]
fn dx_progress_discovery_precedes_first_hash_fraction() {
if skip_unless_pty("dx_progress_discovery_precedes_first_hash_fraction") {
return;
}
let (cache, tree, _file_count, _keep) = prepare_tree();
let tree_str = tree.to_string_lossy().into_owned();
let (pty, out) = match run_under_pty(cache.path(), &["id", &tree_str]) {
Ok(v) => v,
Err(reason) => {
eprintln!("dx_progress_discovery_precedes_first_hash_fraction: SKIP ({reason})");
return;
}
};
assert!(out.status.success(), "id under pty must succeed");
let fs = frames(&pty);
assert!(!fs.is_empty(), "progress must render frames on a pty");
let first_discovery = fs
.iter()
.position(|f| f.to_lowercase().contains("discovering"));
let first_hash_fraction = fs
.iter()
.position(|f| files_fraction_pairs(f).iter().any(|&(_d, t)| t > 0));
assert!(
first_discovery.is_some(),
"no 'discovering' phase frame rendered at all; frames = {fs:?}"
);
if let Some(hash_idx) = first_hash_fraction {
let disc_idx = first_discovery.unwrap();
assert!(
disc_idx <= hash_idx,
"discovery frame (idx {disc_idx}) must precede or coincide with the first \
determinate hash fraction (idx {hash_idx}); frames = {fs:?}"
);
}
}
#[test]
fn dx_progress_done_count_monotonic_and_reaches_total() {
if skip_unless_pty("dx_progress_done_count_monotonic_and_reaches_total") {
return;
}
let (cache, tree, _file_count, _keep) = prepare_tree();
let tree_str = tree.to_string_lossy().into_owned();
let (pty, out) = match run_under_pty(cache.path(), &["id", &tree_str]) {
Ok(v) => v,
Err(reason) => {
eprintln!("dx_progress_done_count_monotonic_and_reaches_total: SKIP ({reason})");
return;
}
};
assert!(out.status.success(), "id under pty must succeed");
let fs = frames(&pty);
assert!(!fs.is_empty(), "progress must render frames on a pty");
let pairs: Vec<(u64, u64)> = fs
.iter()
.flat_map(|f| files_fraction_pairs(f))
.filter(|&(_d, t)| t > 0)
.collect();
assert!(
!pairs.is_empty(),
"no determinate hash fraction rendered; frames = {fs:?}"
);
let mut prev = 0u64;
for (done, total) in &pairs {
assert!(
*done >= prev,
"hash done-count regressed: {done} after {prev} (total {total}); \
pairs = {pairs:?}"
);
assert!(*done <= *total, "done {done} exceeded total {total}");
prev = *done;
}
let total = pairs[0].1;
let max_done = pairs.iter().map(|&(d, _)| d).max().unwrap();
assert!(
max_done >= total / 2,
"hash done-count must climb substantially past the start (≥ half of {total}, \
i.e. real progress not a frozen/near-zero bar); max done seen = {max_done}; \
pairs = {pairs:?}"
);
let max_pct = fs.iter().flat_map(|f| percents(f)).max();
if let Some(p) = max_pct {
assert!(
p > 40,
"rendered percentage must climb well past a low floor (>40%, not frozen \
near 0); max seen = {p}; frames = {fs:?}"
);
}
}
#[test]
fn dx_progress_keystone_three_modes_byte_identical() {
let (cache, tree, _file_count, _keep) = prepare_tree();
let tree_str = tree.to_string_lossy().into_owned();
let is_sandbox = sandbox_tree().is_some();
let id_on = run_ok(cache.path(), &["id", &tree_str]).stdout;
let id_nop = run_ok(cache.path(), &["id", "--no-progress", &tree_str]).stdout;
let id_quiet = run_ok(cache.path(), &["id", "--quiet", &tree_str]).stdout;
assert_eq!(
id_on, id_nop,
"id stdout: progress-on must equal --no-progress byte-for-byte"
);
assert_eq!(
id_on, id_quiet,
"id stdout: progress-on must equal --quiet byte-for-byte"
);
let id = String::from_utf8(id_on.clone())
.unwrap()
.trim_end()
.to_owned();
assert_is_id(&id, "id three-mode");
if is_sandbox {
assert_eq!(
id, SANDBOX_ID,
"printed id must equal the frozen sandbox id"
);
}
let man_on = run_ok(cache.path(), &["manifest", &tree_str]).stdout;
let man_nop = run_ok(cache.path(), &["manifest", "--no-progress", &tree_str]).stdout;
let man_quiet = run_ok(cache.path(), &["manifest", "--quiet", &tree_str]).stdout;
assert!(!man_on.is_empty(), "manifest must print a manifest");
assert_eq!(
man_on, man_nop,
"manifest stdout: progress-on must equal --no-progress byte-for-byte"
);
assert_eq!(
man_on, man_quiet,
"manifest stdout: progress-on must equal --quiet byte-for-byte"
);
}
#[test]
fn dx_progress_single_file_tree_total_is_one() {
let cache = TempDir::new().unwrap();
let one = TempDir::new().unwrap();
one.child("only.bin")
.write_str(&"payload-".repeat(64))
.unwrap();
let one_str = one.path().to_string_lossy().into_owned();
assert_eq!(
count_regular_files(one.path()),
1,
"single-file fixture must contain exactly one regular file"
);
let out = run_ok(cache.path(), &["id", &one_str]);
assert_is_id(&stdout_str(&out), "id single-file");
if !pty_enabled() {
eprintln!(
"dx_progress_single_file_tree_total_is_one: pty leg SKIPPED (set SNAPDIR_PTY_TEST=1); \
piped exit/id leg ran."
);
return;
}
let (pty, out) = match run_under_pty(cache.path(), &["id", &one_str]) {
Ok(v) => v,
Err(reason) => {
eprintln!("dx_progress_single_file_tree_total_is_one: pty leg SKIP ({reason})");
return;
}
};
assert!(
out.status.success(),
"id single-file under pty must exit 0; stderr(pty): {}",
String::from_utf8_lossy(&pty)
);
let stderr = String::from_utf8_lossy(&pty).to_lowercase();
assert!(
!stderr.contains("panic"),
"single-file id under pty must not panic; stderr(pty): {stderr}"
);
let fs = frames(&pty);
assert!(!fs.is_empty(), "progress must render frames on a pty");
let pairs: Vec<(u64, u64)> = fs
.iter()
.flat_map(|f| files_fraction_pairs(f))
.filter(|&(_d, t)| t > 0)
.collect();
assert!(
pairs.iter().all(|&(d, t)| t == 1 && d <= 1),
"single-file determinate denominator must be exactly 1 with done<=1 \
(no off-by-one, no divide-by-zero); pairs = {pairs:?}; frames = {fs:?}"
);
for f in &fs {
for p in percents(f) {
assert!(
p <= 100,
"single-file percentage out of range ({p}); frame = {f:?}"
);
}
}
}