use super::*;
#[test]
fn is_type_excluded_plain_entry_matches_by_name() {
let exclude = vec!["OutputFormat".to_string()];
assert!(
is_type_excluded("OutputFormat", "sample_crate::types::OutputFormat", &exclude),
"plain entry must match when name matches"
);
assert!(
!is_type_excluded("SomethingElse", "sample_crate::types::SomethingElse", &exclude),
"plain entry must not match when name differs"
);
}
#[test]
fn is_type_excluded_qualified_entry_matches_rust_path_not_name() {
let exclude = vec!["sample_crate::core::config::formats::OutputFormat".to_string()];
assert!(
is_type_excluded(
"OutputFormat",
"sample_crate::core::config::formats::OutputFormat",
&exclude
),
"qualified entry must match the exact rust_path"
);
assert!(
!is_type_excluded("OutputFormat", "sample_crate::types::OutputFormat", &exclude),
"qualified entry must NOT match a different rust_path with the same short name"
);
}
#[test]
fn is_type_excluded_normalises_hyphens_in_rust_path() {
let exclude = vec!["my_crate::some_module::Foo".to_string()];
assert!(
is_type_excluded("Foo", "my-crate::some_module::Foo", &exclude),
"hyphens in rust_path should be normalised to underscores"
);
}
#[test]
fn apply_filters_rejects_include_types_entry_that_matches_nothing() {
let surface = surface_with(
vec![make_typedef("Kept"), make_typedef("Other")],
vec![make_funcdef("do_it", TypeRef::Unit, vec![])],
);
let mut config = ResolvedCrateConfig::default();
config.include.types = vec!["Kpet".to_string()];
let error = apply_filters(surface, &config).expect_err("an include.types typo must not silently pass");
let message = error.to_string();
assert!(
message.contains("`Kpet`") && message.contains("[crates.include].types"),
"error must name the unmatched entry and the config key it came from, got: {message}"
);
}
#[test]
fn apply_filters_rejects_include_types_when_only_some_entries_match() {
let surface = surface_with(vec![make_typedef("Kept"), make_typedef("Other")], vec![]);
let mut config = ResolvedCrateConfig::default();
config.include.types = vec!["Kept".to_string(), "Gone".to_string()];
let error = apply_filters(surface, &config).expect_err("an unmatched entry must fail even alongside matched ones");
let message = error.to_string();
assert!(
message.contains("`Gone`") && !message.contains("`Kept`"),
"error must name only the unmatched entry, got: {message}"
);
}
#[test]
fn apply_filters_include_types_accepts_qualified_path_entry() {
let surface = surface_with(vec![make_typedef("Kept"), make_typedef("Other")], vec![]);
let mut config = ResolvedCrateConfig::default();
config.include.types = vec!["my_crate::Kept".to_string()];
let result = apply_filters(surface, &config).expect("a qualified include entry must resolve");
let names: Vec<&str> = result.types.iter().map(|t| t.name.as_str()).collect();
assert_eq!(
names,
vec!["Kept"],
"qualified include entry must retain exactly the type it names"
);
}
#[test]
fn apply_filters_include_types_accepts_declared_opaque_type_name() {
let surface = surface_with(vec![make_typedef("Kept")], vec![]);
let mut config = ResolvedCrateConfig::default();
config.include.types = vec!["Kept".to_string(), "Handle".to_string()];
config
.opaque_types
.insert("Handle".to_string(), "my_crate::Handle".to_string());
apply_filters(surface, &config).expect("a declared opaque type is a valid include entry");
}
#[test]
fn apply_filters_rejects_include_functions_entry_that_matches_nothing() {
let surface = surface_with(vec![], vec![make_funcdef("do_it", TypeRef::Unit, vec![])]);
let mut config = ResolvedCrateConfig::default();
config.include.functions = vec!["do_ti".to_string()];
let error = apply_filters(surface, &config).expect_err("an include.functions typo must not silently pass");
let message = error.to_string();
assert!(
message.contains("`do_ti`") && message.contains("[crates.include].functions"),
"error must name the unmatched entry and the config key it came from, got: {message}"
);
}
#[test]
fn is_type_excluded_accepts_the_same_crate_qualified_shorthand_as_exclude_fields() {
let exclude = vec!["my_crate::Foo".to_string()];
assert!(
is_type_excluded("Foo", "my_crate::inner::Foo", &exclude),
"`crate::Type` shorthand must exclude the type the identical exclude.fields entry matches"
);
assert!(
!is_type_excluded("Foo", "other_crate::inner::Foo", &exclude),
"`crate::Type` shorthand must not reach into a different crate"
);
assert!(
!is_type_excluded("Bar", "my_crate::inner::Bar", &exclude),
"`crate::Type` shorthand must not match a different type name"
);
}
#[test]
fn unmatched_exclude_entries_reports_every_list_that_names_nothing() {
let mut kept = make_typedef("Kept");
kept.methods.push(crate::core::ir::MethodDef {
name: "run".to_string(),
..Default::default()
});
let surface = surface_with(vec![kept], vec![make_funcdef("do_it", TypeRef::Unit, vec![])]);
let exclude = crate::core::config::ExcludeConfig {
types: vec!["Kpet".to_string()],
functions: vec!["do_ti".to_string()],
methods: vec!["Kept.walk".to_string()],
fields: vec![],
};
let mut unmatched = unmatched_exclude_entries(&surface, &exclude);
unmatched.sort();
assert_eq!(
unmatched,
vec![
("functions", "do_ti".to_string()),
("methods", "Kept.walk".to_string()),
("types", "Kpet".to_string()),
],
"each exclude list must surface the entry that matched nothing"
);
}
#[test]
fn unmatched_exclude_entries_stays_silent_when_every_entry_matches() {
let mut kept = make_typedef("Kept");
kept.methods.push(crate::core::ir::MethodDef {
name: "run".to_string(),
..Default::default()
});
let surface = surface_with(vec![kept], vec![make_funcdef("do_it", TypeRef::Unit, vec![])]);
let exclude = crate::core::config::ExcludeConfig {
types: vec!["Kept".to_string()],
functions: vec!["do_it".to_string()],
methods: vec!["Kept.run".to_string()],
fields: vec![],
};
assert!(
unmatched_exclude_entries(&surface, &exclude).is_empty(),
"matched exclude entries must not be reported"
);
}
fn make_errordef(name: &str, rust_path: &str) -> crate::core::ir::ErrorDef {
crate::core::ir::ErrorDef {
name: name.to_string(),
rust_path: rust_path.to_string(),
original_rust_path: String::new(),
variants: Vec::new(),
doc: String::new(),
methods: Vec::new(),
binding_excluded: false,
binding_exclusion_reason: None,
version: Default::default(),
}
}
#[test]
fn apply_filters_include_types_accepts_the_crates_error_enum() {
let mut surface = surface_with(vec![make_typedef("Kept")], vec![]);
surface.errors.push(make_errordef("Error", "my_crate::error::Error"));
let mut config = ResolvedCrateConfig::default();
config.include.types = vec!["Kept".to_string(), "Error".to_string()];
let result = apply_filters(surface, &config).expect("the crate's error enum is a valid include entry");
let names: Vec<&str> = result.types.iter().map(|t| t.name.as_str()).collect();
assert_eq!(names, vec!["Kept"], "the named type must still be the one retained");
assert_eq!(
result.errors.len(),
1,
"errors are never include-filtered and must survive"
);
}
#[test]
fn apply_filters_include_types_accepts_qualified_error_enum_path() {
let mut surface = surface_with(vec![make_typedef("Kept")], vec![]);
surface.errors.push(make_errordef("Error", "my_crate::error::Error"));
let mut config = ResolvedCrateConfig::default();
config.include.types = vec!["Kept".to_string(), "my_crate::error::Error".to_string()];
apply_filters(surface, &config).expect("a qualified error-enum entry must resolve");
}
#[test]
fn apply_filters_rejects_include_types_that_would_empty_the_surface() {
let mut surface = surface_with(vec![make_typedef("Kept")], vec![]);
surface.errors.push(make_errordef("Error", "my_crate::error::Error"));
let mut config = ResolvedCrateConfig::default();
config.include.types = vec!["Error".to_string()];
let err = apply_filters(surface, &config)
.expect_err("an include list that seeds no type or enum must abort, not empty the binding");
let message = err.to_string();
assert!(
message.contains("every binding would be emptied"),
"the error must name the consequence; got: {message}"
);
}