Documentation
¶
Overview ¶
Package sessionfuzz fuzzes a poker engine across whole multi-hand sessions.
Hand-history corpora (PHH, site histories) test single hands: every hand starts from given seats, button and blinds. The bugs that freeze real tables live in the step from one hand to the next — busts, button and blind movement, sit-in / sit-out, joins and leaves, tournament eliminations. This package plays whole sessions against any engine that implements Table, mostly choosing among each seat's own legal actions, and checks invariants that must always hold after every action. There is no external oracle: a violation is a bug.
The engine is plugged in through two small interfaces, Engine and Table. The vocabulary (actions, statuses, rounds) is plain poker English; an adapter maps it to and from the engine's own types.
Index ¶
- Constants
- Variables
- func Deck(seed uint64) []string
- func DefaultAddresses(n int) []string
- func DescribeState(st State) string
- func Fuzz(f *testing.F, engine Engine, opts Options)
- func Replay(t *testing.T, engine Engine, opts Options)
- func SeedBytes(seed uint64, n int) []byte
- func Sweep(t *testing.T, engine Engine, opts Options)
- type Action
- type Config
- type Engine
- type EngineFunc
- type Format
- type KnownIssue
- type LegalAction
- type Options
- type Player
- type Recorded
- type Result
- type Round
- type Rules
- type State
- type Status
- type Table
- type Violation
Constants ¶
const ( ActJoin = "join" ActLeave = "leave" ActSitIn = "sit-in" ActSitInAndWait = "sit-in-and-wait" ActSitOut = "sit-out" ActTopUp = "top-up" ActNewHand = "new-hand" ActTimeout = "timeout" ActDeal = "deal" ActPostSmallBlind = "post-small-blind" ActPostBigBlind = "post-big-blind" ActFold = "fold" ActCheck = "check" ActCall = "call" ActBet = "bet" ActRaise = "raise" ActAllIn = "all-in" ActShow = "show" ActMuck = "muck" )
Action names the fuzzer sends or reasons about. Betting actions are taken verbatim from each player's legal actions, so an engine may offer more.
const ( SweepSessions = 200 SweepBytes = 1000 )
SweepSessions and SweepBytes size Sweep: 200 sessions of 1000 input bytes each take a few seconds against an in-process engine.
const Tail = 40
Tail is how many trailing script lines Result.Report prints.
Variables ¶
var DefaultProgress = map[string]bool{ ActPostSmallBlind: true, ActPostBigBlind: true, ActFold: true, ActCheck: true, ActCall: true, ActBet: true, ActRaise: true, ActAllIn: true, ActShow: true, ActMuck: true, ActDeal: true, ActNewHand: true, }
DefaultProgress is the default set of actions that move play on.
Functions ¶
func DefaultAddresses ¶
DefaultAddresses returns n distinct placeholder player addresses.
func DescribeState ¶
DescribeState renders a table state, one line per seat.
func Fuzz ¶
Fuzz registers seeds and the fuzz target for coverage-guided fuzzing:
func FuzzSession(f *testing.F) { sessionfuzz.Fuzz(f, myEngine, opts) }
go test -run '^$' -fuzz FuzzSession -fuzztime 5m
func Replay ¶
Replay prints one Sweep session in full when SESSIONFUZZ_SEED is set — the script, the final table, and the session as a poker-scene-dsl scenario — and skips otherwise:
SESSIONFUZZ_SEED=5 go test -run TestReplay -v
Types ¶
type Action ¶
type Action struct {
// Player is the acting player's address; empty for a timeout, which acts
// for whoever is on the clock.
Player string
Action string
Amount *big.Int
// Seat is the seat to take (join only).
Seat int
// Deck is the next hand's deck, top first (new-hand only). An engine that
// shuffles itself may ignore it, at the cost of determinism.
Deck []string
// Time is the action's timestamp in Unix milliseconds: a second after the
// last accepted action, or ten minutes and a second for a timeout.
Time int64
}
Action is one move the fuzzer sends.
type Config ¶
type Config struct {
Format Format
MaxPlayers int
// MinPlayers is the fewest players a cash table deals to. For a sit-and-go
// the table starts when MaxPlayers have joined.
MinPlayers int
SmallBlind *big.Int
BigBlind *big.Int
// Cash: the buy-in range. Sit-and-go: MinBuyIn is the entry price.
MinBuyIn *big.Int
MaxBuyIn *big.Int
// Sit-and-go only.
StartingStack *big.Int
BlindLevelMinutes int
}
Config is one table the fuzzer plays. All amounts are chips except BuyIn for a sit-and-go, which is whatever unit the host charges.
func DefaultConfig ¶
DefaultConfig maps a byte to one of ten tables: cash or sit-and-go, with 2, 3, 4, 6 or 9 seats, blinds 50/100. Stacks are short (10 big blinds for a sit-and-go, with one-minute levels) so busts and blind rises happen inside a short session.
type EngineFunc ¶
EngineFunc adapts a function to Engine.
type KnownIssue ¶
KnownIssue marks violations of an open bug. Match is tested against "invariant: detail" (see Violation.Error).
type LegalAction ¶
LegalAction is one action a player may take now, with its amount range when it carries one (nil for fold / check / leave and the like).
type Options ¶
type Options struct {
// Config picks the table for a session from the session's first input byte.
// Nil means [DefaultConfig].
Config func(choice byte) Config
// Rules are the engine's own conventions the invariants depend on.
Rules Rules
// Known are open bugs: a matching violation is counted and play continues.
Known []KnownIssue
// Addresses is the pool of players a session draws from. Nil means
// [DefaultAddresses](12). Use more addresses than seats so that a fresh
// player can take a seat someone left.
Addresses []string
}
Options configures a run. The zero value works: it uses DefaultConfig and the default rules.
type Player ¶
type Player struct {
Address string
Seat int
Stack *big.Int
Status Status
// Pending is chips owned by the player but not yet in the stack (a top-up
// queued during a hand). Nil when none.
Pending *big.Int
Legal []LegalAction
}
Player is one seated player as the engine reports it.
type Result ¶
type Result struct {
Config Config
// Script is the readable log of every accepted action — the repro.
Script []string
// Final is the table state when the session stopped.
Final State
// Violation is the first violation not matched by a known issue, or nil.
Violation *Violation
// Known counts violations of each known issue.
Known map[string]int
// KnownFirst is the first violation of each known issue, for Minimize.
KnownFirst map[string]*Violation
// Actions are the accepted actions in order, with the actor's seat at
// the time (0 for a join or a timeout). [Result.Scene] renders them.
Actions []Recorded
}
Result is one played session.
func Minimize ¶ added in v0.3.0
Minimize shrinks a failing session to the shortest list of actions that still produces the same kind of violation, so Result.Scene() becomes a short scenario worth committing as a test. It replays subsets of r.Actions on fresh tables, dropping chunks while the violation persists (halving the chunk size down to single actions). Actions a smaller replay makes illegal are rejected by the engine and left out of the result.
target is the violation to keep; nil means r.Violation. A known issue's violation can be minimised too: pass it as target.
func Run ¶
Run plays one session from data and returns what happened. The first input byte picks the table; the rest drive the play.
func (*Result) Report ¶
Report is a readable account of a session: the violation, the end of the script that led to it, and the final table.
func (*Result) Scene ¶ added in v0.2.0
Scene renders the session's accepted actions as sentences for github.com/block52/poker-scene-dsl, so a fuzz failure becomes a readable, replayable test. Players are named by seat ("seat 3 calls"); a joining player by address. Amounts are the ones the engine was sent: raises render as "raises by N" and bets as "bets N".
type Round ¶
type Round string
Round is the betting round. Ante covers the time before cards are dealt (blinds being posted); End is the gap between hands.
type Rules ¶
type Rules struct {
// BustBelow is the smallest stack that is NOT busted. Nil means 1: only an
// empty stack busts. An engine that busts a cash stack too small to post a
// blind returns the small blind here for cash tables.
BustBelow func(Config) *big.Int
// NewHandFloor is how many players with chips must want to play before
// someone must be offered new-hand. Nil means MinPlayers for cash and 2
// for a sit-and-go.
NewHandFloor func(Config) int
// Reject is the host's own gate, applied before the engine sees an action:
// return true for actions the host never forwards, so the fuzzer only
// reaches states the host can. Nil forwards everything.
Reject func(Config, State, Action) bool
// Progress are the actions that move a hand or the table on (leave,
// sit-out and top-up do not). Nil means [DefaultProgress].
Progress map[string]bool
}
Rules capture where engines legitimately differ.
type State ¶
type State struct {
Round Round
HandNumber int
NextToAct int
Dealer int
SmallBlind int
BigBlind int
// Pot is every chip in the middle (all streets, all side pots). It is not
// read at End, where the pot has already been paid out.
Pot *big.Int
Players []Player
}
State is the engine's view of the table after an action. Seat numbers are 1-based; 0 means "none".
type Status ¶
type Status string
Status is a player's seat status.
const ( // In a hand. Active Status = "active" Folded Status = "folded" AllIn Status = "all-in" Showing Status = "showing" // Out of the hand. Busted Status = "busted" Seated Status = "seated" // joined, has not chosen to play yet SittingOut Status = "sitting-out" // chose not to play // Pending entrants: will be dealt in on a later hand. SittingIn Status = "sitting-in" // post now, enter next legal hand WaitingForBigBlind Status = "waiting-for-big-blind" // enter when the big blind reaches the seat )
type Table ¶
Table is one live table. Apply must leave the table unchanged when it returns an error (a rejected action is normal: the fuzzer tries things). A panic in Apply is caught and reported as a violation.