Skip to content

Commit 3f47330

Browse files
committed
feat: Use test name for dir when running tests
1 parent bd97934 commit 3f47330

File tree

7 files changed

+174
-19
lines changed

7 files changed

+174
-19
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ cargo-credential-macos-keychain = { version = "0.4.19", path = "credential/cargo
3232
cargo-credential-wincred = { version = "0.4.19", path = "credential/cargo-credential-wincred" }
3333
cargo-platform = { path = "crates/cargo-platform", version = "0.3.0" }
3434
cargo-test-macro = { version = "0.4.8", path = "crates/cargo-test-macro" }
35-
cargo-test-support = { version = "0.9.1", path = "crates/cargo-test-support" }
35+
cargo-test-support = { version = "0.10.0", path = "crates/cargo-test-support" }
3636
cargo-util = { version = "0.2.26", path = "crates/cargo-util" }
3737
cargo-util-schemas = { version = "0.11.0", path = "crates/cargo-util-schemas" }
3838
cargo_metadata = "0.23.1"

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/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "cargo-test-support"
3-
version = "0.9.1"
3+
version = "0.10.0"
44
edition.workspace = true
55
rust-version = "1.91" # MSRV:1
66
license.workspace = true

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

Lines changed: 75 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,59 @@ 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+
root.rm_rf();
117+
SymlinkBuilder::new_dir(r, root).mk();
118+
}
96119
guard
97120
}
98121

99122
impl Drop for TestIdGuard {
100123
fn drop(&mut self) {
124+
#[cfg(windows)]
101125
TEST_ID.with(|n| *n.borrow_mut() = None);
126+
#[cfg(not(windows))]
127+
TEST_NAME.with(|n| *n.borrow_mut() = None);
102128
}
103129
}
104130

105131
/// Path to the test's filesystem scratchpad
106132
///
107133
/// ex: `$CARGO_TARGET_TMPDIR/cit/t0`
134+
#[cfg(windows)]
108135
pub fn root() -> PathBuf {
109136
let id = TEST_ID.with(|n| {
110137
n.borrow().expect(
@@ -118,6 +145,23 @@ pub fn root() -> PathBuf {
118145
root
119146
}
120147

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

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

0 commit comments

Comments
 (0)