#3692: review — pdf and render timing tests get a margin a busy CI machine can't break

Wall-clock bounds of 1-2 s failed on loaded runners. Each bound is now 10 s, at least 10x the
fixed code's time on a loaded machine and well under the old code's on the same input:

- cluster ends: a million marks (was 200,000); 30 ms now. The rescan (the test's brute) took
  2.9 s at 50,000 and is quadratic: about 20 minutes at a million.
- clipped-cell lookups: 100,000 cells and lines (was 20,000); 0.25 s now, a pass over the cells
  per line would be 10^10 steps.
- metafile text with a huge font: ~20 ms now; with the size check off it ran past 4 minutes.
- hostile dash patterns: milliseconds now; drawn as dashes they ran past 4 minutes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CX1hMKX28764Lrx5moxs1o
This commit is contained in:
Martin Ball
2026-10-26 00:44:52 +00:00
co-authored by Claude Opus 5.5
parent 1225f56187
commit 77402c4e7e
2 changed files with 21 additions and 14 deletions
+17 -12
View File
@@ -2438,14 +2438,17 @@ fn cluster_ends_match_the_next_larger_cluster_in_linear_time() {
for cl in [vec![], vec![0], vec![0, 0, 3, 3, 5], vec![5, 3, 3, 0], vec![2, 0, 4, 1, 1, 6, 3], vec![0, 7, 2, 2, 9, 1]] {
assert_eq!(cluster_ends(&cl, 10), brute(&cl, 10), "{cl:?}");
}
// One base letter and 200 000 combining marks in its cluster, then one more letter.
let mut cl = vec![0; 200_001];
cl.push(400_002);
// One base letter and a million combining marks in its cluster, then one more letter.
// Milliseconds; the rescan (`brute`) took 2.9 s at 50,000 and grows with the square, so
// about 20 minutes here.
let n = 1_000_000;
let mut cl = vec![0; n + 1];
cl.push(2 * n + 2);
let start = std::time::Instant::now();
let ends = cluster_ends(&cl, 400_004);
assert!(start.elapsed() < std::time::Duration::from_secs(1), "{:?}", start.elapsed());
assert!(ends[..200_001].iter().all(|e| *e == 400_002));
assert_eq!(ends.last(), Some(&400_004));
let ends = cluster_ends(&cl, 2 * n + 4);
assert!(start.elapsed() < std::time::Duration::from_secs(10), "{:?}", start.elapsed());
assert!(ends[..=n].iter().all(|e| *e == 2 * n + 2));
assert_eq!(ends.last(), Some(&(2 * n + 4)));
}
/// A line in a cell cut off at its edge is drawn with that cell's clip, found by path in an index
@@ -2477,11 +2480,13 @@ fn a_line_finds_its_clipped_cell_by_path() {
assert_eq!(found(StoryRef::Part(7), &[0, 0, 0, 2]), Some(vec![0]));
assert_eq!(found(StoryRef::Body, &[0, 0, 0, 2]), None, "another story");
// 20 000 clipped cells and as many lines: a lookup each, not a pass over the cells each.
let big = Page { items: (0..20_000).map(|r| cell(StoryRef::Body, vec![0], r, 0, true)).collect(), ..Default::default() };
// 100 000 clipped cells and as many lines: a lookup each, not a pass over the cells each
// (10^10 steps, minutes; the lookups take well under a second on a loaded machine).
let n = 100_000;
let big = Page { items: (0..n).map(|r| cell(StoryRef::Body, vec![0], r, 0, true)).collect(), ..Default::default() };
let start = std::time::Instant::now();
let clips = clipped_cells(&big);
let hits = (0..20_000u32).filter(|r| clip_cell(&clips, StoryRef::Body, &[0, *r, 0, 0]).is_some()).count();
assert_eq!(hits, 20_000);
assert!(start.elapsed() < std::time::Duration::from_secs(1), "{:?}", start.elapsed());
let hits = (0..n as u32).filter(|r| clip_cell(&clips, StoryRef::Body, &[0, *r, 0, 0]).is_some()).count();
assert_eq!(hits, n);
assert!(start.elapsed() < std::time::Duration::from_secs(10), "{:?}", start.elapsed());
}
+4 -2
View File
@@ -2488,7 +2488,8 @@ mod tests {
let l = layout(&d, &mut LayoutCache::new(), &LayoutOptions::default());
let start = std::time::Instant::now();
let img = render_page(&d, &l.pages[0], 1.0, &RenderOptions::default());
assert!(start.elapsed() < std::time::Duration::from_secs(2), "{:?}", start.elapsed());
// About 20 ms without the huge glyphs; drawing them ran past 4 minutes without finishing.
assert!(start.elapsed() < std::time::Duration::from_secs(10), "{:?}", start.elapsed());
assert_eq!(img.width, 612);
// Leaving them out is logged (once a run, so it may have been by another test already).
assert!(OVERSIZED_GLYPHS_NOTED.load(std::sync::atomic::Ordering::Relaxed), "the left-out glyphs are logged");
@@ -2675,7 +2676,8 @@ mod tests {
let img = render_items(&Document::new(), &[line(dash.clone())], 0.0, 0.0, 100.0, 40.0, 1.0, &RenderOptions::default());
assert!(img.pixel(50, 20)[3] > 200, "{dash:?} draws solid");
}
assert!(start.elapsed() < std::time::Duration::from_secs(2), "{:?}", start.elapsed());
// Milliseconds drawn solid; drawn as dashes they ran past 4 minutes without finishing.
assert!(start.elapsed() < std::time::Duration::from_secs(10), "{:?}", start.elapsed());
let dashed = render_items(&Document::new(), &[line(vec![10.0, 10.0])], 0.0, 0.0, 100.0, 40.0, 1.0, &RenderOptions::default());
assert!(dashed.pixel(15, 20)[3] > 200 && dashed.pixel(25, 20)[3] == 0, "a normal pattern dashes");
assert!(safe_dashes([3.0, 1.0], 1000.0).len() == 2);