use ifc_geometry::select::{known_entities_in, supertypes_of, supertypes_of_in};
use ifc_schema::{Schema, SchemaVersion};
use super::ifc4x3::spec_root;
use crate::IFC4X3_ADDITIONS;
fn normative() -> Option<(Schema, Schema)> {
let Some(root) = spec_root() else {
assert!(
std::env::var_os("IFC_SPEC_REQUIRED").is_none(),
"IFC_SPEC_REQUIRED is set but references/ifc-spec was not found"
);
eprintln!("skipped: references/ifc-spec not present");
return None;
};
let read = |rel: &str| {
let bytes = std::fs::read(root.join(rel)).unwrap_or_else(|e| panic!("read {rel}: {e}"));
Schema::from_express_bytes(&bytes)
};
Some((
read("ifc4-add2-tc1/IFC4.exp"),
read("ifc4x3-add2/IFC4X3_ADD2.exp"),
))
}
fn chain(schema: &Schema, entity: &str) -> Vec<String> {
schema
.supertypes(entity)
.iter()
.map(|s| s.to_ascii_uppercase())
.collect()
}
#[test]
fn the_compiled_subtype_tables_match_the_normative_express_per_release() {
let Some((ifc4, ifc4x3)) = normative() else {
return;
};
for (version, schema, file, expected_rows) in [
(SchemaVersion::Ifc4, &ifc4, "IFC4.exp", 104),
(SchemaVersion::Ifc4x3, &ifc4x3, "IFC4X3_ADD2.exp", 128),
] {
let mut wrong = Vec::new();
let mut rows = 0;
for entity in known_entities_in(version) {
rows += 1;
if schema.entity(entity).is_none() {
wrong.push(format!(" {entity}: not declared"));
continue;
}
let expected = chain(schema, entity);
let compiled = supertypes_of_in(version, entity);
if compiled != expected {
wrong.push(format!(" {entity}: {compiled:?} != {expected:?}"));
}
}
assert!(
wrong.is_empty(),
"{} compiled chains disagree with {file}:\n{}",
wrong.len(),
wrong.join("\n")
);
assert_eq!(rows, expected_rows, "{version:?} rows checked");
}
}
#[test]
fn the_ifc4x3_table_carries_every_ifc4x3_addition() {
let known: Vec<&str> = known_entities_in(SchemaVersion::Ifc4x3).collect();
let missing: Vec<&&str> = IFC4X3_ADDITIONS
.iter()
.filter(|e| !known.iter().any(|k| k.eq_ignore_ascii_case(e)))
.collect();
assert!(
missing.is_empty(),
"IFC4X3 additions without a compiled chain: {missing:?}"
);
assert_eq!(IFC4X3_ADDITIONS.len(), 19);
}
#[test]
fn the_release_neutral_chain_is_the_earliest_declaring_release() {
let Some((ifc4, ifc4x3)) = normative() else {
return;
};
for entity in known_entities_in(SchemaVersion::Ifc4x3) {
let (schema, file) = if ifc4.entity(entity).is_some() {
(&ifc4, "IFC4.exp")
} else {
(&ifc4x3, "IFC4X3_ADD2.exp")
};
assert_eq!(
supertypes_of(entity),
chain(schema, entity),
"{entity}: release-neutral chain must be {file}'s"
);
}
}