ec435c0e7f
- backend/go.mod pins gitea.iliadenisov.ru/developer/scrabble-solver v1.0.0; the engine's imports use the published module path; go.work drops the solver replace (GOPRIVATE fetches it directly from Gitea). The solver's wordlist/dictdawg are now public packages. - CI (go-unit, integration): drop the solver sibling-clone, set GOPRIVATE, and download the dictionary DAWG release artifact (scrabble-dawg-<DICT_VERSION>.tar.gz from the new scrabble-dictionary repo) for BACKEND_DICT_DIR. - Docs: ARCHITECTURE §5/§11/§13/§14 + backend/README updated to the published-module + release-artifact model. PLAN.md re-scoped Stage 14 to the split and added Stages 15 (deploy infra & test contour), 16 (prod contour), 17 (dual Telegram bots); TODO-1/TODO-2 marked done.
72 lines
2.0 KiB
Go
72 lines
2.0 KiB
Go
package engine
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"gitea.iliadenisov.ru/developer/scrabble-solver/rules"
|
|
"gitea.iliadenisov.ru/developer/scrabble-solver/scrabble"
|
|
)
|
|
|
|
// testVersion labels the single dictionary version the tests register.
|
|
const testVersion = "test"
|
|
|
|
// testReg is the shared registry of all three variants, hydrated once by
|
|
// TestMain and reused by the read-only tests.
|
|
var testReg *Registry
|
|
|
|
// TestMain loads the committed dictionaries once and shares them with every
|
|
// test. It fails loudly when the dictionary directory is absent (per
|
|
// docs/TESTING.md) rather than skipping coverage.
|
|
func TestMain(m *testing.M) {
|
|
reg, err := Open(testDictDir(), testVersion)
|
|
if err != nil {
|
|
fmt.Fprintln(os.Stderr, "engine test setup:", err)
|
|
os.Exit(1)
|
|
}
|
|
testReg = reg
|
|
code := m.Run()
|
|
_ = reg.Close()
|
|
os.Exit(code)
|
|
}
|
|
|
|
// testDictDir resolves the directory holding the committed scrabble-solver
|
|
// DAWGs: BACKEND_DICT_DIR when set (used in CI), otherwise the sibling checkout
|
|
// located relative to this test file.
|
|
func testDictDir() string {
|
|
if dir := os.Getenv("BACKEND_DICT_DIR"); dir != "" {
|
|
return dir
|
|
}
|
|
_, file, _, _ := runtime.Caller(0)
|
|
return filepath.Join(filepath.Dir(file), "..", "..", "..", "..", "scrabble-solver", "dawg")
|
|
}
|
|
|
|
// centre returns the centre square coordinates of rs.
|
|
func centre(rs *rules.Ruleset) (row, col int) {
|
|
return rs.Center / rs.Cols, rs.Center % rs.Cols
|
|
}
|
|
|
|
// placementsForWord lays word out from (row, col) along dir, resolving each rune
|
|
// through the ruleset's alphabet. It expresses no blanks.
|
|
func placementsForWord(t *testing.T, rs *rules.Ruleset, row, col int, dir scrabble.Direction, word string) []scrabble.Placement {
|
|
t.Helper()
|
|
var ps []scrabble.Placement
|
|
for i, r := range []rune(word) {
|
|
idx, err := rs.Alphabet.Index(string(r))
|
|
if err != nil {
|
|
t.Fatalf("index %q: %v", string(r), err)
|
|
}
|
|
rr, cc := row, col
|
|
if dir == scrabble.Horizontal {
|
|
cc += i
|
|
} else {
|
|
rr += i
|
|
}
|
|
ps = append(ps, scrabble.Placement{Row: rr, Col: cc, Letter: idx})
|
|
}
|
|
return ps
|
|
}
|