use super::*;
use std::cell::Cell;
thread_local! {
static COMMENT_AWARE_CALLS: Cell<usize> = const { Cell::new(0) };
}
pub(super) fn mark_comment_aware_call() {
COMMENT_AWARE_CALLS.with(|calls| calls.set(calls.get() + 1));
}
fn comment_aware_calls() -> usize {
COMMENT_AWARE_CALLS.with(Cell::get)
}
#[test]
fn comment_free_lists_stay_out_of_the_comment_aware_loops() {
COMMENT_AWARE_CALLS.with(|calls| calls.set(0));
assert_eq!(parse_coordinates_direct(b"((1.,2.,3.))"), [1.0, 2.0, 3.0]);
assert_eq!(
parse_coordinates_direct_f64(b"((1.,2.,3.))"),
[1.0, 2.0, 3.0]
);
assert_eq!(parse_indices_direct(b"((1,2,3))"), [0, 1, 2]);
assert_eq!(comment_aware_calls(), 0);
assert_eq!(
parse_coordinates_direct(b"((1.,/* 9 */2.,3.))"),
[1.0, 2.0, 3.0]
);
assert_eq!(
parse_coordinates_direct_f64(b"((1.,/* 9 */2.,3.))"),
[1.0, 2.0, 3.0]
);
assert_eq!(parse_indices_direct(b"((1,/* 9 */2,3))"), [0, 1, 2]);
assert_eq!(comment_aware_calls(), 3);
}
#[test]
fn test_parse_coordinates_direct() {
let bytes = b"((0.,0.,150.),(0.,40.,140.),(100.,0.,0.))";
let coords = parse_coordinates_direct(bytes);
assert_eq!(coords.len(), 9);
assert!((coords[0] - 0.0).abs() < 0.001);
assert!((coords[1] - 0.0).abs() < 0.001);
assert!((coords[2] - 150.0).abs() < 0.001);
assert!((coords[3] - 0.0).abs() < 0.001);
assert!((coords[4] - 40.0).abs() < 0.001);
assert!((coords[5] - 140.0).abs() < 0.001);
}
#[test]
fn test_parse_indices_direct() {
let bytes = b"((1,2,3),(2,1,4),(5,6,7))";
let indices = parse_indices_direct(bytes);
assert_eq!(indices.len(), 9);
assert_eq!(indices[0], 0); assert_eq!(indices[1], 1); assert_eq!(indices[2], 2); assert_eq!(indices[3], 1); assert_eq!(indices[4], 0); assert_eq!(indices[5], 3); }
#[test]
fn test_parse_indices_direct_rejects_out_of_range() {
let bytes = b"((4294967297,1,2))";
let indices = parse_indices_direct(bytes);
assert_eq!(indices.len(), 3);
assert_eq!(
indices[0],
u32::MAX - 1,
"overflowing index must saturate, not wrap to a valid vertex"
);
assert_eq!(indices[1], 0); assert_eq!(indices[2], 1); }
#[test]
fn test_parse_indices_direct_boundary_values() {
let bytes = b"((4294967295,4294967296,4294967294))";
let indices = parse_indices_direct(bytes);
assert_eq!(
indices,
vec![u32::MAX - 1, u32::MAX - 1, u32::MAX - 2],
"boundary values must saturate deterministically"
);
}
#[test]
fn test_parse_indices_direct_overflow_consumes_all_digits() {
let bytes = b"((99999999999999999999999999,2))";
let indices = parse_indices_direct(bytes);
assert_eq!(indices, vec![u32::MAX - 1, 1]);
}
#[test]
fn test_parse_indices_direct_matches_old_wrapping_for_in_range_values() {
fn old_parse(bytes: &[u8]) -> Vec<u32> {
let mut result = Vec::new();
let mut pos = 0;
let len = bytes.len();
while pos < len {
while pos < len && !bytes[pos].is_ascii_digit() {
pos += 1;
}
if pos >= len {
break;
}
let mut value: u32 = 0;
while pos < len && bytes[pos].is_ascii_digit() {
value = value
.wrapping_mul(10)
.wrapping_add((bytes[pos] - b'0') as u32);
pos += 1;
}
result.push(value.saturating_sub(1));
}
result
}
let cases: &[&[u8]] = &[
b"((1,2,3),(2,1,4),(5,6,7))",
b"((0,1,4294967295))", b"((429496729,1000000000,999999999))",
b"((10,200,3000),(40000,500000,6000000))",
];
for bytes in cases {
assert_eq!(
parse_indices_direct(bytes),
old_parse(bytes),
"in-range parse changed for {:?}",
std::str::from_utf8(bytes).unwrap()
);
}
}
#[test]
fn test_parse_scientific_notation() {
let bytes = b"((1.5E-10,2.0e+5,-3.14))";
let coords = parse_coordinates_direct(bytes);
assert_eq!(coords.len(), 3);
assert!((coords[0] - 1.5e-10).abs() < 1e-15);
assert!((coords[1] - 2.0e5).abs() < 1.0);
assert!((coords[2] - (-std::f32::consts::PI)).abs() < 0.01);
}
#[test]
fn test_parse_negative_numbers() {
let bytes = b"((-1.0,-2.5,3.0))";
let coords = parse_coordinates_direct(bytes);
assert_eq!(coords.len(), 3);
assert!((coords[0] - (-1.0)).abs() < 0.001);
assert!((coords[1] - (-2.5)).abs() < 0.001);
assert!((coords[2] - 3.0).abs() < 0.001);
}
#[test]
fn test_extract_coordinate_list() {
let entity = b"#78=IFCCARTESIANPOINTLIST3D(((0.,0.,150.),(100.,0.,0.)));";
let coords = extract_coordinate_list_from_entity(entity).unwrap();
assert_eq!(coords.len(), 6);
assert!((coords[0] - 0.0).abs() < 0.001);
assert!((coords[2] - 150.0).abs() < 0.001);
assert!((coords[3] - 100.0).abs() < 0.001);
}
#[test]
fn coordinate_list_is_attribute_zero_found_by_depth() {
let read = extract_coordinate_list_from_entity;
let tagged = b"#1=IFCCARTESIANPOINTLIST3D(((0.,1.,2.)),('P9'));";
assert_eq!(read(tagged).unwrap(), [0.0, 1.0, 2.0]);
let spaced = b"#1=IFCCARTESIANPOINTLIST3D( /* c) */\t( (0.,1.,2.) ),$);";
assert_eq!(read(spaced).unwrap(), [0.0, 1.0, 2.0]);
let head = b"#1=IFCCARTESIANPOINTLIST3D /* (v2) */ (((0.,1.,2.)),$);";
assert_eq!(read(head).unwrap(), [0.0, 1.0, 2.0]);
assert!(read(b"#1=IFCCARTESIANPOINTLIST3D($,(('P1')));").is_none());
assert!(read(b"#1=IFCCARTESIANPOINTLIST3D(((0.,1.,2.)").is_none());
}
#[test]
fn process_triangulated_faceset_direct_does_not_read_the_record_prefix_as_coordinates() {
let faceset = b"#77=IFCTRIANGULATEDFACESET(#78,$,$,((1,2,3)),$);";
let points: &[u8] = b"#78=IFCCARTESIANPOINTLIST3D(((0.,0.,150.),(0.,40.,140.),(100.,0.,0.)));";
let mesh =
process_triangulated_faceset_direct(faceset, |id| (id == 78).then(|| points.to_vec()))
.expect("a well-formed faceset with a resolvable point list");
assert_eq!(
mesh.positions,
[0.0, 0.0, 150.0, 0.0, 40.0, 140.0, 100.0, 0.0, 0.0]
);
assert_eq!(mesh.indices, [0, 1, 2]);
}
#[test]
fn process_triangulated_faceset_direct_refuses_a_point_list_without_a_coordinate_span() {
let faceset = b"#77=IFCTRIANGULATEDFACESET(#78,$,$,((1,2,3)),$);";
let points: &[u8] = b"#78=IFCCARTESIANPOINTLIST3D($);";
assert!(process_triangulated_faceset_direct(faceset, |_| Some(points.to_vec())).is_none());
}
#[test]
fn test_should_use_fast_path() {
assert!(should_use_fast_path("IFCCARTESIANPOINTLIST3D"));
assert!(should_use_fast_path("IFCTRIANGULATEDFACESET"));
assert!(should_use_fast_path("IfcTriangulatedFaceSet"));
assert!(!should_use_fast_path("IFCWALL"));
assert!(!should_use_fast_path("IFCEXTRUDEDAREASOLID"));
}
#[test]
fn extract_entity_type_name_trims_and_rejects_empty() {
for (record, expected) in [
(&b"#12=IFCCOLUMN('g');"[..], Some("IFCCOLUMN")),
(&b"#12= IFCCOLUMN('g');"[..], Some("IFCCOLUMN")),
(&b"#12=\tIFCCOLUMN('g');"[..], Some("IFCCOLUMN")),
(&b"#12=();"[..], None),
(&b"#12= ( );"[..], None),
(&b"IFCCOLUMN('g');"[..], None),
(&b"#12=IFCCOLUMN"[..], None),
(&b""[..], None),
] {
assert_eq!(
extract_entity_type_name(record),
expected,
"{:?}",
std::str::from_utf8(record)
);
}
}
#[test]
fn extract_first_entity_ref_refuses_above_u32_max_and_resolves_at_the_boundary() {
assert_eq!(
extract_first_entity_ref(b"#77=IFCTRIANGULATEDFACESET(#4294967297,$);"),
None,
"a reference above u32::MAX must be refused, not aliased to #1"
);
assert_eq!(
extract_first_entity_ref(b"#77=IFCTRIANGULATEDFACESET(#4294967295,$);"),
Some(u32::MAX)
);
assert_eq!(
extract_first_entity_ref(b"#77=IFCTRIANGULATEDFACESET(#78,$);"),
Some(78)
);
}
#[test]
fn extract_entity_refs_from_list_refuses_above_u32_max_and_resolves_at_the_boundary() {
let ids = extract_entity_refs_from_list(b"(#1,#4294967297,#2)");
assert_eq!(
ids,
vec![1, 2],
"an out-of-range reference must be dropped, not wrapped onto #1"
);
let ids = extract_entity_refs_from_list(b"(#1,#4294967295,#2)");
assert_eq!(ids, vec![1, u32::MAX, 2]);
}
#[test]
fn issue_4687_a_comment_in_a_coordinate_or_index_list_is_trivia() {
let plain = b"#5=IFCCARTESIANPOINTLIST3D(((0.,0.,0.),(1.,0.,0.),(0.,1.,0.)));";
let commented = b"#5=IFCCARTESIANPOINTLIST3D(((0.,0.,0.) /* rev 7 */,(1.,0.,0.),(0.,1.,0.)));";
assert_eq!(extract_coordinate_list_from_entity(commented), extract_coordinate_list_from_entity(plain));
assert_eq!(parse_coordinates_direct_f64(b"((0.,0.,0.)/*-.5*/,(1.,0.,0.))"), [0., 0., 0., 1., 0., 0.]);
assert_eq!(parse_indices_direct(b"((1,2,3) /* face 2 */,(2,1,4))"), [0, 1, 2, 1, 0, 3]);
assert_eq!(parse_indices_direct(b"((1,2,3) /* 9 9"), [0, 1, 2]);
}
#[test]
fn extract_entity_refs_from_list_hash_with_no_digits_does_not_panic() {
let ids = extract_entity_refs_from_list(b"(#,#2)");
assert_eq!(ids, vec![2]);
}
#[test]
fn issue_5266_corrupted_literal_refuses_the_whole_list_not_split_into_two_coordinates() {
assert_eq!(parse_coordinates_direct(b"((1.52.3,4.0,5.0))"), Vec::<f32>::new());
assert_eq!(parse_coordinates_direct_f64(b"((1.52.3,4.0,5.0))"), Vec::<f64>::new());
assert_eq!(
parse_coordinates_direct(b"((1.52.3,4.0,5.0) /* c */,(6.0,7.0,8.0))"),
Vec::<f32>::new()
);
assert_eq!(
parse_coordinates_direct_f64(b"((1.52.3,4.0,5.0) /* c */,(6.0,7.0,8.0))"),
Vec::<f64>::new()
);
assert_eq!(
parse_coordinates_direct_f64(b"((1.5/* c */,2.0,3.0))"),
[1.5, 2.0, 3.0]
);
}
#[test]
fn issue_5266_non_step_tokens_refuse_the_list_instead_of_vanishing() {
for list in [&b"((nan,1.,2.))"[..], b"((inf,1.,2.))", b"((1.,2.,3.x))", b"(($,1.,2.))"] {
let shown = String::from_utf8_lossy(list);
assert_eq!(parse_coordinates_direct_f64(list), Vec::<f64>::new(), "{shown}");
assert_eq!(parse_coordinates_direct(list), Vec::<f32>::new(), "{shown}");
}
assert_eq!(parse_coordinates_direct_f64(b"((nan,1.,2.) /* c */)"), Vec::<f64>::new());
for list in [&b"((1.,,2.,3.),(4.,5.,6.))"[..], b"((1.,2.,))", b"((1.,2.,3.)(4.,5.,6.))", b"((,1.))", b"((1.,2.,3.)", b"((1.,2.,3.)))"] {
assert_eq!(parse_coordinates_direct_f64(list), Vec::<f64>::new(), "{}", String::from_utf8_lossy(list));
}
assert_eq!(
extract_coordinate_list_from_entity(b"#1=IFCCARTESIANPOINTLIST3D(((nan,1.,2.)),$);"),
None
);
assert_eq!(
extract_coordinate_list_from_entity(b"#1=IFCCARTESIANPOINTLIST3D(((0.,1.,2.)),$);"),
Some(vec![0.0, 1.0, 2.0])
);
assert_eq!(parse_coordinates_direct_f64(b"((1.52 .3,4.,5.))"), Vec::<f64>::new());
assert_eq!(parse_coordinates_direct_f64(b"((1.52/*c*/.3,4.,5.))"), Vec::<f64>::new());
assert_eq!(
parse_coordinates_direct_f64(b"((1. , 2. /* y */ ,3. ))"),
[1.0, 2.0, 3.0]
);
assert_eq!(
parse_coordinates_direct_f64(b"((+1.5,.5,-1.E2),(0.,0.,3./* z */))"),
[1.5, 0.5, -100.0, 0.0, 0.0, 3.0]
);
}