This repository has no description
4.0 kB
129 lines
1use std::collections::BTreeSet;
2
3use knot_git::Repo;
4use knot_maintenance::{GeometricFactor, Options, PruneGrace, run_repo};
5use knot_types::{ObjectFormat, Oid, RefName};
6
7mod common;
8use common::{
9 EMPTY_TREE_SHA1, chain, create_repo, delete_ref, empty_tree, fsck_clean, git_available,
10 has_cruft_pack, idx_stems, midx_verifies, now, options, reachable_objects, set_ref,
11};
12
13fn opts(factor: u64, grace: PruneGrace) -> Options {
14 Options {
15 geometric_factor: GeometricFactor::new(factor),
16 prune_grace: grace,
17 commit_graph: false,
18 bitmap: false,
19 ..options()
20 }
21}
22
23fn main_ref(repo: &Repo) -> Option<Oid> {
24 repo.find_ref(&RefName::new("refs/heads/main").unwrap())
25 .unwrap()
26}
27
28fn no_reachable_loss(format: ObjectFormat) {
29 if !git_available() {
30 eprintln!("skipping geometric differential: git unavailable");
31 return;
32 }
33 let scan = tempfile::tempdir().unwrap();
34 let empty = empty_tree(format);
35 let repo = create_repo(scan.path(), format, "did:plc:scallop");
36 let opts = opts(2, PruneGrace::from_secs(86_400));
37
38 (0..6).fold((None, BTreeSet::new()), |(tip, seen), round| {
39 let next = chain(&repo, empty, round * 2..round * 2 + 2, tip);
40 set_ref(&repo, "refs/heads/main", next);
41 run_repo(&repo, now(), &opts).unwrap();
42
43 let r = Repo::open(repo.git().git_dir()).unwrap();
44 assert!(fsck_clean(&r), "{format:?} r{round}: not fsck-clean");
45 if let Some((ok, stderr)) = midx_verifies(&r) {
46 assert!(ok, "{format:?} r{round} midx: {stderr}");
47 }
48 assert_eq!(
49 main_ref(&r),
50 Some(next),
51 "{format:?} r{round}: main lost its tip"
52 );
53 let present = reachable_objects(&r);
54 assert!(
55 seen.is_subset(&present),
56 "{format:?} r{round}: a reachable object went missing"
57 );
58 (Some(next), present)
59 });
60}
61
62#[test]
63fn geometric_no_reachable_loss_sha1() {
64 no_reachable_loss(ObjectFormat::SHA1);
65}
66
67#[test]
68fn geometric_no_reachable_loss_sha256() {
69 no_reachable_loss(ObjectFormat::SHA256);
70}
71
72#[test]
73fn geometric_keeps_the_large_pack_while_rolling_up_then_crufting_small_packs() {
74 if !git_available() {
75 eprintln!("skipping geometric behavior test: git unavailable");
76 return;
77 }
78 let scan = tempfile::tempdir().unwrap();
79 let repo = create_repo(scan.path(), ObjectFormat::SHA1, "did:plc:conch");
80 let opts = opts(2, PruneGrace::from_secs(86_400));
81
82 let big = chain(&repo, EMPTY_TREE_SHA1, 0..10, None);
83 set_ref(&repo, "refs/heads/main", big);
84 run_repo(&repo, now(), &opts).unwrap();
85 let large = idx_stems(&repo);
86 assert_eq!(
87 large.len(),
88 1,
89 "the initial repack settles to one large pack"
90 );
91
92 let advanced = chain(&repo, EMPTY_TREE_SHA1, 100..102, Some(big));
93 set_ref(&repo, "refs/heads/main", advanced);
94 run_repo(&repo, now(), &opts).unwrap();
95 assert!(
96 large.is_subset(&idx_stems(&repo)),
97 "the large pack is kept verbatim"
98 );
99 assert_eq!(
100 idx_stems(&repo).len(),
101 2,
102 "small additions roll into a second pack"
103 );
104
105 let feature = chain(&repo, EMPTY_TREE_SHA1, 200..202, Some(advanced));
106 set_ref(&repo, "refs/heads/feature", feature);
107 run_repo(&repo, now(), &opts).unwrap();
108 delete_ref(&repo, "refs/heads/feature");
109
110 let report = run_repo(&repo, now(), &opts).unwrap();
111 assert!(
112 report.prune.crufted.get() >= 1,
113 "young rolled-up garbage is crufted"
114 );
115 assert!(
116 large.is_subset(&idx_stems(&repo)) && has_cruft_pack(&repo),
117 "large pack untouched, cruft written"
118 );
119
120 let reopened = Repo::open(repo.git().git_dir()).unwrap();
121 assert!(
122 fsck_clean(&reopened),
123 "not fsck-clean after roll-up and cruft"
124 );
125 assert!(
126 reopened.contains(advanced) && reopened.contains(feature),
127 "reachable tip and young orphan both survive"
128 );
129}