Skip to content

Commit c06fb5f

Browse files
committed
Auto merge of #11738 - Muscraft:name-tests, r=epage
feat: Use test name for dir when running tests Tests for Cargo currently put their generated data in `target/tmp/cit/t{}`. This can make it very hard to debug tests if multiple are falling. Having the tests be named would make this much easier when debugging. [This comment](https://github.com/rust-lang/cargo/blob/eff8d693b99077b0b7f70e4bc414fdca53597085/crates/cargo-test-support/src/paths.rs#L54-L64) explains why cargo used a number instead of the test name for the unique identifier. I found a way to get the test name to be the unique identifier. I did this by having `cargo-test-macro` get the name of the test when the macro is looking for where the function body starts. It passes the name of the test and the file that the test comes from into the boilerplate it was already adding. It uses the file and the test name as the directory to place the tests generated data data into, i.e, `target/tmp/cit/testsuite/workspaces/workspace_in_git`. ![Screenshot 2023-02-20 at 20 55 30](https://user-images.githubusercontent.com/23045215/220236036-e1dcbe53-033e-4db7-a6a3-a689eae97386.png) Note: I also found `t{}` to be frustrating when trying to get the size of a test. There is no good way to get the size of a test within `target/tmp/cit`, without running the tests one at a time and checking the size like so: ```python for test in tests: subprocess.run(["cargo", "test", "-p", "cargo", test, "--", "--exact"]) test_size = subprocess.run(["du", "-sk", "target/tmp/cit/t0"], stdout=subprocess.PIPE, text=True) size_dict[test] = int(test_size.stdout.split()[0]) ``` It made it very hard to try and find which tests were causing most of the size of the `target` dir.
2 parents e7b11e7 + 835117d commit c06fb5f

File tree

4 files changed

+113
-30
lines changed

4 files changed

+113
-30
lines changed

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

+25-5
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream {
133133
add_attr(&mut ret, "ignore", reason);
134134
}
135135

136+
let mut test_name = None;
137+
let mut num = 0;
138+
136139
// Find where the function body starts, and add the boilerplate at the start.
137140
for token in item {
138141
let group = match token {
@@ -144,18 +147,35 @@ pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream {
144147
continue;
145148
}
146149
}
150+
TokenTree::Ident(i) => {
151+
// The first time through it will be `fn` the second time is the
152+
// name of the test.
153+
if test_name.is_none() && num == 1 {
154+
test_name = Some(i.to_string())
155+
} else {
156+
num += 1;
157+
}
158+
ret.extend(Some(TokenTree::Ident(i)));
159+
continue;
160+
}
147161
other => {
148162
ret.extend(Some(other));
149163
continue;
150164
}
151165
};
152166

153-
let mut new_body = to_token_stream(
154-
r#"let _test_guard = {
167+
let name = &test_name
168+
.clone()
169+
.map(|n| n.split("::").next().unwrap().to_string())
170+
.unwrap();
171+
172+
let mut new_body = to_token_stream(&format!(
173+
r#"let _test_guard = {{
155174
let tmp_dir = option_env!("CARGO_TARGET_TMPDIR");
156-
cargo_test_support::paths::init_root(tmp_dir)
157-
};"#,
158-
);
175+
let test_dir = cargo_test_support::paths::test_dir(std::file!(), "{name}");
176+
cargo_test_support::paths::init_root(tmp_dir, test_dir)
177+
}};"#
178+
));
159179

