dstsim

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 21, 2026 License: MIT Imports: 7 Imported by: 0

README

dstsim

Deterministic simulation testing for Go, Tiger Style. 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) {
    sim.Assert(seq, count > 0, "count must be positive")
    sim.Assertf(seq, x == y, "x=%d != y=%d", x, y)
})
sim.Run()

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func Pick

func Pick[T any](rng *RNG, items []T) T

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

func PickOrGhostID(rng *RNG, p *Pool[int64]) int64

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

func PickWeighted[T any](rng *RNG, items []T, weights []int) T

PickWeighted returns a random element using weighted probability. weights[i] is the relative probability of items[i].

func Sample

func Sample[T any](rng *RNG, items []T, n int) []T

Sample returns n unique elements chosen at random from items.

func Shuffle

func Shuffle[T any](rng *RNG, items []T) []T

Shuffle returns a new slice with the elements in random order. The original slice is not modified.

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.

func NewClock

func NewClock(start time.Time) *Clock

NewClock returns a simulated clock starting at the given time.

func (*Clock) Advance

func (c *Clock) Advance(d time.Duration)

func (*Clock) Now

func (c *Clock) Now() time.Time

func (*Clock) Set

func (c *Clock) Set(t time.Time)

func (*Clock) Since

func (c *Clock) Since(t time.Time) time.Duration

func (*Clock) Until

func (c *Clock) Until(t time.Time) time.Duration

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

func DefaultConfig(seed int64) Config

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 NewFake

func NewFake(rng *RNG) *Fake

NewFake creates a Fake backed by the given RNG.

func (*Fake) Add

func (f *Fake) Add(name string, items []string)

Add registers a named list of string values for use with From.

func (*Fake) Amount

func (f *Fake) Amount(maxCents int) int64

Amount returns a monetary amount in cents in [1, maxCents].

func (*Fake) Bool

func (f *Fake) Bool(p float64) bool

Bool returns true with probability p in [0.0, 1.0].

func (*Fake) Email

func (f *Fake) Email(tenant string) string

Email returns a valid-format address, scoped to a tenant if non-empty.

func (*Fake) From

func (f *Fake) From(name string) string

From returns a random element from the named resource list. Panics if the resource was not registered with Add.

func (*Fake) Has

func (f *Fake) Has(name string) bool

func (*Fake) Int

func (f *Fake) Int(min, max int) int

Int returns a random int in [min, max] inclusive.

func (*Fake) IntN

func (f *Fake) IntN(n int) int

func (*Fake) Name

func (f *Fake) Name() string

func (*Fake) Percent

func (f *Fake) Percent() int

func (*Fake) RNG

func (f *Fake) RNG() *RNG

func (*Fake) Slug

func (f *Fake) Slug(prefix string) string

func (*Fake) Title

func (f *Fake) Title(domain string) string

func (*Fake) Username

func (f *Fake) Username() string

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

func (inj *Injector) MaybeError(errs ...error) error

MaybeError returns one of the provided errors with probability equal to the configured error rate. Returns nil otherwise.

func (*Injector) MaybeSlow

func (inj *Injector) MaybeSlow()

MaybeSlow advances the simulated clock by a random duration (up to SlowMax) with probability equal to the configured slow rate.

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

type OpEntry struct {
	Name   string
	Weight int
	Run    func(rng *RNG, clock *Clock) error
}

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.

func (*Pool[T]) Contains

func (p *Pool[T]) Contains(v T) bool

func (*Pool[T]) Empty

func (p *Pool[T]) Empty() bool

func (*Pool[T]) Len

func (p *Pool[T]) Len() int

func (*Pool[T]) Move

func (p *Pool[T]) Move(v T, dst *Pool[T])

Move removes v from p and adds it to dst. No-op if v is not in p.

func (*Pool[T]) Pick

func (p *Pool[T]) Pick(rng *RNG) T

Pick returns a random value from the pool. Panics if empty.

func (*Pool[T]) Remove

func (p *Pool[T]) Remove(v T)

Remove removes v from the pool. No-op if v is not present.

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.

func NewRNG

func NewRNG(seed int64) *RNG

NewRNG returns a deterministic RNG seeded with the given value.

func (*RNG) Calls

func (r *RNG) Calls() int64

Calls returns the total number of RNG calls made so far.

func (*RNG) Float64

func (r *RNG) Float64() float64

func (*RNG) Int64

func (r *RNG) Int64() int64

func (*RNG) IntN

func (r *RNG) IntN(n int) int

func (*RNG) Perm

func (r *RNG) Perm(n int) []int

func (*RNG) Shuffle

func (r *RNG) Shuffle(n int, swap func(i, j int))

type Sim

type Sim struct {
	// contains filtered or unexported fields
}

Sim is the deterministic simulation runner.

func New

func New(cfg Config) *Sim

New creates a simulation from the given config.

func (*Sim) Assert

func (s *Sim) Assert(seq int, cond bool, reason string)

Assert halts the simulation if cond is false.

func (*Sim) Assertf

func (s *Sim) Assertf(seq int, cond bool, format string, args ...any)

Assertf is Assert with fmt.Sprintf formatting.

func (*Sim) Clock

func (s *Sim) Clock() *Clock

func (*Sim) CurrentSeq

func (s *Sim) CurrentSeq() int

CurrentSeq returns the current operation sequence number (1-based). Use this inside op Run functions when calling Assert or Assertf.

func (*Sim) Fail

func (s *Sim) Fail(seq int, reason string)

Fail panics with an InvariantViolation containing the seed and op history.

func (*Sim) Injector

func (s *Sim) Injector() *Injector

func (*Sim) Invariant

func (s *Sim) Invariant(fn func(seq int))

Invariant registers a function checked after every operation. The seq argument is the current operation number (1-based).

func (*Sim) RNG

func (s *Sim) RNG() *RNG

func (*Sim) Register

func (s *Sim) Register(op OpEntry)

Register adds an operation to the simulation.

func (*Sim) Run

func (s *Sim) Run()

Run executes the simulation: for each step it picks a random operation (weighted), runs it, then checks all invariants.

Directories

Path Synopsis
example
userapp command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL