pub(crate) fn attribute_marks_test(body: &str) -> bool {
let matches_test = |s: &str| {
matches!(s, "test" | "rstest" | "wasm_bindgen_test" | "test_case")
|| s.ends_with("::test")
|| s.contains("::test(")
|| cfg_inner(s).is_some_and(cfg_predicate_marks_test)
};
let trimmed = body.trim();
if matches_test(trimmed) {
return true;
}
if trimmed.bytes().any(|b| b.is_ascii_whitespace()) {
return matches_test(&strip_whitespace(trimmed));
}
false
}
fn strip_whitespace(s: &str) -> String {
s.chars().filter(|c| !c.is_whitespace()).collect()
}
fn cfg_inner(body: &str) -> Option<&str> {
let rest = body.trim_start().strip_prefix("cfg")?.trim_start();
let after_open = rest.strip_prefix('(')?;
let inner = after_open.strip_suffix(')')?;
Some(inner)
}
fn cfg_predicate_marks_test(pred: &str) -> bool {
let trimmed = pred.trim();
if trimmed == "test" {
return true;
}
if let Some(rest) = trimmed
.strip_prefix("all")
.or_else(|| trimmed.strip_prefix("any"))
&& let Some(args) = rest.trim_start().strip_prefix('(')
&& let Some(args) = args.strip_suffix(')')
{
return cfg_args_any_marks_test(args);
}
if cfg_split_top_level_args(trimmed).nth(1).is_some() {
return cfg_args_any_marks_test(trimmed);
}
false
}
fn cfg_split_top_level_args(args: &str) -> impl Iterator<Item = &str> {
let mut depth = 0_i32;
let mut start = 0_usize;
let mut done = false;
let bytes = args.as_bytes();
std::iter::from_fn(move || {
if done {
return None;
}
let mut i = start;
while i < bytes.len() {
match bytes[i] {
b'(' => depth += 1,
b')' => depth -= 1,
b',' if depth == 0 => {
let slice = &args[start..i];
start = i + 1;
return Some(slice);
}
_ => {}
}
i += 1;
}
done = true;
Some(&args[start..])
})
}
fn cfg_args_any_marks_test(args: &str) -> bool {
cfg_split_top_level_args(args).any(cfg_arg_marks_test)
}
fn cfg_arg_marks_test(arg: &str) -> bool {
let arg = arg.trim();
if arg == "test" {
return true;
}
if let Some(rest) = arg.strip_prefix("not").map(str::trim_start)
&& rest.starts_with('(')
&& rest.ends_with(')')
{
return false;
}
cfg_predicate_marks_test(arg)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rust_attr_test_marks_bare_test_attribute() {
assert!(attribute_marks_test("test"));
assert!(attribute_marks_test("rstest"));
assert!(attribute_marks_test("wasm_bindgen_test"));
assert!(attribute_marks_test("test_case"));
assert!(attribute_marks_test("tokio::test"));
assert!(attribute_marks_test(
"tokio::test(flavor = \"current_thread\")"
));
}
#[test]
fn rust_attr_test_marks_cfg_test_variants() {
assert!(attribute_marks_test("cfg(test)"));
assert!(attribute_marks_test("cfg(test, foo)"));
assert!(attribute_marks_test("cfg(all(test, unix))"));
assert!(attribute_marks_test("cfg(any(test, foo))"));
}
#[test]
fn rust_attr_test_marks_cfg_with_test_not_first() {
assert!(
attribute_marks_test("cfg(all(unix, test))"),
"test as second all() operand must mark test-only"
);
assert!(
attribute_marks_test("cfg(any(feature = \"x\", test))"),
"test as second any() operand must mark test-only"
);
assert!(attribute_marks_test(
"cfg(all(unix, any(test, feature = \"x\")))"
));
}
#[test]
fn rust_attr_test_skips_not_test_and_feature_named_test() {
assert!(!attribute_marks_test("cfg(not(test))"));
assert!(!attribute_marks_test("cfg(all(unix, not(test)))"));
assert!(!attribute_marks_test("cfg(feature = \"test\")"));
assert!(!attribute_marks_test("cfg(all(unix, feature = \"test\"))"));
assert!(!attribute_marks_test("cfg(unix)"));
assert!(!attribute_marks_test("derive(Debug)"));
assert!(!attribute_marks_test(
"cfg(all(unix, target_os = \"linux\"))"
));
assert!(!attribute_marks_test("cfg(any(unix, windows))"));
assert!(!attribute_marks_test(
"cfg(all(unix, any(feature = \"x\", feature = \"y\")))"
));
assert!(!attribute_marks_test("cfg(any(unix, not(test)))"));
}
#[test]
fn rust_attr_test_tolerates_internal_whitespace() {
assert!(attribute_marks_test("cfg( all( unix , test ) )"));
assert!(!attribute_marks_test("cfg( not ( test ) )"));
}
#[test]
fn strip_whitespace_preserves_non_ascii_utf8() {
assert_eq!(strip_whitespace("é test"), "étest");
assert_eq!(strip_whitespace("crate ::ñ::test"), "crate::ñ::test");
assert_eq!(strip_whitespace(" 日本語 test"), "日本語test");
assert_eq!(
strip_whitespace("cfg( all( unix , test ) )"),
"cfg(all(unix,test))"
);
}
}