use super::*;
#[test]
fn test_cfg_test_method_excluded_feature_method_retained() {
let source = r#"
pub struct HeuristicsConfig {
pub threshold: u32,
}
impl HeuristicsConfig {
pub fn new() -> Self {
Self { threshold: 100 }
}
#[cfg(test)]
pub fn test_config() -> Self {
Self { threshold: 1 }
}
#[cfg(feature = "x")]
pub fn feature_config() -> Self {
Self { threshold: 50 }
}
}
"#;
let surface = extract_from_source(source);
let config = surface
.types
.iter()
.find(|t| t.name == "HeuristicsConfig")
.expect("HeuristicsConfig should be extracted");
let method_names: Vec<&str> = config.methods.iter().map(|m| m.name.as_str()).collect();
assert!(
!method_names.contains(&"test_config"),
"#[cfg(test)] method must be excluded, got {method_names:?}"
);
assert!(
method_names.contains(&"feature_config"),
"#[cfg(feature = \"x\")] method must be retained, got {method_names:?}"
);
let feature_config = config
.methods
.iter()
.find(|m| m.name == "feature_config")
.expect("feature_config should be extracted");
assert_eq!(
feature_config.cfg.as_deref(),
Some("feature = \"x\""),
"a gated method must carry its own cfg into the IR"
);
}
#[test]
fn method_cfg_and_combines_with_its_impl_block_cfg() {
let source = r#"
pub struct Gauge {
pub value: u32,
}
#[cfg(feature = "metrics")]
impl Gauge {
pub fn read(&self) -> u32 {
self.value
}
#[cfg(feature = "histogram")]
pub fn buckets(&self) -> u32 {
self.value
}
}
"#;
let surface = extract_from_source(source);
let gauge = surface
.types
.iter()
.find(|t| t.name == "Gauge")
.expect("Gauge should be extracted");
let read = gauge.methods.iter().find(|m| m.name == "read").expect("read");
assert_eq!(
read.cfg.as_deref(),
Some("feature = \"metrics\""),
"an ungated method inherits its impl block's gate verbatim"
);
let buckets = gauge.methods.iter().find(|m| m.name == "buckets").expect("buckets");
assert_eq!(
buckets.cfg.as_deref(),
Some("all(feature = \"metrics\", feature = \"histogram\")"),
"a gated method AND-combines with its impl block's gate"
);
}
#[test]
fn gated_method_survives_extraction_then_drops_for_a_language_without_the_feature() {
let source = r#"
pub struct Client {
pub id: u32,
}
impl Client {
pub fn ping(&self) -> u32 {
self.id
}
#[cfg(feature = "streaming")]
pub fn stream(&self) -> u32 {
self.id
}
}
"#;
let surface = extract_from_source(source);
let extracted: Vec<&str> = surface.types[0].methods.iter().map(|m| m.name.as_str()).collect();
assert_eq!(
extracted,
vec!["ping", "stream"],
"half one: extraction retains the gated method"
);
let with_feature: std::collections::HashSet<&str> = ["streaming"].into_iter().collect();
let kept = surface.with_cfg_filtered_deep(&with_feature);
let kept_names: Vec<&str> = kept.types[0].methods.iter().map(|m| m.name.as_str()).collect();
assert_eq!(
kept_names,
vec!["ping", "stream"],
"half two: a language whose feature set satisfies the gate keeps the method"
);
let without_feature: std::collections::HashSet<&str> = ["other"].into_iter().collect();
let dropped = surface.with_cfg_filtered_deep(&without_feature);
let dropped_names: Vec<&str> = dropped.types[0].methods.iter().map(|m| m.name.as_str()).collect();
assert_eq!(
dropped_names,
vec!["ping"],
"half two: a language whose feature set does not satisfy the gate drops the method, and \
the ungated control is unaffected"
);
}
#[test]
fn test_cfg_test_impl_block_excluded() {
let source = r#"
pub struct Widget {
pub size: u32,
}
impl Widget {
pub fn real_method(&self) -> u32 {
self.size
}
}
#[cfg(test)]
impl Widget {
pub fn fixture() -> Self {
Self { size: 7 }
}
}
"#;
let surface = extract_from_source(source);
let widget = surface
.types
.iter()
.find(|t| t.name == "Widget")
.expect("Widget should be extracted");
let method_names: Vec<&str> = widget.methods.iter().map(|m| m.name.as_str()).collect();
assert!(
method_names.contains(&"real_method"),
"method from normal impl must be retained, got {method_names:?}"
);
assert!(
!method_names.contains(&"fixture"),
"method from #[cfg(test)] impl must be excluded, got {method_names:?}"
);
}
#[test]
fn test_cfg_test_top_level_items_excluded() {
let source = r#"
pub struct NormalType {
pub value: u32,
}
#[cfg(test)]
pub struct TestOnlyType {
pub value: u32,
}
#[cfg(feature = "x")]
pub struct FeatureType {
pub value: u32,
}
pub fn normal_fn() -> u32 {
1
}
#[cfg(test)]
pub fn test_only_fn() -> u32 {
2
}
#[cfg(all(test, feature = "x"))]
pub fn nested_test_fn() -> u32 {
3
}
#[cfg(not(test))]
pub fn non_test_fn() -> u32 {
4
}
"#;
let surface = extract_from_source(source);
let type_names: Vec<&str> = surface.types.iter().map(|t| t.name.as_str()).collect();
assert!(type_names.contains(&"NormalType"), "got {type_names:?}");
assert!(type_names.contains(&"FeatureType"), "got {type_names:?}");
assert!(
!type_names.contains(&"TestOnlyType"),
"#[cfg(test)] struct must be excluded, got {type_names:?}"
);
let fn_names: Vec<&str> = surface.functions.iter().map(|f| f.name.as_str()).collect();
assert!(fn_names.contains(&"normal_fn"), "got {fn_names:?}");
assert!(
fn_names.contains(&"non_test_fn"),
"#[cfg(not(test))] fn must be retained, got {fn_names:?}"
);
assert!(
!fn_names.contains(&"test_only_fn"),
"#[cfg(test)] fn must be excluded, got {fn_names:?}"
);
assert!(
!fn_names.contains(&"nested_test_fn"),
"#[cfg(all(test, ...))] fn must be excluded, got {fn_names:?}"
);
}
#[test]
fn module_cfg_applies_when_the_gated_module_is_not_the_first_source() {
let dir = tempfile::tempdir().expect("tempdir");
let other_rs = dir.path().join("other.rs");
std::fs::write(&other_rs, "pub fn unrelated() -> u32 { 0 }\n").expect("write other.rs");
let lib_rs = dir.path().join("lib.rs");
std::fs::write(
&lib_rs,
r#"
#[cfg(feature = "metrics")]
pub mod metrics {
pub fn record_cost(system: &str) -> u32 { system.len() as u32 }
}
"#,
)
.expect("write lib.rs");
let surface =
super::extract(&[other_rs.as_path(), lib_rs.as_path()], "my_crate", "0.0.0", None).expect("extract failed");
let record_cost = surface
.functions
.iter()
.find(|f| f.name == "record_cost")
.expect("record_cost should be extracted");
assert_eq!(
record_cost.cfg.as_deref(),
Some("feature = \"metrics\""),
"item under a gated module must carry the module's cfg regardless of source order"
);
let unrelated = surface
.functions
.iter()
.find(|f| f.name == "unrelated")
.expect("unrelated should be extracted");
assert_eq!(
unrelated.cfg, None,
"an ungated item must not inherit an unrelated module's cfg"
);
}