Skip to content

Commit e51522a

Browse files
committed
Auto merge of #9375 - vojtechkral:tests-target-dir, r=ehuss
Add CARGO_TARGET_TMPDIR env var for integration tests & benches Hi. Recently I [ran into](https://github.com/vojtechkral/bard/blob/main/tests/util/mod.rs#L32) the problem that integration tests don't have a good way to figure out where `target` dir is. Knowing where `target` is is useful for integration tests that need to setup some filesystem structure for test cases. In fact `cargo` itself does this too (and figures out the path rather clumsily). Another popular way of doing this is to create a directory in `/tmp` (or quivalent on other systems), however, I believe using subdirectory in `target` is better as testcases are easier to debug that way and temporary locations aren't polluted. I think this would also address some concerns in #2841 Another solution might be to provide a dedicated subdirectory in `target` for this, something like `target/scratchpad`, but I'm not convinced this is warranted... Edit: That's what was decided to do, see below... Let me know what you think 🙂
2 parents deb2c61 + 53343bc commit e51522a

File tree

7 files changed

+132
-26
lines changed

7 files changed

+132
-26
lines changed

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

+6-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@ pub fn cargo_test(attr: TokenStream, item: TokenStream) -> TokenStream {
3131
}
3232
};
3333

34-
let mut new_body =
35-
to_token_stream("let _test_guard = cargo_test_support::paths::init_root();");
34+
let mut new_body = to_token_stream(
35+
r#"let _test_guard = {
36+
let tmp_dir = option_env!("CARGO_TARGET_TMPDIR");
37+
cargo_test_support::paths::init_root(tmp_dir)
38+
};"#,
39+
);
3640

3741
// If this is a `build_std` test (aka `tests/build-std/*.rs`) then they
3842
// only run on nightly and they only run when specifically instructed to

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,12 @@ pub fn init(path: &Path) -> git2::Repository {
132132
}
133133

