Skip to main content

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}