106 lines
5 KiB
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)
|
|
}
|
|
}
|