mirror of
https://github.com/tiennm99/noitu.git
synced 2026-10-11 03:13:45 +00:00
refactor: move test-only helpers out of production code and drop stale comments
bot.BoardFor and hub.roomCount were reachable from tests only and now live beside them; session.serve drops an always-true nil check (readLoop never returns nil); invisible characters in test literals become escapes; comments no longer cite plan phases; `.dockerignore` keeps the dump out of the build context. No behaviour change; vet, deadcode, staticcheck, go test -race, svelte-check and vitest green.
This commit is contained in:
1 parent
f00d0ef974
commit
e29c06d6f0
11 files changed
+64
-50
No files matched your search
@@ -8,6 +8,9 @@ web/build
|
|||||||
web/.svelte-kit
|
web/.svelte-kit
|
||||||
web/test-results
|
web/test-results
|
||||||
web/playwright-report
|
web/playwright-report
|
||||||
|
data/*.bz2
|
||||||
|
data/*.bz2.part
|
||||||
|
data/*.xml
|
||||||
data/*.db
|
data/*.db
|
||||||
data/*.db-journal
|
data/*.db-journal
|
||||||
data/*.db-wal
|
data/*.db-wal
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func TestStripWikitext(t *testing.T) {
|
|||||||
"Một nghĩa."},
|
"Một nghĩa."},
|
||||||
{"a stray closer is not text", "Một }} nghĩa.", "Một nghĩa."},
|
{"a stray closer is not text", "Một }} nghĩa.", "Một nghĩa."},
|
||||||
{"only punctuation is empty", "(...).", ""},
|
{"only punctuation is empty", "(...).", ""},
|
||||||
{"control characters and whitespace collapse", " Một từ \t hai ", "Một từ hai"},
|
{"control characters and whitespace collapse", " Một\a từ \t hai ", "Một từ hai"},
|
||||||
{"unclosed template does not leak", "Một {{label|vi|x từ.", "Một"},
|
{"unclosed template does not leak", "Một {{label|vi|x từ.", "Một"},
|
||||||
}
|
}
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package bot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"iter"
|
||||||
|
|
||||||
|
"github.com/tiennm99dev/noitu/server/internal/game"
|
||||||
|
)
|
||||||
|
|
||||||
|
// engineBoard adapts an Engine to Board for the simulations in this package.
|
||||||
|
// The server never needs it: the room hands the bot a Board of its own.
|
||||||
|
type engineBoard struct {
|
||||||
|
e *game.Engine
|
||||||
|
dict game.Dictionary
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoardFor wraps an engine so a test can drive a strategy against it.
|
||||||
|
//
|
||||||
|
// The dictionary comes from the engine rather than the caller: handing in a
|
||||||
|
// different one would let the bot search a graph the engine does not validate
|
||||||
|
// against, and that failure surfaces as the engine rejecting its own bot's
|
||||||
|
// move at runtime.
|
||||||
|
func BoardFor(e *game.Engine) Board {
|
||||||
|
return &engineBoard{e: e, dict: e.Dict()}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *engineBoard) LegalMoves() []string { return b.e.LegalMoves() }
|
||||||
|
|
||||||
|
func (b *engineBoard) Used(word string) bool { return b.e.Used(word) }
|
||||||
|
|
||||||
|
func (b *engineBoard) WordsStartingWith(syllable string) iter.Seq[string] {
|
||||||
|
return b.dict.WordsStartingWith(syllable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *engineBoard) LastSyllable(word string) (string, bool) {
|
||||||
|
return b.dict.LastSyllable(word)
|
||||||
|
}
|
||||||
@@ -14,8 +14,6 @@ import (
|
|||||||
"iter"
|
"iter"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/tiennm99dev/noitu/server/internal/game"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Difficulty selects a strategy.
|
// Difficulty selects a strategy.
|
||||||
@@ -96,34 +94,6 @@ func thinkingDelay(rng *rand.Rand, min, max time.Duration) time.Duration {
|
|||||||
return min + time.Duration(rng.Int64N(int64(max-min)))
|
return min + time.Duration(rng.Int64N(int64(max-min)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// engineBoard adapts an Engine to Board.
|
|
||||||
type engineBoard struct {
|
|
||||||
e *game.Engine
|
|
||||||
dict game.Dictionary
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoardFor wraps an engine so strategies can inspect it.
|
|
||||||
//
|
|
||||||
// The dictionary comes from the engine rather than the caller: handing in a
|
|
||||||
// different one would let the bot search a graph the engine does not validate
|
|
||||||
// against, and that failure surfaces as the engine rejecting its own bot's
|
|
||||||
// move at runtime.
|
|
||||||
func BoardFor(e *game.Engine) Board {
|
|
||||||
return &engineBoard{e: e, dict: e.Dict()}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *engineBoard) LegalMoves() []string { return b.e.LegalMoves() }
|
|
||||||
|
|
||||||
func (b *engineBoard) Used(word string) bool { return b.e.Used(word) }
|
|
||||||
|
|
||||||
func (b *engineBoard) WordsStartingWith(syllable string) iter.Seq[string] {
|
|
||||||
return b.dict.WordsStartingWith(syllable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *engineBoard) LastSyllable(word string) (string, bool) {
|
|
||||||
return b.dict.LastSyllable(word)
|
|
||||||
}
|
|
||||||
|
|
||||||
// remainingOutDegree counts the continuations still available from a syllable,
|
// remainingOutDegree counts the continuations still available from a syllable,
|
||||||
// ignoring `excluding` — the move being considered, which will itself be spent
|
// ignoring `excluding` — the move being considered, which will itself be spent
|
||||||
// once played. Pass "" to exclude nothing.
|
// once played. Pass "" to exclude nothing.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Package wsapi is the transport layer: it translates between the game engine
|
// Package wsapi is the transport layer: it translates between the game engine
|
||||||
// and the generated protobuf wire types, and (from phase 5 on) owns the
|
// and the generated protobuf wire types, and owns the WebSocket sessions and
|
||||||
// WebSocket sessions and rooms built on top of them.
|
// rooms built on top of them.
|
||||||
//
|
//
|
||||||
// The engine's reason enums and the wire enums are deliberately distinct
|
// The engine's reason enums and the wire enums are deliberately distinct
|
||||||
// types. Renumbering an internal constant must never silently change what a
|
// types. Renumbering an internal constant must never silently change what a
|
||||||
|
|||||||
@@ -230,14 +230,6 @@ func (h *hub) shutdown() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// roomCount reports live rooms. Tests use it to assert eviction; nothing in
|
|
||||||
// the server depends on it.
|
|
||||||
func (h *hub) roomCount() int {
|
|
||||||
h.mu.Lock()
|
|
||||||
defer h.mu.Unlock()
|
|
||||||
return len(h.rooms)
|
|
||||||
}
|
|
||||||
|
|
||||||
// playerIDFor is the seat a session holds. Declared here because the mapping
|
// playerIDFor is the seat a session holds. Declared here because the mapping
|
||||||
// between a connection and a seat is registry knowledge, not game knowledge.
|
// between a connection and a seat is registry knowledge, not game knowledge.
|
||||||
func playerIDFor(seatName string) game.PlayerID { return game.PlayerID(seatName) }
|
func playerIDFor(seatName string) game.PlayerID { return game.PlayerID(seatName) }
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package wsapi
|
||||||
|
|
||||||
|
// roomCount reports live rooms, so a test can assert that a room was evicted.
|
||||||
|
// Nothing in the server depends on it.
|
||||||
|
func (h *hub) roomCount() int {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
return len(h.rooms)
|
||||||
|
}
|
||||||
@@ -28,7 +28,7 @@ type Config struct {
|
|||||||
// that also serves the frontend.
|
// that also serves the frontend.
|
||||||
AllowedOrigins []string
|
AllowedOrigins []string
|
||||||
// WebDir is the built frontend. Empty, or missing on disk, serves the API
|
// WebDir is the built frontend. Empty, or missing on disk, serves the API
|
||||||
// alone — which is the state until phase 6 produces a bundle.
|
// alone, which is how the server runs before the frontend has been built.
|
||||||
WebDir string
|
WebDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -276,8 +276,10 @@ func (s *session) run() {
|
|||||||
s.close()
|
s.close()
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
|
// readLoop only ever returns an error; a cancelled context is the one
|
||||||
|
// that means the server chose to close.
|
||||||
status, reason := websocket.StatusNormalClosure, ""
|
status, reason := websocket.StatusNormalClosure, ""
|
||||||
if err != nil && !errors.Is(err, context.Canceled) {
|
if !errors.Is(err, context.Canceled) {
|
||||||
status, reason = websocket.StatusPolicyViolation, "protocol error"
|
status, reason = websocket.StatusPolicyViolation, "protocol error"
|
||||||
}
|
}
|
||||||
_ = s.conn.Close(status, reason)
|
_ = s.conn.Close(status, reason)
|
||||||
|
|||||||
@@ -878,8 +878,8 @@ func TestSanitizeNickname(t *testing.T) {
|
|||||||
{"empty falls back", "", defaultNickname},
|
{"empty falls back", "", defaultNickname},
|
||||||
{"whitespace only falls back", " \t\n ", defaultNickname},
|
{"whitespace only falls back", " \t\n ", defaultNickname},
|
||||||
{"control characters stripped", "Mi\x00nh\x07", "Minh"},
|
{"control characters stripped", "Mi\x00nh\x07", "Minh"},
|
||||||
{"zero width stripped", "Minh", "Minh"},
|
{"zero width stripped", "Mi\u200bnh\u200d", "Minh"},
|
||||||
{"bidi override stripped", "Minh", "Minh"},
|
{"bidi override stripped", "Minh\u202e", "Minh"},
|
||||||
{"whitespace collapsed", " Minh Nguyen ", "Minh Nguyen"},
|
{"whitespace collapsed", " Minh Nguyen ", "Minh Nguyen"},
|
||||||
{"newlines become spaces", "Minh\nNguyen", "Minh Nguyen"},
|
{"newlines become spaces", "Minh\nNguyen", "Minh Nguyen"},
|
||||||
{"over length truncated", strings.Repeat("a", 40), strings.Repeat("a", maxNicknameRunes)},
|
{"over length truncated", strings.Repeat("a", 40), strings.Repeat("a", maxNicknameRunes)},
|
||||||
@@ -939,8 +939,10 @@ func TestBucketRefills(t *testing.T) {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
b := newBucket(5, 2, now)
|
b := newBucket(5, 2, now)
|
||||||
|
|
||||||
if !b.allow(now) || !b.allow(now) {
|
for i := range 2 {
|
||||||
t.Fatal("burst should cover the first two")
|
if !b.allow(now) {
|
||||||
|
t.Fatalf("burst should cover the first two, call %d refused", i+1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if b.allow(now) {
|
if b.allow(now) {
|
||||||
t.Fatal("third call should exhaust the bucket")
|
t.Fatal("third call should exhaust the bucket")
|
||||||
|
|||||||
@@ -57,9 +57,9 @@ const DICTIONARY_WORDS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A budget that binds. The bundle is around 190 KB, so this leaves room for the
|
* A budget that binds. The bundle is around 190 KB, so this leaves room for
|
||||||
* phase-7 screens without leaving room for a wordlist: the derived dictionary
|
* more screens without leaving room for a wordlist: the derived dictionary is
|
||||||
* is about 3 MB, and even a fraction of it would not fit here.
|
* several MB, and even a fraction of it would not fit here.
|
||||||
*/
|
*/
|
||||||
const MAX_TOTAL_BYTES = 400_000;
|
const MAX_TOTAL_BYTES = 400_000;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user