Skip to content

Commit cbdfb3e

Browse files
committed
feat: Use test name for dir when running tests
1 parent b5354b5 commit cbdfb3e

File tree

4 files changed

+170
-16
lines changed

4 files changed

+170
-16
lines changed

crates/cargo-test-macro/src/lib.rs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,9 @@ pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream {
200200
add_attr(&mut ret, "ignore", reason);
201201
}
202202

203+
let mut test_name = None;
204+
let mut num = 0;
205+
203206
// Find where the function body starts, and add the boilerplate at the start.
204207
for token in item {
205208
let group = match token {
@@ -211,18 +214,44 @@ pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream {
211214
continue;
212215
}
213216
}
217+
TokenTree::Ident(i) => {
218+
// The first time through it will be `fn` the second time is the
219+
// name of the test.
220+
if test_name.is_none() && num == 1 {
221+
test_name = Some(i.to_string())
222+
} else {
223+
num += 1;
224+
}
225+
ret.extend(Some(TokenTree::Ident(i)));
226+
continue;
227+
}
214228
other => {
215229
ret.extend(Some(other));
216230
continue;
217231
}
218232
};
219233

220-
let mut new_body = to_token_stream(
221-
r#"let _test_guard = {
222-
let tmp_dir = option_env!("CARGO_TARGET_TMPDIR");
223-
cargo_test_support::paths::init_root(tmp_dir)
224-
};"#,
225-
);
234+
let name = &test_name
235+
.clone()
236+
.map(|n| n.split("::").next().unwrap().to_string())
237+
.unwrap();
238+
239+
let mut new_body = if cfg!(windows) {
240+
to_token_stream(
241+
r#"let _test_guard = {
242+
let tmp_dir = option_env!("CARGO_TARGET_TMPDIR");
243+
cargo_test_support::paths::init_root(tmp_dir)
244+
};"#,
245+
)
246+
} else {
247+
to_token_stream(&format!(
248+
r#"let _test_guard = {{
249+
let tmp_dir = option_env!("CARGO_TARGET_TMPDIR");
250+
let test_dir = cargo_test_support::paths::test_dir(std::file!(), "{name}");
251+
cargo_test_support::paths::init_root(tmp_dir, test_dir)
252+
}};"#
253+
))
254+
};
226255

