scm_diff_editor/
testing.rs

1//! Testing utilities.
2use std::collections::{BTreeMap, BTreeSet};
3use std::io;
4use std::path::{Path, PathBuf};
5
6use scm_record::{File, FileMode};
7
8use crate::{Error, FileContents, FileInfo, Filesystem, Result};
9
10/// In-memory filesystem for testing purposes.
11#[derive(Debug)]
12pub struct TestFilesystem {
13    files: BTreeMap<PathBuf, FileInfo>,
14    dirs: BTreeSet<PathBuf>,
15}
16
17impl TestFilesystem {
18    /// Construct a new [`TestFilesystem`] with the provided set of files.
19    pub fn new(files: BTreeMap<PathBuf, FileInfo>) -> Self {
20        let dirs = files
21            .keys()
22            .flat_map(|path| path.ancestors().skip(1))
23            .map(|path| path.to_owned())
24            .collect();
25        Self { files, dirs }
26    }
27
28    fn assert_parent_dir_exists(&self, path: &Path) {
29        if let Some(parent_dir) = path.parent() {
30            assert!(
31                self.dirs.contains(parent_dir),
32                "parent dir for {path:?} does not exist"
33            );
34        }
35    }
36}
37
38impl Filesystem for TestFilesystem {
39    fn read_dir_diff_paths(&self, left: &Path, right: &Path) -> Result<BTreeSet<PathBuf>> {
40        let left_files = self
41            .files
42            .keys()
43            .filter_map(|path| path.strip_prefix(left).ok());
44        let right_files = self
45            .files
46            .keys()
47            .filter_map(|path| path.strip_prefix(right).ok());
48        Ok(left_files
49            .chain(right_files)
50            .map(|path| path.to_path_buf())
51            .collect())
52    }
53
54    fn read_file_info(&self, path: &Path) -> Result<FileInfo> {
55        match self.files.get(path) {
56            Some(file_info) => Ok(file_info.clone()),
57            None => match self.dirs.get(path) {
58                Some(_path) => Err(Error::ReadFile {
59                    path: path.to_owned(),
60                    source: io::Error::new(io::ErrorKind::Other, "is a directory"),
61                }),
62                None => Ok(FileInfo {
63                    file_mode: FileMode::Absent,
64                    contents: FileContents::Absent,
65                }),
66            },
67        }
68    }
69
70    fn write_file(&mut self, path: &Path, contents: &str) -> Result<()> {
71        self.assert_parent_dir_exists(path);
72        self.files.insert(path.to_owned(), file_info(contents));
73        Ok(())
74    }
75
76    fn copy_file(&mut self, old_path: &Path, new_path: &Path) -> Result<()> {
77        self.assert_parent_dir_exists(new_path);
78        let file_info = self.read_file_info(old_path)?;
79        self.files.insert(new_path.to_owned(), file_info);
80        Ok(())
81    }
82
83    fn remove_file(&mut self, path: &Path) -> Result<()> {
84        self.files.remove(path);
85        Ok(())
86    }
87
88    fn create_dir_all(&mut self, path: &Path) -> Result<()> {
89        self.dirs.insert(path.to_owned());
90        Ok(())
91    }
92}
93
94/// Helper function to create a `FileInfo` object containing the provided file
95/// contents and a default hash and file mode.
96pub fn file_info(contents: impl Into<String>) -> FileInfo {
97    let contents = contents.into();
98    let num_bytes = contents.len().try_into().unwrap();
99    FileInfo {
100        file_mode: FileMode::Unix(0o100644),
101        contents: FileContents::Text {
102            contents,
103            hash: "abc123".to_string(),
104            num_bytes,
105        },
106    }
107}
108
109/// Set all checkboxes in the UI.
110pub fn select_all(files: &mut [File]) {
111    for file in files {
112        file.set_checked(true);
113    }
114}