Documentation
¶
Overview ¶
Package seamster provides two test-only instrumentation seams that a white-box test uses to exercise a host's recovery and race paths deterministically, without forging state or hammering on timing. Both are inert unless the host constructs the Seamster enabled:
- Fault injection makes a site MISBEHAVE: a test arms a named fault, the host consults it at the exact point it affects, and simulates an error, drop, or stale write that is otherwise hard to trigger on demand.
- Execution checkpoints make a site OBSERVABLE and PAUSABLE: a test rendezvouses with the host's progress (Seamster.Waiter / Seamster.Wait / Seamster.WaitTimeout), freezes the host at an exact point (Seamster.Break / Seamster.Resume), or counts arrivals (Seamster.Visits).
A "seam" is a place where a test can alter or observe behavior without editing the code in place; a Seamster is the object that works a host's seams. The host embeds one Seamster, plants consult sites (Seamster.IsFault / Seamster.Checkpoint) at the points they affect, and names the valid fault/checkpoint set next to those sites so a test cannot arm a fault or checkpoint no site consumes. Both seams take an optional trailing scope, spelled the same way at the consult and at the arming, so a test targets one entity rather than the next thing that happens.
Production inertness is by construction: every consult short-circuits on the enabled bool the host passes to New (typically testing.Testing()), so a disabled Seamster pays a single lock-free bool read per site and neither seam can ever fire. The package imports only the standard library, so a host may embed it on a production hot path without dragging in test-only dependencies.
Index ¶
- type Seamster
- func (s *Seamster) Break(checkpointName string, scope ...string)
- func (s *Seamster) Checkpoint(ctx context.Context, checkpointName string, scope ...string)
- func (s *Seamster) Enabled() bool
- func (s *Seamster) Inject(faultName string, scope ...string)
- func (s *Seamster) InjectN(n int, faultName string, scope ...string)
- func (s *Seamster) IsFault(faultName string, scope ...string) bool
- func (s *Seamster) Resume(checkpointName string, scope ...string)
- func (s *Seamster) Visits(checkpointName string, scope ...string) int
- func (s *Seamster) Wait(ctx context.Context, checkpointName string, scope ...string) bool
- func (s *Seamster) WaitTimeout(ctx context.Context, timeout time.Duration, checkpointName string, ...) bool
- func (s *Seamster) Waiter(checkpointName string, scope ...string) <-chan struct{}
- func (s *Seamster) Withdraw(faultName string, scope ...string)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Seamster ¶
type Seamster struct {
// contains filtered or unexported fields
}
Seamster works one host's instrumentation seams. It is safe for concurrent use. The zero value is a disabled Seamster; construct one with New. Embed one Seamster per host instance rather than sharing a package global, so distinct hosts arm and consult independently.
func New ¶
New returns a Seamster. When enabled is false every consult is inert, so a host passes its own under-test signal - most commonly testing.Testing() - and embeds the result unconditionally.
func (*Seamster) Break ¶
Break arms a breakpoint so the host blocks the next time it reaches the named checkpoint, until Seamster.Resume releases it. It holds every goroutine that reaches the checkpoint while armed, not only the first, so a fan-out can be gathered at one point and released together by a single Resume; a rendezvous returns as soon as the first of them arrives.
func (*Seamster) Checkpoint ¶
Checkpoint is the host-side consult at an instrumented site, and the only one a host calls. Free in production: it returns on the enabled bool read, before any lock or key build. When enabled it counts the visit, wakes any waiters, and - if a breakpoint is armed - blocks until Seamster.Resume, or until ctx is done, so a stuck test or a shutdown can never wedge the host goroutine forever.
func (*Seamster) Enabled ¶
Enabled reports whether the seams are live. A host uses it to skip building a scoped consult (a state read, a key concatenation) that would only feed IsFault, so the setup work is also elided in production.
func (*Seamster) Inject ¶
Inject arms the named fault to fire once (additive: calling twice fires twice). Optional scope args target it, joined the same way Seamster.IsFault joins its scope.
func (*Seamster) InjectN ¶
InjectN arms the named fault to fire n more times, added to any current count. Optional scope args target it, joined the same way Seamster.IsFault joins its scope. A non-positive n is a no-op.
func (*Seamster) IsFault ¶
IsFault reports whether the named fault is armed, consuming one fire. It is the only consult entry point and is free in production: it returns on the enabled bool read before any lock or key build. Optional scope args target the fault (a task name, a URL, ...) and are joined into the key, but only once the enabled gate has passed, so a scoped consult on a disabled Seamster allocates no throwaway key. To keep a fault armed for the rest of a test (until Seamster.Withdraw), inject a large count, e.g. s.InjectN(math.MaxInt, name).
func (*Seamster) Resume ¶
Resume releases the host frozen at the named breakpoint and disarms it. A no-op if none is armed.
func (*Seamster) Visits ¶ added in v0.2.0
Visits reports how many times the host has passed the named checkpoint, counting a call that then blocked on a breakpoint (reaching the checkpoint is the visit; blocking comes after). It never blocks, consumes nothing, and can be read repeatedly. The count is monotonic for the Seamster's lifetime, so capture a baseline first to assert on visits accrued within a window. Zero for a checkpoint never reached, and always zero on a disabled Seamster.
func (*Seamster) Wait ¶
Wait blocks until the host reaches the named checkpoint and reports whether it did, abandoning the wait if ctx is done. A false return is the caller's cue to fail:
assert.True(s.Wait(ctx, "beforeCommit"))
Returns true immediately on a disabled Seamster, or if a breakpoint is already holding the host at name.
Use it where the host is ALREADY running into the checkpoint: frozen at a breakpoint, or driven from another goroutine. If the caller's own next statement is the trigger, this cannot arm in time - use Seamster.Waiter.
func (*Seamster) WaitTimeout ¶ added in v0.3.1
func (s *Seamster) WaitTimeout(ctx context.Context, timeout time.Duration, checkpointName string, scope ...string) bool
WaitTimeout is Seamster.Wait bounded by a duration - the usual shape in a test, where the bound is "this long" rather than a deadline inherited from elsewhere. ctx still aborts, so a cancelled parent ends it early.
func (*Seamster) Waiter ¶ added in v0.3.1
Waiter registers a waiter for the named checkpoint and returns a channel closed when the host reaches it. It does not block: arming and receiving are separate steps, which is the whole point. Arm before the operation that triggers the checkpoint, receive after it:
reached := s.Waiter("beforeCommit")
host.DoTheThing()
<-reached
Use it whenever the caller's OWN next statement drives the host to the checkpoint: Seamster.Wait and Seamster.WaitTimeout can only arm once that statement is already running, so an arrival in between is lost.
The channel is already closed if a breakpoint is currently holding the host at name, and on a disabled Seamster - so a receive never blocks in production.