Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 14 additions & 0 deletions src/numbered_dir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self> {
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
Expand Down
6 changes: 6 additions & 0 deletions src/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
62 changes: 62 additions & 0 deletions tests/env_override.rs
Original file line number Diff line number Diff line change
@@ -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();
}
}