From a9036de7fd575b4ec086bb4647e5506469358989 Mon Sep 17 00:00:00 2001 From: Frando Date: Thu, 26 Mar 2026 09:47:24 +0100 Subject: [PATCH] feat: add support for RUST_TESTDIR environment variable --- README.md | 11 ++++++++ src/lib.rs | 8 ++++++ src/numbered_dir.rs | 14 ++++++++++ src/private.rs | 6 +++++ tests/env_override.rs | 62 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+) create mode 100644 tests/env_override.rs diff --git a/README.md b/README.md index ef9386e..62699f0 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,17 @@ target/ +- testdir-current -> testdir-0 ``` +## Environment variables + +- **`RUST_TESTDIR`**: Override the test directory location. When set, + this path is used directly as the testdir root instead of + `target/testdir-$N`. No numbered subdirectories or cleanup are + applied. Test subdirectories are created inside it as usual: + ``` + RUST_TESTDIR=/tmp/my-tests cargo test + # creates /tmp/my-tests/module/path/test_name + ``` + ## Feedback and contributing The code lives in a git repository at https://github.com/flub/testdir diff --git a/src/lib.rs b/src/lib.rs index 832e7c7..5f256c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,14 @@ //! 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. //! +//! Set the `RUST_TESTDIR` environment variable to use a specific directory instead of +//! `target/testdir-$N`. When set, the path is used directly as the testdir root with no +//! numbering or cleanup: +//! +//! ```sh +//! RUST_TESTDIR=/tmp/my-tests cargo test +//! ``` +//! //! 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: diff --git a/src/numbered_dir.rs b/src/numbered_dir.rs index 6e61102..e828afd 100644 --- a/src/numbered_dir.rs +++ b/src/numbered_dir.rs @@ -30,6 +30,20 @@ pub struct NumberedDir { } impl NumberedDir { + /// Creates a [`NumberedDir`] wrapping an existing directory path. + /// + /// This creates the directory if it does not exist yet but does not apply any numbering + /// or cleanup logic. This is useful when the directory location is externally specified, + /// e.g. via an environment variable. + pub(crate) fn from_path(path: PathBuf) -> Result { + fs::create_dir_all(&path).context("Could not create directory")?; + Ok(Self { + path, + base: String::new(), + number: 0, + }) + } + /// Creates the next sequential numbered directory. /// /// The directory will be created inside `parent` and will start with the name given in diff --git a/src/private.rs b/src/private.rs index 8d5ed4c..acc4d6d 100644 --- a/src/private.rs +++ b/src/private.rs @@ -49,8 +49,14 @@ const RUSTDOC_NAME: &str = "rustdoc.exe"; #[cfg(target_family = "windows")] const RUST_OUT_NAME: &str = "rust_out.exe"; +/// The environment variable to override the testdir directory. +const RUST_TESTDIR_ENV: &str = "RUST_TESTDIR"; + /// Implementation of `crate::macros::init_testdir`. pub fn init_testdir() -> NumberedDir { + if let Some(val) = std::env::var_os(RUST_TESTDIR_ENV) { + return NumberedDir::from_path(PathBuf::from(val)).expect("Failed to create RUST_TESTDIR"); + } let parent = cargo_target_dir(); let pkg_name = "testdir"; let mut builder = NumberedDirBuilder::new(pkg_name.to_string()); diff --git a/tests/env_override.rs b/tests/env_override.rs new file mode 100644 index 0000000..726c9d3 --- /dev/null +++ b/tests/env_override.rs @@ -0,0 +1,62 @@ +use std::path::PathBuf; + +use testdir::testdir; + +#[test] +fn test_rust_testdir_env() { + let custom_dir = std::env::temp_dir().join("testdir-env-test"); + // Clean up from any previous run. + let _ = std::fs::remove_dir_all(&custom_dir); + + // Run a subprocess with RUST_TESTDIR set so we get a fresh OnceLock. + let exe = std::env::current_exe().unwrap(); + let output = std::process::Command::new(exe) + .arg("--exact") + .arg("test_rust_testdir_env_inner") + .env("RUST_TESTDIR", &custom_dir) + .output() + .unwrap(); + + let stderr = String::from_utf8_lossy(&output.stderr); + assert!(output.status.success(), "subprocess failed: {stderr}"); + + // The directory itself should be the testdir root — no testdir-N inside it. + assert!(custom_dir.is_dir(), "RUST_TESTDIR was not created"); + + // There should be no testdir-N intermediate directories. + let has_numbered = std::fs::read_dir(&custom_dir) + .unwrap() + .filter_map(|e| e.ok()) + .any(|e| e.file_name().to_string_lossy().starts_with("testdir-")); + assert!( + !has_numbered, + "RUST_TESTDIR should not contain testdir-N subdirs" + ); + + // The file written by the inner test should exist at the expected path. + let marker = custom_dir.join("env_override/test_rust_testdir_env_inner/marker.txt"); + assert!(marker.is_file(), "marker file not found at {}", marker.display()); + assert_eq!(std::fs::read_to_string(&marker).unwrap(), "hello from env override"); + + // Cleanup + let _ = std::fs::remove_dir_all(&custom_dir); +} + +#[test] +fn test_rust_testdir_env_inner() { + // Called from test_rust_testdir_env with RUST_TESTDIR set. + // When called directly (without the env var), it just runs as a normal test. + if let Some(expected_parent) = std::env::var_os("RUST_TESTDIR") { + let dir: PathBuf = testdir!(); + let expected = PathBuf::from(expected_parent); + assert!( + dir.starts_with(&expected), + "testdir {} is not under {}", + dir.display(), + expected.display() + ); + + let marker = dir.join("marker.txt"); + std::fs::write(&marker, "hello from env override").unwrap(); + } +}