use libyang::{Diagnostic, Entry, YangStore, YangType, to_entry};
use std::rc::Rc;
fn load(name: &str, features: &[(&str, &str)]) -> (Rc<Entry>, Vec<Diagnostic>) {
let mut store = YangStore::new();
store.add_path("tests/yang");
for (module, feature) in features {
store.enable_feature(module, feature);
}
store.read_with_resolve(name).expect("parse / resolve");
store.identity_resolve();
let module = store.find_module(name).expect("module found");
let entry = to_entry(&store, module);
let diagnostics = store.take_diagnostics();
(entry, diagnostics)
}
fn find_child(ent: &Rc<Entry>, name: &str) -> Option<Rc<Entry>> {
ent.dir.borrow().iter().find(|e| e.name == name).cloned()
}
fn has_child(ent: &Rc<Entry>, name: &str) -> bool {
find_child(ent, name).is_some()
}
#[test]
fn all_features_off_prunes_guarded_nodes() {
let (root, diagnostics) = load("iffeature-base", &[]);
let routing = find_child(&root, "routing").expect("routing container");
assert!(has_child(&routing, "name"));
assert!(has_child(&routing, "tcp-port"));
for gated in [
"dhcp",
"iso-only",
"both",
"gated-dep",
"remote-gated",
"shared-leaf",
"tsel",
] {
assert!(
!has_child(&routing, gated),
"feature-gated node \"{gated}\" should be pruned by default"
);
}
assert!(
diagnostics.is_empty(),
"no diagnostics expected, got {diagnostics:?}"
);
}
#[test]
fn enable_feature_exposes_guarded_nodes() {
let (root, diagnostics) = load("iffeature-base", &[("iffeature-base", "iso")]);
let routing = find_child(&root, "routing").expect("routing container");
assert!(has_child(&routing, "dhcp"));
assert!(has_child(&routing, "shared-leaf"), "uses gated by iso");
let iso_only = find_child(&routing, "iso-only").expect("iso-only container");
assert!(has_child(&iso_only, "nsap"));
assert!(has_child(&iso_only, "net"), "augment gated by iso applies");
let tsel = find_child(&routing, "tsel").expect("tsel leaf from osi case");
assert_eq!(tsel.choice.borrow().as_deref(), Some("transport"));
assert_eq!(tsel.case.borrow().as_deref(), Some("osi"));
assert!(!has_child(&routing, "both"), "extra is still off");
assert!(!has_child(&routing, "gated-dep"), "dep is still off");
assert!(!has_child(&routing, "remote-gated"));
assert!(
diagnostics.is_empty(),
"no diagnostics expected, got {diagnostics:?}"
);
}
#[test]
fn compound_expression_needs_both_features() {
let (root, _) = load(
"iffeature-base",
&[("iffeature-base", "iso"), ("iffeature-base", "extra")],
);
let routing = find_child(&root, "routing").expect("routing container");
assert!(has_child(&routing, "both"), "if-feature \"iso and extra\"");
}
#[test]
fn dependent_feature_requires_its_own_if_feature() {
let (root, _) = load("iffeature-base", &[("iffeature-base", "dep")]);
let routing = find_child(&root, "routing").expect("routing container");
assert!(
!has_child(&routing, "gated-dep"),
"dep without iso must stay disabled"
);
let (root, _) = load(
"iffeature-base",
&[("iffeature-base", "dep"), ("iffeature-base", "iso")],
);
let routing = find_child(&root, "routing").expect("routing container");
assert!(has_child(&routing, "gated-dep"));
}
#[test]
fn prefixed_reference_resolves_to_imported_module() {
let (root, _) = load("iffeature-base", &[("iffeature-features", "remote")]);
let routing = find_child(&root, "routing").expect("routing container");
assert!(has_child(&routing, "remote-gated"));
let (root, _) = load("iffeature-base", &[("iffeature-base", "remote")]);
let routing = find_child(&root, "routing").expect("routing container");
assert!(!has_child(&routing, "remote-gated"));
}
#[test]
fn unknown_feature_reference_is_diagnosed_and_disabled() {
let (root, diagnostics) = load("iffeature-unknown", &[]);
let top_box = find_child(&root, "top-box").expect("top-box container");
assert!(has_child(&top_box, "fine"));
assert!(!has_child(&top_box, "oops"), "dangling reference disables");
assert!(
diagnostics.contains(&Diagnostic::UnknownFeature {
module: "iffeature-unknown".to_string(),
feature: "no-such".to_string(),
}),
"expected UnknownFeature diagnostic, got {diagnostics:?}"
);
}
#[test]
fn feature_gated_enum_arms_are_filtered() {
let enum_names = |e: &Rc<Entry>| -> Vec<String> {
e.type_node
.as_ref()
.expect("type resolved")
.enum_stmt
.iter()
.map(|en| en.name.clone())
.collect()
};
let union_enum_arm = |e: &Rc<Entry>| -> Vec<String> {
e.type_node
.as_ref()
.expect("type resolved")
.union
.iter()
.find(|arm| arm.kind == YangType::Enumeration)
.expect("enumeration arm in union")
.enum_stmt
.iter()
.map(|en| en.name.clone())
.collect()
};
let (root, diagnostics) = load("iffeature-base", &[]);
let routing = find_child(&root, "routing").expect("routing container");
let mode = find_child(&routing, "transport-mode").expect("transport-mode leaf");
assert_eq!(enum_names(&mode), ["tcp"]);
let addr = find_child(&routing, "addr").expect("addr leaf-list");
assert_eq!(union_enum_arm(&addr), ["none"]);
assert!(
diagnostics.is_empty(),
"no diagnostics expected, got {diagnostics:?}"
);
let (root, _) = load("iffeature-base", &[("iffeature-base", "iso")]);
let routing = find_child(&root, "routing").expect("routing container");
let mode = find_child(&routing, "transport-mode").expect("transport-mode leaf");
assert_eq!(enum_names(&mode), ["tcp", "osi-tp"]);
let addr = find_child(&routing, "addr").expect("addr leaf-list");
assert_eq!(union_enum_arm(&addr), ["dhcp", "none"]);
}
#[test]
fn feature_statements_are_captured_in_the_ast() {
let mut store = YangStore::new();
store.add_path("tests/yang");
store
.read_with_resolve("iffeature-base")
.expect("parse / resolve");
let module = store.find_module("iffeature-base").expect("module found");
let names: Vec<&str> = module.feature.iter().map(|f| f.name.as_str()).collect();
assert_eq!(names, ["iso", "extra", "dep"]);
let iso = &module.feature[0];
assert_eq!(
iso.description.as_deref(),
Some("ISO / OSI protocol support")
);
assert!(iso.if_feature.is_empty());
let dep = &module.feature[2];
assert_eq!(dep.if_feature.len(), 1, "dep depends on iso");
}