package engine import ( "errors" "testing" "github.com/iliadenisov/alphabet" dawg "github.com/iliadenisov/dafsa" ) // buildFinderB builds an in-memory finder over idx from the given words, each // expressed as alphabet-index bytes. The words must be supplied in strictly // increasing alphabet-index order, as the builder requires. func buildFinderB(t *testing.T, idx alphabet.Indexer, words ...[]byte) dawg.Finder { t.Helper() b := dawg.New(idx) for _, w := range words { if err := b.AddB(w); err != nil { t.Fatalf("addB %v: %v", w, err) } } return b.Finish() } // mustDecode decodes index bytes through idx or fails the test. func mustDecode(t *testing.T, idx alphabet.Indexer, word []byte) string { t.Helper() s, err := idx.Decode(word) if err != nil { t.Fatalf("decode %v: %v", word, err) } return s } func equalStrings(a, b []string) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true } // TestDiffWords checks that DiffWords reports the words present only in the new // dictionary as added and those present only in the old one as removed, decoded // to characters through the variant's alphabet. func TestDiffWords(t *testing.T) { rs, _ := VariantEnglish.ruleset() idx := rs.Alphabet old := buildFinderB(t, idx, []byte{0}, []byte{0, 1}, []byte{2}) defer func() { _ = old.Close() }() updated := buildFinderB(t, idx, []byte{0}, []byte{2}, []byte{2, 3}) defer func() { _ = updated.Close() }() diff, err := DiffWords(VariantEnglish, old, updated) if err != nil { t.Fatalf("DiffWords: %v", err) } wantAdded := []string{mustDecode(t, idx, []byte{2, 3})} wantRemoved := []string{mustDecode(t, idx, []byte{0, 1})} if !equalStrings(diff.Added, wantAdded) { t.Errorf("added = %v, want %v", diff.Added, wantAdded) } if !equalStrings(diff.Removed, wantRemoved) { t.Errorf("removed = %v, want %v", diff.Removed, wantRemoved) } } // TestDiffWordsIdentical checks that comparing a dictionary with itself yields an // empty diff. func TestDiffWordsIdentical(t *testing.T) { rs, _ := VariantEnglish.ruleset() f := buildFinderB(t, rs.Alphabet, []byte{0}, []byte{1}, []byte{1, 2}) defer func() { _ = f.Close() }() diff, err := DiffWords(VariantEnglish, f, f) if err != nil { t.Fatalf("DiffWords: %v", err) } if len(diff.Added) != 0 || len(diff.Removed) != 0 { t.Errorf("diff = %+v, want empty", diff) } } // TestDiffWordsUnknownVariant checks that an unrecognised variant is rejected. func TestDiffWordsUnknownVariant(t *testing.T) { rs, _ := VariantEnglish.ruleset() f := buildFinderB(t, rs.Alphabet, []byte{0}) defer func() { _ = f.Close() }() if _, err := DiffWords(Variant(250), f, f); !errors.Is(err, ErrUnknownVariant) { t.Errorf("err = %v, want ErrUnknownVariant", err) } } // TestOpenFinder checks that OpenFinder loads a committed DAWG from a directory. func TestOpenFinder(t *testing.T) { f, err := OpenFinder(testDictDir(), VariantEnglish) if err != nil { t.Fatalf("OpenFinder: %v", err) } defer func() { _ = f.Close() }() if f.NumAdded() == 0 { t.Error("english dictionary is empty") } } // TestRegistryFinder checks that Finder returns the resident finder for a pair // and the right sentinel when the version is absent. func TestRegistryFinder(t *testing.T) { if _, err := testReg.Finder(VariantEnglish, testVersion); err != nil { t.Errorf("Finder(scrabble_en, %q): %v", testVersion, err) } if _, err := testReg.Finder(VariantEnglish, "absent"); !errors.Is(err, ErrUnknownVersion) { t.Errorf("Finder absent version err = %v, want ErrUnknownVersion", err) } } // TestDictFiles checks that DictFiles exposes the variant filename map as a copy // the caller cannot use to mutate the engine's own map. func TestDictFiles(t *testing.T) { files := DictFiles() if len(files) != len(Variants()) { t.Fatalf("DictFiles has %d entries, want %d", len(files), len(Variants())) } files[VariantEnglish] = "tampered" if again := DictFiles(); again[VariantEnglish] == "tampered" { t.Error("DictFiles returned a shared map; mutating it changed the engine's map") } }