Documentation
¶
Overview ¶
Package dstsim provides deterministic simulation testing for Go services.
Replace every source of non-determinism (time, randomness) with seeded, controlled alternatives. Same seed = same run = same bug.
sim := dstsim.New(dstsim.DefaultConfig(seed))
sim.Register(dstsim.OpEntry{Name: "create", Weight: 3, Run: fn})
sim.Invariant(func(seq int) { /* check state */ })
sim.Run()
RNG, Clock, and Injector let you generate data, simulate time, and inject faults, all driven by a single deterministic seed.
Index ¶
- func Pick[T any](rng *RNG, items []T) T
- func PickOrGhostID(rng *RNG, p *Pool[int64]) int64
- func PickWeighted[T any](rng *RNG, items []T, weights []int) T
- func Sample[T any](rng *RNG, items []T, n int) []T
- func Shuffle[T any](rng *RNG, items []T) []T
- type Clock
- type Config
- type Fake
- func (f *Fake) Add(name string, items []string)
- func (f *Fake) Amount(maxCents int) int64
- func (f *Fake) Bool(p float64) bool
- func (f *Fake) Email(tenant string) string
- func (f *Fake) From(name string) string
- func (f *Fake) Has(name string) bool
- func (f *Fake) Int(min, max int) int
- func (f *Fake) IntN(n int) int
- func (f *Fake) Name() string
- func (f *Fake) Percent() int
- func (f *Fake) RNG() *RNG
- func (f *Fake) Slug(prefix string) string
- func (f *Fake) Title(domain string) string
- func (f *Fake) Username() string
- type Injector
- type InvariantViolation
- type OpEntry
- type Pool
- type RNG
- type Sim
- func (s *Sim) Assert(seq int, cond bool, reason string)
- func (s *Sim) Assertf(seq int, cond bool, format string, args ...any)
- func (s *Sim) Clock() *Clock
- func (s *Sim) CurrentSeq() int
- func (s *Sim) Fail(seq int, reason string)
- func (s *Sim) Injector() *Injector
- func (s *Sim) Invariant(fn func(seq int))
- func (s *Sim) RNG() *RNG
- func (s *Sim) Register(op OpEntry)
- func (s *Sim) Run()
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Pick ¶
Pick returns a random element from items using the provided RNG. Package-level generic function because Go doesn't allow generic methods on non-generic types.
func PickOrGhostID ¶
PickOrGhostID returns a real ID from the pool ~80% of the time and a fake "ghost" ID the rest of the time, letting ops exercise not-found paths without always hitting them. Ghost IDs start at 1e9, above any realistic auto-increment sequence, so services that assert id > 0 still work.
func PickWeighted ¶
PickWeighted returns a random element using weighted probability. weights[i] is the relative probability of items[i].
Types ¶
type Clock ¶
type Clock struct {
// contains filtered or unexported fields
}
Clock is a simulated clock for deterministic testing. Time only advances when Advance or Set is called.
type Config ¶
type Config struct {
Seed int64
Start time.Time // simulated clock start
Ops int // number of operations to execute
ErrorRate float64 // probability of injected errors (0.0–1.0)
SlowRate float64 // probability of injected slowness (0.0–1.0)
SlowMax time.Duration // maximum injected delay
}
Config controls the simulation parameters.
func DefaultConfig ¶
DefaultConfig returns a Config with sensible defaults for the given seed.
type Fake ¶
type Fake struct {
// contains filtered or unexported fields
}
Fake provides bounded fake data generators for simulation ops. All methods produce valid domain data — the RNG controls which valid value is selected, not whether it's valid.
Register named string lists with Add, then pick from them with From:
fake.Add("roles", []string{"admin", "editor", "viewer"})
role := fake.From("roles")
Embed Fake in a domain-specific type to add project-specific generators.
func (*Fake) From ¶
From returns a random element from the named resource list. Panics if the resource was not registered with Add.
type Injector ¶
type Injector struct {
// contains filtered or unexported fields
}
Injector provides probabilistic fault injection controlled by a deterministic RNG. Pass it to fake dependencies so they can simulate errors and slowness reproducibly.
func (*Injector) MaybeError ¶
MaybeError returns one of the provided errors with probability equal to the configured error rate. Returns nil otherwise.
type InvariantViolation ¶
type InvariantViolation struct {
Reason string
Op int
Total int
Seed int64
Calls int64
History []string // most recent op names leading to the failure
}
InvariantViolation is the panic value when an invariant or assertion fails.
func (*InvariantViolation) Error ¶
func (v *InvariantViolation) Error() string
type OpEntry ¶
OpEntry defines a weighted operation for the simulation runner.
Weight controls how often this operation is selected relative to others. Run receives the simulation's RNG and Clock so operations can generate data and observe time deterministically.
Return nil for expected application errors (e.g. validation failures). Return a non-nil error only for truly unexpected failures — the simulation will halt and report the seed.
type Pool ¶
type Pool[T comparable] struct { // contains filtered or unexported fields }
Pool tracks a set of values that exist in the system under test. Use one Pool per meaningful entity state (e.g. separate pools for "draft" and "pending" entities rather than one pool for all of them). T must be comparable — typically int64 or string.
func NewPool ¶
func NewPool[T comparable]() *Pool[T]
func (*Pool[T]) Add ¶
func (p *Pool[T]) Add(v T)
Add adds v to the pool. Panics on the zero value of T since that almost always means an uninitialized variable slipped through.
func (*Pool[T]) All ¶
func (p *Pool[T]) All() []T
All returns a copy of all values. Use in invariant checks.
type RNG ¶
type RNG struct {
// contains filtered or unexported fields
}
RNG is a mutex-protected, seeded PRNG for deterministic simulation. Same seed always produces the same sequence.
type Sim ¶
type Sim struct {
// contains filtered or unexported fields
}
Sim is the deterministic simulation runner.
func (*Sim) CurrentSeq ¶
CurrentSeq returns the current operation sequence number (1-based). Use this inside op Run functions when calling Assert or Assertf.
func (*Sim) Invariant ¶
Invariant registers a function checked after every operation. The seq argument is the current operation number (1-based).