textmachine/backend/internal/checks/checkers_pack13_test.go

97 lines
5.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}
})
}
}