common_testing/setup.rs
1use once_cell::sync::Lazy;
2use std::cell::RefCell;
3use std::fs::{File, OpenOptions};
4use std::io::{BufReader, BufWriter, Read, Result, Write};
5use std::path::Path;
6use std::rc::Rc;
7use std::sync::{Mutex, MutexGuard};
8
9static SEQUENTIAL: Lazy<Mutex<()>> = Lazy::new(Mutex::default);
10
11/// Allow tests with side-effects to run without interfering with each other. The
12/// lock is released when the MutexGuard variable goes out of scope. Will ignore
13/// poison errors from other tests so that our test can continue even if theirs fails.
14///
15/// Use this when working with global variables, OS calls, the file system, or other
16/// shared resources.
17///
18/// # Example
19/// ```
20/// use common_testing::setup;
21///
22/// #[test]
23/// fn test_1() {
24/// let _lock = setup::sequential();
25/// // test code
26/// }
27///
28/// #[test]
29/// fn test_2() {
30/// let _lock = setup::sequential();
31/// // test code
32/// }
33/// ```
34///
35/// # See Also
36///
37/// [std::sync::Mutex::lock](https://doc.rust-lang.org/std/sync/struct.Mutex.html#method.lock)
38///
39/// [std::sync::PoisonError](https://doc.rust-lang.org/std/sync/struct.PoisonError.html)
40///
41pub fn sequential<'a>() -> MutexGuard<'a, ()> {
42 // If another test panics while holding the lock, the lock will be poisoned.
43 // We ignore the poison error and return the lock anyway so we can continue
44 // with other tests.
45 SEQUENTIAL.lock().unwrap_or_else(|e| e.into_inner())
46}
47
48/// Get an empty vector wrapped in an Rc<RefCell<>>.
49///
50/// Use to avoid random dependencies in test files for rare test cases.
51///
52/// # Example
53///
54/// ```
55/// use common_testing::setup;
56///
57/// #[test]
58/// fn test_1() {
59/// let vec = setup::get_rc_ref_cell_empty_vec::<u8>();
60/// // test code
61/// }
62/// ```
63pub fn get_rc_ref_cell_empty_vec<T>() -> Rc<RefCell<std::vec::Vec<T>>> {
64 Rc::new(RefCell::new(vec![]))
65}
66
67/// Get a read-only file handle. Use for fixtures you never want to change.
68///
69/// Prefer setup::get_file_contents() when you need to compare the contents
70/// of a file. Prefer setup::get_reader_for_file() when you need a BufReader.
71///
72/// Use to avoid random dependencies in test files for rare test cases.
73///
74/// # Example
75///
76/// ```
77/// use common_testing::setup;
78///
79/// #[test]
80/// fn test_1() {
81/// let mut file = setup::get_read_only_file("./fixtures/test.txt").unwrap();
82/// // some test code
83/// }
84/// ```
85pub fn get_read_only_file(path: &str) -> Result<File> {
86 OpenOptions::new().read(true).open(path)
87}
88
89/// Get a BufReader for a file. Use for fixtures you never want to change.
90///
91/// Prefer setup::get_file_contents() when you need to compare the contents
92/// of a file. Prefer setup::get_read_only_file() when you need a File handle.
93///
94/// Use to avoid random dependencies in test files for rare test cases.
95///
96/// # Example
97///
98/// ```
99/// use common_testing::setup;
100///
101/// #[test]
102/// fn test_1() {
103/// let mut reader = setup::get_reader_for_file("./fixtures/test.txt").unwrap();
104/// // some test code
105/// }
106/// ```
107pub fn get_reader_for_file(path: &str) -> Result<BufReader<File>> {
108 let file: File = get_read_only_file(path)?;
109 Ok(BufReader::new(file))
110}
111
112/// Read the contents of a file into a vector of bytes. Use for fixtures you
113/// never want to change.
114///
115/// Prefer this over get_reader_for_file() or get_read_only_file() when you
116/// need to compare the contents of a file.
117///
118/// # Example
119///
120/// ```
121/// use common_testing::setup;
122///
123/// #[test]
124/// fn test_1() {
125/// let contents = setup::get_file_contents("./fixtures/test.txt").unwrap();
126/// // some test code
127/// }
128/// ```
129pub fn get_file_contents(path: &str) -> Result<Vec<u8>> {
130 let mut buf = Vec::new();
131 get_reader_for_file(path)?.read_to_end(&mut buf)?;
132 Ok(buf.to_owned())
133}
134
135/// Get a read and write file handle. Use this to create temporary files for
136/// testing and comparison. The file will be created if it does not exist, and
137/// it will be overridden if it does.
138///
139/// Prefer this function if you are testing dynamic content being written to a
140/// file during the test. Prefer setup::get_file_contents() when you need to
141/// get the contents of a file to load a fixture or large data. Prefer
142/// assert::equal_file_contents() when you need to compare the contents of a
143/// file as the result of a test.
144///
145/// # Example
146///
147/// ```
148/// use common_testing::setup;
149///
150/// #[test]
151/// fn test_1() {
152/// let mut file = setup::get_read_and_write_file("./test.txt").unwrap();
153/// // some test code
154/// }
155/// ```
156pub fn get_read_and_write_file(path: &str) -> Result<File> {
157 remove_file(path)?;
158 let file = OpenOptions::new()
159 .write(true)
160 .create(true)
161 .truncate(false)
162 .read(true)
163 .open(path)?;
164
165 Ok(file)
166}
167
168/// Get a writer for a file, creating the file if it does not exist.
169/// Use this to create temporary files for testing and comparison.
170///
171/// Prefer this function if you are testing dynamic content being written to a
172/// file during the test, and remember to call flush() when you are done. Prefer
173/// setup::write_file_contents() when you need to create content in a file for
174/// the purpose of a test.
175///
176/// # Example
177///
178/// ```
179/// use common_testing::setup;
180///
181/// #[test]
182/// fn test_1() {
183/// let mut writer = setup::get_writer_for_file("./test.txt").unwrap();
184/// // some test code
185/// }
186pub fn get_writer_for_file(path: &str) -> Result<BufWriter<File>> {
187 let file: File = get_read_and_write_file(path)?;
188 Ok(BufWriter::new(file))
189}
190
191/// Write bytes to a file, creating the file if it does not exist.
192///
193/// Prefer this function if you are creating file content for the purpose of a
194/// test. Prefer setup::get_writer_for_file() when you are testing the act
195/// of writing to a file.
196///
197/// # Example
198///
199/// ```
200/// use common_testing::setup;
201///
202/// #[test]
203/// fn test_1() {
204/// setup::write_file_contents("./test.txt", &[1, 2, 3]).unwrap();
205/// // some test code
206/// }
207/// ```
208pub fn write_file_contents(path: &str, contents: &[u8]) -> Result<()> {
209 let file: File = get_read_and_write_file(path)?;
210 BufWriter::new(file).write_all(contents)
211}
212
213/// Create a directory path if it does not exist. Will not throw an error if the
214/// directory already exists. Use to guarantee the filesystem state before a test
215/// runs.
216///
217/// # Example
218///
219/// ```
220/// use common_testing::setup;
221///
222/// #[test]
223/// fn test_1() {
224/// setup::create_dir_all("./.tmp/tests/test_1").unwrap();
225/// setup::write_file_contents("./.tmp/tests/test_1/test.txt", &[1, 2, 3]).unwrap();
226/// // some test code
227/// }
228/// ```
229pub fn create_dir_all(path_dir: &str) -> Result<()> {
230 if !Path::new(path_dir).is_dir() {
231 std::fs::create_dir_all(path_dir)?;
232 }
233 Ok(())
234}
235
236/// Remove a file if it exists. Will not throw an error if the file does not exist.
237///
238/// Use to clean up temporary files created during a test. Prefer calling this
239/// function at the beginning of a test to ensure filesystem state is clean and to
240/// make debugging easier.
241///
242/// # Example
243///
244/// ```
245/// use common_testing::setup;
246///
247/// #[test]
248/// fn test_1() {
249/// setup::remove_file("./test.txt").unwrap();
250/// // some test code
251/// setup::write_file_contents("./test.txt", &[1, 2, 3]).unwrap();
252/// // some more test code
253/// }
254/// ```
255pub fn remove_file(file_path: &str) -> Result<()> {
256 if Path::new(file_path).is_file() {
257 std::fs::remove_file(file_path)?
258 }
259 Ok(())
260}
261
262#[cfg(test)]
263mod tests {
264 use std::io::Seek;
265
266 use super::*;
267
268 #[test]
269 fn test_sequential_no_error() {
270 let _lock = sequential();
271 }
272
273 #[test]
274 fn test_get_rc_ref_cell_empty_vec() {
275 let _lock = sequential();
276 let vec = get_rc_ref_cell_empty_vec::<u8>();
277 assert_eq!(vec.borrow().len(), 0);
278 }
279
280 #[test]
281 fn test_get_read_only_file() {
282 let _lock = sequential();
283 let mut file = get_read_only_file("./fixtures/test.txt").unwrap();
284 let mut contents = String::new();
285 file.read_to_string(&mut contents).unwrap();
286 assert_eq!(contents, "some file content\n");
287 }
288
289 #[test]
290 fn test_get_reader_for_file() {
291 let _lock = sequential();
292 let mut reader = get_reader_for_file("./fixtures/test.txt").unwrap();
293 let mut contents = String::new();
294 reader.read_to_string(&mut contents).unwrap();
295 assert_eq!(contents, "some file content\n");
296 }
297
298 #[test]
299 fn test_get_file_contents() {
300 let _lock = sequential();
301 let contents = get_file_contents("./fixtures/test.txt").unwrap();
302 assert_eq!(contents, b"some file content\n");
303 }
304
305 #[test]
306 fn test_get_read_and_write_file() {
307 let _lock = sequential();
308 let mut file = get_read_and_write_file("./test.txt").unwrap();
309
310 // write some content to the file
311 file.write_all(b"test\n").unwrap();
312 file.flush().unwrap();
313
314 // read the content back
315 let mut contents = String::new();
316 file.seek(std::io::SeekFrom::Start(0)).unwrap();
317 file.read_to_string(&mut contents).unwrap();
318 assert_eq!(contents, "test\n");
319 }
320
321 #[test]
322 fn test_get_writer_for_file() {
323 let _lock = sequential();
324 let mut writer = get_writer_for_file("./test.txt").unwrap();
325 writer.write_all(b"test\n").unwrap();
326 writer.flush().unwrap();
327 let mut file = get_read_only_file("./test.txt").unwrap();
328 let mut contents = String::new();
329 file.read_to_string(&mut contents).unwrap();
330 assert_eq!(contents, "test\n");
331 }
332
333 #[test]
334 fn test_write_file_contents() {
335 let _lock = sequential();
336 write_file_contents("./test.txt", b"test\n").unwrap();
337 let mut file = get_read_only_file("./test.txt").unwrap();
338 let mut contents = String::new();
339 file.read_to_string(&mut contents).unwrap();
340 assert_eq!(contents, "test\n");
341 }
342}