il2_test_utils/testdir/mod.rs
1/*
2 * BSD 3-Clause License
3 *
4 * Copyright (c) 2019-2020, InterlockLedger Network
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * * Redistributions of source code must retain the above copyright notice, this
11 * list of conditions and the following disclaimer.
12 *
13 * * Redistributions in binary form must reproduce the above copyright notice,
14 * this list of conditions and the following disclaimer in the documentation
15 * and/or other materials provided with the distribution.
16 *
17 * * Neither the name of the copyright holder nor the names of its
18 * contributors may be used to endorse or promote products derived from
19 * this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32//! This module contains utilities that helps the usage of a test directory by
33//! unit-tests.
34#[cfg(test)]
35mod tests;
36
37use std::ffi::OsString;
38use std::fs::{create_dir_all, read, read_dir, remove_dir_all, remove_file, write};
39use std::io::Result;
40use std::path::Path;
41
42//=============================================================================
43// TestDirUtils
44//-----------------------------------------------------------------------------
45/// This struct implements a set of utilities that helps with the management of
46/// test files used inside the unit tests.
47///
48/// By default, it creates the test files inside a directory called
49/// "test_dir.tmp". It is recommended to add this directory to the ignore list
50/// of your version control system in order to prevent the addition of the
51/// test files into the version control by accident.
52///
53/// Since *Rust* runs unit-tests using multiple threads, this instance will
54/// create a unique subdirectory for each instance. By default, this
55/// subdirectory is deleted when the instance goes out of scope.
56pub struct TestDirUtils {
57 test_dir: OsString,
58 delete_on_terminate: bool,
59}
60
61impl TestDirUtils {
62 /// Directory to be used by the unit tests. Its is always "test_dir.tmp".
63 pub const DEFAULT_TEST_DIR: &'static str = "test_dir.tmp";
64
65 /// Creates a new `TestDirUtils` with the default name.
66 /// It will automatically create the test directory if it does not exist.
67 /// If the default path points to a file or a symlink, it will be deleted
68 /// and recreated as a directory.
69 ///
70 /// Returns the new instance of an error if the test directory is invalid
71 /// or cannot be created.
72 pub fn new(name: &str) -> Result<Self> {
73 Self::with_root(Path::new(Self::DEFAULT_TEST_DIR), name)
74 }
75
76 /// Creates a new `TestDirUtils`. It will automatically create
77 /// the test directory if it does not exist. If the path points to a file or a
78 /// symlink, it will be deleted and recreated as a directory.
79 ///
80 /// As a safeguard, this constructor will panic if `test_dir` points to a root
81 /// or a prefix (see [`std::path::Path::parent()`] for further details about how
82 /// the root is detected).
83 ///
84 /// Arguments:
85 /// - `test_dir`: The path to the test directory;
86 ///
87 /// Returns the new instance of an error if the test directory is invalid
88 /// or cannot be created.
89 pub fn with_root(test_root: &Path, name: &str) -> Result<Self> {
90 let unique_test_dir = Self::create_unique_name_for_thread(name);
91 let full_path = test_root.join(Path::new(&unique_test_dir));
92 if full_path.is_file() {
93 remove_file(full_path.as_path())?;
94 }
95 if !full_path.exists() {
96 create_dir_all(full_path.as_path())?;
97 }
98 Ok(Self {
99 test_dir: full_path.into_os_string(),
100 delete_on_terminate: true,
101 })
102 }
103
104 /// Returns the current value of delete_on_terminate. If true, the
105 /// test director will be destroyed when this struct is dropped. If it is
106 /// set to false, the directory will not be deleted.
107 ///
108 /// This flag is true by default.
109 pub fn delete_on_terminate(&self) -> bool {
110 self.delete_on_terminate
111 }
112
113 /// Changes the flag delete_on_terminate.
114 pub fn set_delete_on_terminate(&mut self, delete_on_terminate: bool) {
115 self.delete_on_terminate = delete_on_terminate;
116 }
117
118 fn create_unique_name_for_thread(name: &str) -> String {
119 format!("{}-{:?}", name, std::thread::current().id())
120 }
121
122 /// Returns the path of the test directory.
123 pub fn test_dir(&self) -> &Path {
124 Path::new(&self.test_dir)
125 }
126
127 /// Deletes all of the contents of the test directory without removing it.
128 pub fn reset(&self) -> Result<()> {
129 for entry in read_dir(self.test_dir())? {
130 match entry {
131 Ok(e) => {
132 let file_type = e.file_type()?;
133 if file_type.is_file() || file_type.is_symlink() {
134 remove_file(e.path())?;
135 } else if file_type.is_dir() {
136 remove_dir_all(e.path())?;
137 }
138 }
139 Err(e) => return Err(e),
140 }
141 }
142 Ok(())
143 }
144
145 /// Get the path of a file inside the test directory.
146 pub fn get_test_file_path(&self, name: &str) -> OsString {
147 let path = Path::new(&self.test_dir);
148 path.join(name).into_os_string()
149 }
150
151 /// Creates a test file with the specfied name and write something into it.
152 ///
153 /// Arguments:
154 /// - `name`: The name of the file to be created;
155 /// - `contents`: The contents of the file;
156 ///
157 /// Returns the path to the newly created file.
158 pub fn create_test_file(&self, name: &str, contents: &[u8]) -> Result<OsString> {
159 let full_path = self.get_test_file_path(name);
160 let p = Path::new(&full_path);
161 write(p, contents)?;
162 Ok(full_path)
163 }
164
165 /// Creates an empty test file with the specfied name. The file will have no contents as
166 /// it is equivalent to call [`TestDirUtils::create_test_file()`] with `b""` as its
167 /// contents.
168 ///
169 /// Arguments:
170 /// - `name`: The name of the file to be created;
171 ///
172 /// Returns the path to the newly created file.
173 pub fn touch_test_file(&self, name: &str) -> Result<OsString> {
174 self.create_test_file(name, b"")
175 }
176
177 /// Reads all the contents of the specified test file. It uses [`std::fs::read()`]
178 /// so it is subjected to the same restrictions.
179 ///
180 /// Arguments:
181 /// - `name`: The name of the file to be created;
182 ///
183 /// Returns the contents of the file.
184 pub fn read_test_file(&self, name: &str) -> Result<Vec<u8>> {
185 let full_path = self.get_test_file_path(name);
186 let p = Path::new(&full_path);
187 Ok(read(p)?)
188 }
189
190 /// Deletes the specified file.
191 ///
192 /// This method does nothing if the test file does not exist.
193 ///
194 /// Arguments:
195 /// - `name`: The name of the file to be removed;
196 pub fn delete_test_file(&self, name: &str) -> Result<()> {
197 let full_path = self.get_test_file_path(name);
198 let p = Path::new(&full_path);
199 if p.exists() {
200 remove_file(p)
201 } else {
202 Ok(())
203 }
204 }
205}
206
207impl Drop for TestDirUtils {
208 fn drop(&mut self) {
209 if self.delete_on_terminate {
210 remove_dir_all(Path::new(self.test_dir())).unwrap();
211 }
212 }
213}