160180
new_body.extend(group.stream());
161181
ret.extend(Some(TokenTree::from(Group::new(

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

+34-20
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::fs;
77
use std::io::{self, ErrorKind};
88
use std::path::{Path, PathBuf};
99
use std::process::Command;
10-
use std::sync::atomic::{AtomicUsize, Ordering};
1110
use std::sync::Mutex;
1211

1312
static CARGO_INTEGRATION_TEST_DIR: &str = "cit";
@@ -51,28 +50,20 @@ pub fn global_root() -> PathBuf {
5150
}
5251
}
5352

54-
// We need to give each test a unique id. The test name could serve this
55-
// purpose, but the `test` crate doesn't have a way to obtain the current test
56-
// name.[*] Instead, we used the `cargo-test-macro` crate to automatically
57-
// insert an init function for each test that sets the test name in a thread
58-
// local variable.
59-
//
60-
// [*] It does set the thread name, but only when running concurrently. If not
61-
// running concurrently, all tests are run on the main thread.
53+
// We need to give each test a unique id. The test name serve this
54+
// purpose. We are able to get the test name by having the `cargo-test-macro`
55+
// crate automatically insert an init function for each test that sets the
56+
// test name in a thread local variable.
6257
thread_local! {
63-
static TEST_ID: RefCell<Option<usize>> = RefCell::new(None);
58+
static TEST_NAME: RefCell<Option<PathBuf>> = RefCell::new(None);
6459
}
6560

6661
pub struct TestIdGuard {
6762
_private: (),
6863
}
6964

70-
pub fn init_root(tmp_dir: Option<&'static str>) -> TestIdGuard {
71-
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
72-
73-
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
74-
TEST_ID.with(|n| *n.borrow_mut() = Some(id));
75-
65+
pub fn init_root(tmp_dir: Option<&'static str>, test_name: PathBuf) -> TestIdGuard {
66+
TEST_NAME.with(|n| *n.borrow_mut() = Some(test_name));
7667
let guard = TestIdGuard { _private: () };
7768

7869
set_global_root(tmp_dir);
@@ -85,20 +76,20 @@ pub fn init_root(tmp_dir: Option<&'static str>) -> TestIdGuard {
8576

8677
impl Drop for TestIdGuard {
8778
fn drop(&mut self) {
88-
TEST_ID.with(|n| *n.borrow_mut() = None);
79+
TEST_NAME.with(|n| *n.borrow_mut() = None);
8980
}
9081
}
9182

9283
pub fn root() -> PathBuf {
93-
let id = TEST_ID.with(|n| {
94-
n.borrow().expect(
84+
let test_name = TEST_NAME.with(|n| {
85+
n.borrow().clone().expect(
9586
"Tests must use the `#[cargo_test]` attribute in \
9687
order to be able to use the crate root.",
9788
)
9889
});
9990

10091
let mut root = global_root();
101-
root.push(&format!("t{}", id));
92+
root.push(&test_name);
10293
root
10394
}
10495

@@ -345,3 +336,26 @@ pub fn windows_reserved_names_are_allowed() -> bool {
345336
true
346337
}
347338
}
339+
340+
/// This takes the test location (std::file!() should be passed) and the test name
341+
/// and outputs the location the test should be places in, inside of `target/tmp/cit`
342+
///
343+
/// `path: tests/testsuite/workspaces.rs`
344+
/// `name: `workspace_in_git
345+
/// `output: "testsuite/workspaces/workspace_in_git`
346+
pub fn test_dir(path: &str, name: &str) -> std::path::PathBuf {
347+
let test_dir: std::path::PathBuf = std::path::PathBuf::from(path)
348+
.components()
349+
// Trim .rs from any files
350+
.map(|c| c.as_os_str().to_str().unwrap().trim_end_matches(".rs"))
351+
// We only want to take once we have reached `tests` or `src`. This helps when in a
352+
// workspace: `workspace/more/src/...` would result in `src/...`
353+
.skip_while(|c| c != &"tests" && c != &"src")
354+
// We want to skip "tests" since it is taken in `skip_while`.
355+
// "src" is fine since you could have test in "src" named the same as one in "tests"
356+
// Skip "mod" since `snapbox` tests have a folder per test not a file and the files
357+
// are named "mod.rs"
358+
.filter(|c| c != &"tests" && c != &"mod")
359+
.collect();
360+
test_dir.join(name)
361+
}

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

+6-5
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ The [`#[cargo_test]` attribute](#cargo_test-attribute) is used in place of `#[te
6565
#### `#[cargo_test]` attribute
6666

6767
The `#[cargo_test]` attribute injects code which does some setup before starting the test.
68-
It will create a filesystem "sandbox" under the "cargo integration test" directory for each test, such as `/path/to/cargo/target/tmp/cit/t123/`.
69-
The sandbox will contain a `home` directory that will be used instead of your normal home directory.
68+
It will create a filesystem "sandbox" under the "cargo integration test" directory for each test, such as
69+
`/path/to/cargo/target/tmp/cit/testsuite/bad_config/bad1`. The sandbox will contain a `home` directory that
70+
will be used instead of your normal home directory.
7071

7172
The `#[cargo_test]` attribute takes several options that will affect how the test is generated.
7273
They are listed in parentheses separated with commas, such as:
@@ -200,7 +201,7 @@ Then populate
200201
- This attribute injects code which does some setup before starting the
201202
test, creating a filesystem "sandbox" under the "cargo integration test"
202203
directory for each test such as
203-
`/path/to/cargo/target/cit/t123/`
204+
`/path/to/cargo/target/tmp/cit/testsuite/cargo_add/add_basic`
204205
- The sandbox will contain a `home` directory that will be used instead of your normal home directory
205206

206207
`Project`:
@@ -267,9 +268,9 @@ environment. The general process is:
267268

268269
`cargo test --test testsuite -- features2::inactivate_targets`.
269270
2. In another terminal, head into the sandbox directory to inspect the files and run `cargo` directly.
270-
1. The sandbox directories start with `t0` for the first test.
271+
1. The sandbox directories match the format of `tests/testsuite`, just replace `tests` with `target/tmp/cit`
271272

272-
`cd target/tmp/cit/t0`
273+
`cd target/tmp/cit/testsuite/features2/inactivate_target`
273274
2. Set up the environment so that the sandbox configuration takes effect:
274275

275276
`export CARGO_HOME=$(pwd)/home/.cargo`

tests/testsuite/main.rs

+48
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,51 @@ fn aaa_trigger_cross_compile_disabled_check() {
143143
// This triggers the cross compile disabled check to run ASAP, see #5141
144144
cargo_test_support::cross_compile::disabled();
145145
}
146+
147+
// This is placed here as running tests in `cargo-test-support` would rebuild it
148+
#[cargo_test]
149+
fn check_test_dir() {
150+
let tests = vec![
151+
(
152+
"tests/testsuite/workspaces.rs",
153+
"workspace_in_git",
154+
"testsuite/workspaces/workspace_in_git",
155+
),
156+
(
157+
"tests/testsuite/cargo_remove/invalid_arg/mod.rs",
158+
"case",
159+
"testsuite/cargo_remove/invalid_arg/case",
160+
),
161+
(
162+
"tests/build-std/main.rs",
163+
"cross_custom",
164+
"build-std/main/cross_custom",
165+
),
166+
(
167+
"src/tools/cargo/tests/testsuite/build.rs",
168+
"cargo_compile_simple",
169+
"src/tools/cargo/testsuite/build/cargo_compile_simple",
170+
),
171+
(
172+
"src/tools/cargo/tests/testsuite/cargo_add/add_basic/mod.rs",
173+
"case",
174+
"src/tools/cargo/testsuite/cargo_add/add_basic/case",
175+
),
176+
(
177+
"src/tools/cargo/tests/build-std/main.rs",
178+
"cross_custom",
179+
"src/tools/cargo/build-std/main/cross_custom",
180+
),
181+
(
182+
"workspace/more/src/tools/cargo/tests/testsuite/build.rs",
183+
"cargo_compile_simple",
184+
"src/tools/cargo/testsuite/build/cargo_compile_simple",
185+
),
186+
];
187+
for (path, name, expected) in tests {
188+
assert_eq!(
189+
cargo_test_support::paths::test_dir(path, name),
190+
std::path::PathBuf::from(expected)
191+
);
192+
}
193+
}

0 commit comments

Comments
 (0)