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 } 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}