use super::*;
use ifc_lite_core::{EntityDecoder, IfcSchema};
fn fan_out_ifc(k: u32) -> String {
let segs: Vec<String> = (0..k).map(|i| format!("#{}", 100 + i)).collect();
let mut d = format!("#10=IFCCOMPOSITECURVE(({}),.F.);\n", segs.join(","));
for i in 0..k {
d.push_str(&format!(
"#{}=IFCCOMPOSITECURVESEGMENT(.CONTINUOUS.,.T.,#10);\n",
100 + i
));
}
format!(
"ISO-10303-21;\nHEADER;\nFILE_DESCRIPTION((''),'2;1');\n\
FILE_NAME('t.ifc','2024-01-01T00:00:00',(''),(''),'','','');\n\
FILE_SCHEMA(('IFC4'));\nENDSEC;\nDATA;\n{d}ENDSEC;\nEND-ISO-10303-21;\n"
)
}
#[test]
fn a_self_referential_curve_stops_on_the_depth_cap() {
for k in 1..=3u32 {
let ifc = fan_out_ifc(k);
let mut decoder = EntityDecoder::new(&ifc);
let curve = decoder.decode_by_id(10).expect("decode #10");
let processor = ProfileProcessor::new(IfcSchema::new());
let err = processor
.get_curve_points(&curve, &mut decoder, TessellationQuality::Medium)
.expect_err("a self-referential composite curve must report the depth limit");
assert!(
err.to_string().contains("Curve nesting depth"),
"k={k}: the cap must be the thing that stops it, got: {err}"
);
}
}
fn dag_ifc(levels: u32) -> String {
let mut d = String::new();
for i in 0..levels {
let cc = 10 + i;
let next = if i + 1 == levels { 9000 } else { 10 + i + 1 };
let (s1, s2) = (1000 + i * 2, 1001 + i * 2);
d.push_str(&format!("#{cc}=IFCCOMPOSITECURVE((#{s1},#{s2}),.F.);\n"));
d.push_str(&format!("#{s1}=IFCCOMPOSITECURVESEGMENT(.CONTINUOUS.,.T.,#{next});\n"));
d.push_str(&format!("#{s2}=IFCCOMPOSITECURVESEGMENT(.CONTINUOUS.,.T.,#{next});\n"));
}
d.push_str("#9000=IFCPOLYLINE((#9001,#9002));\n");
d.push_str("#9001=IFCCARTESIANPOINT((0.,0.,0.));\n");
d.push_str("#9002=IFCCARTESIANPOINT((1.,0.,0.));\n");
format!(
"ISO-10303-21;\nHEADER;\nFILE_DESCRIPTION((''),'2;1');\n\
FILE_NAME('t.ifc','2024-01-01T00:00:00',(''),(''),'','','');\n\
FILE_SCHEMA(('IFC4'));\nENDSEC;\nDATA;\n{d}ENDSEC;\nEND-ISO-10303-21;\n"
)
}
fn sample_dag(levels: u32) -> Result<Vec<Point3<f64>>> {
let ifc = dag_ifc(levels);
let mut decoder = EntityDecoder::new(&ifc);
let curve = decoder.decode_by_id(10).expect("decode #10");
ProfileProcessor::new(IfcSchema::new()).get_curve_points(
&curve,
&mut decoder,
TessellationQuality::Medium,
)
}
#[test]
fn a_modest_acyclic_dag_still_resolves_completely() {
assert_eq!(sample_dag(8).expect("8 levels must resolve").len(), 257);
}
#[test]
fn a_wide_acyclic_dag_is_bounded_by_the_node_budget() {
let err = sample_dag(30).expect_err("a 2^30 traversal must be refused, not attempted");
assert!(
err.to_string().contains("Curve traversal exceeded"),
"the CURVE-VISIT budget must be what stops it -- `contains(\"exceeded\")` \
alone would also pass on any other limit's message (CodeRabbit, #2876 \
review). Got: {err}"
);
}