testkit

package
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Jul 7, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Overview

Package testkit is shared test infrastructure for the agent: deterministic fault injection and invariant checks that make rigorous tests cheap to write.

Chaos engineering falls out of the ports architecture - wrap a unit of work or any port (dispatch work, dispatch.EventSink, ...) with a FaultPlan and assert the system degrades and recovers cleanly. Combined with clock.Manual and seeded inputs, the faults are deterministic, so a failing run reproduces exactly.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ActionGen

func ActionGen() *rapid.Generator[dispatch.Action]

ActionGen generates a dispatch.Action.

func AppendInputGen

func AppendInputGen(stream string) *rapid.Generator[spine.AppendInput]

AppendInputGen generates a spine.AppendInput for a fixed stream.

func CountByType

func CountByType(events []dispatch.Event) map[string]int

CountByType tallies events by their Type, for assertions on what was emitted.

func Diff

func Diff[T any](t TB, want, got T)

Diff fails the test with a readable diff when want != got. It is the generic comparison primitive for any type, so packages don't write a bespoke DiffXxx; nil and empty slices/maps compare equal.

func DiffEvents

func DiffEvents(t TB, want, got []dispatch.Event)

DiffEvents is Diff specialised to a dispatch-event stream, kept for readability at call sites. Two runs of the same scenario under a clock.Manual produce byte-identical streams, so this is also the determinism/replay check.

func FaultyBus

func FaultyBus(inner bus.Bus, plan *FaultPlan) bus.Bus

FaultyBus wraps a bus.Bus, injecting plan's faults on Publish before delegating; Subscribe and Close pass through. It proves a publisher tolerates a flaky or dead signal bus: a failed or dropped publish must never corrupt the durable record a subscriber eventually reads. A nil plan never faults. A nil inner bus models a fully dead bus: Publish is a silent no-op and Subscribe returns an inert subscription that never delivers, so a consumer must make progress some other way (a poll floor).

func FaultyDoer

func FaultyDoer(inner request.Doer, plan *FaultPlan) request.Doer

FaultyDoer wraps a request.Doer, injecting plan's faults before delegating, so a flaky network (a few transient failures then recovery, or a hard outage) is modelled deterministically against the shared HTTP transport. The injected error flows through the transport's classification, so a fault.Transient drives the retry path and a fault.Terminal proves a non-retryable failure is not replayed. A nil inner doer returns a minimal 200 response when not faulting, so a plan alone is enough to drive the transport through its retry and recovery path.

func FaultyExecutor

func FaultyExecutor(inner goal.StepExecutor, plan *FaultPlan) goal.StepExecutor

FaultyExecutor wraps a goal.StepExecutor, injecting plan's faults before delegating, so a flaky model or tool call (a step that fails a few times then succeeds) is modelled deterministically. A nil inner executor performs no work when not failing, so a plan alone is enough to drive a goal through its retry and recovery path. The checkpoint of a faulting call is dropped: a failed step makes no progress.

func FaultyLog

func FaultyLog(inner spine.Log, plan *FaultPlan) spine.Log

FaultyLog wraps a spine.Log, injecting plan's faults on Append before delegating; Read passes through. It models a flaky durable log: an append that fails records nothing and assigns no Seq, so the stream stays gap-free (a dropped event simply never existed) rather than leaving a hole. A nil plan never faults; a nil inner log returns a zero Event on a non-faulting append and reads back nothing.

func FaultyModel

func FaultyModel(inner llm.Model, plan *FaultPlan) llm.Model

FaultyModel wraps an llm.Model, injecting plan's faults on Generate before delegating. It models a flaky provider (a transient API error) so a test can drive the conversation loop's retry path, where the fault lands AFTER a turn has already been announced. That is the case the well-formedness invariant guards: a retried turn must not duplicate its turn.started on the event stream.

func FaultyReader

func FaultyReader(inner io.Reader, plan *FaultPlan) io.Reader

