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}