package checks import "testing" // TestPack13Checkers pins the pack-13 GENERAL checkers against representative inputs (no book-specific // word lists): each defect shape fires and a clean counterpart / golden-shape Russian output stays 0 // (precision over recall). The 时辰/千万 cases confirm the pre-existing DC1/DC2 still catch the corpus shapes. func TestPack13Checkers(t *testing.T) { dcc := testCheckers(t) // Percent scale (成 = tenths): a decimal-fraction rendering fires; a correct percent is suppressed. if n, _ := dcc.lintPercentScale("море истинной ци 六成六 …", "море истинной ци — шесть десятых и шесть сотых"); n != 1 { t.Errorf("percent: «шесть десятых и шесть сотых» should fire, got %d", n) } if n, _ := dcc.lintPercentScale("六成六", "море истинной ци — шесть и шесть десятых"); n != 1 { t.Errorf("percent: «шесть и шесть десятых» should fire, got %d", n) } if n, _ := dcc.lintPercentScale("六成六", "заполнено на шестьдесят шесть процентов"); n != 0 { t.Errorf("percent: a correct «процентов» rendering must be suppressed, got %d", n) } if n, _ := dcc.lintPercentScale("нет числа", "шесть десятых чего-то"); n != 0 { t.Errorf("percent: no 成 in source must not fire, got %d", n) } // Latin residue: a lowercase leaked word fires; a hash / Cyrillic / Roman numeral / a brand or proper // noun (any capital) does not — the precision guard the adversarial review's iPhone case exposed. if n, _ := lintLatinResidue("открыл их again, горестно вздохнув", nil); n != 1 { t.Errorf("latin: «again» should fire, got %d", n) } for _, clean := range []string{ "ОТРЕДАКТИРОВАННЫЙ ПЕРЕВОД 5abc35ddfb65. Судзуки шёл по коридорам.", // a body-hash id (has digits) "Классы таланта А, Б, В и Г — от высшей к низшей.", // Cyrillic class letters "Глава II начинается.", // a Roman numeral (uppercase) "Он держал в руках iPhone и MacBook.", // brands — any capital → skipped "Судзуки шёл в Академию.", // Cyrillic proper noun } { if n, det := lintLatinResidue(clean, nil); n != 0 { t.Errorf("latin: clean text must not fire (%q): %d %v", clean, n, det) } } // The per-project allowlist exempts an intentional lowercase Latin surface. if n, _ := lintLatinResidue("сказал again снова", map[string]bool{"again": true}); n != 0 { t.Errorf("latin: an allowlisted surface must not fire") } // Broken word — the general «-йть» rule only (no book-specific lists): «войть» fires, valid words do not. if n, _ := dcc.lintBrokenWord("хотел тихонько войть и закрыть"); n != 1 { t.Errorf("broken: «войть» (-йть) should fire") } for _, clean := range []string{ "он решил войти и закрыть окно", // valid войти "глава клана Гуюэ поклонился", // valid prose (no -йть) "впереди идёт Фан Юань", // valid впереди } { if n, det := dcc.lintBrokenWord(clean); n != 0 { t.Errorf("broken: clean text must not fire (%q): %d %v", clean, n, det) } } // The pre-existing DC1/DC2 still catch the corpus shapes (general Chinese units/idioms). if n, _ := dcc.lintTimeUnits("僵持了三个时辰", "прошло три часа"); n != 1 { t.Errorf("time-unit: 三个时辰→«три часа» should fire") } if n, _ := dcc.lintMagnitudeScale("千万生灵", "погубил тысячи жизней"); n != 1 { t.Errorf("magnitude: 千万→«тысячи» should fire") } } // TestMagnitudeScaleEmptyProbeIsInert pins the pack-15 empty-probe guard: a probe WORD missing from the // pair data must leave DC2's 千万 branch inert, never spurious. strings.Contains(x, "") is true for every // x, so an empty fire word would have flagged every chunk whose source carries 千万 — a data slip reading // as a checker bug — and an empty veto word would have silently disabled the branch. func TestMagnitudeScaleEmptyProbeIsInert(t *testing.T) { full := testCheckers(t) src, bad := "他有千万家产。", "У него тысячи домов." if n, _ := full.lintMagnitudeScale(src, bad); n == 0 { t.Fatalf("test premise broken: the configured probe must flag this pair (got %d)", n) } for _, tc := range []struct { name string mutus func(*Checkers) }{ {"empty fire word", func(c *Checkers) { c.qianwanFireWord = "" }}, {"empty veto word", func(c *Checkers) { c.qianwanVetoWord = "" }}, } { t.Run(tc.name, func(t *testing.T) { c := *testCheckers(t) // copy the compiled spec tc.mutus(&c) if n, det := c.lintMagnitudeScale(src, bad); n != 0 { t.Errorf("an empty probe word must leave the branch INERT, got %d flags: %v", n, det) } // A clean chunk stays clean either way (no spurious fire on unrelated text). if n, _ := c.lintMagnitudeScale("他走了三里。", "Он прошёл три ли."); n != 0 { t.Errorf("clean chunk must not flag, got %d", n) } }) } }