crates/gunnar-wire/tests/emit_amplification.rs
//! **What the engine emitted, against what `select` chose.**
//!
//! `GitServe::emit_pack`'s first parameter is an **exact set** (DECIDED
//! 2026-08-10, `upload_pack.rs`): the engine must not close it over
//! reachability. It may still emit *more* objects than it was given, and
//! legitimately so — a packfile's `OFS_DELTA` names its base by a backwards
//! distance inside the pack, so an engine copying stored entries has to pull in
//! any delta base that fell outside the selection or emit something
//! unparseable. `Caps::thin` is the allowance to skip that, and an engine is
//! free not to take it.
//!
//! So the gap between the two numbers is real, expected, and **unbounded**, and
//! that is exactly why it has to be reported. MEASURED on the laptop 2026-08-10, the
//! `h2h-linear-sha1-2048c-1024f-16k` corpus over the znippy engine, a
//! negotiated fetch from a receiver `N` commits behind the tip:
//!
//! ```text
//! N commits behind select chose engine emitted amplification
//! 1 18 157 8.72x
//! 256 4382 7801 1.78x
//! ```
//!
//! Nothing in gunnar could say so. [`UploadSummary::objects_sent`] carried the
//! right-hand column and there was no left-hand column anywhere: the selected
//! count reached exactly one place, a human-readable sideband progress string
//! ("gunnar: packing N objects from M commits") that `git fetch -q` suppresses
//! and no meter parses. `bench/`'s `gunnar.vs_git_push` then reported a 33.6%
//! receiver divergence with no instrument able to attribute it, and
//! `vs_git_fetch_divergent`'s `wire_out_ratio_x = 1.215` had the same cause and
//! the same silence.
//!
//! This is the shape — a published number describing a path other than
//! the one its name claims — so the test is a **count** and it is asserted, not
//! logged.
mod common;
use gix_hash::{Kind, ObjectId};
use gunnar_store::git_storage_trait::{
Caps, Extent, GcReport, GitOps, GitServe, ObjType, Oid, PackStats, ReachSet, RefCas, RefRow,
RefUpdate, Stored, TxId,
};
use gunnar_wire::upload_pack::FetchRequest;
use gunnar_wire::UploadPack;
use common::Fixture;
/// The fixture's five commits carry one blob and one tree each.
const OBJECTS_PER_COMMIT: usize = 3;
const COMMITS: usize = 5;
/// A `GitServe` whose `emit_pack` adds objects the caller did not select.
///
/// This is what a real engine does when it copies stored pack entries and has
/// to bring an unselected delta base along, and it is the *only* way to see the
/// two counts diverge in a wire test: [`gunnar_store::mem::MemGitServe`] has no
/// packs, so its `emit_pack` is entry-for-entry with its input and the gap this
/// suite exists to measure is structurally zero there.
///
/// It borrows rather than owns, so it lays over the fixture's own store for one
/// test without rebuilding the repository — the same shape as `common`'s
/// `Unindexed`.
struct ClosesOverBases<'a> {
inner: &'a dyn GitServe,
/// Appended to whatever `emit_pack` is handed. Objects that are in the
/// store and are **not** in the selection, so the arithmetic is unambiguous.
extra: Vec<ObjectId>,
}
impl GitOps for ClosesOverBases<'_> {
fn put(&self, pack: &[u8], refs: &[RefUpdate]) -> anyhow::Result<TxId> {
self.inner.put(pack, refs)
}
fn put_pack(&self, bytes: &[u8]) -> anyhow::Result<TxId> {
self.inner.put_pack(bytes)
}
fn put_refs(&self, updates: &[RefUpdate]) -> anyhow::Result<TxId> {
self.inner.put_refs(updates)
}
fn get(&self, oid: Oid<'_>) -> anyhow::Result<Option<Stored>> {
self.inner.get(oid)
}
fn has(&self, oid: Oid<'_>) -> anyhow::Result<bool> {
self.inner.has(oid)
}
fn size(&self, oid: Oid<'_>) -> anyhow::Result<Option<u64>> {
self.inner.size(oid)
}
fn extents(&self, oids: &[Oid<'_>]) -> anyhow::Result<Vec<Option<Extent>>> {
self.inner.extents(oids)
}
fn refs(&self) -> anyhow::Result<Vec<RefRow>> {
self.inner.refs()
}
fn update_ref(
&self,
name: &str,
old: Option<Oid<'_>>,
new: Option<Oid<'_>>,
) -> anyhow::Result<TxId> {
self.inner.update_ref(name, old, new)
}
fn put_refs_cas(&self, edits: &[RefCas<'_>]) -> anyhow::Result<TxId> {
self.inner.put_refs_cas(edits)
}
fn reachable(&self, want: &[Oid<'_>], have: &[Oid<'_>]) -> anyhow::Result<Vec<Vec<u8>>> {
self.inner.reachable(want, have)
}
fn gc(&self) -> anyhow::Result<GcReport> {
self.inner.gc()
}
}
impl GitServe for ClosesOverBases<'_> {
fn read(&self, oid: Oid<'_>) -> anyhow::Result<Option<(ObjType, Vec<u8>)>> {
self.inner.read(oid)
}
fn header(&self, oid: Oid<'_>) -> anyhow::Result<Option<(ObjType, u64)>> {
self.inner.header(oid)
}
fn sizes(&self, oids: &[Oid<'_>]) -> anyhow::Result<Vec<Option<u64>>> {
self.inner.sizes(oids)
}
fn head(&self) -> anyhow::Result<Option<RefRow>> {
self.inner.head()
}
fn set_head(&self, target: &str) -> anyhow::Result<TxId> {
self.inner.set_head(target)
}
fn select(&self, want: &[Oid<'_>], have: &[Oid<'_>]) -> anyhow::Result<Option<ReachSet>> {
self.inner.select(want, have)
}
/// The format closure, simulated: emit the exact set **plus** bases the
/// caller never asked for.
fn emit_pack(
&self,
want: &[Oid<'_>],
have: &[Oid<'_>],
caps: &Caps,
out: &mut dyn std::io::Write,
) -> anyhow::Result<PackStats> {
let mut widened: Vec<Oid<'_>> = want.to_vec();
for id in &self.extra {
if !widened.iter().any(|other| *other == id.as_bytes()) {
widened.push(id.as_bytes());
}
}
self.inner.emit_pack(&widened, have, caps, out)
}
}
/// A clone of `four`, the whole fixture history, with one unselected object
/// dragged in behind it.
///
/// **The assertion is a count and both halves of it matter.** `objects_sent`
/// alone cannot tell an engine that shipped the selection from one that shipped
/// nine times the selection; `objects_selected` alone cannot tell a correct
/// emit from a truncated one. Reported together they are a ratio, and the
/// ratio is what `bench/`'s head-to-head arms compare.
#[test]
fn a_fetch_reports_the_selected_set_beside_what_the_engine_emitted() {
let fixture = Fixture::build(Kind::Sha1);
// The annotated tag. It is in the store, it is reachable from nothing the
// clone below wants (`include_tag` is off), so the selection cannot contain
// it and the engine adding it is unambiguous.
let store = ClosesOverBases {
inner: &fixture.git,
extra: vec![fixture.tag],
};
let upload = UploadPack::new(&store, fixture.kind);
let mut out = Vec::new();
let outcome = upload
.fetch(
&FetchRequest {
wants: vec![fixture.four],
done: true,
thin_pack: true,
ofs_delta: true,
..Default::default()
},
&mut out,
)
.expect("serve the clone");
let selected = COMMITS * OBJECTS_PER_COMMIT;
assert_eq!(
outcome.objects_selected, selected,
"`select` chose the five commits' {selected} objects; the summary must report that \
number and not the engine's"
);
assert_eq!(
outcome.objects_sent as usize,
selected + 1,
"the engine closed one unselected base into the pack, so it emitted one more object \
than it was given"
);
assert!(
outcome.objects_sent as usize > outcome.objects_selected,
"the two counts must be distinguishable — a summary that reports only one of them \
cannot tell a 1.0x emit from the measured 8.72x"
);
}
/// The other direction, and the reason the first test is not enough on its own:
/// with no closure at all the two counts agree, and a field wired to the wrong
/// source would still pass the test above by accident.
#[test]
fn an_engine_that_adds_nothing_reports_the_two_counts_equal() {
let fixture = Fixture::build(Kind::Sha1);
let store = ClosesOverBases {
inner: &fixture.git,
extra: Vec::new(),
};
let upload = UploadPack::new(&store, fixture.kind);
let mut out = Vec::new();
let outcome = upload
.fetch(
&FetchRequest {
wants: vec![fixture.four],
done: true,
thin_pack: true,
ofs_delta: true,
..Default::default()
},
&mut out,
)
.expect("serve the clone");
assert_eq!(outcome.objects_selected, COMMITS * OBJECTS_PER_COMMIT);
assert_eq!(
outcome.objects_sent as usize, outcome.objects_selected,
"nothing was added, so `objects_sent` must equal `objects_selected`; a gap here is \
the field reading the wrong source"
);
}
/// A round that sends no packfile reports no selection either.
///
/// `FetchOutcome::default()` is returned from the negotiation rounds before the
/// last one, and a field that leaked a stale count into them would make
/// `UploadSummary`'s v2 accumulator — which **sums** over rounds — report a
/// multiple of the truth.
#[test]
fn a_negotiation_round_that_sends_no_pack_selects_nothing() {
let fixture = Fixture::build(Kind::Sha1);
let upload = UploadPack::new(&fixture.git, fixture.kind);
let mut out = Vec::new();
let outcome = upload
.fetch(
&FetchRequest {
wants: vec![fixture.four],
done: false,
thin_pack: true,
ofs_delta: true,
..Default::default()
},
&mut out,
)
.expect("serve one negotiation round");
assert!(!outcome.sent_pack, "this round must not carry a packfile");
assert_eq!(outcome.objects_sent, 0);
assert_eq!(
outcome.objects_selected, 0,
"a round with no packfile has no selection to report"
);
}