Skip to main content

csusage_test_support/
lib.rs

1use std::{
2    ffi::{OsStr, OsString},
3    fs,
4    path::{Path, PathBuf},
5    sync::{Mutex, MutexGuard},
6};
7
8use assert_fs::{
9    TempDir,
10    fixture::{ChildPath, FileWriteStr, PathChild, PathCreateDir},
11};
12
13pub mod claude_science;
14pub mod zcode;
15
16static ENV_LOCK: Mutex<()> = Mutex::new(());
17
18fn env_lock() -> MutexGuard<'static, ()> {
19    ENV_LOCK.lock().unwrap_or_else(|error| error.into_inner())
20}
21
22pub struct EnvVarGuard {
23    key: &'static str,
24    previous: Option<OsString>,
25    _guard: MutexGuard<'static, ()>,
26}
27
28impl EnvVarGuard {
29    pub fn set(key: &'static str, value: impl AsRef<OsStr>) -> Self {
30        let guard = env_lock();
31        let previous = std::env::var_os(key);
32        unsafe { std::env::set_var(key, value) };
33        Self {
34            key,
35            previous,
36            _guard: guard,
37        }
38    }
39}
40
41impl Drop for EnvVarGuard {
42    fn drop(&mut self) {
43        match self.previous.take() {
44            Some(value) => unsafe { std::env::set_var(self.key, value) },
45            None => unsafe { std::env::remove_var(self.key) },
46        }
47    }
48}
49
50pub struct EnvVarsGuard {
51    previous: Vec<(&'static str, Option<OsString>)>,
52    _guard: MutexGuard<'static, ()>,
53}
54
55impl EnvVarsGuard {
56    pub fn set_many(vars: impl IntoIterator<Item = (&'static str, Option<OsString>)>) -> Self {
57        let guard = env_lock();
58        let mut previous = Vec::new();
59        for (key, value) in vars {
60            previous.push((key, std::env::var_os(key)));
61            match value {
62                Some(value) => unsafe { std::env::set_var(key, value) },
63                None => unsafe { std::env::remove_var(key) },
64            }
65        }
66        Self {
67            previous,
68            _guard: guard,
69        }
70    }
71}
72
73impl Drop for EnvVarsGuard {
74    fn drop(&mut self) {
75        for (key, value) in self.previous.drain(..).rev() {
76            match value {
77                Some(value) => unsafe { std::env::set_var(key, value) },
78                None => unsafe { std::env::remove_var(key) },
79            }
80        }
81    }
82}
83
84pub struct Fixture {
85    dir: TempDir,
86}
87
88impl Fixture {
89    pub fn new() -> Self {
90        Self {
91            dir: TempDir::new().expect("failed to create temporary fixture directory"),
92        }
93    }
94
95    pub fn root(&self) -> &Path {
96        self.dir.path()
97    }
98
99    #[must_use]
100    pub fn path(&self, path: impl AsRef<Path>) -> PathBuf {
101        self.dir.path().join(path)
102    }
103
104    fn child(&self, path: impl AsRef<Path>) -> ChildPath {
105        self.dir.child(path)
106    }
107
108    #[must_use]
109    pub fn write_file(&self, path: impl AsRef<Path>, contents: impl AsRef<str>) -> PathBuf {
110        let child = self.child(path);
111        if let Some(parent) = child.path().parent() {
112            fs::create_dir_all(parent).expect("failed to create fixture file parent directory");
113        }
114        child
115            .write_str(contents.as_ref())
116            .expect("failed to write fixture file");
117        child.path().to_path_buf()
118    }
119
120    #[must_use]
121    pub fn create_dir_all(&self, path: impl AsRef<Path>) -> PathBuf {
122        let child = self.child(path);
123        child
124            .create_dir_all()
125            .expect("failed to create fixture directory");
126        child.path().to_path_buf()
127    }
128}
129
130impl Default for Fixture {
131    fn default() -> Self {
132        Self::new()
133    }
134}
135
136#[macro_export]
137macro_rules! fs_fixture {
138    ({ $($path:literal : $contents:expr_2021),* $(,)? }) => {{
139        let fixture = $crate::Fixture::new();
140        $(
141            let _ = fixture.write_file($path, $contents);
142        )*
143        fixture
144    }};
145}
146
147#[cfg(test)]
148mod tests {
149    #[test]
150    fn creates_inline_fixture_tree() {
151        let fixture = fs_fixture!({
152            "projects/example/session.jsonl": "{}\n",
153        });
154
155        assert_eq!(
156            std::fs::read_to_string(fixture.path("projects/example/session.jsonl")).unwrap(),
157            "{}\n"
158        );
159    }
160
161    #[test]
162    fn creates_incremental_fixture_tree() {
163        let fixture = fs_fixture!({});
164        let _ = fixture.write_file("projects/example/session/chat.jsonl", "{}\n");
165
166        assert!(
167            fixture
168                .path("projects/example/session/chat.jsonl")
169                .is_file()
170        );
171    }
172}