use cadmpeg_core::CodecError;
use super::{admit_evaluation_cycles, CarrierConstructions};
use crate::report::check::Check;
use crate::test_support::evaluation_cycles::cyclic_model;
use crate::validate::{admit, validate_neutral};
#[test]
fn model_admission_refuses_a_malformed_curve_surface_reference_cycle() {
let (ir, curve, surface) = cyclic_model();
let expected =
format!("malformed curve/surface reference cycle: {curve} -> {surface} -> {curve}");
let report = validate_neutral(&ir, Vec::new()).expect("validate the neutral model");
assert!(report.findings.iter().any(|finding| {
finding.check == Check::ReferentialIntegrity && finding.message == expected
}));
assert!(!admit::admit(
&cadmpeg_test_support::service_decode_context(),
&ir,
admit::DRAFT_CORE_CHECKS,
Vec::new()
)
.expect("admit the model")
.is_ok());
assert!(matches!(
admit_evaluation_cycles(&cadmpeg_test_support::service_decode_context(), &ir),
Err(CodecError::Malformed(message)) if message == expected
));
}
#[test]
fn cycle_admission_preserves_zero_depth_session_refusal() {
use cadmpeg_core::decode::{DecodeArena, DecodeContext, DecodePolicy, ResourceDimension};
let (ir, _, _) = cyclic_model();
let arena = DecodeArena::new();
let mut policy = DecodePolicy::service();
policy.limits.max_recursion_depth = 0;
let (ctx, _) =
DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty root is admitted");
let Err(CodecError::ResourceLimit(limit)) = admit_evaluation_cycles(&ctx, &ir) else {
panic!("traversal frame must be refused");
};
assert_eq!(limit.dimension, ResourceDimension::RecursionDepth);
assert_eq!(limit.operation, "cycle traversal depth");
assert!(
matches!(ctx.finish_session(), Err(CodecError::ResourceLimit(sticky)) if sticky == limit)
);
}
#[test]
fn cycle_check_reports_one_cycle_for_repeated_carrier_rows() {
let (mut ir, curve, surface) = cyclic_model();
ir.model.curves.push(ir.model.curves[0].clone());
let index = crate::index::ModelIndex::new_model_only(&ir);
let ctx = cadmpeg_test_support::service_decode_context();
let mut findings = Vec::new();
super::check_evaluation_cycles(&ctx, &ir, &index, &mut findings).unwrap();
assert_eq!(findings.len(), 1);
assert_eq!(findings[0].check, Check::ReferentialIntegrity);
assert_eq!(findings[0].entity.as_deref(), Some(curve.as_str()));
assert_eq!(findings[0].related, vec![surface.as_str()]);
assert_eq!(
findings[0].message,
format!("malformed curve/surface reference cycle: {curve} -> {surface} -> {curve}")
);
}
#[test]
fn cycle_admission_admits_solved_carriers_among_many_points() {
use cadmpeg_core::decode::{DecodeArena, DecodeContext, DecodePolicy};
let mut ir = crate::examples::unit_cube().expect("cube fixture");
let point = ir.model.points[0].clone();
ir.model.points.extend(std::iter::repeat_n(point, 1_024));
let arena = DecodeArena::new();
let policy = DecodePolicy::service();
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty input");
admit_evaluation_cycles(&ctx, &ir).expect("solved carriers have no cycle index slots");
ctx.finish_session().expect("unfused session");
}
#[test]
fn cycle_admission_finds_cycles_among_many_unrelated_points() {
use cadmpeg_core::decode::{DecodeArena, DecodeContext, DecodePolicy};
let (mut ir, curve, surface) = cyclic_model();
let cube = crate::examples::unit_cube().expect("cube fixture");
ir.model
.points
.extend(std::iter::repeat_n(cube.model.points[0].clone(), 1_024));
let arena = DecodeArena::new();
let policy = DecodePolicy::service();
let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty input");
assert!(
matches!(admit_evaluation_cycles(&ctx, &ir), Err(CodecError::Malformed(message))
if message == format!("malformed curve/surface reference cycle: {curve} -> {surface} -> {curve}"))
);
ctx.finish_session()
.expect("semantic refusal does not fuse resources");
}
#[test]
fn cycle_associations_keep_the_general_index_duplicate_selection_rules() {
use crate::geometry::{
CurveGeometry, ProceduralCurve, ProceduralCurveDefinition, ProceduralSurface,
ProceduralSurfaceDefinition, SurfaceGeometry,
};
use crate::ids::{ProceduralCurveId, ProceduralSurfaceId};
let (mut ir, curve, surface) = cyclic_model();
ir.model.procedural_curves.push(ProceduralCurve::new(
ir.model.procedural_curves[0].id.clone(),
ProceduralCurveDefinition::Exact { cache: None },
));
let other_curve = ProceduralCurveId::mint("test:cycle:construction#other-curve").unwrap();
ir.model.procedural_curves.push(ProceduralCurve::new(
other_curve.clone(),
ProceduralCurveDefinition::Exact { cache: None },
));
let mut carrier = ir.model.curves[0].clone();
carrier.geometry = CurveGeometry::Procedural {
construction: other_curve,
cache: None,
};
ir.model.curves.push(carrier);
let other_surface = ProceduralSurfaceId::mint("test:cycle:construction#other-surface").unwrap();
ir.model.procedural_surfaces.push(ProceduralSurface::new(
other_surface.clone(),
ProceduralSurfaceDefinition::Replica {
source: surface.clone(),
transform: crate::transform::Transform::identity(),
},
None,
));
let mut carrier = ir.model.surfaces[0].clone();
carrier.geometry = SurfaceGeometry::Procedural {
construction: other_surface,
cache: None,
};
ir.model.surfaces.push(carrier);
let index = super::CycleIndex::build(&ir);
let general = crate::index::ModelIndex::new_model_only(&ir);
assert_eq!(index.curve(curve.as_str()), general.curve(curve.as_str()));
assert_eq!(
index.surface(surface.as_str()),
general.surface(surface.as_str())
);
assert_eq!(
index.curve(curve.as_str()),
Some(&ir.model.procedural_curves[0])
);
assert_eq!(
index.surface(surface.as_str()),
Some(&ir.model.procedural_surfaces[1])
);
}