diff options
Diffstat (limited to 'vendor/gix-negotiate/tests')
-rw-r--r-- | vendor/gix-negotiate/tests/baseline/mod.rs | 226 | ||||
-rw-r--r-- | vendor/gix-negotiate/tests/fixtures/generated-archives/make_repos.tar.xz | bin | 0 -> 92828 bytes | |||
-rw-r--r-- | vendor/gix-negotiate/tests/fixtures/make_repos.sh | 146 | ||||
-rw-r--r-- | vendor/gix-negotiate/tests/negotiate.rs | 46 |
4 files changed, 418 insertions, 0 deletions
diff --git a/vendor/gix-negotiate/tests/baseline/mod.rs b/vendor/gix-negotiate/tests/baseline/mod.rs new file mode 100644 index 000000000..e3e209bab --- /dev/null +++ b/vendor/gix-negotiate/tests/baseline/mod.rs @@ -0,0 +1,226 @@ +use std::cell::RefCell; + +use gix_negotiate::Algorithm; +use gix_object::{bstr, bstr::ByteSlice}; +use gix_odb::{Find, FindExt}; +use gix_ref::{file::ReferenceExt, store::WriteReflog}; + +#[test] +fn run() -> crate::Result { + let root = gix_testtools::scripted_fixture_read_only("make_repos.sh")?; + for case in [ + "no_parents", + "clock_skew", + "two_colliding_skips", + "multi_round", + "advertisement_as_filter", + ] { + let base = root.join(case); + + for (algo_name, algo) in [ + ("noop", Algorithm::Noop), + ("consecutive", Algorithm::Consecutive), + ("skipping", Algorithm::Skipping), + ] { + let obj_buf = RefCell::new(Vec::new()); + let buf = std::fs::read(base.join(format!("baseline.{algo_name}")))?; + let store = gix_odb::at(base.join("client").join(".git/objects"))?; + let refs = gix_ref::file::Store::at( + base.join("client").join(".git"), + WriteReflog::Disable, + gix_hash::Kind::Sha1, + ); + let lookup_names = |names: &[&str]| -> Vec<gix_hash::ObjectId> { + names + .iter() + .filter_map(|name| { + refs.try_find(*name).expect("one tag per commit").map(|mut r| { + r.peel_to_id_in_place(&refs, |id, buf| { + store.try_find(id, buf).map(|d| d.map(|d| (d.kind, d.data))) + }) + .expect("works"); + r.target.into_id() + }) + }) + .collect() + }; + let message = |id| { + store + .find_commit(id, obj_buf.borrow_mut().as_mut()) + .expect("present") + .message + .trim() + .as_bstr() + .to_owned() + }; + + let debug = false; + for use_cache in [false, true] { + let cache = use_cache + .then(|| gix_commitgraph::at(store.store_ref().path().join("info")).ok()) + .flatten(); + let mut graph = gix_revision::Graph::new( + |id, buf| { + store + .try_find(id, buf) + .map(|r| r.and_then(|d| d.try_into_commit_iter())) + }, + cache, + ); + let mut negotiator = algo.into_negotiator(); + if debug { + eprintln!("ALGO {algo_name} CASE {case}"); + } + // // In --negotiate-only mode, which seems to be the only thing that's working after trying --dry-run, we unfortunately + // // don't get to see what happens if known-common commits are added as git itself doesn't do that in this mode + // // for some reason. + // for common in lookup_names(&["origin/main"]) { + // eprintln!("COMMON {name} {common}", name = message(common)); + // negotiator.known_common(common)?; + // } + for tip in lookup_names(&["HEAD"]).into_iter().chain( + refs.iter()? + .prefixed("refs/heads")? + .filter_map(Result::ok) + .map(|r| r.target.into_id()), + ) { + if debug { + eprintln!("TIP {name} {tip}", name = message(tip)); + } + negotiator.add_tip(tip, &mut graph)?; + } + for (round, Round { mut haves, common }) in ParseRounds::new(buf.lines()).enumerate() { + if algo == Algorithm::Skipping { + if case == "clock_skew" { + // Here for some reason the prio-queue of git manages to not sort the parent of C2, which is in the future, to be + // ahead of old4 that is in the past. In the git version of this test, they say to expect exactly this sequence + // as well even though it's not actually happening (but that they can't see due to the way they are testing). + haves = lookup_names(&["c2", "c1", "old4", "old2", "old1"]); + } else if case == "two_colliding_skips" { + // The same thing, we actually get exactly the right order, whereas git for some reason doesn't. + // This is the order expected in the git tests. + haves = lookup_names(&["c5side", "c11", "c9", "c6", "c1"]); + } else if case == "multi_round" && round == 1 { + // Here, probably also because of priority queue quirks, `git` emits the commits out of order, with only one + // branch, b5 I think, being out of place. This list puts the expectation in the right order, which is ordered + // by commit date. + haves = lookup_names(&[ + "b8.c14", "b7.c14", "b6.c14", "b5.c14", "b4.c14", "b3.c14", "b2.c14", "b8.c9", "b7.c9", + "b6.c9", "b5.c9", "b4.c9", "b3.c9", "b2.c9", "b8.c1", "b7.c1", "b6.c1", "b5.c1", + "b4.c1", "b3.c1", "b2.c1", "b8.c0", "b7.c0", "b6.c0", "b5.c0", "b4.c0", "b3.c0", + "b2.c0", + ]); + } else if case == "advertisement_as_filter" { + haves = lookup_names(&["c2side", "c5", "origin/main"]) + .into_iter() + .chain(Some( + gix_hash::ObjectId::from_hex(b"f36cefa0be2ac180d360a54b1cc4214985cea60a").unwrap(), + )) + .collect(); + } + } + for have in haves { + let actual = negotiator.next_have(&mut graph).unwrap_or_else(|| { + panic!("{algo_name}:cache={use_cache}: one have per baseline: {have} missing or in wrong order", have = message(have)) + })?; + assert_eq!( + actual, + have, + "{algo_name}:cache={use_cache}: order and commit matches exactly, wanted {expected}, got {actual}, commits left: {:?}", + std::iter::from_fn(|| negotiator.next_have(&mut graph)).map(|id| message(id.unwrap())).collect::<Vec<_>>(), + actual = message(actual), + expected = message(have) + ); + if debug { + eprintln!("have {}", message(actual)); + } + } + for common_revision in common { + if debug { + eprintln!("ACK {}", message(common_revision)); + } + negotiator.in_common_with_remote(common_revision, &mut graph)?; + } + } + assert!( + negotiator.next_have(&mut graph).is_none(), + "{algo_name}:cache={use_cache}: negotiator should be depleted after all recorded baseline rounds" + ); + } + } + } + Ok(()) +} + +struct ParseRounds<'a> { + lines: bstr::Lines<'a>, +} + +impl<'a> ParseRounds<'a> { + pub fn new(mut lines: bstr::Lines<'a>) -> Self { + parse::command(&mut lines, parse::Command::Incoming).expect("handshake"); + Self { lines } + } +} + +impl<'a> Iterator for ParseRounds<'a> { + type Item = Round; + + fn next(&mut self) -> Option<Self::Item> { + let haves = parse::object_ids("have", parse::command(&mut self.lines, parse::Command::Outgoing)?); + let common = parse::object_ids("ACK", parse::command(&mut self.lines, parse::Command::Incoming)?); + if haves.is_empty() { + assert!(common.is_empty(), "cannot ack what's not there"); + return None; + } + Round { haves, common }.into() + } +} + +struct Round { + pub haves: Vec<gix_hash::ObjectId>, + pub common: Vec<gix_hash::ObjectId>, +} + +mod parse { + use gix_object::{ + bstr, + bstr::{BStr, ByteSlice}, + }; + + #[derive(Debug, Eq, PartialEq, Copy, Clone)] + pub enum Command { + Incoming, + Outgoing, + } + + pub fn object_ids(prefix: &str, lines: impl IntoIterator<Item = impl AsRef<[u8]>>) -> Vec<gix_hash::ObjectId> { + lines + .into_iter() + .filter_map(|line| { + line.as_ref() + .strip_prefix(prefix.as_bytes()) + .map(|id| gix_hash::ObjectId::from_hex(id.trim()).expect("valid hash")) + }) + .collect() + } + + pub fn command<'a>(lines: &mut bstr::Lines<'a>, wanted: Command) -> Option<Vec<&'a BStr>> { + let mut out = Vec::new(); + for line in lines { + let pos = line.find(b"fetch").expect("fetch token"); + let line_mode = match &line[pos + 5..][..2] { + b"< " => Command::Incoming, + b"> " => Command::Outgoing, + invalid => unreachable!("invalid fetch token: {:?}", invalid.as_bstr()), + }; + assert_eq!(line_mode, wanted, "command with unexpected mode"); + let line = line[pos + 7..].as_bstr(); + if line == "0000" { + break; + } + out.push(line); + } + (!out.is_empty()).then_some(out) + } +} diff --git a/vendor/gix-negotiate/tests/fixtures/generated-archives/make_repos.tar.xz b/vendor/gix-negotiate/tests/fixtures/generated-archives/make_repos.tar.xz Binary files differnew file mode 100644 index 000000000..93e836931 --- /dev/null +++ b/vendor/gix-negotiate/tests/fixtures/generated-archives/make_repos.tar.xz diff --git a/vendor/gix-negotiate/tests/fixtures/make_repos.sh b/vendor/gix-negotiate/tests/fixtures/make_repos.sh new file mode 100644 index 000000000..b1b96527d --- /dev/null +++ b/vendor/gix-negotiate/tests/fixtures/make_repos.sh @@ -0,0 +1,146 @@ +#!/bin/bash +set -eu -o pipefail + +function tick () { + if test -z "${tick+set}" + then + tick=1112911993 + else + tick=$(($tick + 60)) + fi + GIT_COMMITTER_DATE="$tick -0700" + GIT_AUTHOR_DATE="$tick -0700" + export GIT_COMMITTER_DATE GIT_AUTHOR_DATE +} + +tick +function commit() { + local message=${1:?first argument is the commit message} + local file="$message.t" + echo "$1" > "$file" + git add -- "$file" + tick + git commit -m "$message" + git tag "$message" +} + +function negotiation_tips () { + local tips="" + for arg in "$@"; do + tips+=" --negotiation-tip=$arg" + done + echo "$tips" +} + +function trace_fetch_baseline () { + local remote="${1:?need remote url}"; shift + git -C client commit-graph write --no-progress --reachable + git -C client repack -adq + + for algo in noop consecutive skipping; do + GIT_TRACE_PACKET="$PWD/baseline.$algo" \ + git -C client -c fetch.negotiationAlgorithm="$algo" fetch --negotiate-only $(negotiation_tips "$@") \ + --upload-pack 'unset GIT_TRACE_PACKET; git-upload-pack' \ + "$remote" || : + done +} + + +(mkdir no_parents && cd no_parents + git init -q server && cd server + commit to_fetch + cd .. + + (git init -q client && cd client + for i in $(seq 7); do + commit c$i + done + ) + + trace_fetch_baseline file://$PWD/server main +) + +(mkdir two_colliding_skips && cd two_colliding_skips + git init -q server && cd server + commit to_fetch + cd .. + + (git init -q client && cd client + for i in $(seq 11); do + commit c$i + done + git checkout c5 + commit c5side + ) + + trace_fetch_baseline file://$PWD/server HEAD main +) + +(mkdir advertisement_as_filter && cd advertisement_as_filter + git init -q server && cd server + commit c1 + commit c2 + commit c3 + git tag -d c1 c2 c3 + cd .. + git clone server client && cd client + commit c4 + commit c5 + git checkout c4^^ + commit c2side + cd .. + (cd server + git checkout --orphan anotherbranch + commit to_fetch + ) + + trace_fetch_baseline origin HEAD main +) + + +(mkdir multi_round && cd multi_round + git init -q server && cd server + commit to_fetch + cd .. + + git init -q client && cd client + for i in $(seq 8); do + git checkout --orphan b$i && + commit b$i.c0 + done + + for j in $(seq 19); do + for i in $(seq 8); do + git checkout b$i && + commit b$i.c$j + done + done + cd .. + (cd server + git fetch --no-tags "$PWD/../client" b1:refs/heads/b1 + git checkout b1 + commit commit-on-b1 + ) + trace_fetch_baseline file://$PWD/server $(ls client/.git/refs/heads | sort) +) + +(mkdir clock_skew && cd clock_skew + git init -q server && cd server + commit to_fetch + cd .. + + (git init -q client && cd client + tick=2000000000 + commit c1 + commit c2 + + tick=1000000000 + git checkout c1 + commit old1 + commit old2 + commit old3 + commit old4 + ) + + trace_fetch_baseline file://$PWD/server HEAD main +) diff --git a/vendor/gix-negotiate/tests/negotiate.rs b/vendor/gix-negotiate/tests/negotiate.rs new file mode 100644 index 000000000..357b54d61 --- /dev/null +++ b/vendor/gix-negotiate/tests/negotiate.rs @@ -0,0 +1,46 @@ +use gix_testtools::Result; + +mod window_size { + use gix_negotiate::window_size; + + #[test] + fn initial_value_without_previous_window_size() { + assert_eq!(window_size(false, None), 16); + assert_eq!(window_size(true, None), 16); + } + + #[test] + fn transport_is_stateless() { + let mut ws = window_size(true, None); + for expected in [32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 18022, 19824] { + ws = window_size(true, ws); + assert_eq!(ws, expected); + } + } + + #[test] + fn transport_is_not_stateless() { + let mut ws = window_size(false, None); + for expected in [32, 64, 96] { + ws = window_size(false, ws); + assert_eq!(ws, expected); + } + + let mut ws = 4; + for expected in [8, 16, 32, 64, 96] { + ws = window_size(false, ws); + assert_eq!(ws, expected); + } + } +} + +mod baseline; + +#[test] +fn size_of_entry() { + assert_eq!( + std::mem::size_of::<gix_revision::graph::Commit<gix_negotiate::Metadata>>(), + 56, + "we may keep a lot of these, so let's not let them grow unnoticed" + ); +} |