use crate::adapters::analyzers::architecture::matcher::find_path_prefix_matches;
use crate::adapters::analyzers::architecture::ViolationKind;
fn parse(src: &str) -> syn::File {
syn::parse_str(src).expect("test fixture must parse")
}
fn find(
src: &str,
prefixes: &[&str],
) -> Vec<crate::adapters::analyzers::architecture::MatchLocation> {
let ast = parse(src);
let owned: Vec<String> = prefixes.iter().map(|s| (*s).to_string()).collect();
find_path_prefix_matches("fixture.rs", &ast, &owned)
}
#[test]
fn clean_file_produces_no_matches() {
let src = r#"
use std::collections::HashMap;
fn foo() -> Option<HashMap<String, i32>> { None }
"#;
let hits = find(src, &["tokio::"]);
assert!(
hits.is_empty(),
"clean file should produce no hits: {hits:?}"
);
}
#[test]
fn matches_use_statement() {
let src = r#"
use tokio::spawn;
"#;
let hits = find(src, &["tokio::"]);
assert_eq!(hits.len(), 1, "expected exactly one hit: {hits:?}");
match &hits[0].kind {
ViolationKind::PathPrefix {
prefix,
rendered_path,
} => {
assert_eq!(prefix, "tokio::");
assert!(rendered_path.starts_with("tokio::"));
}
other => panic!("unexpected kind: {other:?}"),
}
}
#[test]
fn matches_nested_use_groups() {
let src = r#"
use tokio::{spawn, sync::Mutex};
"#;
let hits = find(src, &["tokio::"]);
assert!(!hits.is_empty(), "nested use must match");
}
#[test]
fn matches_function_call_path() {
let src = r#"
fn run() {
tokio::spawn(async {});
}
"#;
let hits = find(src, &["tokio::"]);
assert!(
hits.iter().any(|h| matches!(
&h.kind,
ViolationKind::PathPrefix { prefix, .. } if prefix == "tokio::"
)),
"function call path should match: {hits:?}"
);
}
#[test]
fn matches_attribute_path() {
let src = r#"
#[tokio::main]
async fn main() {}
"#;
let hits = find(src, &["tokio::"]);
assert!(
hits.iter().any(|h| matches!(
&h.kind,
ViolationKind::PathPrefix { rendered_path, .. } if rendered_path.starts_with("tokio::")
)),
"attribute path should match: {hits:?}"
);
}
#[test]
fn matches_impl_trait_for_type() {
let src = r#"
struct X;
impl tokio::io::AsyncRead for X {}
"#;
let hits = find(src, &["tokio::"]);
assert!(!hits.is_empty(), "impl trait path should match: {hits:?}");
}
#[test]
fn matches_return_type() {
let src = r#"
fn foo() -> tokio::io::Result<()> { unimplemented!() }
"#;
let hits = find(src, &["tokio::"]);
assert!(!hits.is_empty(), "return type should match: {hits:?}");
}
#[test]
fn matches_let_type_annotation() {
let src = r#"
fn foo() {
let _x: tokio::sync::Mutex<i32>;
}
"#;
let hits = find(src, &["tokio::"]);
assert!(
!hits.is_empty(),
"let type annotation should match: {hits:?}"
);
}
#[test]
fn matches_struct_field_type() {
let src = r#"
struct Holder {
lock: tokio::sync::Mutex<i32>,
}
"#;
let hits = find(src, &["tokio::"]);
assert!(!hits.is_empty(), "struct field type should match: {hits:?}");
}
#[test]
fn matches_generic_bound() {
let src = r#"
fn generic<T: tokio::io::AsyncRead>(_t: T) {}
"#;
let hits = find(src, &["tokio::"]);
assert!(!hits.is_empty(), "generic bound should match: {hits:?}");
}
#[test]
fn matches_where_clause_bound() {
let src = r#"
fn generic<T>(_t: T) where T: tokio::io::AsyncRead {}
"#;
let hits = find(src, &["tokio::"]);
assert!(
!hits.is_empty(),
"where-clause bound should match: {hits:?}"
);
}
#[test]
fn matches_extern_crate() {
let src = r#"
extern crate tokio;
"#;
let hits = find(src, &["tokio"]);
assert!(!hits.is_empty(), "extern crate should match: {hits:?}");
}
#[test]
fn reports_each_occurrence_separately() {
let src = r#"
use tokio::spawn;
fn run() {
tokio::spawn(async {});
tokio::spawn(async {});
}
"#;
let hits = find(src, &["tokio::"]);
assert!(
hits.len() >= 3,
"expected ≥3 hits (one use + two calls), got {}: {hits:?}",
hits.len()
);
}
#[test]
fn multiple_prefixes_both_match() {
let src = r#"
use tokio::spawn;
use anyhow::Result;
"#;
let hits = find(src, &["tokio::", "anyhow::"]);
let has_tokio = hits.iter().any(|h| {
matches!(
&h.kind,
ViolationKind::PathPrefix { prefix, .. } if prefix == "tokio::"
)
});
let has_anyhow = hits.iter().any(|h| {
matches!(
&h.kind,
ViolationKind::PathPrefix { prefix, .. } if prefix == "anyhow::"
)
});
assert!(
has_tokio && has_anyhow,
"both prefixes should produce hits: {hits:?}"
);
}
#[test]
fn does_not_match_string_containing_prefix() {
let src = r#"
fn foo() {
let s = "tokio::spawn is great";
// tokio::spawn mentioned in comment
let _ = s;
}
"#;
let hits = find(src, &["tokio::"]);
assert!(
hits.is_empty(),
"string and comment mentions must not match: {hits:?}"
);
}
#[test]
fn does_not_match_similar_but_different_prefix() {
let src = r#"
use tokio_util::codec::Decoder;
"#;
let hits = find(src, &["tokio::"]);
assert!(
hits.is_empty(),
"tokio_util is not tokio:: and must not match: {hits:?}"
);
}
#[test]
fn bare_crate_use_matches_trailing_colon_prefix() {
let src = r#"
use tokio;
"#;
let hits = find(src, &["tokio::"]);
assert_eq!(
hits.len(),
1,
"`use tokio;` must match the `tokio::` prefix: {hits:?}"
);
}
#[test]
fn bare_crate_rename_matches_trailing_colon_prefix() {
let src = r#"
use tokio as t;
"#;
let hits = find(src, &["tokio::"]);
assert_eq!(
hits.len(),
1,
"`use tokio as t;` must match the `tokio::` prefix: {hits:?}"
);
}
#[test]
fn bare_prefix_matches_exact_and_segment_boundary_only() {
let src = r#"
use tokio_util::codec::Decoder;
"#;
let hits = find(src, &["tokio"]);
assert!(
hits.is_empty(),
"bare prefix `tokio` must not match `tokio_util`: {hits:?}"
);
}