testdir/macros.rs
1//! The `testdir!()` macro and friends.
2//!
3//! Tests for this are almost exclusively in the integration tests at `tests/macro.rs`.
4
5/// Creates a test directory at the requested scope.
6///
7/// This macro creates a new or re-uses an existing [`NumberedDir`] in the cargo target
8/// directory. It than creates the requested sub-directory within this [`NumberedDir`].
9/// The path for this directory is returned as a [`PathBuf`].
10///
11/// For the typical `testdir!()` invocation in a test function this would result in
12/// `target/testdir-$N/$CARGO_CRATE_NAME/module/path/to/test_function_name1. A symbolic
13/// link to the most recent [`NumberedDir`] is also created as `target/testdir-current ->
14/// testdir-$N`.
15///
16/// **Reuse** of the [`NumberedDir`] is triggered when this process is being run as a
17/// subprocess of Cargo, as is typical when running `cargo test`. In this case the same
18/// [`NumberedDir`] is re-used between all Cargo sub-processes, which means it is shared
19/// between unittests, integration tests and doctests of the same test run.
20///
21/// The path within the numbered directory is created based on the context and how it is
22/// invoked. There are several ways to specify this:
23///
24/// * Use the scope of the current test function to create a unique and predictable
25/// directory: `testdir!(TestScope)`. This is the default when invoked as without any
26/// arguments as well: `testdir!()`. In this case the directory path will follow the crate
27/// name and module path, ending with the test function name. This also works in
28/// integration and doctests.
29///
30/// * Use the scope of the current module: `testdir!(ModuleScope)`. In this case the crate
31/// name and module path is used, but with an additional final `mod` component.
32///
33/// * Directly provide the path using an expression, e.g. `testdir!("sub/dir"). This
34/// expression will be passed to [`NumberedDir::create_subdir`] and thus must evaluate to
35/// something which implements ``AsRef<Path>``, e.g. a simple `"sub/dir"` can be used or
36/// something more advanced evaluating to a path, usually [`Path`] or [`PathBuf`].
37///
38/// # Panics
39///
40/// If there is any problem with creating the directories or cleaning up old ones this will
41/// panic.
42///
43/// # Examples
44///
45/// Inside a test function you can use the shorthand:
46/// ```
47/// use std::path::PathBuf;
48/// use testdir::testdir;
49///
50/// let path0: PathBuf = testdir!();
51/// ```
52///
53/// This is the same as invoking:
54/// ```
55/// # use testdir::testdir;
56/// let path1 = testdir!(TestScope);
57/// ```
58/// These constructs can also be used in a doctest.
59///
60/// The module path is valid in any scope, so can be used together with [once_cell] (or
61/// [lazy_static]) to share a common directory between different tests.
62/// ```no_run
63/// use std::path::PathBuf;
64/// use once_cell::sync::Lazy;
65/// use testdir::testdir;
66///
67/// static TDIR: Lazy<PathBuf> = Lazy::new(|| testdir!(ModuleScope));
68///
69/// #[test]
70/// fn test_module_scope() {
71/// assert!(TDIR.ends_with("mod"));
72/// }
73/// ```
74///
75/// [lazy_static]: https://docs.rs/lazy_static
76/// [`NumberedDir`]: crate::NumberedDir
77/// [`PathBuf`]: std::path::PathBuf
78#[macro_export]
79macro_rules! testdir {
80 () => {
81 $crate::testdir!(TestScope)
82 };
83 ( TestScope ) => {{
84 $crate::init_testdir!();
85 let module_path = ::std::module_path!();
86 let test_name = $crate::private::extract_test_name(&module_path);
87 let subdir_path = ::std::path::Path::new(&module_path.replace("::", "/")).join(&test_name);
88 $crate::with_testdir(move |tdir| {
89 tdir.create_subdir(subdir_path)
90 .expect("Failed to create test-scoped sub-directory")
91 })
92 }};
93 ( ModuleScope ) => {{
94 $crate::init_testdir!();
95 let module_path = ::std::module_path!();
96 let subdir_path = ::std::path::Path::new(&module_path.replace("::", "/")).join("mod");
97 $crate::with_testdir(move |tdir| {
98 tdir.create_subdir(subdir_path)
99 .expect("Failed to create module-scoped sub-directory")
100 })
101 }};
102 ( $e:expr ) => {{
103 $crate::init_testdir!();
104 $crate::with_testdir(move |tdir| {
105 tdir.create_subdir($e)
106 .expect("Failed to create sub-directory")
107 })
108 }};
109}
110
111/// Initialises the global [`NumberedDir`] used by the [`testdir`] macro.
112///
113/// This macro is implicitly called by the [`testdir`] macro to initialise the global
114/// [`NumberedDir`] instance inside the cargo target directory. It must be called before
115/// any call to [`with_testdir`](crate::with_testdir) to ensure this is initialised.
116///
117/// # Examples
118///
119/// ```
120/// use testdir::{init_testdir, with_testdir};
121///
122/// init_testdir!();
123/// let path = with_testdir(|dir| dir.create_subdir("some/subdir").unwrap());
124/// assert!(path.is_dir());
125/// assert!(path.ends_with("some/subdir"));
126/// ```
127///
128/// [`NumberedDir`]: crate::NumberedDir
129#[macro_export]
130macro_rules! init_testdir {
131 () => {{
132 $crate::TESTDIR.get_or_init(move || {
133 let parent = match $crate::private::cargo_metadata::MetadataCommand::new().exec() {
134 Ok(metadata) => metadata.target_directory.into(),
135 Err(_) => {
136 // In some environments cargo-metadata is not available,
137 // e.g. cargo-dinghy. Use the directory of test executable.
138 let current_exe = ::std::env::current_exe().expect("no current exe");
139 current_exe
140 .parent()
141 .expect("no parent dir for current exe")
142 .into()
143 }
144 };
145 let pkg_name = "testdir";
146 let mut builder = $crate::NumberedDirBuilder::new(pkg_name.to_string());
147 builder.set_parent(parent);
148 builder.reusefn($crate::private::reuse_cargo);
149 let testdir = builder.create().expect("Failed to create testdir");
150 $crate::private::create_cargo_pid_file(testdir.path());
151 testdir
152 })
153 }};
154}