Skip to main content

scheduling/
fixture.rs

1//! Disposable original data; Git creates the formats, girt reads them.
2use std::io::Write;
3use std::path::Path;
4use std::process::{Command, Stdio};
5
6use girt::{ObjectId, Repository};
7
8pub struct Gate {
9    pub started: tokio::sync::oneshot::Sender<()>,
10    pub release: std::sync::mpsc::Receiver<()>,
11}
12
13pub struct Fixture {
14    pub root: tempfile::TempDir,
15    tip: ObjectId,
16    blob: ObjectId,
17}
18
19impl Fixture {
20    pub fn new(packed: bool) -> Self {
21        let root = tempfile::tempdir().unwrap();
22        git(
23            root.path(),
24            &["init", "--bare", "--object-format=sha1", "--template=", "."],
25            b"",
26        );
27
28        let mut state = 42u32;
29        let bytes: Vec<u8> = (0..2 * 1024 * 1024)
30            .map(|_| {
31                state ^= state << 13;
32                state ^= state >> 17;
33                state ^= state << 5;
34                state as u8
35            })
36            .collect();
37
38        let blob = ObjectId::for_blob(girt::ObjectFormat::Sha1, &bytes);
39        let mut input = format!("blob\nmark :1\ndata {}\n", bytes.len()).into_bytes();
40        input.extend(bytes);
41        input.extend(b"\n");
42
43        for i in 0..512 {
44            input.extend(format!("commit refs/heads/main\ncommitter A <a@example.com> 1700000000 +0000\ndata {}\nnode {i}\n", format!("node {i}").len()).bytes());
45            if i == 0 {
46                input.extend(b"M 100644 :1 payload\n");
47            }
48            input.extend(b"\n");
49        }
50
51        git(root.path(), &["fast-import", "--quiet"], &input);
52
53        // fast-import writes a pack. Unpack in a separate disposable object directory to obtain
54        // genuinely loose storage; then let Git repack only for the packed scenario.
55        let pack_dir = root.path().join("objects/pack");
56        let pack = std::fs::read_dir(&pack_dir)
57            .unwrap()
58            .find_map(|e| {
59                let path = e.unwrap().path();
60                (path.extension().is_some_and(|e| e == "pack")).then_some(path)
61            })
62            .unwrap();
63
64        let data = std::fs::read(pack).unwrap();
65        std::fs::remove_dir_all(&pack_dir).unwrap();
66        std::fs::create_dir(&pack_dir).unwrap();
67        git(root.path(), &["unpack-objects"], &data);
68
69        if packed {
70            git(root.path(), &["repack", "-ad"], b"");
71            git(root.path(), &["prune-packed"], b"");
72        }
73
74        let tip = String::from_utf8(git(root.path(), &["rev-parse", "refs/heads/main"], b""))
75            .unwrap()
76            .trim()
77            .parse()
78            .unwrap();
79
80        Self { root, tip, blob }
81    }
82
83    pub fn operation(&self, gate: Option<Gate>) {
84        let repo = Repository::open(self.root.path()).unwrap();
85        let objects = repo.objects().unwrap();
86
87        if let Some(gate) = gate {
88            gate.started.send(()).unwrap();
89            gate.release.recv().unwrap();
90        }
91
92        let blob = objects.read(self.blob).unwrap().unwrap();
93        assert_eq!(blob.data().len(), 2 * 1024 * 1024);
94        assert_eq!(objects.walk(&[self.tip]).unwrap().len(), 512);
95    }
96}
97
98fn git(path: &Path, args: &[&str], input: &[u8]) -> Vec<u8> {
99    let mut command = Command::new("git");
100    for (key, _) in std::env::vars_os() {
101        if key.to_string_lossy().starts_with("GIT_") {
102            command.env_remove(key);
103        }
104    }
105
106    let mut child = command
107        .current_dir(path)
108        .args(args)
109        .env("GIT_CONFIG_NOSYSTEM", "1")
110        .env("GIT_CONFIG_GLOBAL", path.join("absent-config"))
111        .stdin(Stdio::piped())
112        .stdout(Stdio::piped())
113        .stderr(Stdio::piped())
114        .spawn()
115        .unwrap();
116
117    child.stdin.take().unwrap().write_all(input).unwrap();
118    let output = child.wait_with_output().unwrap();
119    assert!(
120        output.status.success(),
121        "{}",
122        String::from_utf8_lossy(&output.stderr)
123    );
124    output.stdout
125}