mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-08 19:42:12 +00:00
storytold/wordcraft#3692: review — proof 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 and well under the old code's on the same input (measured by running the pre-fix code): - self-continuing rules: 40,000 rules (was 16,000); ~1 ms now, old checker 14 s at 16,000. - many strips per suffix: 200,000 rules (was 30,000); 0.4 s now, old index 3 s at 30,000 and 11 min at 300,000. - forms of self-continuing rules: the MAX_FORMS count catches the old code (9 million forms); the time bound is a backstop. - overlong Turkish words: ~20 ms now; without the caps it ran past 5 minutes. - capped Hunspell reads: no clock; the "not a regular file" error is the proof (the old reader never returned from /dev/zero). 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:
1 parent
935c10d3d0
commit
06af26ed67
3 files changed
+18
-12
No files matched your search
@@ -1310,11 +1310,13 @@ pub(crate) mod tests {
|
||||
#[test]
|
||||
fn self_continuing_rules_check_in_linear_time() {
|
||||
// Thousands of empty-append rules that continue with their own flag: each check used to
|
||||
// follow every rule from every rule, millions of steps a word (#3613).
|
||||
let d = many_rules(16_000, |_| "0 0/A .".into());
|
||||
// follow every rule from every rule, millions of steps a word (#3613). The checks take
|
||||
// about a millisecond; the old checker took 14 s for two of them at 16,000 rules, so at
|
||||
// 40,000 (quadratic: minutes) the 10 s bound leaves a busy machine a wide margin.
|
||||
let d = many_rules(40_000, |_| "0 0/A .".into());
|
||||
let t = std::time::Instant::now();
|
||||
assert!(d.check("kot") && !d.check("pies") && !d.check("kotx"));
|
||||
assert!(t.elapsed().as_secs_f64() < 2.0, "{:?}", t.elapsed());
|
||||
assert!(t.elapsed().as_secs_f64() < 10.0, "{:?}", t.elapsed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1324,7 +1326,9 @@ pub(crate) mod tests {
|
||||
let distinct = many_rules(3000, |i| format!("0 x{i}/A ."));
|
||||
let t = std::time::Instant::now();
|
||||
let (a, b) = (same.forms("kot"), distinct.forms("kot"));
|
||||
assert!(t.elapsed().as_secs_f64() < 2.0, "{:?}", t.elapsed());
|
||||
// The cap on the count is what tells the old code (9 million forms each) apart; the
|
||||
// generous time bound (tens of ms here) only backs up the rule-visit cap.
|
||||
assert!(t.elapsed().as_secs_f64() < 10.0, "{:?}", t.elapsed());
|
||||
assert!(a.len() <= MAX_FORMS && b.len() <= MAX_FORMS, "{} {}", a.len(), b.len());
|
||||
assert!(b.iter().any(|f| f == "kotx0x1"), "chained forms are still listed");
|
||||
}
|
||||
@@ -1332,11 +1336,13 @@ pub(crate) mod tests {
|
||||
#[test]
|
||||
fn many_strips_per_suffix_index_in_linear_time() {
|
||||
// One append with thousands of strips: the index used to scan the append's groups for
|
||||
// each rule.
|
||||
// each rule. Building 200,000 rules takes well under a second; the old index took 3 s at
|
||||
// 30,000 and over 10 minutes at 300,000.
|
||||
let n = 200_000;
|
||||
let t = std::time::Instant::now();
|
||||
let d = many_rules(30_000, |i| format!("k{i} y ."));
|
||||
assert!(t.elapsed().as_secs_f64() < 2.0, "{:?}", t.elapsed());
|
||||
assert_eq!(d.sfx_index.get("y").map(Vec::len), Some(30_000));
|
||||
let d = many_rules(n, |i| format!("k{i} y ."));
|
||||
assert!(t.elapsed().as_secs_f64() < 10.0, "{:?}", t.elapsed());
|
||||
assert_eq!(d.sfx_index.get("y").map(Vec::len), Some(n));
|
||||
assert!(!d.contains("koty"));
|
||||
}
|
||||
|
||||
|
||||
@@ -329,13 +329,12 @@ mod tests {
|
||||
// One byte over the cap (sparse: nothing is written) is refused without reading it.
|
||||
let big = dir.join("big.dic");
|
||||
std::fs::File::create(&big).unwrap().set_len(MAX_FILE + 1).unwrap();
|
||||
let t = std::time::Instant::now();
|
||||
assert!(load(&aff, &big).unwrap_err().contains("too large"));
|
||||
// A directory, and on Unix an endless device, are not dictionary files.
|
||||
// A directory, and on Unix an endless device, are not dictionary files. (No clock: a
|
||||
// reader that read `/dev/zero` would never return, so the error itself is the proof.)
|
||||
assert!(load(&aff, &dir).unwrap_err().contains("not a regular file"));
|
||||
#[cfg(unix)]
|
||||
assert!(load(&aff, Path::new("/dev/zero")).unwrap_err().contains("not a regular file"));
|
||||
assert!(t.elapsed().as_secs_f64() < 1.0, "{:?}", t.elapsed());
|
||||
// Turkish reads through the same reader.
|
||||
let (tr_aff, tr_dic) = (dir.join("tr_TR.aff"), dir.join("tr_TR.dic"));
|
||||
std::fs::write(&tr_aff, "SET UTF-8\nSFX 1 Y 1\nSFX 1 0 lar .\n").unwrap();
|
||||
|
||||
@@ -298,7 +298,8 @@ mod tests {
|
||||
assert!(suggest_with(&d, &long, 8).is_empty());
|
||||
assert!(!d.contains(&long) && !d.contains(&format!("kitap{long}")));
|
||||
assert!(!super::super::is_correct_with(&d, &long));
|
||||
assert!(t.elapsed().as_secs_f64() < 1.0, "{:?}", t.elapsed());
|
||||
// Tens of milliseconds with the caps; without them this ran for over 5 minutes.
|
||||
assert!(t.elapsed().as_secs_f64() < 10.0, "{:?}", t.elapsed());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in new issue
Block a user