speculoos/
path.rs

1use super::{AssertionFailure, DescriptiveSpec, Spec};
2
3use std::borrow::Borrow;
4use std::path::Path;
5
6pub trait PathAssertions {
7    #[track_caller]
8    fn exists(&mut self);
9    #[track_caller]
10    fn does_not_exist(&mut self);
11    #[track_caller]
12    fn is_a_file(&mut self);
13    #[track_caller]
14    fn is_a_directory(&mut self);
15    #[track_caller]
16    fn has_file_name<'r, E: Borrow<&'r str>>(&mut self, expected_file_name: E);
17}
18
19impl<T> PathAssertions for Spec<'_, T>
20where
21    T: AsRef<Path>,
22{
23    /// Asserts that the subject `Path` refers to an existing location.
24    ///
25    /// ```rust, ignore
26    /// assert_that(&Path::new("/tmp/file")).exists();
27    /// ```
28    fn exists(&mut self) {
29        exists(self.subject.as_ref(), self)
30    }
31
32    /// Asserts that the subject `Path` does not refer to an existing location.
33    ///
34    /// ```rust
35    /// # use speculoos::prelude::*;
36    /// # use std::path::Path;
37    /// assert_that(&Path::new("/tmp/file")).does_not_exist();
38    /// ```
39    fn does_not_exist(&mut self) {
40        does_not_exist(self.subject.as_ref(), self)
41    }
42
43    /// Asserts that the subject `Path` refers to an existing file.
44    ///
45    /// ```rust, ignore
46    /// assert_that(&Path::new("/tmp/file")).is_a_file();
47    /// ```
48    fn is_a_file(&mut self) {
49        is_a_file(self.subject.as_ref(), self)
50    }
51
52    /// Asserts that the subject `Path` refers to an existing directory.
53    ///
54    /// ```rust, ignore
55    /// assert_that(&Path::new("/tmp/dir/")).is_a_directory();
56    /// ```
57    fn is_a_directory(&mut self) {
58        is_a_directory(self.subject.as_ref(), self)
59    }
60
61    /// Asserts that the subject `Path` has the expected file name.
62    ///
63    /// ```rust
64    /// # use speculoos::prelude::*;
65    /// # use std::path::Path;
66    /// assert_that(&Path::new("/tmp/file")).has_file_name(&"file");
67    /// ```
68    fn has_file_name<'r, E: Borrow<&'r str>>(&mut self, expected_file_name: E) {
69        has_file_name(self.subject.as_ref(), expected_file_name.borrow(), self)
70    }
71}
72
73fn exists<'s, S: DescriptiveSpec<'s>>(subject: &Path, spec: &'s S) {
74    if !subject.exists() {
75        AssertionFailure::from_spec(spec)
76            .with_expected(format!("Path of <{:?}> to exist", subject))
77            .with_actual("a non-existent Path".to_string())
78            .fail();
79    }
80}
81
82fn does_not_exist<'s, S: DescriptiveSpec<'s>>(subject: &Path, spec: &'s S) {
83    if subject.exists() {
84        AssertionFailure::from_spec(spec)
85            .with_expected(format!("Path of <{:?}> to not exist", subject))
86            .with_actual("a resolvable Path".to_string())
87            .fail();
88    }
89}
90
91fn is_a_file<'s, S: DescriptiveSpec<'s>>(subject: &Path, spec: &'s S) {
92    if !subject.is_file() {
93        AssertionFailure::from_spec(spec)
94            .with_expected(format!("Path of <{:?}> to be a file", subject))
95            .with_actual("not a resolvable file".to_string())
96            .fail();
97    }
98}
99
100fn is_a_directory<'s, S: DescriptiveSpec<'s>>(subject: &Path, spec: &'s S) {
101    if !subject.is_dir() {
102        AssertionFailure::from_spec(spec)
103            .with_expected(format!("Path of <{:?}> to be a directory", subject))
104            .with_actual("not a resolvable directory".to_string())
105            .fail();
106    }
107}
108
109fn has_file_name<'s, S: DescriptiveSpec<'s>>(
110    subject: &Path,
111    expected_file_name: &str,
112    spec: &'s S,
113) {
114    let subject_file_name = match subject.file_name() {
115        Some(os_string) => match os_string.to_str() {
116            Some(val) => val,
117            None => {
118                fail_from_file_name(
119                    spec,
120                    expected_file_name,
121                    "an invalid UTF-8 file name".to_string(),
122                );
123                unreachable!();
124            }
125        },
126        None => {
127            fail_from_file_name(
128                spec,
129                expected_file_name,
130                format!("a non-resolvable path <{:?}>", subject),
131            );
132            unreachable!();
133        }
134    };
135
136    if !subject_file_name.eq(expected_file_name) {
137        fail_from_file_name(spec, expected_file_name, format!("<{}>", subject_file_name));
138    }
139}
140
141fn fail_from_file_name<'s, S: DescriptiveSpec<'s>>(spec: &'s S, expected: &str, actual: String) {
142    AssertionFailure::from_spec(spec)
143        .with_expected(build_file_name_message(expected))
144        .with_actual(actual)
145        .fail();
146}
147
148fn build_file_name_message(file_name: &str) -> String {
149    format!("Path with file name of <{}>", file_name)
150}
151
152#[cfg(test)]
153mod tests {
154    #![allow(clippy::needless_borrows_for_generic_args)]
155    use super::super::prelude::*;
156
157    use std::path::{Path, PathBuf};
158
159    static MANIFEST_PATH: &str = env!("CARGO_MANIFEST_DIR");
160
161    #[test]
162    fn should_accept_any_path_reference() {
163        assert_that(&Path::new(MANIFEST_PATH)).exists();
164        assert_that(&PathBuf::from(MANIFEST_PATH)).exists();
165        assert_that(&MANIFEST_PATH).exists();
166    }
167
168    #[test]
169    pub fn should_not_panic_if_path_exists() {
170        assert_that(&Path::new(MANIFEST_PATH)).exists();
171    }
172
173    #[test]
174    // It's unfortunately a bit hard to expect a message without knowing the manifest path
175    #[should_panic]
176    pub fn should_panic_if_path_does_not_exist() {
177        let failing_path = MANIFEST_PATH.to_string() + "/does-not-exist";
178        assert_that(&Path::new(&failing_path)).exists();
179    }
180
181    #[test]
182    pub fn should_not_panic_if_path_represents_a_directory() {
183        assert_that(&Path::new(MANIFEST_PATH)).is_a_directory();
184    }
185
186    #[test]
187    pub fn should_not_panic_if_path_does_not_exist_when_expected() {
188        let failing_path = MANIFEST_PATH.to_string() + "/does-not-exist";
189        assert_that(&Path::new(&failing_path)).does_not_exist();
190    }
191
192    #[test]
193    // It's unfortunately a bit hard to expect a message without knowing the manifest path
194    #[should_panic]
195    pub fn should_panic_if_path_exists_when_not_expected() {
196        assert_that(&Path::new(MANIFEST_PATH)).does_not_exist();
197    }
198
199    #[test]
200    // It's unfortunately a bit hard to expect a message without knowing the manifest path
201    #[should_panic]
202    pub fn should_panic_if_path_does_not_represent_a_directory() {
203        let path = MANIFEST_PATH.to_string() + "/Cargo.toml";
204        assert_that(&Path::new(&path)).is_a_directory();
205    }
206
207    #[test]
208    pub fn should_not_panic_if_path_represents_a_file() {
209        let path = MANIFEST_PATH.to_string() + "/Cargo.toml";
210        assert_that(&Path::new(&path)).is_a_file();
211    }
212
213    #[test]
214    // It's unfortunately a bit hard to expect a message without knowing the manifest path
215    #[should_panic]
216    pub fn should_panic_if_path_does_not_represent_a_file() {
217        assert_that(&Path::new(&MANIFEST_PATH)).is_a_file();
218    }
219
220    #[test]
221    pub fn has_file_name_should_allow_multiple_borrow_forms_for_path() {
222        let path = MANIFEST_PATH.to_string() + "/Cargo.toml";
223        assert_that(&Path::new(&path)).has_file_name("Cargo.toml");
224        assert_that(&Path::new(&path)).has_file_name(&mut "Cargo.toml");
225        assert_that(&Path::new(&path)).has_file_name(&"Cargo.toml");
226    }
227
228    #[test]
229    pub fn should_not_panic_if_path_has_correct_file_name() {
230        let path = MANIFEST_PATH.to_string() + "/Cargo.toml";
231        assert_that(&Path::new(&path)).has_file_name(&"Cargo.toml");
232    }
233
234    #[test]
235    // It's unfortunately a bit hard to expect a message without knowing the manifest path
236    #[should_panic]
237    pub fn should_panic_if_path_does_not_have_correct_file_name() {
238        let path = MANIFEST_PATH.to_string() + "/Cargo.toml";
239        assert_that(&Path::new(&path)).has_file_name(&"pom.xml");
240    }
241
242    #[test]
243    // It's unfortunately a bit hard to expect a message without knowing the manifest path
244    #[should_panic]
245    pub fn should_panic_if_path_does_not_have_a_file_name() {
246        let path = MANIFEST_PATH.to_string() + "/..";
247        assert_that(&Path::new(&path)).has_file_name(&"pom.xml");
248    }
249
250    #[test]
251    pub fn should_not_panic_if_pathbuf_exists() {
252        assert_that(&PathBuf::from(MANIFEST_PATH)).exists();
253    }
254
255    #[test]
256    // It's unfortunately a bit hard to expect a message without knowing the manifest path
257    #[should_panic]
258    pub fn should_panic_if_pathbuf_does_not_exist() {
259        let failing_path = MANIFEST_PATH.to_string() + "/does-not-exist";
260        assert_that(&PathBuf::from(&failing_path)).exists();
261    }
262
263    #[test]
264    pub fn should_not_panic_if_pathbuf_represents_a_directory() {
265        assert_that(&PathBuf::from(MANIFEST_PATH)).is_a_directory();
266    }
267
268    #[test]
269    pub fn should_not_panic_if_pathbuf_does_not_exist_when_expected() {
270        let failing_path = MANIFEST_PATH.to_string() + "/does-not-exist";
271        assert_that(&PathBuf::from(&failing_path)).does_not_exist();
272    }
273
274    #[test]
275    // It's unfortunately a bit hard to expect a message without knowing the manifest path
276    #[should_panic]
277    pub fn should_panic_if_pathbuf_exists_when_not_expected() {
278        assert_that(&PathBuf::from(MANIFEST_PATH)).does_not_exist();
279    }
280
281    #[test]
282    // It's unfortunately a bit hard to expect a message without knowing the manifest path
283    #[should_panic]
284    pub fn should_panic_if_pathbuf_does_not_represent_a_directory() {
285        let path = MANIFEST_PATH.to_string() + "/Cargo.toml";
286        assert_that(&PathBuf::from(&path)).is_a_directory();
287    }
288
289    #[test]
290    pub fn should_not_panic_if_pathbuf_represents_a_file() {
291        let path = MANIFEST_PATH.to_string() + "/Cargo.toml";
292        assert_that(&PathBuf::from(&path)).is_a_file();
293    }
294
295    #[test]
296    // It's unfortunately a bit hard to expect a message without knowing the manifest path
297    #[should_panic]
298    pub fn should_panic_if_pathbuf_does_not_represent_a_file() {
299        assert_that(&PathBuf::from(&MANIFEST_PATH)).is_a_file();
300    }
301
302    #[test]
303    pub fn has_file_name_should_allow_multiple_borrow_forms_for_pathbuf() {
304        let path = MANIFEST_PATH.to_string() + "/Cargo.toml";
305        assert_that(&PathBuf::from(&path)).has_file_name("Cargo.toml");
306        assert_that(&PathBuf::from(&path)).has_file_name(&mut "Cargo.toml");
307        assert_that(&PathBuf::from(&path)).has_file_name(&"Cargo.toml");
308    }
309
310    #[test]
311    pub fn should_not_panic_if_pathbuf_has_correct_file_name() {
312        let path = MANIFEST_PATH.to_string() + "/Cargo.toml";
313        assert_that(&PathBuf::from(&path)).has_file_name(&"Cargo.toml");
314    }
315
316    #[test]
317    // It's unfortunately a bit hard to expect a message without knowing the manifest path
318    #[should_panic]
319    pub fn should_panic_if_pathbuf_does_not_have_correct_file_name() {
320        let path = MANIFEST_PATH.to_string() + "/Cargo.toml";
321        assert_that(&PathBuf::from(&path)).has_file_name(&"pom.xml");
322    }
323
324    #[test]
325    // It's unfortunately a bit hard to expect a message without knowing the manifest path
326    #[should_panic]
327    pub fn should_panic_if_pathbuf_does_not_have_a_file_name() {
328        let path = MANIFEST_PATH.to_string() + "/..";
329        assert_that(&PathBuf::from(&path)).has_file_name(&"pom.xml");
330    }
331}