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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
//! The `testdir!()` macro and friends.
//!
//! Tests for this are almost exclusively in the integration tests at `tests/macro.rs`.
/// Creates a test directory at the requested scope.
///
/// This macro creates a new or re-uses an existing [`NumberedDir`] in the cargo target
/// directory. It than creates the requested sub-directory within this [`NumberedDir`].
/// The path for this directory is returned as a [`PathBuf`].
///
/// For the typical `testdir!()` invocation in a test function this would result in
/// `target/testdir-$N/$CARGO_CRATE_NAME/module/path/to/test_function_name1. A symbolic
/// link to the most recent [`NumberedDir`] is also created as `target/testdir-current ->
/// testdir-$N`.
///
/// **Reuse** of the [`NumberedDir`] is triggered when this process is being run as a
/// subprocess of Cargo, as is typical when running `cargo test`. In this case the same
/// [`NumberedDir`] is re-used between all Cargo sub-processes, which means it is shared
/// between unittests, integration tests and doctests of the same test run.
///
/// The path within the numbered directory is created based on the context and how it is
/// invoked. There are several ways to specify this:
///
/// * Use the scope of the current test function to create a unique and predictable
/// directory: `testdir!(TestScope)`. This is the default when invoked as without any
/// arguments as well: `testdir!()`. In this case the directory path will follow the crate
/// name and module path, ending with the test function name. This also works in
/// integration and doctests.
///
/// * Use the scope of the current module: `testdir!(ModuleScope)`. In this case the crate
/// name and module path is used, but with an additional final `mod` component.
///
/// * Directly provide the path using an expression, e.g. `testdir!("sub/dir"). This
/// expression will be passed to [`NumberedDir::create_subdir`] and thus must evaluate to
/// something which implements `AsRef<Path>`, e.g. a simple `"sub/dir"` can be used or
/// something more advanced evaluating to a path, usually [`Path`] or [`PathBuf`].
///
/// # Panics
///
/// If there is any problem with creating the directories or cleaning up old ones this will
/// panic.
///
/// # Examples
///
/// Inside a test function you can use the shorthand:
/// ```
/// use std::path::PathBuf;
/// use testdir::testdir;
///
/// let path0: PathBuf = testdir!();
/// ```
///
/// This is the same as invoking:
/// ```
/// # use testdir::testdir;
/// let path1 = testdir!(TestScope);
/// ```
/// These constructs can also be used in a doctest.
///
/// The module path is valid in any scope, so can be used together with [once_cell] (or
/// [lazy_static]) to share a common directory between different tests.
/// ```
/// use std::path::PathBuf;
/// use once_cell::sync::Lazy;
/// use testdir::testdir;
///
/// static TDIR: Lazy<PathBuf> = Lazy::new(|| testdir!(ModuleScope));
///
/// #[test]
/// fn test_module_scope() {
/// assert!(TDIR.ends_with("mod"));
/// }
/// ```
///
/// [lazy_static]: https://docs.rs/lazy_static
/// [`NumberedDir`]: crate::NumberedDir
/// [`PathBuf`]: std::path::PathBuf
#[macro_export]
macro_rules! testdir {
() => {
testdir!(TestScope)
};
( TestScope ) => {{
$crate::init_testdir!();
let module_path = ::std::module_path!();
let test_name = $crate::private::extract_test_name(&module_path);
let subdir_path = ::std::path::Path::new(&module_path.replace("::", "/")).join(&test_name);
$crate::with_testdir(move |tdir| {
tdir.create_subdir(subdir_path)
.expect("Failed to create test-scoped sub-directory")
})
}};
( ModuleScope ) => {{
$crate::init_testdir!();
let module_path = ::std::module_path!();
let subdir_path = ::std::path::Path::new(&module_path.replace("::", "/")).join("mod");
$crate::with_testdir(move |tdir| {
tdir.create_subdir(subdir_path)
.expect("Failed to create module-scoped sub-directory")
})
}};
( $e:expr ) => {{
$crate::init_testdir!();
$crate::with_testdir(move |tdir| {
tdir.create_subdir($e)
.expect("Failed to create sub-directory")
})
}};
}
/// Initialises the global [`NumberedDir`] used by the [`testdir`] macro.
///
/// This macro is implicitly called by the [`testdir`] macro to initialise the global
/// [`NumberedDir`] instance inside the cargo target directory. It must be called before
/// any call to [`with_testdir`](crate::with_testdir) to ensure this is initialised.
///
/// # Examples
///
/// ```
/// use testdir::{init_testdir, with_testdir};
///
/// init_testdir!();
/// let path = with_testdir(|dir| dir.create_subdir("some/subdir").unwrap());
/// assert!(path.is_dir());
/// assert!(path.ends_with("some/subdir"));
/// ```
///
/// [`NumberedDir`]: crate::NumberedDir
#[macro_export]
macro_rules! init_testdir {
() => {{
$crate::TESTDIR.get_or_init(move || {
let metadata = $crate::private::cargo_metadata::MetadataCommand::new()
.exec()
.expect("cargo metadata failed");
// let pkg_name = String::from(::std::env!("CARGO_PKG_NAME"));
let pkg_name = "testdir";
let mut builder = $crate::NumberedDirBuilder::new(pkg_name.to_string());
builder.set_parent(metadata.target_directory.into());
builder.reusefn($crate::private::reuse_cargo);
let testdir = builder.create().expect("Failed to create testdir");
$crate::private::create_cargo_pid_file(testdir.path());
testdir
})
}};
}