1use ruff_notebook::{Notebook, NotebookError};
2use rustc_hash::FxHashMap;
3use std::panic::RefUnwindSafe;
4use std::sync::{Arc, Mutex};
5
6use crate::Db;
7use crate::files::File;
8use crate::system::{
9 CommandExecutor, DirectoryEntry, MemoryFileSystem, Metadata, Result, System, SystemPath,
10 SystemPathBuf, SystemVirtualPath, WhichError, WhichResult,
11};
12
13use super::WritableSystem;
14use super::walk_directory::WalkDirectoryBuilder;
15
16#[derive(Debug)]
24pub struct TestSystem {
25 inner: Arc<dyn WritableSystem + RefUnwindSafe + Send + Sync>,
26 env_overrides: Arc<Mutex<FxHashMap<String, Option<String>>>>,
29}
30
31impl Clone for TestSystem {
32 fn clone(&self) -> Self {
33 Self {
34 inner: self.inner.clone(),
35 env_overrides: self.env_overrides.clone(),
36 }
37 }
38}
39
40impl TestSystem {
41 pub fn new(inner: impl WritableSystem + RefUnwindSafe + Send + Sync + 'static) -> Self {
42 Self {
43 inner: Arc::new(inner),
44 env_overrides: Arc::new(Mutex::new(FxHashMap::default())),
45 }
46 }
47
48 pub fn set_env_var(&self, name: impl Into<String>, value: impl Into<String>) {
50 self.env_overrides
51 .lock()
52 .unwrap()
53 .insert(name.into(), Some(value.into()));
54 }
55
56 pub fn remove_env_var(&self, name: impl Into<String>) {
58 self.env_overrides.lock().unwrap().insert(name.into(), None);
59 }
60
61 pub fn in_memory(&self) -> &InMemorySystem {
66 self.as_in_memory()
67 .expect("The test db is not using a memory file system")
68 }
69
70 fn as_in_memory(&self) -> Option<&InMemorySystem> {
72 self.system().as_any().downcast_ref::<InMemorySystem>()
73 }
74
75 pub fn memory_file_system(&self) -> &MemoryFileSystem {
80 self.in_memory().fs()
81 }
82
83 fn use_system<S>(&mut self, system: S)
84 where
85 S: WritableSystem + Send + Sync + RefUnwindSafe + 'static,
86 {
87 self.inner = Arc::new(system);
88 }
89
90 fn system(&self) -> &dyn WritableSystem {
91 &*self.inner
92 }
93}
94
95impl System for TestSystem {
96 fn path_metadata(&self, path: &SystemPath) -> Result<Metadata> {
97 self.system().path_metadata(path)
98 }
99
100 fn canonicalize_path(&self, path: &SystemPath) -> Result<SystemPathBuf> {
101 self.system().canonicalize_path(path)
102 }
103
104 fn is_same_file(&self, first: &SystemPath, second: &SystemPath) -> Result<bool> {
105 self.system().is_same_file(first, second)
106 }
107
108 fn read_to_string(&self, path: &SystemPath) -> Result<String> {
109 self.system().read_to_string(path)
110 }
111
112 fn read_to_notebook(&self, path: &SystemPath) -> std::result::Result<Notebook, NotebookError> {
113 self.system().read_to_notebook(path)
114 }
115
116 fn read_virtual_path_to_string(&self, path: &SystemVirtualPath) -> Result<String> {
117 self.system().read_virtual_path_to_string(path)
118 }
119
120 fn read_virtual_path_to_notebook(
121 &self,
122 path: &SystemVirtualPath,
123 ) -> std::result::Result<Notebook, NotebookError> {
124 self.system().read_virtual_path_to_notebook(path)
125 }
126
127 fn current_directory(&self) -> &SystemPath {
128 self.system().current_directory()
129 }
130
131 fn user_config_directory(&self) -> Option<SystemPathBuf> {
132 self.system().user_config_directory()
133 }
134
135 fn cache_dir(&self) -> Option<SystemPathBuf> {
136 self.system().cache_dir()
137 }
138
139 fn which(&self, _name: &str) -> WhichResult {
140 Err(WhichError::CannotFindBinaryPath)
141 }
142
143 fn command_executor(&self) -> Option<&dyn CommandExecutor> {
144 self.system().command_executor()
145 }
146
147 fn read_directory<'a>(
148 &'a self,
149 path: &SystemPath,
150 ) -> Result<Box<dyn Iterator<Item = Result<DirectoryEntry>> + 'a>> {
151 self.system().read_directory(path)
152 }
153
154 fn walk_directory(&self, path: &SystemPath) -> WalkDirectoryBuilder {
155 self.system().walk_directory(path)
156 }
157
158 fn as_writable(&self) -> Option<&dyn WritableSystem> {
159 Some(self)
160 }
161
162 fn as_any(&self) -> &dyn std::any::Any {
163 self
164 }
165
166 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
167 self
168 }
169
170 fn env_var(&self, name: &str) -> std::result::Result<String, std::env::VarError> {
171 if let Some(override_value) = self.env_overrides.lock().unwrap().get(name) {
173 return match override_value {
174 Some(value) => Ok(value.clone()),
175 None => Err(std::env::VarError::NotPresent),
176 };
177 }
178 self.system().env_var(name)
180 }
181
182 fn dyn_clone(&self) -> Box<dyn System> {
183 Box::new(self.clone())
184 }
185}
186
187impl Default for TestSystem {
188 fn default() -> Self {
189 Self::new(InMemorySystem::default())
190 }
191}
192
193impl WritableSystem for TestSystem {
194 fn create_new_file(&self, path: &SystemPath) -> Result<()> {
195 self.system().create_new_file(path)
196 }
197
198 fn write_file_bytes(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
199 self.system().write_file_bytes(path, content)
200 }
201
202 fn create_directory_all(&self, path: &SystemPath) -> Result<()> {
203 self.system().create_directory_all(path)
204 }
205
206 fn dyn_clone(&self) -> Box<dyn WritableSystem> {
207 Box::new(self.clone())
208 }
209}
210
211pub trait DbWithWritableSystem: Db + Sized {
215 type System: WritableSystem;
216
217 fn writable_system(&self) -> &Self::System;
218
219 fn write_file(&mut self, path: impl AsRef<SystemPath>, content: impl AsRef<str>) -> Result<()> {
221 let path = path.as_ref();
222 match self.writable_system().write_file(path, content.as_ref()) {
223 Ok(()) => {
224 File::sync_path(self, path);
225 Ok(())
226 }
227 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
228 if let Some(parent) = path.parent() {
229 self.writable_system().create_directory_all(parent)?;
230
231 for ancestor in parent.ancestors() {
232 File::sync_path(self, ancestor);
233 }
234
235 self.writable_system().write_file(path, content.as_ref())?;
236 File::sync_path(self, path);
237
238 Ok(())
239 } else {
240 Err(error)
241 }
242 }
243 err => err,
244 }
245 }
246
247 fn write_dedented(&mut self, path: &str, content: &str) -> Result<()> {
249 self.write_file(path, ruff_python_trivia::textwrap::dedent(content))?;
250 Ok(())
251 }
252
253 fn write_files<P, C, I>(&mut self, files: I) -> Result<()>
255 where
256 I: IntoIterator<Item = (P, C)>,
257 P: AsRef<SystemPath>,
258 C: AsRef<str>,
259 {
260 for (path, content) in files {
261 self.write_file(path, content)?;
262 }
263
264 Ok(())
265 }
266}
267
268pub trait DbWithTestSystem: Db + Sized {
272 fn test_system(&self) -> &TestSystem;
273
274 fn test_system_mut(&mut self) -> &mut TestSystem;
275
276 fn write_virtual_file(
281 &mut self,
282 path: impl AsRef<SystemVirtualPath>,
283 content: impl AsRef<[u8]>,
284 ) {
285 let path = path.as_ref();
286 self.test_system()
287 .memory_file_system()
288 .write_virtual_file(path, content);
289 }
290
291 fn use_system<S>(&mut self, os: S)
297 where
298 S: WritableSystem + Send + Sync + RefUnwindSafe + 'static,
299 {
300 self.test_system_mut().use_system(os);
301 }
302
303 fn memory_file_system(&self) -> &MemoryFileSystem {
308 self.test_system().memory_file_system()
309 }
310}
311
312impl<T> DbWithWritableSystem for T
313where
314 T: DbWithTestSystem,
315{
316 type System = TestSystem;
317
318 fn writable_system(&self) -> &Self::System {
319 self.test_system()
320 }
321}
322
323#[derive(Clone, Default, Debug)]
324pub struct InMemorySystem {
325 user_config_directory: Arc<Mutex<Option<SystemPathBuf>>>,
326 memory_fs: MemoryFileSystem,
327}
328
329impl InMemorySystem {
330 pub fn from_memory_fs(memory_fs: MemoryFileSystem) -> Self {
331 Self {
332 user_config_directory: Mutex::new(None).into(),
333 memory_fs,
334 }
335 }
336
337 pub fn fs(&self) -> &MemoryFileSystem {
338 &self.memory_fs
339 }
340
341 pub fn set_user_configuration_directory(&self, directory: Option<SystemPathBuf>) {
342 let mut user_directory = self.user_config_directory.lock().unwrap();
343 *user_directory = directory;
344 }
345}
346
347impl System for InMemorySystem {
348 fn path_metadata(&self, path: &SystemPath) -> Result<Metadata> {
349 self.memory_fs.metadata(path)
350 }
351
352 fn canonicalize_path(&self, path: &SystemPath) -> Result<SystemPathBuf> {
353 self.memory_fs.canonicalize(path)
354 }
355
356 fn is_same_file(&self, first: &SystemPath, second: &SystemPath) -> Result<bool> {
357 Ok(self.canonicalize_path(first)? == self.canonicalize_path(second)?)
360 }
361
362 fn read_to_string(&self, path: &SystemPath) -> Result<String> {
363 self.memory_fs.read_to_string(path)
364 }
365
366 fn read_to_notebook(&self, path: &SystemPath) -> std::result::Result<Notebook, NotebookError> {
367 let content = self.read_to_string(path)?;
368 Notebook::from_source_code(&content)
369 }
370
371 fn read_virtual_path_to_string(&self, path: &SystemVirtualPath) -> Result<String> {
372 self.memory_fs.read_virtual_path_to_string(path)
373 }
374
375 fn read_virtual_path_to_notebook(
376 &self,
377 path: &SystemVirtualPath,
378 ) -> std::result::Result<Notebook, NotebookError> {
379 let content = self.read_virtual_path_to_string(path)?;
380 Notebook::from_source_code(&content)
381 }
382
383 fn current_directory(&self) -> &SystemPath {
384 self.memory_fs.current_directory()
385 }
386
387 fn user_config_directory(&self) -> Option<SystemPathBuf> {
388 self.user_config_directory.lock().unwrap().clone()
389 }
390
391 fn cache_dir(&self) -> Option<SystemPathBuf> {
392 None
393 }
394
395 fn which(&self, _name: &str) -> WhichResult {
396 Err(WhichError::CannotFindBinaryPath)
397 }
398
399 fn read_directory<'a>(
400 &'a self,
401 path: &SystemPath,
402 ) -> Result<Box<dyn Iterator<Item = Result<DirectoryEntry>> + 'a>> {
403 Ok(Box::new(self.memory_fs.read_directory(path)?))
404 }
405
406 fn walk_directory(&self, path: &SystemPath) -> WalkDirectoryBuilder {
407 self.memory_fs.walk_directory(path)
408 }
409
410 fn as_writable(&self) -> Option<&dyn WritableSystem> {
411 Some(self)
412 }
413
414 fn as_any(&self) -> &dyn std::any::Any {
415 self
416 }
417
418 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
419 self
420 }
421
422 fn dyn_clone(&self) -> Box<dyn System> {
423 Box::new(self.clone())
424 }
425}
426
427impl WritableSystem for InMemorySystem {
428 fn create_new_file(&self, path: &SystemPath) -> Result<()> {
429 self.memory_fs.create_new_file(path)
430 }
431
432 fn write_file_bytes(&self, path: &SystemPath, content: &[u8]) -> Result<()> {
433 self.memory_fs.write_file(path, content)
434 }
435
436 fn create_directory_all(&self, path: &SystemPath) -> Result<()> {
437 self.memory_fs.create_directory_all(path)
438 }
439
440 fn dyn_clone(&self) -> Box<dyn WritableSystem> {
441 Box::new(self.clone())
442 }
443}