227256
new_body.extend(group.stream());
228257
ret.extend(Some(TokenTree::from(Group::new(

crates/cargo-test-support/src/paths.rs

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,15 @@ pub fn global_root() -> PathBuf {
6262
}
6363
}
6464

65-
// We need to give each test a unique id. The test name could serve this
66-
// purpose, but the `test` crate doesn't have a way to obtain the current test
67-
// name.[*] Instead, we used the `cargo-test-macro` crate to automatically
68-
// insert an init function for each test that sets the test name in a thread
69-
// local variable.
70-
//
71-
// [*] It does set the thread name, but only when running concurrently. If not
72-
// running concurrently, all tests are run on the main thread.
65+
// We need to give each test a unique id. The test name serve this
66+
// purpose. We are able to get the test name by having the `cargo-test-macro`
67+
// crate automatically insert an init function for each test that sets the
68+
// test name in a thread local variable.
7369
thread_local! {
70+
#[cfg(windows)]
7471
static TEST_ID: RefCell<Option<usize>> = const { RefCell::new(None) };
72+
#[cfg(not(windows))]
73+
static TEST_NAME: RefCell<Option<PathBuf>> = const { RefCell::new(None) };
7574
}
7675

7776
/// See [`init_root`]
@@ -80,31 +79,58 @@ pub struct TestIdGuard {
8079
}
8180

8281
/// For test harnesses like [`crate::cargo_test`]
82+
#[cfg(windows)]
8383
pub fn init_root(tmp_dir: Option<&'static str>) -> TestIdGuard {
8484
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
8585

8686
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
8787
TEST_ID.with(|n| *n.borrow_mut() = Some(id));
88+
let guard = TestIdGuard { _private: () };
89+
90+
set_global_root(tmp_dir);
91+
let r = root();
92+
r.rm_rf();
93+
r.mkdir_p();
94+
95+
guard
96+
}
97+
98+
/// For test harnesses like [`crate::cargo_test`]
99+
#[cfg(not(windows))]
100+
pub fn init_root(tmp_dir: Option<&'static str>, test_name: PathBuf) -> TestIdGuard {
101+
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
102+
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
88103

104+
TEST_NAME.with(|n| *n.borrow_mut() = Some(test_name));
89105
let guard = TestIdGuard { _private: () };
90106

91107
set_global_root(tmp_dir);
92108
let r = root();
93109
r.rm_rf();
94110
r.mkdir_p();
111+
if id == 0 {
112+
use crate::SymlinkBuilder;
95113

114+
let mut root = global_root();
115+
root.push(&format!("t{}", id));
116+
SymlinkBuilder::new_dir(r, root).mk();
117+
}
96118
guard
97119
}
98120

99121
impl Drop for TestIdGuard {
100122
fn drop(&mut self) {
123+
#[cfg(windows)]
101124
TEST_ID.with(|n| *n.borrow_mut() = None);
125+
#[cfg(not(windows))]
126+
TEST_NAME.with(|n| *n.borrow_mut() = None);
102127
}
103128
}
104129

105130
/// Path to the test's filesystem scratchpad
106131
///
107132
/// ex: `$CARGO_TARGET_TMPDIR/cit/t0`
133+
#[cfg(windows)]
108134
pub fn root() -> PathBuf {
109135
let id = TEST_ID.with(|n| {
110136
n.borrow().expect(
@@ -118,6 +144,23 @@ pub fn root() -> PathBuf {
118144
root
119145
}
120146

147+
/// Path to the test's filesystem scratchpad
148+
///
149+
/// ex: `$CARGO_TARGET_TMPDIR/cit/t0`
150+
#[cfg(not(windows))]
151+
pub fn root() -> PathBuf {
152+
let test_name = TEST_NAME.with(|n| {
153+
n.borrow().clone().expect(
154+
"Tests must use the `#[cargo_test]` attribute in \
155+
order to be able to use the crate root.",
156+
)
157+
});
158+
159+
let mut root = global_root();
160+
root.push(&test_name);
161+
root
162+
}
163+
121164
/// Path to the current test's `$HOME`
122165
///
123166
/// ex: `$CARGO_TARGET_TMPDIR/cit/t0/home`
@@ -489,3 +532,26 @@ pub fn windows_reserved_names_are_allowed() -> bool {
489532
true
490533
}
491534
}
535+
536+
/// This takes the test location (std::file!() should be passed) and the test name
537+
/// and outputs the location the test should be places in, inside of `target/tmp/cit`
538+
///
539+
/// `path: tests/testsuite/workspaces.rs`
540+
/// `name: `workspace_in_git
541+
/// `output: "testsuite/workspaces/workspace_in_git`
542+
pub fn test_dir(path: &str, name: &str) -> std::path::PathBuf {
543+
let test_dir: std::path::PathBuf = std::path::PathBuf::from(path)
544+
.components()
545+
// Trim .rs from any files
546+
.map(|c| c.as_os_str().to_str().unwrap().trim_end_matches(".rs"))
547+
// We only want to take once we have reached `tests` or `src`. This helps when in a
548+
// workspace: `workspace/more/src/...` would result in `src/...`
549+
.skip_while(|c| c != &"tests" && c != &"src")
550+
// We want to skip "tests" since it is taken in `skip_while`.
551+
// "src" is fine since you could have test in "src" named the same as one in "tests"
552+
// Skip "mod" since `snapbox` tests have a folder per test not a file and the files
553+
// are named "mod.rs"
554+
.filter(|c| c != &"tests" && c != &"mod")
555+
.collect();
556+
test_dir.join(name)
557+
}

src/doc/contrib/src/tests/writing.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,10 @@ Then populate
200200
- This is used in place of `#[test]`
201201
- This attribute injects code which does some setup before starting the
202202
test, creating a filesystem "sandbox" under the "cargo integration test"
203-
directory for each test such as
204-
`/path/to/cargo/target/cit/t123/`
203+
directory for each test. The directory for each test is based on the
204+
integration test name, module (if there is one), and function name[^1]:
205+
206+
`/path/to/cargo/target/tmp/cit/<integration test>/<module>/<fn name>/`
205207
- The sandbox will contain a `home` directory that will be used instead of your normal home directory
206208

207209
`Project`:
@@ -258,6 +260,11 @@ or overwrite a binary immediately after running it. Under some conditions
258260
Windows will fail with errors like "directory not empty" or "failed to remove"
259261
or "access is denied".
260262

263+
On Windows, to avoid path length limitations, the tests use the following
264+
directory structure instead:
265+
266+
`/path/to/cargo/target/tmp/cit/t123/`
267+
261268
## Debugging tests
262269

263270
In some cases, you may need to dig into a test that is not working as you
@@ -299,3 +306,6 @@ environment. The general process is:
299306
[`Command`]: https://docs.rs/snapbox/latest/snapbox/cmd/struct.Command.html
300307
[`OutputAssert`]: https://docs.rs/snapbox/latest/snapbox/cmd/struct.OutputAssert.html
301308
[`Assert`]: https://docs.rs/snapbox/latest/snapbox/struct.Assert.html
309+
310+
[^1]: Windows uses a separate directory layout, see [Platform-Specific Notes](#platform-specific-notes)
311+
for more details.

tests/testsuite/main.rs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,3 +209,52 @@ fn aaa_trigger_cross_compile_disabled_check() {
209209
// This triggers the cross compile disabled check to run ASAP, see #5141
210210
crate::utils::cross_compile::disabled();
211211
}
212+
213+
// This is placed here as running tests in `cargo-test-support` would rebuild it
214+
#[cargo_test]
215+
#[cfg(not(windows))]
216+
fn check_test_dir() {
217+
let tests = vec![
218+
(
219+
"tests/testsuite/workspaces.rs",
220+
"workspace_in_git",
221+
"testsuite/workspaces/workspace_in_git",
222+
),
223+
(
224+
"tests/testsuite/cargo_remove/invalid_arg/mod.rs",
225+
"case",
226+
"testsuite/cargo_remove/invalid_arg/case",
227+
),
228+
(
229+
"tests/build-std/main.rs",
230+
"cross_custom",
231+
"build-std/main/cross_custom",
232+
),
233+
(
234+
"src/tools/cargo/tests/testsuite/build.rs",
235+
"cargo_compile_simple",
236+
"src/tools/cargo/testsuite/build/cargo_compile_simple",
237+
),
238+
(
239+
"src/tools/cargo/tests/testsuite/cargo_add/add_basic/mod.rs",
240+
"case",
241+
"src/tools/cargo/testsuite/cargo_add/add_basic/case",
242+
),
243+
(
244+
"src/tools/cargo/tests/build-std/main.rs",
245+
"cross_custom",
246+
"src/tools/cargo/build-std/main/cross_custom",
247+
),
248+
(
249+
"workspace/more/src/tools/cargo/tests/testsuite/build.rs",
250+
"cargo_compile_simple",
251+
"src/tools/cargo/testsuite/build/cargo_compile_simple",
252+
),
253+
];
254+
for (path, name, expected) in tests {
255+
assert_eq!(
256+
cargo_test_support::paths::test_dir(path, name),
257+
std::path::PathBuf::from(expected)
258+
);
259+
}
260+
}

0 commit comments

Comments
 (0)