test(inttest): de-flake the no-repeat pin guard
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 12s
CI / integration (pull_request) Successful in 21s
CI / ui (pull_request) Successful in 1m16s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m51s

The bag seed comes from crypto/rand, so an opening rack occasionally has no
playable word — a legitimate engine answer that says nothing about the pin the
test is guarding. Reproduced at roughly one run in three hundred locally, and
it turned CI red once. Retry with fresh games instead: an unlucky draw no
longer decides the result, while a genuinely broken path still fails every
attempt.
This commit is contained in:
Ilia Denisov
2026-07-28 15:47:19 +02:00
parent e3c2e80a0a
commit f8efa21158
+18 -2
View File
@@ -4,6 +4,7 @@ package inttest
import ( import (
"context" "context"
"errors"
"testing" "testing"
"github.com/google/uuid" "github.com/google/uuid"
@@ -64,8 +65,15 @@ func TestNoRepeatWordsPinnedFromTheVariant(t *testing.T) {
func TestNoRepeatWordsHonoursThePinNotTheVariant(t *testing.T) { func TestNoRepeatWordsHonoursThePinNotTheVariant(t *testing.T) {
ctx := context.Background() ctx := context.Background()
svc := newGameService() svc := newGameService()
g := createTwoSeatGame(t, svc, engine.VariantErudit)
// Each game draws from a bag seeded with crypto/rand, so an opening rack occasionally has no
// playable word at all. That is a legitimate engine answer which says nothing about the pin
// under test, and taking it for a failure made this test flake (~1 run in 300 on the committed
// dictionary, and more often on another one). Retry with fresh games instead: one unlucky draw
// no longer decides the result, while a genuinely broken path still fails every attempt.
const draws = 5
for attempt := 1; ; attempt++ {
g := createTwoSeatGame(t, svc, engine.VariantErudit)
if _, err := testDB.ExecContext(ctx, if _, err := testDB.ExecContext(ctx,
`UPDATE backend.games SET no_repeat_words = false WHERE game_id = $1`, g.ID); err != nil { `UPDATE backend.games SET no_repeat_words = false WHERE game_id = $1`, g.ID); err != nil {
t.Fatalf("clear the pin: %v", err) t.Fatalf("clear the pin: %v", err)
@@ -80,7 +88,15 @@ func TestNoRepeatWordsHonoursThePinNotTheVariant(t *testing.T) {
} }
// The rule reaches the engine from the row, so the reconstructed game plays unrestricted: // The rule reaches the engine from the row, so the reconstructed game plays unrestricted:
// its generated moves are the solver's full list, none filtered away. // its generated moves are the solver's full list, none filtered away.
if _, err := svc.Hint(ctx, reloaded.ID, reloaded.Seats[reloaded.ToMove].AccountID); err != nil { _, err = svc.Hint(ctx, reloaded.ID, reloaded.Seats[reloaded.ToMove].AccountID)
if err == nil {
return
}
if !errors.Is(err, game.ErrNoHintAvailable) {
t.Fatalf("hint on an unrestricted Erudit game: %v", err) t.Fatalf("hint on an unrestricted Erudit game: %v", err)
} }
if attempt == draws {
t.Fatalf("no opening rack in %d games had a playable move", draws)
}
}
} }