FaultyReader wraps an io.Reader, injecting plan's faults before delegating, so an input stream that fails mid-read (a hung console, a torn multiplexer session) is modelled deterministically against anything that consumes it. A firing fault consumes nothing and returns the error; a nil inner reader reports io.EOF when not faulting.

func FaultySink

func FaultySink(inner dispatch.EventSink, plan *FaultPlan) dispatch.EventSink

FaultySink wraps a dispatch.EventSink, injecting plan's faults before delegating - used to prove that event-sink failures never break a dispatch. A nil inner sink discards events when not failing.

func FaultySource

func FaultySource(inner secret.Source, plan *FaultPlan) secret.Source

FaultySource wraps a secret.Source, injecting plan's faults on Lookup before delegating, so a flaky or unreachable credential store (a locked OS keychain, an unreachable vault or broker) is modelled deterministically. It proves a credential consumer fails closed on a resolve failure: a step that needs a secret must stop, never proceed without it or act on a partial value. A nil inner source resolves every reference to an empty value when not faulting, so a plan alone drives the consumer's failure path.

func FaultyWork

func FaultyWork(inner func(context.Context) (dispatch.Metering, error), plan *FaultPlan) func(context.Context) (dispatch.Metering, error)

FaultyWork wraps a unit of dispatch work, injecting plan's faults before delegating. A nil inner returns zero Metering when not failing, so a plan alone is enough to drive a Dispatcher.Govern call through its retry and recovery path.

func FaultyWriter

func FaultyWriter(inner io.Writer, plan *FaultPlan) io.Writer

FaultyWriter wraps an io.Writer, injecting plan's faults before delegating, so a terminal that dies mid-session (a closed pty, a dropped SSH connection) is modelled deterministically against anything that renders. A firing fault writes nothing and returns the error; a nil inner writer discards writes when not faulting, so a plan alone can drive a renderer's error path.

func Golden

func Golden[T any](t TB, name string, got T)

Golden compares got against testdata/<name>.golden (pretty JSON). Run the tests with -update to (re)write the file. It lets an entire output - a full mission replay, a rendered spec, a whole event stream - be a single snapshot with no hand-written expected value, which is what keeps large tests small.

func MemoryItemGen

func MemoryItemGen() *rapid.Generator[state.MemoryItem]

MemoryItemGen generates a state.MemoryItem.

func RequireLifecycle

func RequireLifecycle(t TB, events []dispatch.Event)

RequireLifecycle asserts a coherent event lifecycle on a recorded stream: every dispatched action is either a start paired with an end, or a single rejection. An imbalance means an action was lost or double-recorded.

func ScopeGen

func ScopeGen() *rapid.Generator[state.Scope]

ScopeGen generates a state.Scope on the instance/project/workspace axis.

func SkillGen

func SkillGen() *rapid.Generator[state.Skill]

SkillGen generates a state.Skill with a non-empty slug.

Types

type FaultPlan

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

FaultPlan decides, deterministically, when to inject a fault. Each wrapped call advances a counter; the plan returns an error to inject on that call, or nil to pass through. Safe for concurrent use.

func Always

func Always(err error) *FaultPlan

Always injects err on every call.

func FailEvery

func FailEvery(n int, err error) *FaultPlan

FailEvery injects err on every nth call (n <= 0 never fails).

func FailFirst

func FailFirst(k int, err error) *FaultPlan

FailFirst injects err on the first k calls, then passes through. Models a flaky dependency that recovers - exercises retry and degrade paths.

func FailOnCall

func FailOnCall(call int, err error) *FaultPlan

FailOnCall injects err only on the call with the given 1-based index.

type TB

type TB interface {
	Helper()
	Fatalf(format string, args ...any)
}

TB is the subset of testing.TB the testkit helpers use. Both *testing.T and *rapid.T satisfy it, so the same helpers work in ordinary unit tests and inside rapid property checks.

Jump to

Keyboard shortcuts

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