Skip to content

Commit 5cd7748

Browse files
committed
A baseline test for the noop negotiator
1 parent 50b45dc commit 5cd7748

File tree

7 files changed

+268
-1
lines changed

7 files changed

+268
-1
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gix-negotiate/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,7 @@ gix-hash = { version = "^0.11.1", path = "../gix-hash" }
1717
gix-object = { version = "^0.29.2", path = "../gix-object" }
1818
gix-commitgraph = { version = "^0.15.0", path = "../gix-commitgraph" }
1919
gix-revision = { version = "^0.14.0", path = "../gix-revision" }
20+
21+
[dev-dependencies]
22+
gix-testtools = { path = "../tests/tools" }
23+
gix-odb = { path = "../gix-odb" }

gix-negotiate/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ mod noop;
99
/// The way the negotiation is performed.
1010
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
1111
pub enum Algorithm {
12-
/// Do not send any information at all, likely at cost of larger-than-necessary packs.
12+
/// Do not send any information at all, which typically leads to complete packs to be sent.
1313
Noop,
1414
/// Walk over consecutive commits and check each one. This can be costly be assures packs are exactly the size they need to be.
1515
#[default]

gix-negotiate/tests/baseline/mod.rs

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
use gix_negotiate::Algorithm;
2+
use gix_object::bstr;
3+
use gix_object::bstr::ByteSlice;
4+
use gix_odb::Find;
5+
6+
#[test]
7+
fn run() -> crate::Result {
8+
let root = gix_testtools::scripted_fixture_read_only("make_repos.sh")?;
9+
for case in ["no_parents"] {
10+
let base = root.join(case);
11+
12+
for (algo_name, algo) in [
13+
("noop", Algorithm::Noop),
14+
// ("consecutive", Algorithm::Consecutive),
15+
// ("skipping", Algorithm::Skipping),
16+
] {
17+
let buf = std::fs::read(base.join(format!("baseline.{algo_name}")))?;
18+
let tips = parse::object_ids("", std::fs::read(base.join("tips"))?.lines());
19+
let store = gix_odb::at(base.join("client").join(".git/objects"))?;
20+
21+
for use_cache in [false, true] {
22+
let cache = use_cache
23+
.then(|| gix_commitgraph::at(store.store_ref().path().join("info")).ok())
24+
.flatten();
25+
let mut negotiator = algo.into_negotiator(
26+
|id, buf| {
27+
store
28+
.try_find(id, buf)
29+
.map(|r| r.and_then(|d| d.try_into_commit_iter()))
30+
},
31+
cache,
32+
);
33+
for tip in &tips {
34+
negotiator.add_tip(tip);
35+
}
36+
for Round { haves, common } in ParseRounds::new(buf.lines()) {
37+
for have in haves {
38+
let actual = negotiator.next_have().unwrap_or_else(|| {
39+
panic!(
40+
"{algo_name}: one have per baseline: {have} missing or in wrong order, left: {:?}",
41+
std::iter::from_fn(|| negotiator.next_have()).collect::<Vec<_>>()
42+
)
43+
});
44+
assert_eq!(actual, have, "{algo_name}: order and commit matches exactly");
45+
}
46+
for common_revision in common {
47+
negotiator.in_common_with_remote(&common_revision);
48+
}
49+
}
50+
assert_eq!(
51+
negotiator.next_have(),
52+
None,
53+
"{algo_name}: negotiator should be depleted after all recorded baseline rounds"
54+
);
55+
}
56+
}
57+
}
58+
Ok(())
59+
}
60+
61+
struct ParseRounds<'a> {
62+
lines: bstr::Lines<'a>,
63+
}
64+
65+
impl<'a> ParseRounds<'a> {
66+
pub fn new(mut lines: bstr::Lines<'a>) -> Self {
67+
parse::command(&mut lines, parse::Command::Incoming).expect("handshake");
68+
Self { lines }
69+
}
70+
}
71+
72+
impl<'a> Iterator for ParseRounds<'a> {
73+
type Item = Round;
74+
75+
fn next(&mut self) -> Option<Self::Item> {
76+
let haves = parse::object_ids("have", parse::command(&mut self.lines, parse::Command::Outgoing)?);
77+
let common = parse::object_ids("ACK", parse::command(&mut self.lines, parse::Command::Incoming)?);
78+
if haves.is_empty() {
79+
assert!(common.is_empty(), "cannot ack what's not there");
80+
return None;
81+
}
82+
Round { haves, common }.into()
83+
}
84+
}
85+
86+
struct Round {
87+
pub haves: Vec<gix_hash::ObjectId>,
88+
pub common: Vec<gix_hash::ObjectId>,
89+
}
90+
91+
mod parse {
92+
use gix_object::bstr;
93+
use gix_object::bstr::{BStr, ByteSlice};
94+
95+
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
96+
pub enum Command {
97+
Incoming,
98+
Outgoing,
99+
}
100+
101+
pub fn object_ids(prefix: &str, lines: impl IntoIterator<Item = impl AsRef<[u8]>>) -> Vec<gix_hash::ObjectId> {
102+
lines
103+
.into_iter()
104+
.filter_map(|line| {
105+
line.as_ref()
106+
.strip_prefix(prefix.as_bytes())
107+
.map(|id| gix_hash::ObjectId::from_hex(id.trim()).expect("valid hash"))
108+
})
109+
.collect()
110+
}
111+
112+
pub fn command<'a>(lines: &mut bstr::Lines<'a>, wanted: Command) -> Option<Vec<&'a BStr>> {
113+
let mut out = Vec::new();
114+
for line in lines {
115+
let pos = line.find(b"fetch").expect("fetch token");
116+
let line_mode = match &line[pos + 5..][..2] {
117+
b"< " => Command::Incoming,
118+
b"> " => Command::Outgoing,
119+
invalid => unreachable!("invalid fetch token: {:?}", invalid.as_bstr()),
120+
};
121+
assert_eq!(line_mode, wanted, "command with unexpected mode");
122+
let line = line[pos + 7..].as_bstr();
123+
if line == "0000" {
124+
break;
125+
}
126+
out.push(line);
127+
}
128+
(!out.is_empty()).then_some(out)
129+
}
130+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:157b0dfa79e30e3b1f6808f4e6f9c62e3ec136b2366b16e81007644db49c6d9a
3+
size 87912
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/bin/bash
2+
set -eu -o pipefail
3+
4+
function tick () {
5+
if test -z "${tick+set}"
6+
then
7+
tick=1112911993
8+
else
9+
tick=$(($tick + 60))
10+
fi
11+
GIT_COMMITTER_DATE="$tick -0700"
12+
GIT_AUTHOR_DATE="$tick -0700"
13+
export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
14+
}
15+
16+
tick
17+
function commit() {
18+
local message=${1:?first argument is the commit message}
19+
local file="$message.t"
20+
echo "$1" > "$file"
21+
git add -- "$file"
22+
git commit -m "$message"
23+
git tag "$message"
24+
tick
25+
}
26+
27+
function negotiation_tips () {
28+
local tips=""
29+
for arg in "$@"; do
30+
tips+=" --negotiation-tip=$arg"
31+
done
32+
echo "$tips"
33+
}
34+
35+
function trace_fetch_baseline () {
36+
git -C client commit-graph write --no-progress --reachable
37+
git -C client repack -adq
38+
39+
for tip in "$@"; do git -C client rev-parse "$tip" >> tips; done
40+
for algo in noop consecutive skipping; do
41+
GIT_TRACE_PACKET="$PWD/baseline.$algo" \
42+
git -C client -c fetch.negotiationAlgorithm="$algo" fetch --negotiate-only $(negotiation_tips "$@") \
43+
--upload-pack 'unset GIT_TRACE_PACKET; git-upload-pack' \
44+
file://$PWD/server || :
45+
done
46+
}
47+
48+
49+
(mkdir no_parents && cd no_parents
50+
(git init -q server && cd server
51+
commit to_fetch
52+
)
53+
54+
(git init -q client && cd client
55+
for i in $(seq 7); do
56+
commit c$i
57+
done
58+
)
59+
60+
trace_fetch_baseline main
61+
)
62+
63+
(mkdir two_colliding_skips && cd two_colliding_skips
64+
(git init -q server && cd server
65+
commit to_fetch
66+
)
67+
68+
(git init -q client && cd client
69+
for i in $(seq 11); do
70+
commit c$i
71+
done
72+
git checkout c5
73+
commit c5side
74+
)
75+
76+
trace_fetch_baseline HEAD main
77+
)
78+
79+
(mkdir multi_round && cd multi_round
80+
(git init -q server && cd server
81+
commit to_fetch
82+
)
83+
84+
(git init -q client && cd client
85+
for i in $(seq 8); do
86+
git checkout --orphan b$i &&
87+
commit b$i.c0
88+
done
89+
90+
for j in $(seq 19); do
91+
for i in $(seq 8); do
92+
git checkout b$i &&
93+
commit b$i.c$j
94+
done
95+
done
96+
)
97+
(cd server
98+
git fetch --no-tags "$PWD/../client" b1:refs/heads/b1
99+
git checkout b1
100+
commit commit-on-b1
101+
)
102+
trace_fetch_baseline $(ls client/.git/refs/heads | sort)
103+
)
104+
105+
(mkdir clock_skew && cd clock_skew
106+
(git init -q server && cd server
107+
commit to_fetch
108+
)
109+
110+
(git init -q client && cd client
111+
tick=2000000000
112+
commit c1
113+
commit c2
114+
115+
tick=1000000000
116+
git checkout c1
117+
commit old1
118+
commit old2
119+
commit old3
120+
commit old4
121+
)
122+
123+
trace_fetch_baseline HEAD main
124+
)

gix-negotiate/tests/negotiate.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use gix_testtools::Result;
2+
13
mod window_size {
24
use gix_negotiate::window_size;
35

@@ -31,3 +33,5 @@ mod window_size {
3133
}
3234
}
3335
}
36+
37+
mod baseline;

0 commit comments

Comments
 (0)