use std::process::Command;
use assert_cmd::prelude::*;
use assert_fs::TempDir;
fn snapdir_clean(home: &TempDir) -> Command {
let mut cmd = Command::cargo_bin("snapdir").expect("snapdir binary built");
cmd.env_clear();
if let Ok(path) = std::env::var("PATH") {
cmd.env("PATH", path);
}
cmd.env("HOME", home.path());
cmd
}
fn defaults_lines(cmd: &mut Command, extra: &[&str]) -> Vec<String> {
let out = cmd
.arg("defaults")
.args(extra)
.output()
.expect("run snapdir defaults");
assert!(
out.status.success(),
"snapdir defaults {extra:?} failed ({:?})\nstderr: {}",
out.status.code(),
String::from_utf8_lossy(&out.stderr),
);
String::from_utf8(out.stdout)
.expect("utf8 stdout")
.lines()
.map(ToOwned::to_owned)
.collect()
}
fn defaults_stdout(cmd: &mut Command, extra: &[&str]) -> String {
let out = cmd
.arg("defaults")
.args(extra)
.output()
.expect("run snapdir defaults");
assert!(
out.status.success(),
"snapdir defaults {extra:?} failed ({:?})\nstderr: {}",
out.status.code(),
String::from_utf8_lossy(&out.stderr),
);
String::from_utf8(out.stdout).expect("utf8 stdout")
}
fn line_assocs(lines: &[String], knob: &str, value: &str) -> bool {
let knob_us = knob.replace('-', "_");
let val = value.to_lowercase();
lines.iter().any(|l| {
let low = l.to_lowercase();
(low.contains(knob) || low.contains(&knob_us)) && low.contains(&val)
})
}
fn line_has_knob(lines: &[String], knob: &str) -> bool {
let knob_us = knob.replace('-', "_");
lines.iter().any(|l| {
let low = l.to_lowercase();
low.contains(knob) || low.contains(&knob_us)
})
}
fn knob_line(lines: &[String], knob: &str) -> String {
let knob_us = knob.replace('-', "_");
lines
.iter()
.find(|l| {
let low = l.to_lowercase();
low.contains(knob) || low.contains(&knob_us)
})
.cloned()
.unwrap_or_else(|| panic!("no line names knob `{knob}` in:\n{}", lines.join("\n")))
}
const REQUIRED_KNOBS: &[&str] = &[
"cache-dir",
"store",
"catalog",
"jobs",
"walk-jobs",
"color",
"no-progress",
"fsync",
"clonefile",
];
#[test]
fn dx_defaults_clean_env_lists_every_required_knob() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
let lines = defaults_lines(&mut cmd, &[]);
for knob in REQUIRED_KNOBS {
assert!(
line_has_knob(&lines, knob),
"clean-env defaults must list knob `{knob}` in:\n{}",
lines.join("\n"),
);
}
}
#[test]
fn dx_defaults_clean_env_lists_extended_knob_set() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
let lines = defaults_lines(&mut cmd, &[]);
for knob in [
"objects-store",
"limit-rate",
"adaptive",
"max-jobs",
"max-retries",
"retry-base-ms",
"retry-max-ms",
"max-requests",
"verify-copies",
] {
assert!(
line_has_knob(&lines, knob),
"clean-env defaults must list extended knob `{knob}` in:\n{}",
lines.join("\n"),
);
}
}
#[test]
fn dx_defaults_source_tag_vocabulary_present() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
let out = defaults_stdout(&mut cmd, &[]).to_lowercase();
assert!(
out.contains("default"),
"a `default` source tag must appear on a clean env:\n{out}",
);
}
#[test]
fn dx_defaults_clean_env_auto_jobs_tagged_default() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
let lines = defaults_lines(&mut cmd, &[]);
let jobs = knob_line(&lines, "jobs").to_lowercase();
assert!(
jobs.contains("default"),
"clean-env `jobs` (auto-resolved) must be tagged `default`, got: {jobs:?}",
);
}
#[test]
fn dx_defaults_clean_env_clonefile_tagged_default() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
let lines = defaults_lines(&mut cmd, &[]);
let clonefile = knob_line(&lines, "clonefile").to_lowercase();
assert!(
clonefile.contains("default"),
"clean-env `clonefile` must be tagged `default`, got: {clonefile:?}",
);
}
#[test]
fn dx_defaults_jobs_resolved_to_concrete_number() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
let lines = defaults_lines(&mut cmd, &[]);
for knob in ["jobs", "walk-jobs"] {
let line = knob_line(&lines, knob);
assert!(
line.chars().any(|c| c.is_ascii_digit()),
"`{knob}` must show a concrete resolved number, got: {line:?}",
);
}
}
#[test]
fn dx_defaults_cache_dir_resolved_to_concrete_path() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
let lines = defaults_lines(&mut cmd, &[]);
let line = knob_line(&lines, "cache-dir");
assert!(
line.contains('/'),
"`cache-dir` must show a concrete path, got: {line:?}",
);
let leaf = cache
.path()
.file_name()
.unwrap()
.to_string_lossy()
.into_owned();
assert!(
lines.iter().any(|l| l.contains(&leaf)),
"the pinned cache dir `{leaf}` must be reflected in:\n{}",
lines.join("\n"),
);
}
#[test]
fn dx_defaults_clonefile_and_fsync_show_default_values() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
let lines = defaults_lines(&mut cmd, &[]);
let clonefile = knob_line(&lines, "clonefile").to_lowercase();
assert!(
["true", "on", "enabled", "yes"]
.iter()
.any(|v| clonefile.contains(v)),
"`clonefile` must show its enabled default value, got: {clonefile:?}",
);
let fsync = knob_line(&lines, "fsync").to_lowercase();
assert!(
fsync.contains("batch"),
"`fsync` must show its `batch` default value, got: {fsync:?}",
);
}
#[test]
fn dx_defaults_cache_dir_flag_override_tagged_flag() {
let home = TempDir::new().unwrap();
let flag_dir = "/tmp/dxc-XYZ-adversary";
let mut cmd = snapdir_clean(&home);
let lines = defaults_lines(&mut cmd, &["--cache-dir", flag_dir]);
assert!(
line_assocs(&lines, "cache-dir", flag_dir),
"`--cache-dir {flag_dir}` must be reflected as the cache-dir value in:\n{}",
lines.join("\n"),
);
let line = knob_line(&lines, "cache-dir").to_lowercase();
assert!(
line.contains("flag"),
"flag-overridden `cache-dir` must be tagged `flag`, got: {line:?}",
);
}
#[test]
fn dx_defaults_jobs_flag_override_tagged_flag() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
let lines = defaults_lines(&mut cmd, &["--jobs", "3"]);
assert!(
line_assocs(&lines, "jobs", "3"),
"`--jobs 3` must show jobs=3 in:\n{}",
lines.join("\n"),
);
let line = knob_line(&lines, "jobs").to_lowercase();
assert!(
line.contains("flag"),
"flag-overridden `jobs` must be tagged `flag`, got: {line:?}",
);
}
#[test]
fn dx_defaults_cache_dir_flag_changes_output() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut bare = snapdir_clean(&home);
bare.env("SNAPDIR_CACHE_DIR", cache.path());
let bare_out = defaults_stdout(&mut bare, &[]);
let mut flagged = snapdir_clean(&home);
flagged.env("SNAPDIR_CACHE_DIR", cache.path());
let flagged_out = defaults_stdout(&mut flagged, &["--cache-dir", "/tmp/dxc-DIFFERENT"]);
assert_ne!(
bare_out, flagged_out,
"`defaults --cache-dir <X>` must differ from bare `defaults` (the prior bug)",
);
}
#[test]
fn dx_defaults_jobs_env_override_tagged_env() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
cmd.env("SNAPDIR_JOBS", "7");
let lines = defaults_lines(&mut cmd, &[]);
assert!(
line_assocs(&lines, "jobs", "7"),
"`SNAPDIR_JOBS=7` must show jobs=7 in:\n{}",
lines.join("\n"),
);
let line = knob_line(&lines, "jobs").to_lowercase();
assert!(
line.contains("env"),
"env-overridden `jobs` must be tagged `env`, got: {line:?}",
);
}
#[test]
fn dx_defaults_store_env_override_tagged_env() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let store = "file:///tmp/dx-env-store";
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
cmd.env("SNAPDIR_STORE", store);
let lines = defaults_lines(&mut cmd, &[]);
assert!(
line_assocs(&lines, "store", store),
"`SNAPDIR_STORE={store}` must be reflected as the store value in:\n{}",
lines.join("\n"),
);
let store_line = lines
.iter()
.find(|l| l.contains(store))
.cloned()
.unwrap_or_default()
.to_lowercase();
assert!(
store_line.contains("env"),
"env-set `store` must be tagged `env`, got: {store_line:?}",
);
}
#[test]
fn dx_defaults_flag_beats_env_for_jobs() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
cmd.env("SNAPDIR_JOBS", "7");
let lines = defaults_lines(&mut cmd, &["--jobs", "3"]);
let line = knob_line(&lines, "jobs").to_lowercase();
assert!(
line.contains('3'),
"flag --jobs 3 must win over SNAPDIR_JOBS=7, got jobs line: {line:?}",
);
assert!(
line.contains("flag"),
"flag-winning `jobs` must be tagged `flag`, got: {line:?}",
);
assert!(
!line.contains('7'),
"the overridden env value 7 must not be the reported jobs value: {line:?}",
);
}
#[test]
fn dx_defaults_still_surfaces_set_snapdir_env_var() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
cmd.env("SNAPDIR_CATALOG", "my-catalog-name");
let lines = defaults_lines(&mut cmd, &[]);
assert!(
lines.iter().any(|l| l.contains("my-catalog-name")),
"a set SNAPDIR_CATALOG must still be reflected in:\n{}",
lines.join("\n"),
);
}
#[test]
fn dx_defaults_legacy_manifest_vars_not_live_knobs() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
let lines = defaults_lines(&mut cmd, &[]);
for legacy in ["SNAPDIR_MANIFEST_CONTEXT", "SNAPDIR_MANIFEST_EXCLUDE"] {
for line in lines.iter().filter(|l| l.contains(legacy)) {
let low = line.to_lowercase();
assert!(
low.contains("legacy") || low.contains("compat") || low.contains("deprecat"),
"legacy `{legacy}` must not appear as a live knob; if present it \
must be labeled legacy/compat, got: {line:?}",
);
}
let empty = format!("{legacy}=");
assert!(
!lines.iter().any(|l| l.trim() == empty),
"the old empty `{empty}` legacy line must not appear in:\n{}",
lines.join("\n"),
);
}
}
#[test]
fn dx_defaults_two_runs_byte_identical() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut a = snapdir_clean(&home);
a.env("SNAPDIR_CACHE_DIR", cache.path());
a.env("SNAPDIR_STORE", "file:///tmp/dx-det");
let out_a = defaults_stdout(&mut a, &[]);
let mut b = snapdir_clean(&home);
b.env("SNAPDIR_CACHE_DIR", cache.path());
b.env("SNAPDIR_STORE", "file:///tmp/dx-det");
let out_b = defaults_stdout(&mut b, &[]);
assert_eq!(
out_a, out_b,
"two `defaults` runs on the same env must be byte-identical",
);
}
#[test]
fn dx_defaults_each_required_knob_line_has_value_and_tag() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
let lines = defaults_lines(&mut cmd, &[]);
for knob in REQUIRED_KNOBS {
let line = knob_line(&lines, knob);
let low = line.to_lowercase();
assert!(
low.contains("flag") || low.contains("env") || low.contains("default"),
"knob `{knob}` line must carry a source tag (flag|env|default), got: {line:?}",
);
let stripped: String = low
.replace(knob, " ")
.replace(&knob.replace('-', "_"), " ")
.replace("default", " ")
.replace("flag", " ")
.replace("env", " ");
assert!(
stripped
.chars()
.any(|c| c.is_alphanumeric() || c == '/' || c == '.'),
"knob `{knob}` line must carry a resolved VALUE beyond its name+tag, got: {line:?}",
);
}
}
#[test]
fn dx_defaults_output_is_line_oriented() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
let out = defaults_stdout(&mut cmd, &[]);
let non_blank: Vec<&str> = out.lines().filter(|l| !l.trim().is_empty()).collect();
assert!(
non_blank.len() >= REQUIRED_KNOBS.len(),
"expected at least one line per required knob, got {} lines:\n{out}",
non_blank.len(),
);
assert!(
!out.contains('\0'),
"output must not contain NUL bytes (line-oriented text)",
);
}
#[test]
fn dx_defaults_literal_source_tokens_exact() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
let out = defaults_stdout(&mut cmd, &["--jobs", "4"]);
for token in ["source=default", "source=env", "source=flag"] {
assert!(
out.contains(token),
"expected literal `{token}` in defaults output:\n{out}",
);
}
assert!(
!out.contains("source ="),
"source tag must be the tight `source=<tag>` token, not `source = …`:\n{out}",
);
}
#[test]
fn dx_defaults_other_env_section_lists_arbitrary_snapdir_var() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
cmd.env("SNAPDIR_FOO", "bar");
let lines = defaults_lines(&mut cmd, &[]);
assert!(
lines.iter().any(|l| l == "other-env:"),
"expected a literal `other-env:` superset header in:\n{}",
lines.join("\n"),
);
assert!(
lines.iter().any(|l| l.contains("SNAPDIR_FOO=bar")),
"the arbitrary set `SNAPDIR_FOO=bar` must be listed under other-env in:\n{}",
lines.join("\n"),
);
let foo_line = lines
.iter()
.find(|l| l.contains("SNAPDIR_FOO=bar"))
.expect("the SNAPDIR_FOO line");
assert!(
!foo_line.contains("source="),
"an unrecognized SNAPDIR_* var must be raw env, not a tagged knob: {foo_line:?}",
);
}
#[test]
fn dx_defaults_legacy_manifest_context_surfaced_as_legacy_not_knob() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut cmd = snapdir_clean(&home);
cmd.env("SNAPDIR_CACHE_DIR", cache.path());
cmd.env("SNAPDIR_MANIFEST_CONTEXT", "mykey");
let lines = defaults_lines(&mut cmd, &[]);
let manifest = lines
.iter()
.find(|l| l.contains("SNAPDIR_MANIFEST_CONTEXT"))
.expect("a set SNAPDIR_MANIFEST_CONTEXT must still be surfaced");
assert!(
manifest.contains("mykey") && manifest.contains("(legacy)"),
"legacy manifest var must carry its value and the `(legacy)` label: {manifest:?}",
);
assert!(
!manifest.contains("source="),
"legacy manifest var must NOT appear as a `source=`-tagged effective knob: {manifest:?}",
);
}
#[test]
fn dx_defaults_clonefile_default_and_env_flip() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut on = snapdir_clean(&home);
on.env("SNAPDIR_CACHE_DIR", cache.path());
let on_lines = defaults_lines(&mut on, &[]);
let on_line = knob_line(&on_lines, "clonefile");
assert!(
on_line.contains("enabled") && on_line.contains("source=default"),
"default clonefile must be `enabled source=default`, got: {on_line:?}",
);
let mut off = snapdir_clean(&home);
off.env("SNAPDIR_CACHE_DIR", cache.path());
off.env("SNAPDIR_CLONEFILE", "0");
let off_lines = defaults_lines(&mut off, &[]);
let off_line = knob_line(&off_lines, "clonefile");
assert!(
off_line.contains("disabled") && off_line.contains("source=env"),
"SNAPDIR_CLONEFILE=0 must flip clonefile to `disabled source=env`, got: {off_line:?}",
);
}
#[test]
fn dx_defaults_fsync_default_and_env_flip() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut def = snapdir_clean(&home);
def.env("SNAPDIR_CACHE_DIR", cache.path());
let def_line = knob_line(&defaults_lines(&mut def, &[]), "fsync");
assert!(
def_line.contains("batch") && def_line.contains("source=default"),
"default fsync must be `batch source=default`, got: {def_line:?}",
);
let mut off = snapdir_clean(&home);
off.env("SNAPDIR_CACHE_DIR", cache.path());
off.env("SNAPDIR_FSYNC", "off");
let off_line = knob_line(&defaults_lines(&mut off, &[]), "fsync");
assert!(
off_line.contains("off") && off_line.contains("source=env"),
"SNAPDIR_FSYNC=off must flip fsync to `off source=env`, got: {off_line:?}",
);
}
#[test]
fn dx_defaults_verify_copies_default_and_env_flip() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut def = snapdir_clean(&home);
def.env("SNAPDIR_CACHE_DIR", cache.path());
let def_line = knob_line(&defaults_lines(&mut def, &[]), "verify-copies");
assert!(
def_line.contains("disabled") && def_line.contains("source=default"),
"default verify-copies must be `disabled source=default`, got: {def_line:?}",
);
let mut on = snapdir_clean(&home);
on.env("SNAPDIR_CACHE_DIR", cache.path());
on.env("SNAPDIR_VERIFY_COPIES", "1");
let on_line = knob_line(&defaults_lines(&mut on, &[]), "verify-copies");
assert!(
on_line.contains("enabled") && on_line.contains("source=env"),
"SNAPDIR_VERIFY_COPIES=1 must flip verify-copies to `enabled source=env`, got: {on_line:?}",
);
}
#[test]
fn dx_defaults_objects_store_flag_and_env_source() {
let home = TempDir::new().unwrap();
let cache = TempDir::new().unwrap();
let mut flagged = snapdir_clean(&home);
flagged.env("SNAPDIR_CACHE_DIR", cache.path());
let flag_lines = defaults_lines(
&mut flagged,
&["--objects-store", "file:///tmp/dx-obj-flag"],
);
let flag_line = knob_line(&flag_lines, "objects-store");
assert!(
flag_line.contains("file:///tmp/dx-obj-flag") && flag_line.contains("source=flag"),
"`--objects-store …` must show that URL tagged source=flag, got: {flag_line:?}",
);
let mut enved = snapdir_clean(&home);
enved.env("SNAPDIR_CACHE_DIR", cache.path());
enved.env("SNAPDIR_OBJECTS_STORE", "file:///tmp/dx-obj-env");
let env_lines = defaults_lines(&mut enved, &[]);
let env_line = knob_line(&env_lines, "objects-store");
assert!(
env_line.contains("file:///tmp/dx-obj-env") && env_line.contains("source=env"),
"`SNAPDIR_OBJECTS_STORE=…` must show that URL tagged source=env, got: {env_line:?}",
);
}