Don't rely on a thread local to uniquely create test roots

This commit is contained in:
Jethro Beekman 2019-05-02 14:58:52 -07:00 committed by Eric Huss
parent e157b6d84c
commit a598309cb6
6 changed files with 109 additions and 33 deletions

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
/target /target
/tests/testsuite/support/cargo-test-macro/target
Cargo.lock Cargo.lock
.cargo .cargo
/config.stamp /config.stamp

View File

@ -104,6 +104,7 @@ features = [
bufstream = "0.1" bufstream = "0.1"
proptest = "0.9.1" proptest = "0.9.1"
varisat = "0.2.1" varisat = "0.2.1"
cargo-test-macro = { "path" = "tests/testsuite/support/cargo-test-macro" }
[[bin]] [[bin]]
name = "cargo" name = "cargo"

View File

@ -6,6 +6,9 @@
#![warn(clippy::needless_borrow)] #![warn(clippy::needless_borrow)]
#![warn(clippy::redundant_clone)] #![warn(clippy::redundant_clone)]
#[macro_use]
extern crate cargo_test_macro;
#[macro_use] #[macro_use]
mod support; mod support;

View File

@ -0,0 +1,12 @@
[package]
name = "cargo-test-macro"
version = "0.1.0"
authors = ["Jethro Beekman <jethro@fortanix.com>"]
edition = "2018"
[lib]
proc-macro = true
[dependencies]
quote = "0.6"
syn = { version = "0.15", features = ["full"] }

View File

@ -0,0 +1,24 @@
extern crate proc_macro;
use quote::{quote, ToTokens};
use syn::{*, parse::Parser};
#[proc_macro_attribute]
pub fn cargo_test(
_attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let mut fn_def = parse_macro_input!(item as ItemFn);
let attr = quote! {
#[test]
};
fn_def.attrs.extend(Attribute::parse_outer.parse2(attr).unwrap());
let stmt = quote! {
let _test_guard = crate::support::paths::init_root();
};
fn_def.block.stmts.insert(0, parse2(stmt).unwrap());
fn_def.into_token_stream().into()
}

View File

@ -1,58 +1,93 @@
use std::cell::Cell; use std::cell::RefCell;
use std::collections::HashMap;
use std::env; use std::env;
use std::fs; use std::fs;
use std::io::{self, ErrorKind}; use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Once, ONCE_INIT}; use std::sync::Mutex;
use filetime::{self, FileTime}; use filetime::{self, FileTime};
use lazy_static::lazy_static;
static CARGO_INTEGRATION_TEST_DIR: &'static str = "cit"; static CARGO_INTEGRATION_TEST_DIR: &'static str = "cit";
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
thread_local!(static TASK_ID: usize = NEXT_ID.fetch_add(1, Ordering::SeqCst)); lazy_static! {
static ref GLOBAL_ROOT: PathBuf = {
let mut path = t!(env::current_exe());
path.pop(); // chop off exe name
path.pop(); // chop off 'debug'
fn init() { // If `cargo test` is run manually then our path looks like
static GLOBAL_INIT: Once = ONCE_INIT; // `target/debug/foo`, in which case our `path` is already pointing at
thread_local!(static LOCAL_INIT: Cell<bool> = Cell::new(false)); // `target`. If, however, `cargo test --target $target` is used then the
GLOBAL_INIT.call_once(|| { // output is `target/$target/debug/foo`, so our path is pointing at
global_root().mkdir_p(); // `target/$target`. Here we conditionally pop the `$target` name.
}); if path.file_name().and_then(|s| s.to_str()) != Some("target") {
LOCAL_INIT.with(|i| { path.pop();
if i.get() {
return;
} }
i.set(true);
root().rm_rf(); path.push(CARGO_INTEGRATION_TEST_DIR);
home().mkdir_p();
}) path.rm_rf();
path.mkdir_p();
path
};
static ref TEST_ROOTS: Mutex<HashMap<String, PathBuf>> = Default::default();
} }
fn global_root() -> PathBuf { // We need to give each test a unique id. The test name could serve this
let mut path = t!(env::current_exe()); // purpose, but the `test` crate doesn't have a way to obtain the current test
path.pop(); // chop off exe name // name.[*] Instead, we used the `cargo-test-macro` crate to automatically
path.pop(); // chop off 'debug' // insert an init function for each test that sets the test name in a thread
// local variable.
//
// [*] It does set the thread name, but only when running concurrently. If not
// running concurrently, all tests are run on the main thread.
thread_local! {
static TEST_ID: RefCell<Option<usize>> = RefCell::new(None);
}
// If `cargo test` is run manually then our path looks like pub struct TestIdGuard {
// `target/debug/foo`, in which case our `path` is already pointing at _private: ()
// `target`. If, however, `cargo test --target $target` is used then the }
// output is `target/$target/debug/foo`, so our path is pointing at
// `target/$target`. Here we conditionally pop the `$target` name. pub fn init_root() -> TestIdGuard {
if path.file_name().and_then(|s| s.to_str()) != Some("target") { static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
path.pop();
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
TEST_ID.with(|n| { *n.borrow_mut() = Some(id) } );
let guard = TestIdGuard {
_private: ()
};
root().mkdir_p();
guard
}
impl Drop for TestIdGuard {
fn drop(&mut self) {
TEST_ID.with(|n| { *n.borrow_mut() = None } );
} }
path.join(CARGO_INTEGRATION_TEST_DIR)
} }
pub fn root() -> PathBuf { pub fn root() -> PathBuf {
init(); let id = TEST_ID.with(|n| {
global_root().join(&TASK_ID.with(|my_id| format!("t{}", my_id))) n.borrow().expect("Tests must use the `#[cargo_test]` attribute in \
order to be able to use the crate root.")
} );
GLOBAL_ROOT.join(&format!("t{}", id))
} }
pub fn home() -> PathBuf { pub fn home() -> PathBuf {
root().join("home") let mut path = root();
path.push("home");
path.mkdir_p();
path
} }
pub trait CargoPathExt { pub trait CargoPathExt {