seamster

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Jul 18, 2026 License: Apache-2.0 Imports: 3 Imported by: 0

README

Seamster

Seamster works the test-only instrumentation seams of a host package: named points planted in the host's code that a white-box test hooks into to drive failure and timing paths deterministically. 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 them.

It has two halves:

  • 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, freezes the host at an exact point (so a concurrent operation can be driven into a precise window with no timing hammer), or simply counts how many times the host has passed the point.

Both are inert in production 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 pulling in test-only dependencies.

Usage

The host embeds one Seamster per instance, plants consult sites where they affect, and names the valid fault/checkpoint set next to those sites.

type Host struct {
    // ...
    seams *seamster.Seamster
}

func NewHost() *Host {
    return &Host{seams: seamster.New(testing.Testing())}
}

Production consult sites (free when disabled):

// Make this write misbehave when a test arms the fault, scoped to one task.
if h.seams.IsFault("transitionCommit", taskName) {
    return errors.New("injected fault")
}

// Mark this point observable/pausable.
h.seams.Checkpoint(ctx, "beforeTransitionTx")

Test side:

// Fault injection: arm a scoped fault to fire once (scope args match IsFault's).
h.seams.Inject("transitionCommit", "Charge")

// Rendezvous: block the test until the host reaches a checkpoint.
h.seams.Wait("beforeTransitionTx")

// Freeze: drive a concurrent op into a precise window.
h.seams.Break("beforeTransitionTx")
go h.Process(ctx)          // runs into the breakpoint and blocks
h.seams.Wait("beforeTransitionTx")  // returns once frozen
// ... run the racing operation while the host is held ...
h.seams.Resume("beforeTransitionTx")

// Count: assert how many times the host passed a checkpoint.
if h.seams.Visits("beforeTransitionTx") != 3 {
    t.Fatalf("expected 3 attempts, got %d", h.seams.Visits("beforeTransitionTx"))
}

API

Method Side Purpose
New(enabled bool) host Construct a Seamster; inert when enabled is false.
Enabled() bool host Skip building a scoped consult that would only feed IsFault.
IsFault(name, scope...) bool host Consult a fault, consuming one fire.
Inject(name, scope...) test Arm a fault once (additive); scope args match IsFault.
InjectN(n, name, scope...) test Arm a fault n times (additive); scope args match IsFault.
Withdraw(name, scope...) test Disarm a fault.
Checkpoint(ctx, name) host Pass through a checkpoint; counts the visit, wakes waiters, honors a breakpoint.
Wait(name) test Block until the host next reaches the checkpoint.
Break(name) test Freeze the host at the checkpoint.
Resume(name) test Unfreeze the host at the checkpoint.
Visits(name) int test Count how many times the host has passed the checkpoint (monotonic; never blocks).

Scoping: a fault is usually scoped so a test targets one entity rather than "the next thing that happens." The consult passes the scope to IsFault; the test arms the fault with the same scope args (Inject/InjectN/ Withdraw all join scope the same way), so it is spelled one way at both ends. Unscoped (process-wide) faults take no scope.

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.Wait) or freezes the host at an exact point (Seamster.Break / Seamster.Resume).

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 no site consumes.

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

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. A single Seamster is meant to be embedded per host instance (one per engine, one per connector, ...), not shared as a package global, so distinct hosts arm and consult independently.

func New

func New(enabled bool) *Seamster

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

func (s *Seamster) Break(checkpointName string)

Break arms a breakpoint so the host blocks the next time it reaches the named checkpoint, until Resume releases it.

func (*Seamster) Checkpoint

func (s *Seamster) Checkpoint(ctx context.Context, checkpointName string)

Checkpoint is the host-side consult at an instrumented site. Free in production (returns on the enabled bool read). When enabled it wakes any Wait(name) waiters and, if a breakpoint is armed for name, blocks until Resume(name) - or until ctx is done, so a stuck test or a shutdown can never wedge the host goroutine forever. Waking waiters and marking the breakpoint hit happen under one lock hold, so a Wait racing the host's arrival is woken or sees hit - never lost between the two.

func (*Seamster) Enabled

func (s *Seamster) Enabled() bool

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

func (s *Seamster) Inject(faultName string, scope ...string)

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

func (s *Seamster) InjectN(n int, faultName string, scope ...string)

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

func (s *Seamster) IsFault(faultName string, scope ...string) bool

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

func (s *Seamster) Resume(checkpointName string)

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

func (s *Seamster) Visits(checkpointName string) int

Visits reports how many times the host has passed the named checkpoint - each Checkpoint(name) call counts one, including a call that then blocked on a breakpoint (reaching the checkpoint is the visit; blocking comes after). It is a passive counter for assertions: it never blocks, consumes nothing, and can be read repeatedly. The count is monotonic for the Seamster's lifetime (never reset), so capture a baseline first if a test asserts on visits accrued only within a window. Zero for a checkpoint never reached, and always zero on a disabled Seamster (Checkpoint counts nothing when inert).

func (*Seamster) Wait

func (s *Seamster) Wait(checkpointName string)

Wait blocks until the host reaches the named checkpoint. If a breakpoint is already holding the host at name, it returns immediately; otherwise it registers a waiter woken by the host's next arrival.

func (*Seamster) Withdraw

func (s *Seamster) Withdraw(faultName string, scope ...string)

Withdraw disarms the named fault regardless of its remaining count. Optional scope args target it, joined the same way Seamster.IsFault joins its scope.

Jump to

Keyboard shortcuts

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