1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
pub use bstr;
use once_cell::sync::Lazy;
use std::{collections::BTreeMap, path::Path, path::PathBuf, sync::Mutex};

pub use tempfile;

static SCRIPT_IDENTITY: Lazy<Mutex<BTreeMap<PathBuf, u32>>> = Lazy::new(|| Mutex::new(BTreeMap::new()));

pub fn fixture_path(path: impl AsRef<str>) -> PathBuf {
    PathBuf::from("tests").join("fixtures").join(path.as_ref())
}
pub fn scripted_fixture_repo_read_only(script_name: &str) -> std::result::Result<PathBuf, Box<dyn std::error::Error>> {
    scripted_fixture_repo_read_only_with_args(script_name, None)
}

/// Returns the directory at which the data is present
pub fn scripted_fixture_repo_read_only_with_args(
    script_name: &str,
    args: impl IntoIterator<Item = &'static str>,
) -> std::result::Result<PathBuf, Box<dyn std::error::Error>> {
    use bstr::ByteSlice;
    let script_path = fixture_path(script_name);

    // keep this lock to assure we don't return unfinished directories for threaded callers
    let args: Vec<String> = args.into_iter().map(Into::into).collect();
    let mut map = SCRIPT_IDENTITY.lock().unwrap();
    let script_identity = map
        .entry(args.iter().fold(script_path.clone(), |p, a| p.join(a)))
        .or_insert_with(|| {
            let mut crc_value = crc::crc32::update(
                0,
                &crc::crc32::IEEE_TABLE,
                &std::fs::read(&script_path).expect("file can be read entirely"),
            );
            for arg in args.iter() {
                crc_value = crc::crc32::update(crc_value, &crc::crc32::IEEE_TABLE, arg.as_bytes());
            }
            crc_value
        })
        .to_owned();
    let script_result_directory = fixture_path(
        Path::new("generated")
            .join(format!("{}", script_identity))
            .to_string_lossy(),
    );
    if !script_result_directory.is_dir() {
        std::fs::create_dir_all(&script_result_directory)?;
        let script_absolute_path = std::env::current_dir()?.join(script_path);
        let output = std::process::Command::new("bash")
            .arg(script_absolute_path)
            .args(args)
            .stdout(std::process::Stdio::piped())
            .stderr(std::process::Stdio::piped())
            .current_dir(&script_result_directory)
            .env_remove("GIT_DIR")
            .env("GIT_AUTHOR_DATE", "2000-01-01 00:00:00 +0000")
            .env("GIT_AUTHOR_EMAIL", "author@example.com")
            .env("GIT_AUTHOR_NAME", "author")
            .env("GIT_COMMITTER_DATE", "2000-01-02 00:00:00 +0000")
            .env("GIT_COMMITTER_EMAIL", "committer@example.com")
            .env("GIT_COMMITTER_NAME", "committer")
            .output()?;
        assert!(
            output.status.success(),
            "repo script failed: stdout: {}\nstderr: {}",
            output.stdout.as_bstr(),
            output.stderr.as_bstr()
        );
    }
    Ok(script_result_directory)
}