enginetest

package
v0.10.3 Latest Latest
Warning

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

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

Documentation

Overview

Package enginetest holds the engine test helpers that are shared across package boundaries - by the white-box tests that stay in package engine AND by the black-box tests in the fixtures package. A helper lands here only when it is (a) used from more than one package and (b) expressible through the engine's exported test surface alone (the DB and Seams accessors and the public operations). A helper that needs an unexported internal (driveLeaseRecovery calling recoverExpiredLeases, startSolo touching testConnCap, the peer-row writers) cannot live here and stays in package engine with the white-box tests that use it.

This package must NOT import github.com/microbus-io/dwarf/engine. The package engine white-box tests import this package, and if it imported engine back, that pair would be an illegal import cycle in the engine test binary (engine-test -> enginetest -> engine). So the helpers take the small Engine interface below, which *engine.Engine satisfies structurally, rather than the concrete type - which also lets the same helper serve both the package engine tests and the fixtures tests with no duplication.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AssertInvariants

func AssertInvariants(t *testing.T, e Engine)

AssertInvariants is the reusable end-of-test sweep that proves a workload never *created* any of the states the background wedge sweep / orphan detector exist to catch. It runs a fixed set of structural checks by direct SQL against every shard and must be called only after the workload has quiesced (all Awaits returned), because several checks are transiently violated mid-flight (a straggler sibling still settling, a fan-in mid-commit). A clean sweep means the recovery machinery had nothing to do.

func AwaitAndAssertComplete

func AwaitAndAssertComplete(t *testing.T, e Engine, flowKey string) *workflow.FlowOutcome

AwaitAndAssertComplete awaits a flow (bounded) and asserts it completed, returning the outcome.

func AwaitFlowStatus

func AwaitFlowStatus(t *testing.T, e Engine, flowKey, want string, timeout time.Duration)

AwaitFlowStatus blocks until the given flow has reached the given stop status, failing the test on timeout. It rendezvouses with the engine's post-commit flowStopped checkpoint rather than polling the flow row, so a return means the status is durable. The order is load-bearing: arm the waiter FIRST, then check Visits - a stop before the call is caught by the count, one after by the channel, one landing between the two lines by the channel (the waiter is already registered). Reversing the two lines reintroduces the race the split-arm waiter exists to remove. Two limits: it only sees stops routed through signalStop (completed/failed/cancelled/interrupted), and because Visits is monotonic a REPEATED status is satisfied by the earlier occurrence (wait on such a status with a Waiter armed around the specific trigger instead).

func AwaitShardCycles

func AwaitShardCycles(t *testing.T, e Engine, shards, extra int)

AwaitShardCycles blocks until every shard's piston has completed `extra` further pushing cycles, counted from the moment of the call.

A pushing cycle is the point at which that shard's cache partition has been reconciled against the plan, so this is how a test waits for the fleet's cached hints to agree with what the planner actually chose. There is no wall-clock stand-in for it, and that is the whole reason the checkpoint exists: each piston turns on its own cadence, so one starved or slow shard holds an unreconciled partition for as long as it likes while its peers turn normally - the asymmetric case, which no uniform delay reproduces or waits out.

TWO cycles, not one, is the usual ask: a cycle already in flight when the work committed may have scanned before it existed, so its push proves nothing about it. The second is the one whose scan is guaranteed to have seen it.

func AwaitVisits

func AwaitVisits(t *testing.T, seams *seamster.Seamster, n int, timeout time.Duration, checkpointName string)

AwaitVisits blocks until the host passes the named checkpoint n FURTHER times, counted from the moment of the call, and fails the test if it does not inside the timeout.

The arm-check-block loop is the whole content, and it earns a shared helper because the ways to get it wrong are quiet ones. seamster's Waiter is ONE-SHOT and arms for the host's NEXT arrival, while Visits is a monotonic count - so waiting for several occurrences means re-arming per occurrence, and the two have to be read in the order below: arm FIRST, then read the count, so an arrival landing between the two lines is caught by the channel that is already registered rather than lost between them. The deadline is taken once rather than per iteration, or each arrival would silently renew the whole budget.

The timeout is a "did it hang" ceiling, never a timing contract, so it stretches under -race like every other ceiling here. Waiting for N occurrences of a checkpoint is how a test states "the engine has had its chance" without naming a duration: a slow machine makes each occurrence later, not fewer.

Call it from the TEST goroutine, like every other helper here that fails the test: t.Fatalf from a task handler or any other engine-owned goroutine is illegal, and here it is also useless - the engine goroutine it kills is the one that would have driven the checkpoint, so the suite wedges instead of reporting.

func BoundedAwait

func BoundedAwait(t *testing.T, e Engine, flowKey string) (*workflow.FlowOutcome, error)

BoundedAwait awaits a flow with a 30s ctx bound so a wedge fails the test instead of hanging.

func BoundedRun

func BoundedRun(t *testing.T, e Engine, url string) *workflow.FlowOutcome

BoundedRun runs a flow with a generous ctx bound (scaled under -race) so a wedge fails the test instead of hanging the suite.

func CountRows

func CountRows(t *testing.T, e Engine, shard int, query string, args ...any) int

CountRows runs a COUNT(*)-style query on the given shard and returns the scalar count (-1 on a shard error).

func FlowStatus

func FlowStatus(t *testing.T, e Engine, flowKey string) string

FlowStatus returns the flow's current status column, read by direct SQL on the flow's shard.

func TimeoutScale

func TimeoutScale() time.Duration

TimeoutScale is the multiplier the shared wait helpers apply to their "don't hang" ceilings - 1 normally, 5 under -race. Exported for a fixture that must hold its own ceiling rather than route through a helper here: -race slows execution ~10x and, with the whole suite parallel, compounds with CPU oversubscription, so an unscaled ceiling that passes serially flakes under it. Scale the ceiling, never the assertion - a genuine wedge never completes and trips even the stretched bound.

func WaitUntil

func WaitUntil(t *testing.T, timeout time.Duration, cond func() bool) bool

WaitUntil polls cond until it returns true or timeout elapses, returning cond's final value. A generic timing helper with no engine dependency; the cadence (5ms) is fine-grained enough for test observation without busy-spinning.

Types

type Engine

type Engine interface {
	DB() *database.ShardSet
	Seams() *seamster.Seamster
	Run(ctx context.Context, workflowURL string, initialState any, opts *workflow.FlowOptions) (string, *workflow.FlowOutcome, error)
	Await(ctx context.Context, flowKey string) (*workflow.FlowOutcome, error)
}

Engine is the slice of *engine.Engine's exported test surface these helpers use. Defining it here (rather than importing engine) is what keeps this package free of the import cycle described in the package doc; *engine.Engine satisfies it structurally.

type NoopHost

type NoopHost struct{}

NoopHost is a Host that does nothing - LoadGraph returns no graph and ExecuteTask is a no-op. It satisfies the engine's Host interface structurally (the methods reference only workflow types, so this package needs no import of engine), and is used by tests that stand an engine up purely to exercise wiring/config paths where no task ever runs.

func (NoopHost) ExecuteTask

func (NoopHost) ExecuteTask(ctx context.Context, name string, flow *workflow.Flow) error

func (NoopHost) LoadGraph

func (NoopHost) LoadGraph(ctx context.Context, name string) (*workflow.Graph, error)

Jump to

Keyboard shortcuts

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