use super::*;
static TEST_ASSERTION_REGEX: OnceLock<Regex> = OnceLock::new();
static SLEEP_IN_TEST_REGEX: OnceLock<Regex> = OnceLock::new();
static CONDITIONAL_LOGIC_REGEX: OnceLock<Regex> = OnceLock::new();
static UNWRAP_IN_TEST_REGEX: OnceLock<Regex> = OnceLock::new();
static ASSERTION_MACRO_START_REGEX: OnceLock<Regex> = OnceLock::new();
static SHOULD_PANIC_ATTR_REGEX: OnceLock<Regex> = OnceLock::new();
static SHOULD_PANIC_EXPECTED_REGEX: OnceLock<Regex> = OnceLock::new();
const TEST_ASSERTION_PATTERN: &str = r"(?:\b(?:assert!|assert_eq!|assert_ne!|matches!|panic!)|\bassert_[A-Za-z0-9_]*!\s*|\bassert_[A-Za-z0-9_]*(?:\s*::\s*<[^;\n()]+>)?)\s*\(|\.\s*expect\s*\(";
const TEST_CHECKS: &[RegexRule] = &[
RegexRule {
rule_id: "test-quality.sleep-in-test",
regex: &SLEEP_IN_TEST_REGEX,
pattern: r"(std::thread::sleep|tokio::time::sleep)",
message: "Test sleeps instead of synchronising on behaviour.",
},
RegexRule {
rule_id: "test-quality.conditional-logic",
regex: &CONDITIONAL_LOGIC_REGEX,
pattern: r"\b(if|match)\b",
message: "Test contains conditional logic.",
},
RegexRule {
rule_id: "test-quality.unwrap-in-test",
regex: &UNWRAP_IN_TEST_REGEX,
pattern: r"\.unwrap\(\)",
message: "Test uses unwrap(), which can hide setup intent.",
},
];
pub(crate) fn analyse_test_block(
file: &SourceFile,
block: &FunctionBlock,
config: &Config,
findings: &mut Vec<Finding>,
) {
if path_is_calibration_fixture(&file.display_path) {
return;
}
analyse_ignored_test(file, block, findings);
analyse_test_size(file, block, config, findings);
analyse_should_panic_without_expected(file, block, findings);
let searchable_body =
strip_rust_comments_after_string_mask(&strip_rust_string_literals(&block.body));
analyse_test_assertions(file, block, &searchable_body, findings);
analyse_test_regex_checks(file, block, &searchable_body, findings);
}
pub(crate) fn analyse_should_panic_without_expected(
file: &SourceFile,
block: &FunctionBlock,
findings: &mut Vec<Finding>,
) {
if !block.is_test {
return;
}
let code = body_without_doc_comments(&block.body);
let has_should_panic =
static_regex(&SHOULD_PANIC_ATTR_REGEX, r"#\s*\[\s*should_panic\b").is_match(&code);
if !has_should_panic {
return;
}
let has_expected = static_regex(
&SHOULD_PANIC_EXPECTED_REGEX,
r"#\s*\[\s*should_panic\s*\([^)]*\bexpected\s*=",
)
.is_match(&code);
if has_expected {
return;
}
findings.push(block_finding_with_extras(
BlockFindingDescriptor {
rule_id: "test-quality.should-panic-without-expected",
message: format!(
"Test `{}` uses #[should_panic] without an `expected = \"...\"` clause.",
block.name
),
file,
block,
severity: Severity::Advisory,
pillar: Pillar::TestQuality,
},
BlockFindingExtras {
confidence: Confidence::High,
remediation: Some(
"Add `expected = \"message substring\"` so the test fails when an unrelated panic occurs."
.to_string(),
),
metadata: json!({}),
},
));
}
pub(crate) fn analyse_ignored_test(
file: &SourceFile,
block: &FunctionBlock,
findings: &mut Vec<Finding>,
) {
if block.ignore_without_reason {
findings.push(block_finding(BlockFindingDescriptor {
rule_id: "test-quality.ignored-without-reason",
message: format!(
"Ignored test `{}` does not explain why it is skipped.",
block.name
),
file,
block,
severity: Severity::Advisory,
pillar: Pillar::TestQuality,
}));
}
}
pub(crate) fn analyse_test_size(
file: &SourceFile,
block: &FunctionBlock,
config: &Config,
findings: &mut Vec<Finding>,
) {
if path_is_test_infrastructure(&file.display_path) {
return;
}
let rule_id = "test-quality.long-test";
let threshold = config.threshold(rule_id, 120.0) as usize;
let effective_lines = long_test_effective_line_count(block);
if effective_lines > threshold {
findings.push(block_finding_with_metadata(
BlockFindingDescriptor {
rule_id,
message: format!(
"Test `{}` has {effective_lines} lines from its first assertion onward, above the threshold of {threshold}.",
block.name
),
file,
block,
severity: config.severity(rule_id, Severity::Advisory),
pillar: Pillar::TestQuality,
},
json!({
"lines": effective_lines,
"totalLines": block.line_count,
"measured": effective_lines,
"threshold": threshold,
"unit": "lines",
"direction": "above"
}),
));
}
}
fn long_test_effective_line_count(block: &FunctionBlock) -> usize {
let searchable =
strip_rust_comments_after_string_mask(&strip_rust_string_literals(&block.body));
let Some(first_assertion) = searchable
.lines()
.position(|line| test_assertion_regex().is_match(line))
else {
return block.line_count;
};
block.body.lines().count().saturating_sub(first_assertion)
}
pub(crate) fn analyse_test_assertions(
file: &SourceFile,
block: &FunctionBlock,
searchable_body: &str,
findings: &mut Vec<Finding>,
) {
if has_trivial_assertion(searchable_body) {
findings.push(block_finding(BlockFindingDescriptor {
rule_id: "test-quality.trivial-assertion",
message: format!(
"Test `{}` asserts a value the code already fixes, not behavior. Assert a computed, parsed, or returned result instead of a literal or a binding's own initializer.",
block.name
),
file,
block,
severity: Severity::Warning,
pillar: Pillar::TestQuality,
}));
}
}
fn test_assertion_regex() -> &'static Regex {
static_regex(&TEST_ASSERTION_REGEX, TEST_ASSERTION_PATTERN)
}
pub(crate) fn analyse_test_regex_checks(
file: &SourceFile,
block: &FunctionBlock,
searchable_body: &str,
findings: &mut Vec<Finding>,
) {
for rule in TEST_CHECKS {
if !static_regex(rule.regex, rule.pattern).is_match(searchable_body) {
continue;
}
if is_test_rule_exempt(rule.rule_id, searchable_body) {
continue;
}
findings.push(block_finding(BlockFindingDescriptor {
rule_id: rule.rule_id,
message: rule.message.into(),
file,
block,
severity: Severity::Advisory,
pillar: Pillar::TestQuality,
}));
}
}
fn is_test_rule_exempt(rule_id: &str, body: &str) -> bool {
match rule_id {
"test-quality.conditional-logic" => conditional_is_platform_gate(body),
"test-quality.unwrap-in-test" => body_contains_only_assertion_subject_unwraps(body),
_ => false,
}
}
fn conditional_is_platform_gate(body: &str) -> bool {
static CFG_GATE_REGEX: OnceLock<Regex> = OnceLock::new();
let cfg_gate = static_regex(&CFG_GATE_REGEX, r"\bif\s+cfg!\s*\(");
cfg_gate.is_match(body)
}
fn body_contains_only_assertion_subject_unwraps(body: &str) -> bool {
let unwrap_call = static_regex(&UNWRAP_IN_TEST_REGEX, r"\.unwrap\(\)");
let unwrap_positions: Vec<usize> = unwrap_call
.find_iter(body)
.map(|found| found.start())
.collect();
if unwrap_positions.is_empty() {
return false;
}
let assertion_ranges = assertion_macro_ranges(body);
!assertion_ranges.is_empty()
&& unwrap_positions.iter().all(|unwrap_position| {
assertion_ranges
.iter()
.any(|(start, end)| *start <= *unwrap_position && *unwrap_position <= *end)
&& unwrap_receiver_is_call_result(body, *unwrap_position)
})
}
fn unwrap_receiver_is_call_result(body: &str, unwrap_position: usize) -> bool {
body[..unwrap_position]
.chars()
.rev()
.find(|character| !character.is_whitespace())
== Some(')')
}
fn assertion_macro_ranges(body: &str) -> Vec<(usize, usize)> {
let assertion_start = static_regex(
&ASSERTION_MACRO_START_REGEX,
r"\b(?:assert|assert_eq|assert_ne|matches|assert_matches|assert_[A-Za-z0-9_]*)!\s*\(",
);
assertion_start
.find_iter(body)
.filter_map(|found| {
let open_index = body[..found.end()].rfind('(')?;
let close_index = matching_close_paren(body, open_index)?;
Some((found.start(), close_index))
})
.collect()
}
fn matching_close_paren(body: &str, open_index: usize) -> Option<usize> {
let bytes = body.as_bytes();
if bytes.get(open_index) != Some(&b'(') {
return None;
}
let mut depth = 1usize;
for (offset, byte) in bytes[open_index + 1..].iter().enumerate() {
match byte {
b'(' => depth += 1,
b')' => {
depth = depth.saturating_sub(1);
if depth == 0 {
return Some(open_index + offset + 1);
}
}
_ => {}
}
}
None
}