This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / knot2 / crates / knot-maintenance / tests / engine.rs
7.0 kB 200 lines
1use std::collections::HashSet; 2 3use knot_git::Repo; 4use knot_maintenance::{Options, PruneGrace, RepackStatus, run_repo}; 5use knot_types::{ObjectFormat, Oid}; 6 7mod common; 8use common::{ 9 commit, create_repo, delete_ref, git, has_bitmap, has_midx_bitmap, now, options, set_ref, 10}; 11 12fn create(scan: &std::path::Path, did: &str) -> Repo { 13 create_repo(scan, ObjectFormat::SHA1, did) 14} 15 16fn loose_count(repo: &Repo) -> usize { 17 walkdir::WalkDir::new(repo.git().git_dir().join("objects")) 18 .into_iter() 19 .filter_map(Result::ok) 20 .filter(|e| e.file_type().is_file()) 21 .filter(|e| { 22 e.path() 23 .parent() 24 .and_then(|p| p.file_name()) 25 .and_then(|n| n.to_str()) 26 .is_some_and(|n| n.len() == 2) 27 }) 28 .count() 29} 30 31#[test] 32fn a_single_pack_maintenance_pass_packs_graphs_bitmaps_prunes_then_settles() { 33 let scan = tempfile::tempdir().unwrap(); 34 let repo = create(scan.path(), "did:plc:squid"); 35 36 let base = commit(&repo, 0, Vec::new()); 37 let main_tip = commit(&repo, 1, vec![base]); 38 set_ref(&repo, "refs/heads/main", main_tip); 39 set_ref(&repo, "refs/heads/feature", commit(&repo, 2, vec![base])); 40 let orphan = commit(&repo, 9, vec![main_tip]); 41 42 let info = repo.git().git_dir().join("objects/info"); 43 std::fs::create_dir_all(&info).unwrap(); 44 let leaked = info.join("commit-graph.knot-tmp.999999"); 45 std::fs::write(&leaked, b"a graph write that a kill -9 interrupted").unwrap(); 46 let long_ago = std::time::SystemTime::now() - std::time::Duration::from_secs(7 * 3600); 47 std::fs::File::options() 48 .write(true) 49 .open(&leaked) 50 .unwrap() 51 .set_times(std::fs::FileTimes::new().set_modified(long_ago)) 52 .unwrap(); 53 assert!(loose_count(&repo) >= 4 && repo.contains(orphan)); 54 55 let report = run_repo(&repo, now(), &options()).unwrap(); 56 assert_eq!(report.repack.status, RepackStatus::Repacked); 57 assert!( 58 report.repack.removed_loose.get() >= 4, 59 "loose objects folded into the pack" 60 ); 61 assert!(report.commit_graph && info.join("commit-graph").exists()); 62 assert!( 63 !leaked.exists(), 64 "maintenance sweeps a crashed graph write's temp once it is too old to still have a writer" 65 ); 66 assert!(report.bitmap && has_bitmap(&repo)); 67 let (ok, stderr) = git(&repo, &["rev-list", "--test-bitmap", "main"]); 68 assert!(ok, "canonical git accepts our bitmap: {stderr}"); 69 assert!(report.packed_refs.packed >= 1); 70 assert!( 71 report.prune.ran && report.prune.removed.get() >= 1, 72 "the orphan is pruned" 73 ); 74 75 let reopened = Repo::open(repo.git().git_dir()).unwrap(); 76 assert!(!reopened.contains(orphan) && reopened.contains(main_tip)); 77 let (clean, stderr) = git(&reopened, &["fsck", "--no-progress"]); 78 assert!(clean, "fsck-clean after the pass: {stderr}"); 79 80 let settled = run_repo(&reopened, now(), &options()).unwrap(); 81 assert_eq!(settled.repack.status, RepackStatus::Clean); 82 assert!( 83 !settled.prune.ran && !settled.commit_graph && !settled.bitmap, 84 "a settled repo is a no-op" 85 ); 86 assert_eq!(settled.packed_refs.packed, 0); 87} 88 89#[test] 90fn the_written_graph_verifies_and_accelerates_selection_through_an_octopus_merge() { 91 let scan = tempfile::tempdir().unwrap(); 92 let repo = create(scan.path(), "did:plc:cuttle"); 93 94 let a = commit(&repo, 1, Vec::new()); 95 let b = commit(&repo, 2, Vec::new()); 96 let c = commit(&repo, 3, Vec::new()); 97 let pair = commit(&repo, 4, vec![a, b]); 98 let octopus = commit(&repo, 5, vec![a, b, c]); 99 let tip = commit(&repo, 6, vec![pair, octopus]); 100 set_ref(&repo, "refs/heads/main", tip); 101 102 let graph = repo.git().git_dir().join("objects/info/commit-graph"); 103 let closure = |r: &Repo| -> HashSet<Oid> { 104 r.select_pack_objects(knot_git::Wants::new(&[tip]), knot_git::Haves::new(&[])) 105 .unwrap() 106 .into_iter() 107 .collect() 108 }; 109 let decode_closure = closure(&repo); 110 assert!(!graph.exists()); 111 112 assert!(run_repo(&repo, now(), &options()).unwrap().commit_graph && graph.exists()); 113 let (ok, stderr) = git(&repo, &["commit-graph", "verify"]); 114 assert!( 115 ok, 116 "git accepts the hand-written graph with an octopus: {stderr}" 117 ); 118 119 let reopened = Repo::open(repo.git().git_dir()).unwrap(); 120 assert_eq!( 121 closure(&reopened), 122 decode_closure, 123 "graph-accelerated selection equals the decode walk" 124 ); 125} 126 127#[test] 128fn commit_graph_knob_lifecycle_skips_backfills_and_sweeps() { 129 let scan = tempfile::tempdir().unwrap(); 130 let repo = create(scan.path(), "did:plc:limpet"); 131 132 let base = commit(&repo, 0, Vec::new()); 133 set_ref(&repo, "refs/heads/main", commit(&repo, 1, vec![base])); 134 135 let off = Options { 136 commit_graph: false, 137 ..options() 138 }; 139 let graph = repo.git().git_dir().join("objects/info/commit-graph"); 140 141 let first = run_repo(&repo, now(), &off).unwrap(); 142 assert_eq!(first.repack.status, RepackStatus::Repacked); 143 assert!( 144 !first.commit_graph && !graph.exists(), 145 "no graph is written when the knob is off" 146 ); 147 148 let reopened = Repo::open(repo.git().git_dir()).unwrap(); 149 let backfill = run_repo(&reopened, now(), &options()).unwrap(); 150 assert!( 151 backfill.commit_graph && graph.exists(), 152 "a packed repo with no graph backfills it" 153 ); 154 assert_eq!(backfill.repack.status, RepackStatus::Clean); 155 156 let settled = Repo::open(repo.git().git_dir()).unwrap(); 157 assert!( 158 !run_repo(&settled, now(), &options()).unwrap().commit_graph, 159 "a present graph settles to no-op" 160 ); 161 162 let swept = Repo::open(repo.git().git_dir()).unwrap(); 163 assert!(!run_repo(&swept, now(), &off).unwrap().commit_graph); 164 assert!( 165 !graph.exists(), 166 "flipping the knob off sweeps the orphan graph" 167 ); 168} 169 170#[test] 171fn cruft_second_pack_gets_a_midx_bitmap_canonical_git_accepts() { 172 let scan = tempfile::tempdir().unwrap(); 173 let repo = create(scan.path(), "did:plc:mussel"); 174 175 let base = commit(&repo, 0, Vec::new()); 176 let doomed = commit(&repo, 1, vec![base]); 177 set_ref(&repo, "refs/heads/main", base); 178 set_ref(&repo, "refs/heads/feature", doomed); 179 180 let opts = Options { 181 prune_grace: PruneGrace::from_secs(86_400), 182 ..options() 183 }; 184 run_repo(&repo, now(), &opts).unwrap(); 185 186 delete_ref(&repo, "refs/heads/feature"); 187 set_ref(&repo, "refs/heads/main", commit(&repo, 2, vec![base])); 188 189 let report = run_repo(&repo, now(), &opts).unwrap(); 190 assert!( 191 report.prune.crufted.get() >= 1, 192 "young unreachable object is crufted" 193 ); 194 assert!( 195 report.bitmap && has_midx_bitmap(&repo), 196 "the multi-pack repo gets a midx bitmap" 197 ); 198 let (ok, stderr) = git(&repo, &["rev-list", "--test-bitmap", "main"]); 199 assert!(ok, "canonical git accepts our midx bitmap: {stderr}"); 200}