xtask/tests/pack_pipeline.rs
//! **The pack-pipeline source guard, seen to go red.**
//!
//! Its first subject does not exist on the serve path yet (pack-copy has not
//! landed), and a guard whose subject is absent is the most dangerous state a
//! guard can be in, because "found no violations" and "cannot find anything"
//! print the same thing. So it is asserted in both directions, permanently:
//!
//! * **red**, against source samples that call `iter_from_counts` with no
//! `thread_limit`, with `thread_limit: None`, with a pool-sized limit and no
//! declaration, and against a `FromEntriesIter::new` with nothing said about
//! its trailer.
//! * **green**, against the two declared forms, and against gunnar's own
//! shipped sources, with the file count asserted so an empty scan cannot
//! masquerade as a clean one.
//!
//! Every red also asserts **which** rule fired. A guard that answered
//! "violation" to everything would satisfy a bare `assert!(!findings
//! .is_empty())` and prove nothing: the identity-value trap.
use xtask::pack_pipeline::{
check_impls, check_source, object_store_sources, scan_object_stores, scan_workspace,
shipped_sources, Rule, NOT_FORWARDED_MARKER, OPTIONAL_BUT_LOAD_BEARING,
};
/// The list rules 3 and 4 are PROVEN against, which is deliberately not the live
/// [`OPTIONAL_BUT_LOAD_BEARING`].
///
/// The live list is empty, and correctly so: `GitOps` has no defaulted method,
/// so the class of defect is unrepresentable on the contract. But when it was
/// emptied on 2026-08-10 these four proofs were left asserting findings the rule
/// could no longer produce, and **six of the fifteen tests in this file were
/// RED** until 2026-08-10. A guard against silent-green had gone silently red.
///
/// So the proofs drive [`check_impls`] with names of their own. What they hold
/// is the MECHANISM — omission caught, `None`-from-a-wrapper caught, leaf
/// backend spared, one marker excusing exactly one method — and none of that
/// should depend on which names happen to be load-bearing this week.
/// [`the_live_list_is_empty_and_that_is_a_declaration`] is what holds the other
/// half: that the emptiness is a decision somebody wrote down.
const PROVING_LIST: &[&str] = &["fn packed(", "fn bitmap("];
/// The shape the pack-copy fix produces if the second half of the rule is missed.
const UNLIMITED: &str = r#"
fn upload_pack(counts: Vec<Count>) -> Result<()> {
let entries = output::entry::iter_from_counts(
counts,
db,
progress,
output::entry::iter_from_counts::Options {
mode: Mode::PackCopyAndBaseObjects,
chunk_size: 1000,
version: Version::V2,
..Default::default()
},
);
Ok(())
}
"#;
/// The field present and defaulted: the case a guard that only looked for the
/// identifier `thread_limit` would wave through.
const EXPLICIT_NONE: &str = r#"
let entries = iter_from_counts(counts, db, progress, Options {
thread_limit: None,
version: Version::V2,
});
"#;
/// A pool-sized limit with nothing said. Legal on the batch tier, forbidden on
/// serve, and text cannot tell them apart, so the author has to.
const WIDE_UNDECLARED: &str = r#"
let entries = iter_from_counts(counts, db, progress, Options {
thread_limit: options.thread_limit,
version: Version::V2,
});
"#;
/// What the serve path must look like.
const SERVE: &str = r#"
let entries = iter_from_counts(counts, db, progress, Options {
thread_limit: Some(1),
version: Version::V2,
});
"#;
/// What the batch tier must look like.
const BATCH: &str = r#"
// pack-pipeline: batch tier — repack is a gatling batch job, not a
// per-request path, so the pool is the right answer here.
let entries = iter_from_counts(counts, db, progress, Options {
thread_limit: options.thread_limit,
version: Version::V2,
});
"#;
/// The trailer rule's subject, undeclared.
const TRAILER_UNDECLARED: &str = r#"
let mut pack = output::bytes::FromEntriesIter::new(
InOrderIter::from(entries),
&mut writer,
num_entries,
Version::V2,
hash_kind,
);
"#;
/// The trailer rule's subject, declared.
const TRAILER_DECLARED: &str = r#"
// pack-pipeline: sha1dc trailer accepted — a repack runs once, off the request path.
let mut pack = output::bytes::FromEntriesIter::new(
InOrderIter::from(entries),
&mut writer,
num_entries,
Version::V2,
hash_kind,
);
"#;
fn one(sample: &str, rule: Rule) {
let findings = check_source("sample.rs", sample);
assert_eq!(
findings.len(),
1,
"expected exactly one {rule:?} finding, got {findings:?}"
);
assert_eq!(findings[0].rule, rule, "the wrong rule fired");
assert!(
!findings[0].why.is_empty(),
"a finding that does not explain itself sends the reader to xtask instead \
of to the call site"
);
}
#[test]
fn a_fan_out_call_with_no_declaration_is_a_violation() {
one(UNLIMITED, Rule::PerRequestFanOut);
assert_eq!(
check_source("sample.rs", UNLIMITED)[0].line,
3,
"the call line was misreported. A multi-line call must be counted once, \
at its head"
);
}
#[test]
fn thread_limit_none_is_a_violation() {
one(EXPLICIT_NONE, Rule::PerRequestFanOut);
}
#[test]
fn a_pool_sized_limit_must_declare_its_tier() {
one(WIDE_UNDECLARED, Rule::PerRequestFanOut);
}
#[test]
fn an_undeclared_from_entries_iter_is_a_violation() {
one(TRAILER_UNDECLARED, Rule::Sha1dcTrailer);
}
#[test]
fn both_declared_forms_are_accepted() {
for (name, sample) in [
("serve", SERVE),
("batch", BATCH),
("trailer", TRAILER_DECLARED),
] {
assert!(
check_source("sample.rs", sample).is_empty(),
"the guard rejects the `{name}` form the rule itself prescribes: {:?}",
check_source("sample.rs", sample)
);
}
}
/// Prose naming either function is not a call. Without this the guard fires on
/// its own documentation and on every `.nornir` quotation in a doc comment.
#[test]
fn a_mention_in_a_comment_is_not_a_call() {
for sample in [
"// iter_from_counts( fans out above 4000 counts",
"/// See `output::bytes::FromEntriesIter::new(`.",
"//! `iter_from_counts(` is the function pack-copy needs.",
] {
assert!(
check_source("sample.rs", sample).is_empty(),
"the guard fired on a comment: {sample}"
);
}
}
/// Green against the real tree, and the scan is asserted to have read
/// something. Zero findings over zero files is not a pass.
#[test]
fn gunnars_shipped_sources_declare_what_they_cost() {
let root = xtask::guard::workspace_root();
let (findings, files) = scan_workspace(&root);
assert!(
files > 50,
"the scan read only {files} file(s) under {}/crates, and an empty scan is \
shaped exactly like a clean one",
root.display()
);
// ── the blindness anchor, re-aimed 2026-08-10 ────────────────────────────
//
// This named `gunnar-store/src/repack.rs`, "the one file in the tree that
// uses all three gix output stages" — and that file **does not exist**. It
// went with the packfile engine, and the assertion went with it: the test
// had been failing on a missing anchor rather than passing on a clean scan,
// which is a guard reporting on itself and being ignored.
//
// Where the subject went: `iter_from_counts` and `FromEntriesIter::new` have
// **no live call site left in gunnar**. The four remaining mentions
// (`gunnar-wire/src/{upload_pack,limits}.rs`, `gunnar-server/src/serve.rs`,
// `bench/examples/forge/parity.rs`) are all comments, and comments are
// stripped before the call is looked for. The engine emits the pack now
// (`upload_pack.rs`: *"the engine emits; this streams"*), so gunnar no longer
// drives gix's output pipeline at all.
//
// That makes the rule dormant, not dead — `gunnar_pack` still exists and the
// day anyone reaches for those stages again the rule must be watching. So the
// anchor is now the two files that would hold the call if it came back, and
// the emptiness of the finding list is checked against a scan proven to have
// read them.
for anchor in [
"gunnar-wire/src/upload_pack.rs",
"gunnar-server/src/serve.rs",
] {
assert!(
shipped_sources(&root).iter().any(|p| p.ends_with(anchor)),
"the scan did not reach `{anchor}`, which is where pack production lives. If \
this guard cannot see the pack path, it is green because it is blind"
);
}
assert!(
findings.is_empty(),
"undeclared gix pack-pipeline costs:\n{}",
findings
.iter()
.map(|f| format!(" {f}\n"))
.collect::<String>()
);
}
// ── rule 3: a storage impl that opts out of an optimisation in silence ──────
//
// A defaulted trait method returning `None` is not "I did not answer" — it is
// "turn it off". A wrapper that forwards the read methods and omits one of these
// makes everything served or measured through it take the walk path, or the
// re-deflate path, while every wire byte, object count and fidelity digest stays
// identical. Five instances in one tree on 2026-08-04; two of them were the
// BEFORE column of a published comparison.
//
// ⚠ The samples below are `impl GitOps` — the live contract, so that the
// impl-detection under test is the real one — but the two methods they hang the
// proof on are `packed()` and `bitmap()`, the historical pair from
// `gunnar_store::ObjectStore`. That is deliberate and it is why [`PROVING_LIST`]
// exists: `GitOps` has no defaulted method for the rule to fire on, so a proof
// written against today's real names could not go red at all, and this whole
// section would quietly stop testing anything — which is exactly what it did
// between the list being emptied and 2026-08-10. The names are a reconstruction
// of a defect class; the mechanism they exercise is current.
/// The shape the rule exists to catch: a forwarding wrapper missing `bitmap`.
const WRAPPER_WITHOUT_BITMAP: &str = r#"
impl<S: GitOps> GitOps for Counting<S> {
fn put_pack(&self, bytes: &[u8]) -> R<TxId> { self.inner.put_pack(bytes) }
fn extents(&self, oids: &[Oid<'_>]) -> R<Vec<Option<Extent>>> { self.inner.extents(oids) }
fn update_ref(&self, n: &str, o: Option<Oid<'_>>, w: Option<Oid<'_>>) -> R<TxId> { self.inner.update_ref(n, o, w) }
fn packed(&self) -> Option<PackedObjects> { self.inner.packed() }
}
"#;
/// The same wrapper, complete.
const WRAPPER_COMPLETE: &str = r#"
impl<S: GitOps> GitOps for Counting<S> {
fn extents(&self, oids: &[Oid<'_>]) -> R<Vec<Option<Extent>>> { self.inner.extents(oids) }
fn packed(&self) -> Option<PackedObjects> { self.inner.packed() }
fn bitmap(&self) -> Option<Arc<PackBitmap>> { self.inner.bitmap() }
}
"#;
#[test]
fn rule_three_fires_on_a_wrapper_that_omits_bitmap() {
let findings = check_impls("sample.rs", WRAPPER_WITHOUT_BITMAP, PROVING_LIST);
assert_eq!(findings.len(), 1, "expected exactly the `bitmap` finding");
assert_eq!(findings[0].rule, Rule::SilentOptOut);
assert!(
findings[0].why.contains("fn bitmap("),
"the message must name the method that is missing: {}",
findings[0].why
);
assert!(
check_impls("sample.rs", WRAPPER_COMPLETE, PROVING_LIST).is_empty(),
"the guard fired on a wrapper that answers both methods"
);
}
/// The rule must read the block's **own** braces, not the file's.
///
/// Written because the obvious implementation — "does the file contain
/// `fn bitmap(`" — is green on a file holding one complete impl and one
/// incomplete one, which is exactly `bench/examples/nornir-bench.rs`.
#[test]
fn rule_three_is_per_impl_and_not_per_file() {
let text = format!("{WRAPPER_COMPLETE}\n{WRAPPER_WITHOUT_BITMAP}");
let findings = check_impls("sample.rs", &text, PROVING_LIST);
assert_eq!(
findings.len(),
1,
"one complete impl in the same file hid the incomplete one: {findings:?}"
);
}
/// The trait's own definition is not an `impl` and must not fire.
#[test]
fn rule_three_leaves_the_trait_definition_alone() {
let trait_text = r#"
pub trait GitOps: Send + Sync {
fn extents(&self, oids: &[Oid<'_>]) -> R<Vec<Option<Extent>>>;
fn packed(&self) -> Option<PackedObjects> { None }
fn bitmap(&self) -> Option<Arc<PackBitmap>> { None }
}
"#;
assert!(check_impls("object.rs", trait_text, PROVING_LIST).is_empty());
}
// ── rule 4: a wrapper that reaches the same disabled state by typing `None` ──
//
// Rule 3 is satisfied by *writing the method out*. A forwarding wrapper can
// satisfy it with `{ None }`, which is byte-for-byte the behaviour the omission
// had: same disabled bitmap, same identical wire bytes, same green fidelity
// check — and now a guard has signed off on it. Rule 4 is the one that makes
// the class impossible: a forwarder forwards, or it says why not.
/// The shape rule 4 exists to catch: rule 3 satisfied, optimisation still off.
const WRAPPER_WITH_NONE: &str = r#"
impl<S: GitOps> GitOps for Counting<S> {
fn extents(&self, oids: &[Oid<'_>]) -> R<Vec<Option<Extent>>> { self.inner.extents(oids) }
fn gc(&self) -> R<GcReport> { self.inner.gc() }
fn packed(&self) -> Option<PackedObjects> { self.inner.packed() }
fn bitmap(&self) -> Option<Arc<PackBitmap>> { None }
}
"#;
/// A **leaf** backend over a `RwLock<HashMap>`. It has nothing to forward to,
/// and `self.objects.read()` is a lock, not a store.
///
/// Written because the first version of rule 4 counted `read`/`write` as trait
/// methods and demanded a marker from `MemObjectStore` — the one store in the
/// tree with genuinely no inner store. A rule that cries wolf gets deleted.
const LEAF_WITH_NONE: &str = r#"
impl GitOps for MemGitStore {
fn has(&self, oid: Oid<'_>) -> R<bool> { Ok(self.objects.read().unwrap().contains_key(oid)) }
fn put_pack(&self, bytes: &[u8]) -> R<TxId> {
let mut objects = self.objects.write().unwrap();
Ok(TxId::default())
}
fn packed(&self) -> Option<PackedObjects> { None }
fn bitmap(&self) -> Option<Arc<PackBitmap>> { None }
}
"#;
#[test]
fn rule_four_fires_on_a_forwarder_that_answers_none() {
let findings = check_impls("sample.rs", WRAPPER_WITH_NONE, PROVING_LIST);
assert_eq!(
findings.len(),
1,
"expected exactly the `bitmap` finding, got {findings:?}"
);
assert_eq!(
findings[0].rule,
Rule::NotForwarded,
"rule 3 must NOT be what fires here: the method IS written out"
);
assert!(
findings[0].why.contains("fn bitmap("),
"the message must name the method: {}",
findings[0].why
);
}
/// The identity-value trap, in this rule's own shape: a rule 4 that fired on
/// every `None` would be satisfied by this test and would still be wrong,
/// because it would also fire on every leaf backend in the tree.
#[test]
fn rule_four_leaves_a_leaf_backend_alone() {
let findings = check_impls("mem.rs", LEAF_WITH_NONE, PROVING_LIST);
assert!(
findings.is_empty(),
"rule 4 fired on a store with no inner store to forward to. `self.objects.read()` \
is an `RwLock`, not a `GitOps`: {findings:?}"
);
}
#[test]
fn rule_four_accepts_a_declared_opt_out() {
let declared = WRAPPER_WITH_NONE.replace(
" fn bitmap(&self) -> Option<Arc<PackBitmap>> { None }",
&format!(
" /// {NOT_FORWARDED_MARKER}. This wrapper's whole job is to cut \
the bitmap seam.\n fn bitmap(&self) -> Option<Arc<PackBitmap>> {{ None }}"
),
);
assert!(
check_impls("sample.rs", &declared, PROVING_LIST).is_empty(),
"the declared form the rule itself prescribes was rejected: {:?}",
check_impls("sample.rs", &declared, PROVING_LIST)
);
}
/// One marker must not excuse the other method. The marker is read from the
/// method's own doc comment, not from anywhere in the block.
#[test]
fn a_marker_on_one_method_does_not_excuse_the_other() {
let both_none = WRAPPER_WITH_NONE.replace(
" fn packed(&self) -> Option<PackedObjects> { self.inner.packed() }",
&format!(
" /// {NOT_FORWARDED_MARKER}. Only this one.\n \
fn packed(&self) -> Option<PackedObjects> {{ None }}"
),
);
let findings = check_impls("sample.rs", &both_none, PROVING_LIST);
assert_eq!(
findings.len(),
1,
"a marker on `packed` excused `bitmap` too, which is one declaration \
covering two decisions: {findings:?}"
);
assert!(findings[0].why.contains("fn bitmap("));
}
/// **The live list is empty, and that is a decision rather than an oversight.**
///
/// The rest of this section proves the mechanism against [`PROVING_LIST`], which
/// would stay green forever if somebody quietly emptied the real list — the
/// state this file was actually in between 2026-08-10 and 2026-08-10. So the
/// emptiness itself is asserted, with the reason in the failure message: the day
/// a name is added, this test goes red and whoever added it has to come here and
/// say why it is load-bearing.
#[test]
fn the_live_list_is_empty_and_that_is_a_declaration() {
assert!(
OPTIONAL_BUT_LOAD_BEARING.is_empty(),
"`OPTIONAL_BUT_LOAD_BEARING` gained {:?}. That is allowed — but the emptiness was a \
PROPERTY, not an accident: `GitOps` has no defaulted method, so no implementer can \
inherit a behaviour it did not write and this class of defect is unrepresentable on \
the contract. If a defaulted, optimisation-toggling method now exists, say here which \
trait grew it and why the default could not be removed instead — fixing by \
construction beats a guard over two copies",
OPTIONAL_BUT_LOAD_BEARING
);
}
/// Green against the real tree, over a set asserted to include the places the
/// five real instances were found — `bench/` and `tests/`, which
/// [`shipped_sources`] deliberately excludes.
#[test]
fn every_object_store_in_the_tree_says_what_it_turns_off() {
let root = xtask::guard::workspace_root();
let (findings, files) = scan_object_stores(&root);
assert!(
files > 100,
"the scan read only {files} file(s), and an empty scan is shaped exactly like a \
clean one"
);
let sources = object_store_sources(&root);
// ── DIRECTORIES, not files, since 2026-08-10 ─────────────────────────────
//
// This was a list of eight FILES, and two of them —
// `gunnar-store/src/object.rs` and the deleted engine crate’s objects.rs —
// had been deleted, so the test failed on a missing anchor instead of
// passing on a clean scan. That is this guard's own disease: it was red, and
// being red it said nothing about the tree.
//
// The claim was never really about those files. It is about **coverage of
// the directories a scan can silently omit**, which is what the 2026-08-04
// widening was: the original set was `crates/*/{src,tests}` plus
// `bench/{src,examples}`, which quietly missed every `crates/*/examples`,
// every `benches/`, `bench/tests`, `installtest/`, and
// the old znippy engine bench — a whole second harness on a store the
// workspace `exclude` already hides from `cargo test`. A rule about
// instruments cannot be scoped to the directories the last five instruments
// happened to be in.
//
// Stated as directories, the assertion survives any single file being
// renamed or deleted and still goes red the moment a whole area stops being
// read — which is the only failure it was ever able to catch.
// The three deleted-crate entries (`crates/gunnar-znippy/{src,examples}`
// and its detached bench) left this list on 2026-08-16. The crate was
// deleted 2026-08-10 (the engine is `gunnar-server`'s `engine-znippy`
// feature now), and a mass comment
// rename had already replaced the crate's NAME inside these string
// literals with prose, so the guard was demanding directories that never
// existed under any name — no scan of any tree can contain a path spelled
// `/the old znippy engine crate/src/`, so this guard had been **red ever
// since**, which is the disease the comment above describes caught a
// second time by the same mistake in the same list: "it was red, and being
// red it said nothing about the tree."
//
// They are replaced rather than merely dropped, three for three.
// `crates/gunnar-server/src/` is the directory the engine seam actually
// moved to, and it holds `storage/znippy_engine.rs` — the instrument the
// deleted entries were covering. `crates/gunnar-server/examples/` and
// `crates/gunnar-store/tests/` stand in for the other two: both are
// scanned by `object_store_sources` and both hold Rust today, so the
// count of areas a scan can silently stop reading is unchanged.
for must in [
"/bench/src/",
"/bench/examples/",
"/crates/gunnar-wire/src/",
"/crates/gunnar-wire/tests/",
"/crates/gunnar-store/src/",
"/crates/gunnar-store/tests/",
"/crates/gunnar-server/src/",
"/crates/gunnar-server/examples/",
"/installtest/src/",
] {
assert!(
sources.iter().any(|p| p.to_string_lossy().contains(must)),
"the scan read no file under `{must}`. Every entry in this list is a directory \
that held one of the known instances, or one the first version of this scan \
could not see at all. A guard that cannot see them is green because it is blind"
);
}
assert!(
findings.is_empty(),
"an `ObjectStore` turns an optimisation off in silence:\n{}",
findings
.iter()
.map(|f| format!(" {f}\n"))
.collect::<String>()
);
}