crates/gunnar-sendpack/tests/driver.rs
//! The driver's own refusals, and the framing, asserted without a remote.
//!
//! `tests/against_real_git.rs` proves the happy paths against the real
//! `git receive-pack`. What it cannot show is the requests this crate declines
//! to put on the wire at all: by construction, a refused push produces no
//! bytes, so a remote never sees it and cannot report on it.
use std::io::{Cursor, Write};
use gix_hash::{Kind, ObjectId};
use gunnar_sendpack::driver;
use gunnar_sendpack::{advertisement, send_pack, PushCommand, SendPackOptions, Transport};
fn oid(c: char) -> ObjectId {
ObjectId::from_hex(c.to_string().repeat(40).as_bytes()).unwrap()
}
/// A transport that answers nothing and records everything written to it.
struct Recorder {
reader: Cursor<Vec<u8>>,
written: Vec<u8>,
}
impl Recorder {
fn new() -> Self {
Recorder {
reader: Cursor::new(Vec::new()),
written: Vec::new(),
}
}
}
impl Transport for Recorder {
fn io(&mut self) -> (&mut dyn std::io::Read, &mut dyn Write) {
(&mut self.reader, &mut self.written)
}
}
fn adv(caps: &str) -> gunnar_sendpack::Advertisement {
advertisement::parse(&[format!("{} refs/heads/main\0{caps}\n", oid('1')).into_bytes()]).unwrap()
}
const CAPS: &str = "report-status-v2 delete-refs side-band-64k atomic object-format=sha1";
fn pack_writer(w: &mut dyn Write) -> gunnar_sendpack::Result<()> {
w.write_all(b"PACK-not-really")?;
Ok(())
}
/// An all-deletes push introduces no objects. Sending a pack for it is a lie
/// about what the client did, and the remote would index it and find nothing.
#[test]
fn an_all_deletes_push_carrying_a_pack_is_refused_before_a_byte_is_written() {
let mut t = Recorder::new();
let err = send_pack(
&mut t,
&adv(CAPS),
&[PushCommand::delete("refs/heads/main", oid('1'))],
Kind::Sha1,
Some(pack_writer),
&SendPackOptions::default(),
)
.unwrap_err();
assert!(format!("{err}").contains("must not carry a pack"), "{err}");
assert!(
t.written.is_empty(),
"a refused push must put NOTHING on the wire; it wrote {} bytes",
t.written.len()
);
}
/// The other direction, which is the one that corrupts a repository rather than
/// merely wasting bytes: a create or an update with no pack behind it moves a
/// reference to an object the remote does not have.
#[test]
fn an_update_with_no_pack_is_refused_before_a_byte_is_written() {
let mut t = Recorder::new();
let err = send_pack(
&mut t,
&adv(CAPS),
&[PushCommand::update("refs/heads/main", oid('1'), oid('2'))],
Kind::Sha1,
None::<fn(&mut dyn Write) -> gunnar_sendpack::Result<()>>,
&SendPackOptions::default(),
)
.unwrap_err();
assert!(format!("{err}").contains("must carry a pack"), "{err}");
assert!(t.written.is_empty(), "a refused push writes nothing");
}
/// A guarantee the remote does not offer is refused, and — the part that is
/// easy to get wrong — refused *before* the command list is sent, not after.
#[test]
fn an_unofferable_guarantee_is_refused_before_the_command_list_goes_out() {
let mut t = Recorder::new();
let err = send_pack(
&mut t,
&adv("report-status object-format=sha1"),
&[PushCommand::update("refs/heads/main", oid('1'), oid('2'))],
Kind::Sha1,
Some(pack_writer),
&SendPackOptions {
atomic: true,
..Default::default()
},
)
.unwrap_err();
assert!(format!("{err}").contains("atomic"), "{err}");
assert!(t.written.is_empty());
}
/// The packfile follows the flush-pkt **raw**. Framing it produces a corrupt
/// push whose checksum error points nowhere near the framing layer.
#[test]
fn the_packfile_is_written_unframed_after_the_command_lists_flush() {
let mut t = Recorder::new();
// No report is expected back, so the driver returns without reading — which
// is what lets this run against a transport that answers nothing.
let _ = send_pack(
&mut t,
&adv("delete-refs object-format=sha1"),
&[PushCommand::update("refs/heads/main", oid('1'), oid('2'))],
Kind::Sha1,
Some(pack_writer),
&SendPackOptions::default(),
)
.expect("a remote offering no report-status is legal");
let expected_commands = driver::encode_command_list(
&[PushCommand::update("refs/heads/main", oid('1'), oid('2'))],
&["object-format=sha1".to_string()],
)
.unwrap();
assert!(
t.written.starts_with(&expected_commands),
"the command list must lead, framed and flush-terminated"
);
assert_eq!(
&t.written[expected_commands.len()..],
b"PACK-not-really",
"the pack follows the flush-pkt with NO length prefix in front of it"
);
}
/// Text framing: `<4-hex-length><payload>\n`, and the newline is the framer's.
/// A payload that carried its own would be double-terminated by one end of this
/// crate and single-terminated by the other.
#[test]
fn a_framed_section_is_byte_exact_and_the_newline_belongs_to_the_framer() {
let mut out = Vec::new();
driver::frame_section(
&[b"unpack ok".to_vec(), b"ok refs/heads/main".to_vec()],
&mut out,
)
.unwrap();
assert_eq!(
out,
b"000e"
.to_vec() // 4 + 9 + 1
.into_iter()
.chain(b"unpack ok\n".iter().copied())
.chain(b"0017".iter().copied()) // 4 + 18 + 1
.chain(b"ok refs/heads/main\n".iter().copied())
.chain(b"0000".iter().copied())
.collect::<Vec<u8>>()
);
}
#[test]
fn push_options_are_one_pkt_line_each_and_a_newline_in_one_is_refused() {
let out =
driver::encode_push_options(&["ci.skip".to_string(), "reviewer=r".to_string()]).unwrap();
assert_eq!(out, b"000cci.skip\n000freviewer=r\n0000".to_vec());
assert!(driver::encode_push_options(&["bad\nline".to_string()]).is_err());
}
/// **The client says `ofs-delta` exactly when it will write `OBJ_OFS_DELTA`.**
///
/// # The class, on the push side
///
/// `ofs-delta` is a thing the *client* says: it tells receive-pack the pack may
/// name a delta's base by distance backwards instead of by object id. gunnar's
/// client never said it. `negotiate` had no arm for it at all, while
/// `gunnar-client::push` built `WriteOptions::new(kind)` — whose `ofs_delta`
/// defaults to `true` — so every push gunnar sent used an encoding the wire
/// never declared.
///
/// Nothing broke, and that is the point. `git index-pack` decodes
/// `OBJ_OFS_DELTA` whether or not the capability arrived, so this is capability
/// dishonesty rather than a measured wrong answer — the same class as a policy
/// no capability names, seen from the other end of the connection. A
/// receive-pack that refused what was never offered would be within its rights
/// and gunnar would be the one at fault.
///
/// # What is asserted where, said plainly
///
/// * **Here**, the applied output is the capability string gunnar puts on the
/// wire, read back out of the recorded bytes: present when the remote offers
/// `ofs-delta`, absent when it does not, with the rest of the line unchanged
/// so that "absent" is not "the line collapsed".
/// * The **encoding** half — that `WriteOptions::ofs_delta` really changes the
/// entry headers — is measured against stock `git verify-pack -v`'s entry
/// type column in `gunnar-pack/tests/ofs_delta_off.rs`, and is not re-measured
/// here.
/// * The join is `options::ofs_delta_agreed`, which is the only thing either
/// half reads, and the last arm below pins that both really consult it.
///
/// Seen red: with the `ofs_delta_agreed(adv)` arm removed from `negotiate` —
/// the state this shipped in — the first arm fails with *"the client used
/// `OBJ_OFS_DELTA` … and did not say `ofs-delta`"*.
#[test]
fn the_client_declares_ofs_delta_exactly_when_it_will_use_it() {
/// The capability string of the first command line, off the wire.
fn capabilities_on_the_wire(written: &[u8]) -> String {
let text = String::from_utf8_lossy(written);
let nul = text
.find('\0')
.expect("the first command line carries a NUL and a capability list");
let rest = &text[nul + 1..];
rest[..rest.find('\n').unwrap_or(rest.len())].to_owned()
}
fn push_to(caps: &str) -> String {
let mut t = Recorder::new();
let _ = send_pack(
&mut t,
&adv(caps),
&[PushCommand::update("refs/heads/main", oid('1'), oid('2'))],
Kind::Sha1,
Some(pack_writer),
&SendPackOptions::default(),
)
.expect("a remote offering no report-status is legal");
capabilities_on_the_wire(&t.written)
}
// ── the remote offers it: gunnar says it ───────────────────────────────
let offered = push_to("ofs-delta side-band-64k object-format=sha1");
assert!(
offered.split(' ').any(|c| c == "ofs-delta"),
"the client used `OBJ_OFS_DELTA` in the pack it writes and did not say `ofs-delta` on \
the wire. The remote is entitled to refuse an encoding it never offered to accept. \
The capability line was {offered:?}"
);
// ── the remote does not: gunnar does not ───────────────────────────────
let withheld = push_to("side-band-64k object-format=sha1");
assert!(
!withheld.split(' ').any(|c| c == "ofs-delta"),
"the client claimed `ofs-delta` against a remote that never offered it: {withheld:?}"
);
// The control. Without it, a client that sent an EMPTY capability list
// would satisfy the assertion above.
assert!(
withheld.split(' ').any(|c| c == "side-band-64k")
&& withheld.contains("object-format=sha1"),
"withholding one capability emptied the whole line: {withheld:?}"
);
// …and the two lines really differ in exactly `ofs-delta`.
let difference: Vec<&str> = offered
.split(' ')
.filter(|c| !withheld.split(' ').any(|w| w == *c))
.collect();
assert_eq!(
difference,
vec!["ofs-delta"],
"the two pushes differ in more than the capability under test: {offered:?} against \
{withheld:?}"
);
// ── and the writer is driven by the SAME predicate, not by a second read
// of the advertisement that could drift from this one.
assert!(gunnar_sendpack::options::ofs_delta_agreed(&adv(
"ofs-delta object-format=sha1"
)));
assert!(!gunnar_sendpack::options::ofs_delta_agreed(&adv(
"object-format=sha1"
)));
}