package casino import ( "sync" "testing" "time" ) // rebet is called through Manager.Rebet and reads the seat map; it must take // the table lock like every other game method, or a future caller off the // owner goroutine would race the live seat state. func TestRebetTakesTheTableLock(t *testing.T) { blackjack := newBlackjackTable(1, TableConfig{ID: "blackjack", Game: GameBlackjack}, newTestRand()) blackjack.join("alice") assertRebetWaitsForLock(t, "blackjack", &blackjack.mu, func() { blackjack.rebet("alice") }) baccarat := newBaccaratTable(1, TableConfig{ID: "baccarat", Game: GameBaccarat}, newTestRand()) baccarat.join("alice") assertRebetWaitsForLock(t, "baccarat", &baccarat.mu, func() { baccarat.rebet("alice") }) } func assertRebetWaitsForLock(t *testing.T, name string, mu *sync.Mutex, rebet func()) { t.Helper() mu.Lock() done := make(chan struct{}) go func() { rebet() close(done) }() select { case <-done: mu.Unlock() t.Fatalf("%s rebet read the seat map without the table lock", name) case <-time.After(50 * time.Millisecond): } mu.Unlock() select { case <-done: case <-time.After(2 * time.Second): t.Fatalf("%s rebet did not return after the lock was released", name) } } // A lone player at the table is deliberately never timed out: neither the // action window nor the turn ceiling advances, so the hand waits for the // player's own decision. (Multiplayer turns are still bounded; see // TestBlackjackWarnsOnceThenAutoStands.) func TestLoneBettorHasNoTurnTimer(t *testing.T) { table := newBlackjackTable(1, TableConfig{ID: "blackjack", Game: GameBlackjack, Blackjack: BlackjackConfig{ActionTicks: 2}}, newTestRand()) table.shoe = tableauShoe( card{rank: "10"}, card{rank: "7"}, // dealer 17 card{rank: "10"}, card{rank: "6"}, // alice 16 ) wallet := &testWallet{credits: 100} table.join("alice") table.bet("alice", "", 10, wallet) if table.phase != blackjackPlaying { t.Fatalf("setup: expected a dealt hand (phase=%s)", table.phase) } // Far past both the action window and the old turn ceiling, the hand must // still be waiting: neither deadline runs for a lone player. ceiling := table.Rules.ActionTicks * loneTurnGrace for i := 0; i < ceiling+5; i++ { table.tick() } if table.phase != blackjackPlaying { t.Fatalf("lone player's hand was settled by a timer (phase=%s)", table.phase) } if table.ceiling.clock != 0 || table.timer.clock != 0 { t.Fatalf("a lone player's timer advanced: ceiling=%d timer=%d", table.ceiling.clock, table.timer.clock) } if len(wallet.open) != 1 { t.Fatalf("stake resolved without player action: %+v", wallet.open) } }