fuzz

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 16, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package fuzz is the target-agnostic half of tergo's property fuzzer: an action alphabet, a seeded generator that biases toward the shapes that have actually broken this codebase, a driver loop, and a shrinker.

It knows nothing about tergo itself. A caller supplies a Target, which applies one Action and reports which invariants the resulting state violates, and the loop here handles seeding, reproduction, and minimisation. Two targets exist: an in-process one that drives app.OS through its real bubbletea Update and reads the invariants off the model (internal/app), and a tuitest one that replays the same stream against a real binary in a PTY (e2e/tui). The alphabet is shared so a failure found by either is a repro the other can also run.

Nothing in the shipped binary imports this package; it exists for `go test`.

Index

Constants

View Source
const (
	ButtonNone = iota
	ButtonLeft
	ButtonRight
	ButtonMiddle
)

Mouse buttons, matching the order the generator picks from.

Variables

This section is empty.

Functions

func Script

func Script(as []Action) string

Script renders a whole run as a pasteable repro.

Types

type Action

type Action struct {
	Kind    Kind
	A, B, C int
	S       string
}

Action is one step of a run. The three integers and the string cover every kind, which keeps the repro format a single flat line per action and the shrinker's per-field simplification uniform.

func Generate

func Generate(seed uint64, n int) []Action

Generate produces a whole run up front, which is what the shrinker replays.

func GenerateBytes

func GenerateBytes(b []byte, n int) []Action

GenerateBytes is Generate for a coverage-guided input.

func GenerateBytesFloor

func GenerateBytesFloor(b []byte, n, minW, minH int) []Action

GenerateBytesFloor is GenerateBytes with a host-size floor.

func GenerateFloor

func GenerateFloor(seed uint64, n, minW, minH int) []Action

GenerateFloor is Generate with a lower bound on the host sizes it picks.

func ParseAction

func ParseAction(line string) (Action, error)

ParseAction reads back one line of String's output.

func ParseScript

func ParseScript(s string) ([]Action, error)

ParseScript reads back Script's output, skipping blanks and # comments so a maintainer can annotate a saved repro.

func (Action) String

func (a Action) String() string

String renders one action as the line the repro file carries. Strings are quoted with %q so a name holding a newline, a path separator, or a combining mark survives the round trip through a terminal and a paste buffer.

type Config

type Config struct {
	// Seed identifies the run. It is printed on failure and re-running it
	// reproduces the finding exactly.
	Seed uint64
	// Steps is how many actions to generate when Actions is empty.
	Steps int
	// MinWidth and MinHeight floor the host sizes the generator picks, which is
	// how a campaign steps over a bug class it has already reported in order to
	// reach the rest of the space.
	MinWidth, MinHeight int
	// Weights overrides the generator's action weights, indexed by Kind. A
	// target that can express actions the other cannot spends its budget there
	// rather than on the alphabet the cheaper target already covers exhaustively.
	Weights []int
	// Actions overrides generation, which is how the coverage-guided entry
	// point and a saved repro both drive the same loop.
	Actions []Action
	// NoShrink reports the raw failing sequence. Only useful when a target's
	// Reset is too slow to replay hundreds of times.
	NoShrink bool
	// ShrinkBudget caps predicate replays. Zero picks a default scaled to the
	// sequence length.
	ShrinkBudget int
	Observer     Observer
}

Config is one fuzzing run.

type Generator

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

Generator turns a source into a stream of actions. It carries the small amount of state needed to emit sequences that are awkward on purpose: whether a mouse button is currently held, so it can slip a host resize or a detach between a press and its release.

func NewByteGenerator

func NewByteGenerator(b []byte) *Generator

NewByteGenerator decodes a `go test -fuzz` byte slice into the same alphabet.

func NewGenerator

func NewGenerator(seed uint64) *Generator

NewGenerator seeds a generator for the local loop. The same seed always yields the same run.

func (*Generator) Bias

func (g *Generator) Bias(w []int) *Generator

Bias replaces the action weights, indexed by Kind. A short slice leaves the kinds past its end at their default, so a caller names only what it wants to move. Nil keeps the defaults, which is what makes it safe to wire straight through from a Config field that most callers never set.

func (*Generator) Floor

func (g *Generator) Floor(w, h int) *Generator

Floor restricts the host sizes the generator produces. Zero means no floor, which is the campaign that hunts degenerate viewports.

func (*Generator) Next

func (g *Generator) Next() Action

Next returns the next action. Bursts are emitted one at a time through buf so a caller can interleave a check after every single action.

func (*Generator) Take

func (g *Generator) Take(n int) []Action

Take draws n actions.

type Kind

type Kind uint8

Kind is one action in the alphabet. The set covers what a user does and, more to the point, what has broken: the leader chords, drags that end outside the target they started on, host resizes landing mid-gesture, and the runtime settings flips that make a pane's border appear under a guest that was never told about it.

