diff options
Diffstat (limited to 'internal/game/hacking/liars_dice_test.go')
| -rw-r--r-- | internal/game/hacking/liars_dice_test.go | 68 |
1 files changed, 68 insertions, 0 deletions
diff --git a/internal/game/hacking/liars_dice_test.go b/internal/game/hacking/liars_dice_test.go new file mode 100644 index 0000000..59ebb92 --- /dev/null +++ b/internal/game/hacking/liars_dice_test.go @@ -0,0 +1,68 @@ +package hacking + +import ( + "strings" + "testing" +) + +// Regression: round resolution (challenge and exact calls) passed an +// already-used strings.Builder BY VALUE into checkGameOver, panicking with +// "illegal use of non-zero Builder copied by value" on ordinary input — a +// forced challenge resolves on the very first bid. A panic here runs on the +// game owner goroutine, which has no recovery, so it terminated the server. +// See building_guide/concurrency.md for the owner's no-recovery guarantee. + +func TestLiarsDiceForcedChallengeDoesNotPanic(t *testing.T) { + g := NewLiarsDiceGame() + g.Init(1) + // An impossible bid deterministically forces an immediate AI challenge. + out, _, _ := g.HandleInput("bid 100 6") + if !strings.Contains(out, "Actual count") { + t.Fatalf("challenge output missing the reveal: %q", out) + } + if !strings.Contains(out, "You lose a fragment") && !strings.Contains(out, "System loses a fragment") { + t.Fatalf("challenge did not resolve a fragment loss: %q", out) + } +} + +func TestLiarsDiceCallAndExactResolveWithoutPanic(t *testing.T) { + // Call path: bid, let the system raise, then challenge it. + g := NewLiarsDiceGame() + g.Init(1) + if out, _, _ := g.HandleInput("bid 2 3"); out == "" { + t.Fatal("opening bid produced no output") + } + if out, _, _ := g.HandleInput("call"); out == "" { + t.Fatal("call produced no output") + } + + // Exact path: bid, let the system raise, then claim exact. + g = NewLiarsDiceGame() + g.Init(1) + if out, _, _ := g.HandleInput("bid 2 3"); out == "" { + t.Fatal("opening bid produced no output") + } + if out, _, _ := g.HandleInput("exact"); out == "" { + t.Fatal("exact produced no output") + } +} + +func TestLiarsDiceFullLossGameResolvesWithoutPanic(t *testing.T) { + // Bid high enough that the system always calls and the player always + // loses a fragment, driving the game to its terminal resolution. + g := NewLiarsDiceGame() + g.Init(1) + for i := 0; i < 10; i++ { + out, done, won := g.HandleInput("bid 100 6") + if done { + if won { + t.Fatal("all-in losing bids cannot win") + } + return + } + if out == "" { + t.Fatal("round produced no output") + } + } + t.Fatal("game did not terminate within 10 forced-loss rounds") +} |
