1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
//! Semi-persistent, scoped test directories
//!
//! This module provides a convenient way to have an empty directory for tests which can be
//! inspected after the test run in a predicatable location. On subsequent test runs the
//! directory trees of previous runs will be cleaned up to keep the total number of
//! directories limited.
//!
//! # Quickstart
//!
//! ```no_run
//! mod tests {
//! use std::path::PathBuf;
//! use testdir::testdir;
//!
//! #[test]
//! fn test_write() {
//! let dir: PathBuf = testdir!();
//! let path = dir.join("hello.txt");
//! std::fs::write(&path, "hi there").ok();
//! assert!(path.exists());
//! }
//!
//! #[test]
//! fn test_nonexisting() {
//! let dir: PathBuf = testdir!();
//! let path = dir.join("hello.txt");
//! assert!(!path.exists());
//! }
//! }
//! # fn main() { }
//! ````
//!
//! For each `cargo test` invocation this will create a directory named `testdir-$N` in the
//! cargo target directory. The number suffix will increase each time you run the tests and
//! a `testdir-current` symlink is created to the most recent suffix created. Only the 8
//! most recent directories are kept so that this does not keep growing forever.
//!
//! Inside the numbered directory you will find a directory structure resembling your
//! crate's modules structure. For example if the above tests are in `lib.rs` of a crate
//! called `mycrate`, than on my UNIX system it looks like this:
//!
//! ```sh
//! $ tree target/
//! target/
//! +- testdir-0/
//! | +- tests/
//! | +- test_nonexisting/
//! | +- test_write/
//! | +- hello.txt
//! +- testdir-current -> testdir-0
//! ```
#![warn(missing_docs, missing_debug_implementations, clippy::all)]
use std::num::NonZeroU8;
use once_cell::sync::OnceCell;
mod builder;
mod macros;
mod numbered_dir;
#[doc(hidden)]
pub mod private;
pub use builder::NumberedDirBuilder;
pub use numbered_dir::{NumberedDir, NumberedDirIter};
/// Default to build the `root` for [`NumberedDirBuilder`] and [`testdir!`] from: `testdir`.
pub const ROOT_DEFAULT: &str = "testdir";
/// The default number of test directories retained by [`NumberedDirBuilder`] and
/// [`testdir!`]: `8`.
pub const KEEP_DEFAULT: Option<NonZeroU8> = NonZeroU8::new(8);
/// **Private** The global [`NumberedDir`] instance used by [`with_testdir`].
///
/// Do not use this directly, use [`init_testdir!`] to initialise this.
#[doc(hidden)]
pub static TESTDIR: OnceCell<NumberedDir> = OnceCell::new();
/// Executes a function passing the global [`NumberedDir`] instance.
///
/// This is used by the [`testdir!`] macro to create subdirectories inside one global
/// [`NumberedDir`] instance for each test using [`NumberedDir::create_subdir`]. You may
/// use this for similar purposes.
///
/// Be aware that you should have called [`init_testdir!`] before calling this so that the
/// global testdir was initialised correctly. Otherwise you will get a dummy testdir name.
///
/// # Examples
///
/// ```
/// use testdir::{init_testdir, with_testdir};
///
/// init_testdir!();
/// let path = with_testdir(|dir| dir.create_subdir("some/path").unwrap());
/// assert!(path.is_dir());
/// assert!(path.ends_with("some/path"));
/// ```
pub fn with_testdir<F, R>(func: F) -> R
where
F: FnOnce(&NumberedDir) -> R,
{
let test_dir = TESTDIR.get_or_init(|| {
let mut builder = NumberedDirBuilder::new(String::from("init_testdir-not-called"));
builder.reusefn(private::reuse_cargo);
let testdir = builder.create().expect("Failed to create testdir");
private::create_cargo_pid_file(testdir.path());
testdir
});
func(test_dir)
}