use super::native::{range_end, with_chunks_counted};
use ifc_lite_core::{build_entity_index, EntityScanner};
fn serial_refusals(content: &[u8]) -> usize {
let mut scanner = EntityScanner::new(content);
while scanner.next_entity().is_some() {}
scanner.skipped_oversized_ids()
}
fn assert_parallel_matches_serial(content: &[u8], label: &str) {
let serial = build_entity_index(content);
let serial_refused = serial_refusals(content);
for n in [1usize, 2, 3, 4, 5, 7, 8, 11, 16, 32, 64] {
let stitched = with_chunks_counted(content, n);
let (par, refused, malformed) = (stitched.index, stitched.refused, stitched.malformed);
assert_eq!(
par, serial,
"parallel index (n_chunks={n}) != serial for {label}"
);
assert_eq!(
refused, serial_refused,
"attributed refusals (n_chunks={n}) != serial ({serial_refused}) for {label}"
);
assert!(
!malformed,
"n_chunks={n}: {label} must not report a malformed-record stop — none of \
this file's `assert_parallel_matches_serial` fixtures declare one"
);
}
}
#[test]
fn empty_and_tiny_and_malformed() {
assert_parallel_matches_serial(b"", "empty");
assert_parallel_matches_serial(b"\n", "single-newline");
assert_parallel_matches_serial(
b"ISO-10303-21;\nHEADER;\nENDSEC;\nDATA;\nENDSEC;\n",
"header-only",
);
assert_parallel_matches_serial(
b"#1=IFCWALL('g',$,$,$,$,$,$,$);\n",
"no-header",
);
assert_parallel_matches_serial(
b"DATA;\n#1=IFCWALL('g',$,$\n#2=IFCDOOR( #notanid #=x ; ;",
"malformed",
);
}
#[test]
fn simple_data_section() {
let mut content = String::from("ISO-10303-21;\nHEADER;\nENDSEC;\nDATA;\n");
for id in 1..=200u32 {
content.push_str(&format!(
"#{id}=IFCCARTESIANPOINT(({}.,{}.,{}.));\n",
id, id, id
));
}
content.push_str("ENDSEC;\nEND-ISO-10303-21;\n");
assert_parallel_matches_serial(content.as_bytes(), "simple-200");
}
#[test]
fn duplicate_ids_last_wins() {
let mut content = String::from("DATA;\n");
for _ in 0..3 {
for id in 1..=50u32 {
content.push_str(&format!("#{id}=IFCWALL('g{id}',$,$,$,$,$,$,$);\n"));
}
}
assert_parallel_matches_serial(content.as_bytes(), "duplicate-ids");
}
#[test]
fn chunk_boundary_inside_quoted_string() {
let mut fake = String::new();
for k in 0..400 {
fake.push_str(&format!(";\\n#{}=IFCWALL(fake ; still in string ", 90000 + k));
}
let mut content = String::from("ISO-10303-21;\nHEADER;\nENDSEC;\nDATA;\n");
content.push_str("#1=IFCPROJECT('guid',$,$,$,$,$,$,$,$);\n");
content.push_str(&format!("#2=IFCWALL('{fake}',$,$,$,$,$,$,$);\n"));
for id in 3..=120u32 {
content.push_str(&format!("#{id}=IFCDOOR('g{id}',$,$,$,$,$,$,$);\n"));
}
content.push_str("ENDSEC;\n");
assert_parallel_matches_serial(content.as_bytes(), "in-string-boundary");
}
fn clean_file_with_oversized_shaped_text_in_a_string() -> String {
let mut fake = String::new();
for k in 0..400 {
fake.push_str(&format!(
"#4294967297=IFCWALL(fake {k} ; still in string "
));
}
let mut content = String::from("ISO-10303-21;\nHEADER;\nENDSEC;\nDATA;\n");
content.push_str("#1=IFCPROJECT('guid',$,$,$,$,$,$,$,$);\n");
content.push_str(&format!("#2=IFCWALL('{fake}',$,$,$,$,$,$,$);\n"));
for id in 3..=120u32 {
content.push_str(&format!("#{id}=IFCDOOR('g{id}',$,$,$,$,$,$,$);\n"));
}
content.push_str("ENDSEC;\n");
content
}
#[test]
fn clean_file_with_an_oversized_shaped_string_reports_no_refusal() {
let content = clean_file_with_oversized_shaped_text_in_a_string();
let bytes = content.as_bytes();
assert_eq!(
serial_refusals(bytes),
0,
"fixture must declare no oversized id — the serial scan refuses none"
);
let mut saw_a_speculative_refusal = false;
for n in [2usize, 3, 4, 5, 7, 8, 11, 16, 32, 64] {
let raw: usize = (0..n)
.map(|i| {
let start = i * bytes.len() / n;
let end = range_end(i, n, bytes.len());
super::scan_shard_with_refusals(bytes, start, end).2.len()
})
.sum();
if raw > 0 {
saw_a_speculative_refusal = true;
}
assert_eq!(
with_chunks_counted(bytes, n).refused,
0,
"n_chunks={n}: a file with no oversized id must report no refusal \
(raw per-shard sum was {raw})"
);
}
assert!(
saw_a_speculative_refusal,
"fixture never made a shard refuse a false record — it cannot \
discriminate the fix from the bug"
);
assert_parallel_matches_serial(bytes, "clean-file-oversized-shaped-string");
}
#[test]
fn a_real_oversized_id_near_a_boundary_counts_exactly_once() {
let mut content = String::from("ISO-10303-21;\nHEADER;\nENDSEC;\nDATA;\n");
for id in 1..=60u32 {
content.push_str(&format!("#{id}=IFCDOOR('g{id}',$,$,$,$,$,$,$);\n"));
}
content.push_str("#4294967297=IFCWALL('over',$,$,$,$,$,$,$);\n");
for id in 61..=120u32 {
content.push_str(&format!("#{id}=IFCDOOR('g{id}',$,$,$,$,$,$,$);\n"));
}
content.push_str("ENDSEC;\n");
let bytes = content.as_bytes();
assert_eq!(serial_refusals(bytes), 1, "one real oversized declaration");
for n in [1usize, 2, 3, 4, 5, 7, 8, 11, 16, 32, 64] {
assert_eq!(
with_chunks_counted(bytes, n).refused,
1,
"n_chunks={n}: the one real refusal must be counted once"
);
}
assert_parallel_matches_serial(bytes, "real-oversized-near-boundary");
}
fn fixture_behind_an_inescapable_quoted_value(
pad_to: usize,
tail_to: usize,
) -> (String, usize, usize, usize) {
let a = "A".repeat(200);
let b = "B".repeat(200);
let mut content = String::from("ISO-10303-21;\nHEADER;\nENDSEC;\nDATA;\n");
while content.len() < pad_to {
content.push_str("#1=IFCDOOR('gg',$,$,$,$,$,$,$);\n");
}
let record_at = content.len();
content.push_str(&format!("#5=IFCWALL('{a}#4294967297=IFCWALL({b}',$,$,$,$,$,$,$);\n"));
let handoff_at = content.len();
content.push_str("#6=IFCDOOR('g6',$,$,$,$,$,$,$);\n");
let oversized_at = content.len();
content.push_str("#4294967297=IFCWALL('over',$,$,$,$,$,$,$);\n");
while content.len() < tail_to {
content.push_str("#7=IFCDOOR('gg',$,$,$,$,$,$,$);\n");
}
(content, record_at, handoff_at, oversized_at)
}
fn assert_chunk_cannot_resynchronise(
bytes: &[u8],
i: usize,
n: usize,
record_at: usize,
handoff_at: usize,
oversized_at: usize,
) {
let chunk_start = i * bytes.len() / n;
let chunk_end = range_end(i, n, bytes.len());
assert!(
chunk_start > record_at && chunk_start < handoff_at,
"chunk {i}/{n} must start inside the quoted value (got {chunk_start})"
);
assert!(
chunk_end > oversized_at,
"chunk {i}/{n} must reach past the refusal (end {chunk_end})"
);
let (recs, _, _) = super::scan_shard_with_refusals(bytes, chunk_start, chunk_end);
assert!(
!recs.iter().any(|&(_, s, _)| s == handoff_at),
"chunk {i}/{n} must FAIL to resynchronise at the handoff, or the \
serial-rescan arm is never taken and this test proves nothing"
);
}
#[test]
fn an_oversized_id_in_a_serially_rescanned_range_counts_once() {
let (content, record_at, handoff_at, oversized_at) =
fixture_behind_an_inescapable_quoted_value(2_900, 20_000);
let bytes = content.as_bytes();
assert_eq!(serial_refusals(bytes), 1, "one real oversized declaration");
assert_chunk_cannot_resynchronise(bytes, 2, 13, record_at, handoff_at, oversized_at);
for n in 2..=40usize {
assert_eq!(
with_chunks_counted(bytes, n).refused,
1,
"n_chunks={n}: the refusal inside the rescanned range, counted once"
);
}
assert_parallel_matches_serial(bytes, "oversized-in-rescanned-range");
}
#[test]
fn an_oversized_id_in_a_rescanned_range_that_hits_eof_counts_once() {
let (content, record_at, handoff_at, oversized_at) =
fixture_behind_an_inescapable_quoted_value(17_000, 18_400);
let bytes = content.as_bytes();
assert_eq!(serial_refusals(bytes), 1, "one real oversized declaration");
assert_chunk_cannot_resynchronise(bytes, 13, 14, record_at, handoff_at, oversized_at);
for n in 2..=40usize {
assert_eq!(
with_chunks_counted(bytes, n).refused,
1,
"n_chunks={n}: the refusal in the file's tail, counted once"
);
}
assert_parallel_matches_serial(bytes, "oversized-in-rescanned-tail");
}
#[test]
fn a_real_oversized_id_behind_an_adversarial_string_counts_once() {
let mut fake = String::new();
for k in 0..400 {
fake.push_str(&format!("#4294967297=IFCWALL(fake {k} ; still in string "));
}
let mut content = String::from("ISO-10303-21;\nHEADER;\nENDSEC;\nDATA;\n");
content.push_str("#1=IFCPROJECT('guid',$,$,$,$,$,$,$,$);\n");
content.push_str(&format!("#2=IFCWALL('{fake}',$,$,$,$,$,$,$);\n"));
content.push_str("#3=IFCDOOR('g3',$,$,$,$,$,$,$);\n");
content.push_str("#4294967297=IFCWALL('over',$,$,$,$,$,$,$);\n");
for id in 4..=120u32 {
content.push_str(&format!("#{id}=IFCDOOR('g{id}',$,$,$,$,$,$,$);\n"));
}
content.push_str("ENDSEC;\n");
let bytes = content.as_bytes();
assert_eq!(serial_refusals(bytes), 1, "one real oversized declaration");
for n in [1usize, 2, 3, 4, 5, 7, 8, 11, 16, 32, 64] {
assert_eq!(
with_chunks_counted(bytes, n).refused,
1,
"n_chunks={n}: exactly the one real refusal, none of the in-string ones"
);
}
assert_parallel_matches_serial(bytes, "real-oversized-behind-adversarial-string");
}
#[test]
fn an_oversized_id_after_a_chunk_spanning_record_counts_once() {
let big_name = "X".repeat(20_000);
let mut content = String::from("DATA;\n");
content.push_str("#1=IFCPROJECT('g',$,$,$,$,$,$,$,$);\n");
content.push_str(&format!("#2=IFCWALL('{big_name}',$,$,$,$,$,$,$);\n"));
content.push_str("#4294967297=IFCWALL('over',$,$,$,$,$,$,$);\n");
for id in 3..=40u32 {
content.push_str(&format!("#{id}=IFCDOOR('g{id}',$,$,$,$,$,$,$);\n"));
}
let bytes = content.as_bytes();
assert_eq!(serial_refusals(bytes), 1, "one real oversized declaration");
for n in [1usize, 2, 3, 4, 5, 7, 8, 11, 16, 32, 64] {
assert_eq!(
with_chunks_counted(bytes, n).refused,
1,
"n_chunks={n}: the refusal in the rescanned range must be counted once"
);
}
assert_parallel_matches_serial(bytes, "oversized-after-chunk-spanning-record");
}
#[test]
fn record_larger_than_chunk() {
let big_name = "X".repeat(20_000);
let mut content = String::from("DATA;\n");
content.push_str("#1=IFCPROJECT('g',$,$,$,$,$,$,$,$);\n");
content.push_str(&format!("#2=IFCWALL('{big_name}',$,$,$,$,$,$,$);\n"));
for id in 3..=40u32 {
content.push_str(&format!("#{id}=IFCDOOR('g{id}',$,$,$,$,$,$,$);\n"));
}
assert_parallel_matches_serial(content.as_bytes(), "record-larger-than-chunk");
}
fn malformed_record_fixture() -> (String, u32, u32) {
let mut content = String::from("ISO-10303-21;\nHEADER;\nENDSEC;\nDATA;\n");
for id in 1..=200u32 {
content.push_str(&format!("#{id}=IFCDOOR('g{id}',$,$,$,$,$,$,$);\n"));
}
content.push_str("#201=IFCWALL('never closes,$,$,$,$,$,$,$);\n");
for id in 202..=400u32 {
content.push_str(&format!("#{id}=IFCDOOR('g{id}',$,$,$,$,$,$,$);\n"));
}
(content, 200, 201)
}
#[test]
fn malformed_record_truncates_and_is_reported_at_every_chunk_count() {
let (content, last_good_id, malformed_id) = malformed_record_fixture();
let bytes = content.as_bytes();
let serial = build_entity_index(bytes);
assert!(serial.contains_key(&last_good_id), "the record before the malformed one must survive");
assert!(!serial.contains_key(&malformed_id), "the malformed record itself must not appear");
assert!(!serial.contains_key(&(malformed_id + 1)), "every record after the malformed one must be gone");
for n in [1usize, 2, 3, 4, 5, 7, 8, 11, 16, 32, 64] {
let stitched = with_chunks_counted(bytes, n);
let (par, refused, malformed) = (stitched.index, stitched.refused, stitched.malformed);
assert_eq!(
par, serial,
"n_chunks={n}: parallel index must match the serial truncation exactly"
);
assert_eq!(refused, 0, "n_chunks={n}: this fixture has no oversized ids");
assert!(
malformed,
"n_chunks={n}: the malformed record must be attributed, not silently dropped \
the way the pre-#3695 scanner dropped it"
);
}
assert_eq!(
super::build_entity_index_parallel(bytes),
serial,
"build_entity_index_parallel must match the serial truncation exactly"
);
}
#[test]
fn fixtures_byte_identical() {
for rel in [
"ara3d/schependomlaan.ifc",
"ara3d/advanced_model.ifc",
"various/01_BIMcollab_Example_ARC.ifc",
] {
let path = format!("{}/../../tests/models/{}", env!("CARGO_MANIFEST_DIR"), rel);
let Ok(content) = std::fs::read(&path) else {
eprintln!("skipping {rel}: fixture absent — run `pnpm fixtures`");
continue;
};
assert_parallel_matches_serial(&content, rel);
assert_eq!(
super::build_entity_index_parallel(&content),
build_entity_index(&content),
"public build_entity_index_parallel != serial for {rel}"
);
}
}