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