use super::native::{range_end, with_chunks_counted};
use ifc_lite_core::{build_entity_index, EntityScanner};
fn serial_refusals(content: &[u8]) -> usize {
serial_diagnostics(content).0
}
fn serial_malformed(content: &[u8]) -> bool {
serial_diagnostics(content).1
}
fn serial_diagnostics(content: &[u8]) -> (usize, bool) {
let mut scanner = EntityScanner::new(content);
while scanner.next_entity().is_some() {}
(
scanner.skipped_oversized_ids(),
scanner.malformed_record_start().is_some(),
)
}
fn assert_parallel_matches_serial(content: &[u8], label: &str) {
let serial = build_entity_index(content);
let (serial_refused, serial_stopped) = serial_diagnostics(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_eq!(
malformed, serial_stopped,
"attributed malformed-record stop (n_chunks={n}) != serial \
({serial_stopped}) for {label}"
);
}
}
#[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");
let bytes = content.as_bytes();
let string_body = content.find("#2=IFCWALL('").unwrap() + 12;
let string_end = content[string_body..].find("',$,").unwrap() + string_body;
let saw_in_string_garbage = [2usize, 3, 4, 5, 7, 8, 11, 16, 32, 64].iter().any(|&n| {
(0..n).any(|i| {
let start = i * bytes.len() / n;
super::scan_shard(bytes, start, range_end(i, n, bytes.len()))
.0
.iter()
.any(|&(_, s, _)| s > string_body && s < string_end)
})
});
assert!(
saw_in_string_garbage,
"no shard ever mis-parsed the quoted value — this fixture cannot \
exercise the stitch's speculative-prefix drop"
);
assert_parallel_matches_serial(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");
let n = 8usize;
let speculative: usize = (0..n)
.map(|i| {
let start = i * bytes.len() / n;
super::scan_shard_with_refusals(bytes, start, range_end(i, n, bytes.len()))
.2
.len()
})
.sum();
assert!(
speculative > 1,
"the quoted value made the shards refuse {speculative} record(s); with \
nothing false to exclude, the assertion below cannot discriminate the \
fix from the bug"
);
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}"
);
}
}
#[test]
fn missing_terminator_does_not_cost_the_sharded_scan_its_tail_4179() {
let mut content = String::from("ISO-10303-21;\nHEADER;\nENDSEC;\nDATA;\n");
for id in 1..=40u32 {
let terminator = if id == 20 { "" } else { ";" };
content.push_str(&format!("#{id}=IFCDOOR('g{id}',$,$,$,$,$,$,$){terminator}\n"));
}
content.push_str("ENDSEC;\nEND-ISO-10303-21;\n");
let bytes = content.as_bytes();
let serial = build_entity_index(bytes);
assert_eq!(serial.len(), 39, "serial scan must drop only the broken record");
assert!(!serial.contains_key(&20));
assert!(serial.contains_key(&40), "the tail must survive a serial scan");
assert!(serial_malformed(bytes), "the broken record must still be reported");
assert_parallel_matches_serial(bytes, "missing-terminator-mid-file");
}
#[test]
fn a_speculative_stop_does_not_mask_a_real_one_4179() {
let mut fake = String::new();
for k in 0..400 {
fake.push_str(&format!("#9000{k}=IFCWALL(fake {k}) x; 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..=60u32 {
content.push_str(&format!("#{id}=IFCDOOR('g{id}',$,$,$,$,$,$,$);\n"));
}
content.push_str("#900=IFCWALL('real',$,$,$,$,$,$,$)\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!(serial_malformed(bytes), "the fixture must declare a real stop");
for n in [1usize, 2, 3, 4, 5, 7, 8, 11, 16, 32, 64] {
assert!(
with_chunks_counted(bytes, n).malformed,
"n_chunks={n}: the real malformed record went unreported"
);
}
}
#[test]
fn a_shard_reports_every_record_it_dropped_not_just_the_first_4179() {
let mut fake = String::new();
for k in 0..400 {
fake.push_str(&format!("#9000{k}=IFCWALL(fake {k}) x; 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..=60u32 {
content.push_str(&format!("#{id}=IFCDOOR('g{id}',$,$,$,$,$,$,$);\n"));
}
let real = content.len();
content.push_str("#900=IFCWALL('real',$,$,$,$,$,$,$)\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!(serial_malformed(bytes), "the fixture must declare a real drop");
let mut checked = 0usize;
for n in [2usize, 4, 8, 16] {
for i in 0..n {
let start = i * bytes.len() / n;
let end = range_end(i, n, bytes.len());
if !(start <= real && real < end) {
continue;
}
let (_records, _handoff, _refusals, malformed) =
super::scan_shard_with_diagnostics(bytes, start, end);
assert!(
malformed.contains(&real),
"n_chunks={n} shard {i} covers the real drop at {real} but reported {malformed:?}"
);
checked += 1;
}
}
assert!(checked >= 4, "fixture never put the real drop inside a shard");
}