testdata_rt/
fixtures.rs

1use std::env;
2use std::fs;
3use std::io;
4use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
5use std::path::{Path, PathBuf};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
8pub struct TestFile {
9    pub paths: Vec<PathBuf>,
10}
11
12impl TestFile {
13    pub fn raw_read(&self) -> Vec<u8> {
14        self.try_raw_read().unwrap()
15    }
16
17    pub fn raw_read_opt(&self) -> Option<Vec<u8>> {
18        match self.try_raw_read() {
19            Err(e) if e.kind() == io::ErrorKind::NotFound => None,
20            result => Some(result.unwrap()),
21        }
22    }
23
24    pub fn try_raw_read(&self) -> io::Result<Vec<u8>> {
25        let mut first_error = None;
26        for path in &self.paths {
27            match fs::read(path) {
28                Err(e) if e.kind() == io::ErrorKind::NotFound => {
29                    if first_error.is_none() {
30                        first_error = Some(e);
31                    }
32                    continue;
33                }
34                result => return result,
35            }
36        }
37        if let Some(first_error) = first_error {
38            Err(first_error)
39        } else {
40            panic!("TestFile.paths is empty");
41        }
42    }
43
44    pub fn remove(&self) {
45        self.try_remove().unwrap();
46    }
47
48    pub fn try_remove(&self) -> io::Result<()> {
49        fs::remove_file(self.path_for_writing())?;
50        for path in &self.paths[1..] {
51            if path.exists() {
52                return Err(io::Error::new(
53                    io::ErrorKind::PermissionDenied,
54                    format!("Cannot remove readonly test file: {}", path.display()),
55                ));
56            }
57        }
58        Ok(())
59    }
60
61    pub fn raw_write(&self, contents: &[u8]) {
62        self.try_raw_write(contents).unwrap();
63    }
64
65    pub fn try_raw_write(&self, contents: &[u8]) -> io::Result<()> {
66        let path = self.path_for_writing();
67        if let Some(parent) = path.parent() {
68            fs::create_dir_all(parent)?;
69        }
70        fs::write(path, contents)
71    }
72
73    pub fn exists(&self) -> bool {
74        self.paths.iter().any(|path| path.exists())
75    }
76
77    pub fn path(&self) -> Option<&Path> {
78        self.paths
79            .iter()
80            .map(|path| &**path)
81            .find(|&path| path.exists())
82    }
83
84    pub fn path_for_writing(&self) -> &Path {
85        self.paths.first().expect("TestFile.paths is empty")
86    }
87}
88
89pub fn pending<F>(test_file: &TestFile, f: F)
90where
91    F: FnOnce(),
92{
93    let update_pending = env::var_os("UPDATE_PENDING").unwrap_or_default() == "true";
94    let result = catch_unwind(AssertUnwindSafe(f));
95    let actual = result.as_ref().copied().map_err(|e| {
96        if let Some(e) = e.downcast_ref::<String>() {
97            &e[..]
98        } else if let Some(&e) = e.downcast_ref::<&'static str>() {
99            e
100        } else {
101            "Box<Any>"
102        }
103        .to_owned()
104    });
105    let expected = if let Some(s) = test_file.raw_read_opt() {
106        Err(String::from_utf8_lossy(&s).trim_end().to_owned())
107    } else {
108        Ok(())
109    };
110    let ok = match (&expected, &actual) {
111        (Ok(_), Ok(_)) => true,
112        (Err(expected), Err(actual)) => actual.contains(expected),
113        (_, _) => false,
114    };
115    if ok {
116        // do nothing
117    } else if update_pending {
118        match &actual {
119            Ok(_) => test_file.remove(),
120            Err(e) => {
121                let e = if e.is_empty() || e.ends_with("\n") {
122                    e.to_owned()
123                } else {
124                    format!("{}\n", e)
125                };
126                test_file.raw_write(e.as_bytes());
127            }
128        }
129    } else {
130        match result {
131            Ok(()) => panic!(
132                "Expected the test to panic (pending):\n{}",
133                expected.as_ref().unwrap_err()
134            ),
135            Err(e) => resume_unwind(e),
136        }
137    }
138}