134134
fn default_search_path() {
135-
use crate::paths::GLOBAL_ROOT;
135+
use crate::paths::global_root;
136136
use git2::{opts::set_search_path, ConfigLevel};
137+
137138
static INIT: Once = Once::new();
138139
INIT.call_once(|| unsafe {
139-
let path = GLOBAL_ROOT.join("blank_git_search_path");
140+
let path = global_root().join("blank_git_search_path");
140141
t!(set_search_path(ConfigLevel::System, &path));
141142
t!(set_search_path(ConfigLevel::Global, &path));
142143
t!(set_search_path(ConfigLevel::XDG, &path));

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

+41-21
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,44 @@ use std::sync::Mutex;
1414
static CARGO_INTEGRATION_TEST_DIR: &str = "cit";
1515

1616
lazy_static! {
17-
pub static ref GLOBAL_ROOT: PathBuf = {
18-
let mut path = t!(env::current_exe());
19-
path.pop(); // chop off exe name
20-
path.pop(); // chop off 'debug'
21-
22-
// If `cargo test` is run manually then our path looks like
23-
// `target/debug/foo`, in which case our `path` is already pointing at
24-
// `target`. If, however, `cargo test --target $target` is used then the
25-
// output is `target/$target/debug/foo`, so our path is pointing at
26-
// `target/$target`. Here we conditionally pop the `$target` name.
27-
if path.file_name().and_then(|s| s.to_str()) != Some("target") {
28-
path.pop();
29-
}
30-
31-
path.push(CARGO_INTEGRATION_TEST_DIR);
32-
path.mkdir_p();
33-
path
34-
};
17+
// TODO: Use `SyncOnceCell` when stable
18+
static ref GLOBAL_ROOT: Mutex<Option<PathBuf>> = Mutex::new(None);
3519

3620
static ref TEST_ROOTS: Mutex<HashMap<String, PathBuf>> = Default::default();
3721
}
3822

23+
/// This is used when running cargo is pre-CARGO_TARGET_TMPDIR
24+
/// TODO: Remove when CARGO_TARGET_TMPDIR grows old enough.
25+
fn global_root_legacy() -> PathBuf {
26+
let mut path = t!(env::current_exe());
27+
path.pop(); // chop off exe name
28+
path.pop(); // chop off "deps"
29+
path.push("tmp");
30+
path.mkdir_p();
31+
path
32+
}
33+
34+
fn set_global_root(tmp_dir: Option<&'static str>) {
35+
let mut lock = GLOBAL_ROOT.lock().unwrap();
36+
if lock.is_none() {
37+
let mut root = match tmp_dir {
38+
Some(tmp_dir) => PathBuf::from(tmp_dir),
39+
None => global_root_legacy(),
40+
};
41+
42+
root.push(CARGO_INTEGRATION_TEST_DIR);
43+
*lock = Some(root);
44+
}
45+
}
46+
47+
pub fn global_root() -> PathBuf {
48+
let lock = GLOBAL_ROOT.lock().unwrap();
49+
match lock.as_ref() {
50+
Some(p) => p.clone(),
51+
None => unreachable!("GLOBAL_ROOT not set yet"),
52+
}
53+
}
54+
3955
// We need to give each test a unique id. The test name could serve this
4056
// purpose, but the `test` crate doesn't have a way to obtain the current test
4157
// name.[*] Instead, we used the `cargo-test-macro` crate to automatically
@@ -52,14 +68,15 @@ pub struct TestIdGuard {
5268
_private: (),
5369
}
5470

55-
pub fn init_root() -> TestIdGuard {
71+
pub fn init_root(tmp_dir: Option<&'static str>) -> TestIdGuard {
5672
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
5773

58-
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
74+
let id = NEXT_ID.fetch_add(1, Ordering::SeqCst);
5975
TEST_ID.with(|n| *n.borrow_mut() = Some(id));
6076

6177
let guard = TestIdGuard { _private: () };
6278

79+
set_global_root(tmp_dir);
6380
let r = root();
6481
r.rm_rf();
6582
r.mkdir_p();
@@ -80,7 +97,10 @@ pub fn root() -> PathBuf {
8097
order to be able to use the crate root.",
8198
)
8299
});
83-
GLOBAL_ROOT.join(&format!("t{}", id))
100+
101+
let mut root = global_root();
102+
root.push(&format!("t{}", id));
103+
root
84104
}
85105

86106
pub fn home() -> PathBuf {

src/cargo/core/compiler/layout.rs

+8
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ pub struct Layout {
125125
examples: PathBuf,
126126
/// The directory for rustdoc output: `$root/doc`
127127
doc: PathBuf,
128+
/// The directory for temporary data of integration tests and benches: `$dest/tmp`
129+
tmp: PathBuf,
128130
/// The lockfile for a build (`.cargo-lock`). Will be unlocked when this
129131
/// struct is `drop`ped.
130132
_lock: FileLock,
@@ -170,6 +172,7 @@ impl Layout {
170172
fingerprint: dest.join(".fingerprint"),
171173
examples: dest.join("examples"),
172174
doc: root.join("doc"),
175+
tmp: dest.join("tmp"),
173176
root,
174177
dest,
175178
_lock: lock,
@@ -219,4 +222,9 @@ impl Layout {
219222
pub fn build(&self) -> &Path {
220223
&self.build
221224
}
225+
/// Create and return the tmp path.
226+
pub fn prepare_tmp(&self) -> CargoResult<&Path> {
227+
paths::create_dir_all(&self.tmp)?;
228+
Ok(&self.tmp)
229+
}
222230
}

src/cargo/core/compiler/mod.rs

+5
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,11 @@ fn prepare_rustc(
581581
base.env("CARGO_PRIMARY_PACKAGE", "1");
582582
}
583583

584+
if unit.target.is_test() || unit.target.is_bench() {
585+
let tmp = cx.files().layout(unit.kind).prepare_tmp()?;
586+
base.env("CARGO_TARGET_TMPDIR", tmp.display().to_string());
587+
}
588+
584589
if cx.bcx.config.cli_unstable().jobserver_per_rustc {
585590
let client = cx.new_jobserver()?;
586591
base.inherit_jobserver(&client);

src/doc/src/reference/environment-variables.md

+6
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,12 @@ corresponding environment variable is set to the empty string, `""`.
221221
on the current directory and the default workspace members. This environment
222222
variable will not be set when building dependencies. This is only set when
223223
compiling the package (not when running binaries or tests).
224+
* `CARGO_TARGET_TMPDIR` — Only set when building [integration test] or benchmark code.
225+
This is a path to a directory inside the target directory
226+
where integration tests or benchmarks are free to put any data needed by
227+
the tests/benches. Cargo initially creates this directory but doesn't
228+
manage its content in any way, this is the responsibility of the test code.
229+
There are separate directories for `debug` and `release` profiles.
224230

225231
[integration test]: cargo-targets.md#integration-tests
226232
[`env` macro]: ../../std/macro.env.html

tests/testsuite/build.rs

+63-1
Original file line numberDiff line numberDiff line change
@@ -1334,12 +1334,18 @@ fn crate_env_vars() {
13341334
let s = format!("{}.{}.{}-{}", VERSION_MAJOR,
13351335
VERSION_MINOR, VERSION_PATCH, VERSION_PRE);
13361336
assert_eq!(s, VERSION);
1337+
1338+
// Verify CARGO_TARGET_TMPDIR isn't set for bins
1339+
assert!(option_env!("CARGO_TARGET_TMPDIR").is_none());
13371340
}
13381341
"#,
13391342
)
13401343
.file(
13411344
"src/lib.rs",
13421345
r#"
1346+
use std::env;
1347+
use std::path::PathBuf;
1348+
13431349
pub fn version() -> String {
13441350
format!("{}-{}-{} @ {} in {}",
13451351
env!("CARGO_PKG_VERSION_MAJOR"),
@@ -1348,9 +1354,60 @@ fn crate_env_vars() {
13481354
env!("CARGO_PKG_VERSION_PRE"),
13491355
env!("CARGO_MANIFEST_DIR"))
13501356
}
1357+
1358+
pub fn check_no_int_test_env() {
1359+
env::var("CARGO_TARGET_DIR").unwrap_err();
1360+
}
1361+
1362+
pub fn check_tmpdir(tmp: Option<&'static str>) {
1363+
let tmpdir: PathBuf = tmp.unwrap().into();
1364+
1365+
let exe: PathBuf = env::current_exe().unwrap().into();
1366+
let mut expected: PathBuf = exe.parent().unwrap().parent().unwrap().into();
1367+
expected.push("tmp");
1368+
assert_eq!(tmpdir, expected);
1369+
1370+
// Check that CARGO_TARGET_TMPDIR isn't set for lib code
1371+
assert!(option_env!("CARGO_TARGET_TMPDIR").is_none());
1372+
env::var("CARGO_TARGET_TMPDIR").unwrap_err();
1373+
}
1374+
1375+
#[test]
1376+
fn env() {
1377+
// Check that CARGO_TARGET_TMPDIR isn't set for unit tests
1378+
assert!(option_env!("CARGO_TARGET_TMPDIR").is_none());
1379+
env::var("CARGO_TARGET_TMPDIR").unwrap_err();
1380+
}
13511381
"#,
13521382
)
1353-
.build();
1383+
.file(
1384+
"tests/env.rs",
1385+
r#"
1386+
#[test]
1387+
fn env() {
1388+
foo::check_tmpdir(option_env!("CARGO_TARGET_TMPDIR"));
1389+
}
1390+
"#,
1391+
);
1392+
1393+
let p = if is_nightly() {
1394+
p.file(
1395+
"benches/env.rs",
1396+
r#"
1397+
#![feature(test)]
1398+
extern crate test;
1399+
use test::Bencher;
1400+
1401+
#[bench]
1402+
fn env(_: &mut Bencher) {
1403+
foo::check_tmpdir(option_env!("CARGO_TARGET_TMPDIR"));
1404+
}
1405+
"#,
1406+
)
1407+
.build()
1408+
} else {
1409+
p.build()
1410+
};
13541411

13551412
println!("build");
13561413
p.cargo("build -v").run();
@@ -1362,6 +1419,11 @@ fn crate_env_vars() {
13621419

13631420
println!("test");
13641421
p.cargo("test -v").run();
1422+
1423+
if is_nightly() {
1424+
println!("bench");
1425+
p.cargo("bench -v").run();
1426+
}
13651427
}
13661428

13671429
#[cargo_test]

0 commit comments

Comments
 (0)