Skip to main content

cortexkit_test_support/
stable.rs

1#[cfg(unix)]
2use std::path::PathBuf;
3
4/// Immutable content-addressed fake executables shared across processes.
5/// Do not modify or delete the returned directory. Each complete file is copied
6/// by a child and atomically renamed, so concurrent readers never see a partial write.
7#[cfg(unix)]
8pub fn stable_executable_dir(files: &[(&str, &[u8])]) -> PathBuf {
9    use sha2::{Digest, Sha256};
10    use std::os::unix::fs::PermissionsExt;
11    use std::sync::atomic::{AtomicU64, Ordering};
12    static NEXT: AtomicU64 = AtomicU64::new(0);
13    let mut hash = Sha256::new();
14    for (name, bytes) in files {
15        assert!(
16            !name.is_empty()
17                && !name.contains('/')
18                && !name.contains('\\')
19                && !name.starts_with('.'),
20            "stable executable name must be plain"
21        );
22        for piece in [name.as_bytes(), *bytes] {
23            hash.update((piece.len() as u64).to_le_bytes());
24            hash.update(piece);
25        }
26    }
27    let key = format!("{:x}", hash.finalize());
28    let dir = crate::shared_scratch_root()
29        .join(crate::STABLE_BIN_DIR)
30        .join(key);
31    std::fs::create_dir_all(&dir).unwrap();
32    for (name, bytes) in files {
33        let path = dir.join(name);
34        let current = std::fs::metadata(&path)
35            .ok()
36            .filter(|m| m.permissions().mode() & 0o777 == 0o755)
37            .and_then(|_| std::fs::read(&path).ok());
38        if current.as_deref() == Some(*bytes) {
39            continue;
40        }
41        let nonce = NEXT.fetch_add(1, Ordering::Relaxed);
42        let input = dir.join(format!(".input-{}-{nonce}", std::process::id()));
43        let staging = dir.join(format!(".stage-{}-{nonce}", std::process::id()));
44        std::fs::write(&input, bytes).unwrap();
45        crate::checked_output(std::process::Command::new("cp").arg(&input).arg(&staging)).unwrap();
46        std::fs::remove_file(input).unwrap();
47        std::fs::set_permissions(&staging, std::fs::Permissions::from_mode(0o755)).unwrap();
48        std::fs::rename(staging, &path).unwrap();
49    }
50    dir
51}
52/// Single-file form of [`stable_executable_dir`].
53#[cfg(unix)]
54pub fn stable_executable(name: &str, contents: impl AsRef<[u8]>) -> PathBuf {
55    stable_executable_dir(&[(name, contents.as_ref())]).join(name)
56}
57
58#[cfg(all(test, unix))]
59mod tests {
60    use super::*;
61    #[test]
62    fn stable_executable_from_eight_threads_leaves_one_file_with_the_right_bytes() {
63        use std::os::unix::fs::{MetadataExt, PermissionsExt};
64        let body = format!(
65            "#!/bin/sh\n# {}-{:?}\nexit 0\n",
66            std::process::id(),
67            std::time::SystemTime::now()
68        );
69        let barrier = std::sync::Arc::new(std::sync::Barrier::new(8));
70        let threads: Vec<_> = (0..8)
71            .map(|_| {
72                let barrier = barrier.clone();
73                let body = body.clone();
74                std::thread::spawn(move || {
75                    barrier.wait();
76                    stable_executable("probe", body)
77                })
78            })
79            .collect();
80        let paths: Vec<_> = threads.into_iter().map(|t| t.join().unwrap()).collect();
81        assert!(paths.iter().all(|p| p == &paths[0]));
82        let path = &paths[0];
83        assert!(path.starts_with(crate::shared_scratch_root().join(crate::STABLE_BIN_DIR)));
84        assert_eq!(
85            std::fs::read_dir(path.parent().unwrap()).unwrap().count(),
86            1
87        );
88        assert_eq!(std::fs::read(path).unwrap(), body.as_bytes());
89        let meta = std::fs::metadata(path).unwrap();
90        assert_eq!(meta.permissions().mode() & 0o777, 0o755);
91        assert_eq!(stable_executable("probe", body), *path);
92        assert_eq!(std::fs::metadata(path).unwrap().ino(), meta.ino());
93    }
94}