const (
	Key             Kind = iota // a single key press, S is the key name
	Chord                       // the leader key then S, as two presses
	Text                        // S typed a rune at a time, for rename fields
	MousePress                  // A,B is the cell, C is the button
	MouseMotion                 // A,B is the cell, C is the button held (0 = none)
	MouseRelease                // A,B is the cell, C is the button
	MouseWheel                  // A,B is the cell, C is the direction
	Resize                      // A,B is the new host size in cells
	NewPane                     //
	ClosePane                   // A selects which pane, modulo the count
	ZoomPane                    //
	FocusPane                   // A selects which pane, modulo the count
	MovePane                    // A is a direction index
	SwitchWorkspace             // A is the workspace, 1..NumWorkspaces
	SwitchSession               // A selects a session, modulo the count
	ToggleTiling                //
	ToggleShared                // shared borders
	LayoutMode                  // A selects bsp/master-stack/scrolling
	ToggleSidebar               //
	SidebarCollapse             //
	SidebarPosition             // A picks left or right
	OpenOverlay                 // A selects which overlay
	CloseOverlay                //
	Rename                      // S is the new name
	Detach                      //
	Attach                      //
	Setting                     // A selects a runtime setting, B its new value
	Tick                        // one maintenance tick
	Guest                       // S is written to the focused pane's emulator
	AltScreen                   // A odd enters the alternate screen, B picks the pane
	Burst                       // A lines out of the pane B picks, to outrun a buffer
	SecondClient                // a second client attaches to the same live session
	DaemonRestart               // the daemon goes away and its sessions come back

)

func (Kind) String

func (k Kind) String() string

type NopObserver

type NopObserver struct{}

NopObserver is the default. Embed it to implement only the methods a display cares about.

func (NopObserver) Done

func (NopObserver) Done(Result)

func (NopObserver) Rule

func (NopObserver) Rule(int, string, bool)

func (NopObserver) Shrink

func (NopObserver) Shrink(string, int, bool)

func (NopObserver) Start

func (NopObserver) Start(uint64, int)

func (NopObserver) Step

func (NopObserver) Step(int, Action, []Violation)

type Observer

type Observer interface {
	Start(seed uint64, steps int)
	Step(i int, a Action, vs []Violation)
	Rule(step int, rule string, ok bool)
	Shrink(pass string, size int, accepted bool)
	Done(r Result)
}

Observer watches a run. It is the only seam a display attaches to, and it is deliberately narrow and read only: the driver hands out what happened and takes nothing back, so no display can change what the fuzzer does. Every method is called from the driver's goroutine and must not block.

The four calls are the whole vocabulary:

Start  once, before the first action
Step   one action executed, with whatever it broke
Rule   one invariant checked and its result, for every rule, every step
Shrink one minimisation candidate and whether it was kept
Done   once, with the final result and the minimal repro

type Result

type Result struct {
	Seed       uint64
	Failed     bool
	Step       int // the index of the action that broke it, in the minimal sequence
	Violations []Violation
	// Actions is the minimal sequence that still breaks the same rule, or the
	// whole run when it passed.
	Actions  []Action
	Executed int
	Replays  int
}

Result is what a run found.

func Run

func Run(newTarget func() (Target, error), cfg Config) (Result, error)

Run generates a sequence, replays it against a fresh target checking after every action, and on failure minimises it against the same rule.

newTarget is called once per replay rather than once per run, because shrinking needs to re-run a candidate sequence from a clean start.

func (Result) Repro

func (r Result) Repro() string

Repro is the pasteable reproduction: the seed to re-run, and the minimal action script that stands alone even if generation ever changes.

type RuleInfo

type RuleInfo struct {
	Name   string
	Family string
	Doc    string
}

RuleInfo describes one invariant. Name is what a Violation carries, so a display can map a failure onto the exact rule that produced it; Family groups related rules for presentation; Doc is the one line a display shows to say what went wrong in words rather than in an identifier.

Family is a field rather than a prefix on Name because a prefix is a parsing convention nothing enforces: a typo makes a phantom group that looks deliberate. A field is checked by the compiler and listed in one place.

type RuleLister

type RuleLister interface{ Rules() []RuleInfo }

RuleLister is an optional Target capability. A target that can name its rules gets per-rule results reported to the Observer; one that cannot still gets Step, carrying whichever rules actually broke. It is optional so the oracle never has to know an observer exists.

The names must be the ones Violations carry, and in the order Check applies them, because that is what makes "everything after the break went unrun" true.

type Target

type Target interface {
	Reset() error
	Apply(Action) error
	Check() []Violation
	Close()
}

Target is the system under test. The driver owns sequencing, seeding, and minimisation; a Target owns nothing but "put me back at the start", "do this one thing", and "which invariants are broken right now".

Check runs after every action, so it must be cheap enough to run thousands of times. Reset must return the target to a state that depends only on the actions replayed since, or a shrunk repro will not reproduce.

type Violation

type Violation struct {
	Rule   string
	Detail string
}

Violation is one broken invariant. Rule names the property, and it is what the shrinker holds fixed: a sequence that shrinks into a different failure is a different finding, and reporting it under the first one's name is how a fuzzer sends a maintainer after the wrong bug.

func (Violation) String

func (v Violation) String() string

Directories

Path Synopsis
Package apptarget drives a real app.OS through its real bubbletea Update as a fuzz.Target, using nothing but package app's exported surface.
Package apptarget drives a real app.OS through its real bubbletea Update as a fuzz.Target, using nothing but package app's exported surface.
Package vis draws a fuzzing run while it happens.
Package vis draws a fuzzing run while it happens.

Jump to

Keyboard shortcuts

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