textmachine/backend/internal/lang/bankdata_test.go

106 lines
5 KiB
Go

package lang
import (
"strings"
"testing"
)
// TestBankPlaneIsOutsideTheWaveFold is the load-bearing property of the whole plane, and the reason it is a
// separate embed rather than another row in data/script-series.txt: editing family morphology must NOT move
// the wave snapshot. EmbeddedVersion folds into every book's snapshot, so a row added there would re-bill a
// whole draft wave for a knob that only decides which candidates share a terminologist CALL — the axis
// config.TerminologyGate is deliberately kept off the snapshot for.
func TestBankPlaneIsOutsideTheWaveFold(t *testing.T) {
for _, f := range embeddedDataFiles() {
if strings.Contains(f.name, "family") {
t.Fatalf("the bank plane's %s reached the WAVE-folded embed set — editing it would re-snapshot every book", f.name)
}
}
// Both versions exist and are distinct namespaces, so a log line naming one can never be read as the other.
if v := BankDataVersion(); !strings.HasPrefix(v, bankDataAlgoVersion+"-") {
t.Fatalf("BankDataVersion must be tagged with its own recipe, got %q", v)
}
if strings.HasPrefix(BankDataVersion(), embedAlgoVersion) {
t.Fatal("the two data planes must not share a version namespace")
}
}
// TestFamilyMorphologyIsData drives the parser with a synthetic table: a script's family rules — including
// the name/realia asymmetry and the head side — are DATA, so a pair that is not in the repo declares its own
// and needs no Go edit. The script here is a REGISTERED one that ships no family rows of its own — the first
// version of this test declared «devanagari», which resolves to no Unicode range and was therefore dropped
// in silence, so the flagship generality test proved nothing at all.
func TestFamilyMorphologyIsData(t *testing.T) {
got, err := parseFamilyMorphology([]byte(strings.Join([]string{
"# a source whose NAMES are head-final and whose realia lead with the root",
"family_affix\thangul\tname\tsuffix\t3",
"family_affix\thangul\tterm\tprefix\t2",
"family_min_members\thangul\t4",
"family_containment_runes\thangul\t3",
}, "\n")))
if err != nil {
t.Fatal(err)
}
sf := got["hangul"]
if sf == nil {
t.Fatalf("the declared script must be present: %v", got)
}
if a := sf.affix["name"]; !a.Suffix || a.MinRunes != 3 {
t.Fatalf("a head-final name rule is one data row, got %+v", a)
}
if a := sf.affix["term"]; a.Suffix || a.MinRunes != 2 {
t.Fatalf("the asymmetry is per-type data, got %+v", a)
}
if sf.minMembers != 4 || sf.containmentRunes != 3 || sf.maxMembers != 0 {
t.Fatalf("bounds come from the rows, absent ones stay unset: %+v", sf)
}
}
// TestFamilyMorphologyRefusesCorruptRows: a malformed row is a corrupt asset, and the failure mode it would
// otherwise produce — a channel that parses fine and silently forms nothing — is the exact drift the pack
// loader already refuses for the Palladius tables.
func TestFamilyMorphologyRefusesCorruptRows(t *testing.T) {
for _, bad := range []string{
"family_affix\than\tname\tmiddle\t2", // no such side
"family_affix\than\tname\tprefix\t0", // a zero-rune root matches everything
"family_affix\than\tname\tprefix", // truncated row
"family_minmembers\than\t2", // typo'd key
"family_min_members\than\tlots", // non-integer
// A typo'd SCRIPT resolves to no range, so every row naming it is silently ignored and the pair that
// asked for the channel gets an inert one.
"family_affix\thann\tname\tprefix\t2\nfamily_min_members\thann\t2",
// A HALF table: rules with no quorum, or a quorum with nothing to form a family from. Both parse and
// then do nothing, which is the failure this pack declared intolerable everywhere else.
"family_affix\than\tname\tprefix\t2",
"family_min_members\than\t2",
} {
if _, err := parseFamilyMorphology([]byte(bad)); err == nil {
t.Fatalf("a corrupt row must fail loud: %q", bad)
}
}
}
// TestFamilyMorphologyForZhIsLive pins the shipped table through the resolver a book actually calls: zh is
// written in a dense script that declares rules, an alphabetic source is inert, and the ja case shows the
// union — kana declare nothing, so the language inherits han's rules rather than losing them.
func TestFamilyMorphologyForZhIsLive(t *testing.T) {
zh := FamilyMorphology("zh")
if !zh.Enabled() {
t.Fatal("zh is the measured pair and must have a live family channel")
}
if a := zh.Affix["name"]; a.Suffix || a.MinRunes < 2 {
t.Fatalf("a han name family leads with the clan morpheme: %+v", a)
}
if a := zh.Affix["term"]; !a.Suffix || a.MinRunes < 2 {
t.Fatalf("a han realia family ends with the generic head: %+v", a)
}
if zh.MinMembers < 2 || zh.MaxMembers <= 0 || zh.ContainmentRunes < 2 {
t.Fatalf("the bounds must all be declared: %+v", zh)
}
if en := FamilyMorphology("en"); en.Enabled() {
t.Fatalf("an alphabetic source forms no morpheme families: %+v", en)
}
if ja := FamilyMorphology("ja"); !ja.Enabled() || ja.Affix["term"] != zh.Affix["term"] {
t.Fatalf("a language written in han inherits han's rules through the union: %+v", ja)
}
}