#![cfg(test)]
use crate::spec::ProbeCallDetails;
use crate::spec::ProbeCallSpecification;
use crate::spec::TracerAttribute;
#[cfg(target_os = "windows")]
use dunce::canonicalize; use fs_extra::{copy_items, dir};
use lazy_static::lazy_static;
use proc_macro2::TokenStream;
use quote::quote;
use std::env;
use std::fmt;
#[cfg(not(target_os = "windows"))]
use std::fs::canonicalize; use std::path::PathBuf;
use std::sync::{Mutex, MutexGuard};
use tempfile::tempdir;
use tracers_core::argtypes::{CType, ProbeArgNativeTypeInfo, ProbeArgType, ProbeArgWrapper};
type EnvVarsVec = Vec<(String, String, Option<String>)>;
lazy_static! {
static ref ENV_VARS_MUTEX: Mutex<EnvVarsVec> = Mutex::new(Vec::new());
}
fn unset_vars(vars: &mut EnvVarsVec) {
println!("Restoring environment variables");
for (key, _, old_val) in vars.iter() {
if let Some(old_val) = old_val {
println!("Restoring '{}' to '{}'", key, old_val);
env::set_var(key, old_val)
} else {
println!("Unsetting '{}'", key);
env::remove_var(key);
}
}
vars.clear();
}
pub(crate) struct EnvVarsSetterGuard<'a> {
guard: MutexGuard<'a, EnvVarsVec>,
}
impl<'a> EnvVarsSetterGuard<'a> {
fn unset(&mut self) {
unset_vars(&mut self.guard);
}
}
impl<'a> Drop for EnvVarsSetterGuard<'a> {
fn drop(&mut self) {
self.unset();
}
}
pub(crate) fn with_env_vars<'a, K: AsRef<str>, V: AsRef<str>>(
vars: Vec<(K, V)>,
) -> EnvVarsSetterGuard<'a> {
println!("Acquiring mutex");
let mut guard = match ENV_VARS_MUTEX.lock() {
Err(e) => {
println!("Mutex is poisoned; cleaning up previous env vars");
e.into_inner()
}
Ok(guard) => guard,
};
unset_vars(&mut guard);
let vars: EnvVarsVec = vars
.into_iter()
.map(|(k, v)| {
(
k.as_ref().to_owned(),
v.as_ref().to_owned(),
env::var(k.as_ref()).ok(),
)
})
.collect();
for variable in vars.into_iter() {
guard.push(variable);
}
for (key, value, _) in guard.iter() {
println!("Setting '{}' to '{}'", key, value);
env::set_var(key, value);
}
EnvVarsSetterGuard { guard: guard }
}
pub(crate) struct Target {
pub name: &'static str,
pub entrypoint: &'static str,
pub additional_source_files: Vec<&'static str>,
pub expected_errors: Vec<(&'static str, &'static str)>,
}
impl Target {
pub fn new(
name: &'static str,
entrypoint: &'static str,
additional_source_files: Vec<&'static str>,
expected_errors: Option<Vec<(&'static str, &'static str)>>,
) -> Target {
Target {
name,
entrypoint,
additional_source_files,
expected_errors: expected_errors.unwrap_or(Vec::new()),
}
}
}
pub(crate) struct TestCrate {
pub root_directory: PathBuf,
pub package_name: &'static str,
pub targets: Vec<Target>,
}
#[derive(Clone)]
pub(crate) struct TestProviderTrait {
pub description: &'static str,
pub provider_name: &'static str,
pub attr_tokenstream: TokenStream,
pub tokenstream: TokenStream,
pub expected_error: Option<&'static str>,
pub probes: Option<Vec<TestProbe>>,
}
impl fmt::Debug for TestProviderTrait {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"TestProviderTrait(
desc='{}',
provider_name='{}',
expected_error='{:?}',
probes:\n",
self.description, self.provider_name, self.expected_error
)?;
if let Some(ref probes) = self.probes {
for probe in probes.iter() {
write!(f, " {:?},\n", probe)?;
}
}
write!(f, ")")
}
}
impl TestProviderTrait {
fn new_invalid(
description: &'static str,
provider_name: &'static str,
attr_tokenstream: TokenStream,
tokenstream: TokenStream,
expected_error: &'static str,
) -> TestProviderTrait {
TestProviderTrait {
description,
provider_name,
attr_tokenstream,
tokenstream,
expected_error: Some(expected_error),
probes: None,
}
}
fn new_valid(
description: &'static str,
provider_name: &'static str,
attr_tokenstream: TokenStream,
tokenstream: TokenStream,
probes: Vec<TestProbe>,
) -> TestProviderTrait {
TestProviderTrait {
description,
provider_name,
attr_tokenstream,
tokenstream,
expected_error: None,
probes: Some(probes),
}
}
pub fn get_attr_and_item_trait(&self) -> (TracerAttribute, syn::ItemTrait) {
(
syn::parse2(self.attr_tokenstream.clone()).expect("Expected valid tracer args"),
syn::parse2(self.tokenstream.clone()).expect("Expected a valid trait"),
)
}
}
#[derive(Clone)]
pub(crate) struct TestProbe {
pub name: &'static str,
pub args: Vec<(&'static str, syn::Type, CType)>,
}
impl fmt::Debug for TestProbe {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "TestProbe(name={}, args=(", self.name)?;
for (name, typ, _) in self.args.iter() {
write!(f, "{}: {},", name, quote! { #typ }.to_string())?;
}
write!(f, ")")
}
}
impl TestProbe {
fn new(name: &'static str, args: Vec<(&'static str, &'static str, CType)>) -> TestProbe {
let args: Vec<_> = args
.into_iter()
.map(|(arg_name, rust_type, c_type)| {
let rust_type: syn::Type =
syn::parse_str(rust_type).expect("Invalid Rust type expression");
(arg_name, rust_type, c_type)
})
.collect();
TestProbe { name, args }
}
}
#[derive(Debug)]
pub(crate) struct TestProbeCall {
pub call: TokenStream,
pub expected: Result<ProbeCallSpecification, &'static str>,
}
macro_rules! probe_arg {
($name:expr,$typ:ty) => {
(
$name,
stringify!($typ),
<<$typ as ProbeArgType<$typ>>::WrapperType as ProbeArgWrapper>::CType::get_c_type(),
)
};
}
pub(crate) fn get_test_provider_traits<F: FnMut(&TestProviderTrait) -> bool>(
filter: impl Into<Option<F>>,
) -> Vec<TestProviderTrait> {
let default_attr_tokenstream = quote! { #[tracer] };
let traits = vec![
TestProviderTrait::new_valid(
"empty trait",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {}
},
vec![],
),
TestProviderTrait::new_valid(
"simple trait",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
fn probe0(arg0: i32);
fn probe1(arg0: &str);
fn probe2(arg0: &str, arg1: usize);
}
},
vec![
TestProbe::new("probe0", vec![probe_arg!("arg0", i32)]),
TestProbe::new("probe1", vec![probe_arg!("arg0", &str)]),
TestProbe::new(
"probe2",
vec![probe_arg!("arg0", &str), probe_arg!("arg1", usize)],
),
],
),
TestProviderTrait::new_valid(
"simple trait with provider name override",
"my_provider",
quote! { #[tracer(provider_name = "my_provider")] },
quote! {
trait ProviderTrait {
fn probe0(arg0: i32);
fn probe1(arg0: &str);
fn probe2(arg0: &str, arg1: usize);
}
},
vec![
TestProbe::new("probe0", vec![probe_arg!("arg0", i32)]),
TestProbe::new("probe1", vec![probe_arg!("arg0", &str)]),
TestProbe::new(
"probe2",
vec![probe_arg!("arg0", &str), probe_arg!("arg1", usize)],
),
],
),
TestProviderTrait::new_valid(
"valid with many refs",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
fn probe0(arg0: i32);
fn probe1(arg0: &str);
fn probe2(arg0: &str, arg1: usize);
fn probe3(arg0: &str, arg1: &usize, arg2: &Option<i32>);
}
},
vec![
TestProbe::new("probe0", vec![probe_arg!("arg0", i32)]),
TestProbe::new("probe1", vec![probe_arg!("arg0", &str)]),
TestProbe::new(
"probe2",
vec![probe_arg!("arg0", &str), probe_arg!("arg1", usize)],
),
TestProbe::new(
"probe3",
vec![
probe_arg!("arg0", &str),
probe_arg!("arg1", &usize),
probe_arg!("arg2", &Option<i32>),
],
),
],
),
TestProviderTrait::new_invalid(
"has trait type param",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait<T: Debug> {
}
},
"type parameter",
),
TestProviderTrait::new_invalid(
"has const",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
fn probe0(arg0: i32);
const FOO: usize = 5;
}
},
"no other contents",
),
TestProviderTrait::new_invalid(
"has type alias",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
fn probe0(arg0: i32);
type Foo = Debug;
}
},
"no other contents",
),
TestProviderTrait::new_invalid(
"has macro invocation",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
println!("WTF");
fn probe0(arg0: i32);
}
},
"no other contents",
),
TestProviderTrait::new_invalid(
"has const function",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
const fn probe0(arg0: i32);
}
},
"Probe methods cannot be",
),
TestProviderTrait::new_invalid(
"has unsafe function",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
unsafe fn probe0(arg0: i32);
}
},
"Probe methods cannot be",
),
TestProviderTrait::new_invalid(
"has extern function",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
extern "C" fn probe0(arg0: i32);
}
},
"Probe methods cannot be",
),
TestProviderTrait::new_invalid(
"has fn type param",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
fn probe0<T: Debug>(arg0: T);
}
},
"Probe methods must not take any type parameters",
),
TestProviderTrait::new_invalid(
"has explicit unit retval",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
fn probe0(arg0: usize) -> ();
}
},
"Probe methods must not have an explicit return",
),
TestProviderTrait::new_invalid(
"has non-unit retval",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
fn probe0(arg0: usize) -> bool;
}
},
"Probe methods must not have an explicit return",
),
TestProviderTrait::new_invalid(
"has default impl",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
fn probe0(arg0: i32) { prinln!("{}", arg0); }
}
},
"Probe methods must NOT have a default impl",
),
TestProviderTrait::new_invalid(
"has self method",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
fn probe0(&self, arg0: i32);
}
},
"Probe methods must not have any `&self`",
),
TestProviderTrait::new_invalid(
"has mut self method",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
fn probe0(&mut self, arg0: i32);
}
},
"Probe methods must not have any `&self`",
),
TestProviderTrait::new_invalid(
"has self by-val method",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
fn probe0(self, arg0: i32);
}
},
"Probe methods must not have any `&self`",
),
TestProviderTrait::new_invalid(
"has mut self by-val method",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
fn probe0(mut self, arg0: i32);
}
},
"Probe methods must not have any `&self`",
),
TestProviderTrait::new_invalid(
"has a nested Option parameter which is not supported",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
fn probe0(arg0: &Option<Option<&str>>);
}
},
"is not supported for probing",
),
TestProviderTrait::new_invalid(
"has a Result parameter which is not supported",
"test_provider_trait",
default_attr_tokenstream.clone(),
quote! {
trait ProviderTrait {
fn probe0(arg0: &Result<&str, &str>);
}
},
"is not supported for probing",
),
];
let filter = filter.into();
if let Some(filter) = filter {
traits.into_iter().filter(filter).collect()
} else {
traits
}
}
macro_rules! test_probe_call {
($call:expr, @result $provider:path, $probe:path, $($arg:expr),*) => {
TestProbeCall {
call: quote! { $call },
expected: Ok(
ProbeCallSpecification::FireOnly(
ProbeCallDetails {
call: {
match ::syn::parse2::<syn::Expr>(quote! { $provider::$probe($($arg),*) }).unwrap(){
syn::Expr::Call(call) => call,
_ => {
assert!(false, "The impossible happened!");
unimplemented!()
}
}
},
probe_fq_path: ::syn::parse2::<syn::Path>(quote! { $provider::$probe }).unwrap(),
provider: ::syn::parse2::<syn::Path>(quote! { $provider }).unwrap(),
probe: ::syn::parse2::<syn::PathSegment>(quote! { $probe }).unwrap(),
args: vec![
$(
syn::parse2::<syn::Expr>(quote! { $arg }).unwrap()
),*
]
}
)
)
}
};
($call:expr, @result $provider:path, $probe:path) => {
test_probe_call!($call, @result $provider, $probe, )
};
($call:expr, @error $error_msg:expr) => {
TestProbeCall {
call: quote! { $call },
expected: Err($error_msg)
}
};
}
pub(crate) fn get_test_probe_calls() -> Vec<TestProbeCall> {
vec![
test_probe_call!(MyProvider::my_probe(), @result MyProvider, my_probe),
test_probe_call!(MyProvider::my_probe(arg0), @result MyProvider, my_probe, arg0),
test_probe_call!(MyProvider::my_probe(&arg0), @result MyProvider, my_probe, &arg0),
test_probe_call!(MyProvider::my_probe(someobj.callsomething().callsomethingelse(foo).unwrap()), @result MyProvider, my_probe, someobj.callsomething().callsomethingelse(foo).unwrap()),
test_probe_call!(MyProvider::my_probe(somefunc(arg1, arg2, arg3)), @result MyProvider, my_probe, somefunc(arg1, arg2, arg3)),
test_probe_call!(MyProvider::my_probe(arg0, arg1, arg3), @result MyProvider, my_probe, arg0, arg1, arg3),
test_probe_call!(my_module::my_othermodule::my_foomodule::MyProvider::my_probe(arg0), @result my_module::my_othermodule::my_foomodule::MyProvider, my_probe, arg0),
test_probe_call!(not_even_a_function_call, @error "requires the name of a provider trait and its probe method"),
test_probe_call!(missing_provider(), @error "is missing the name of the provider trait"),
test_probe_call!(MyProvider::not_even_a_function_call, @error "requires the name of a provider trait and its probe method"),
test_probe_call!({ MyProvider::my_probe() }, @error "requires the name of a provider trait and its probe method"),
]
}
pub(crate) static TEST_CRATE_NAME: &'static str = "test";
lazy_static! {
pub(crate) static ref TEST_CRATE_DIR: PathBuf = {
let src_file = file!(); let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let workspace_dir = manifest_dir.parent().expect("Manifest dir has no parent that's not possible");
let src_path = workspace_dir.join(src_file);
let mut src_dir = canonicalize(&src_path).expect(&format!("Failed to canonicalize source path: {}", &src_path.display()));
src_dir.pop();
let testdata_dir = src_dir.join("..").join("testdata");
let testdata_dir = canonicalize(&testdata_dir).expect(&format!("Failed to canonicalize test data path: {}", &testdata_dir.display()));
let temp_dir = tempdir().expect("Failed to create temporary directory").into_path();
copy_items(&vec![testdata_dir.as_path()],
temp_dir.as_path(),
&dir::CopyOptions::new()).expect(&format!("Failed to copy {} to {}", testdata_dir.display(), temp_dir.display()));
temp_dir.join("testdata").to_owned()
};
pub(crate) static ref TEST_CRATES: Vec<TestCrate> = vec![
TestCrate {
root_directory: TEST_CRATE_DIR.join("simplelib"),
package_name: "simplelib",
targets: vec![Target::new(
"simplelib",
"src/lib.rs",
vec!["src/child_module.rs"],
None
),
Target::new("simplelib", "build.rs", vec![], None)],
},
TestCrate {
root_directory: TEST_CRATE_DIR.join("simplebin"),
package_name: "simplebin",
targets: vec![Target::new(
"simplebin",
"src/main.rs",
vec!["src/child_module.rs"],
None
)],
},
TestCrate {
root_directory: TEST_CRATE_DIR.join("complexlib"),
package_name: "complexlib",
targets: vec![
Target::new("complexlib", "src/lib.rs", vec![], None),
Target::new("bin1", "src/bin/bin1.rs", vec![], None),
Target::new("bin2", "src/bin/bin2.rs", vec![], None),
Target::new("ex1", "examples/ex1.rs", vec![], None),
Target::new("test1", "tests/test1.rs", vec![ "tests/static/mod.rs"], None),
Target::new("test2", "tests/test2.rs", vec![], None),
Target::new("complexlib", "build.rs", vec![], None),
],
},
TestCrate {
root_directory: TEST_CRATE_DIR.join("errors"),
package_name: "erroneous",
targets: vec![
Target::new(
"erroneous",
"src/main.rs",
vec!["src/child_mod/mod.rs", "src/child_mod/grandchild_mod.rs"],
Some(vec![(
"src/child_mod/grandchild_mod.rs",
"this_mod_doesnt_exist"
)])
),
Target::new(
"compile_errors",
"tests/compile_errors.rs",
vec![],
Some(vec![("tests/with_errors/mod.rs", "expected `!")])
),
],
},
];
}