Documentation
¶
Overview ¶
Package storetest provides conformance suites that every Dispatch store backend must pass. The suites are shared so five implementations cannot quietly disagree about semantics that only one of them has tests for.
RunLeaseSuite covers the opt-in lease capability. RunDequeueSuite covers the resource-aware dequeue contract, where disagreement is not cosmetic: the same job would become eligible on different workers depending only on which store the operator chose, and the dimension that silently drifts is the one deciding whether a 32 GB job lands on a 4 GB machine. Its two load-bearing cases are ZeroBudgetSelectsEverything, the guarantee that an unconstrained caller still sees exactly what it saw before the option existed, and ClaimIsAtomicUnderConcurrency, which proves the fit predicate did not cost the claim its atomicity.
Known limitations of RunDequeueSuite, so a backend author knows what is unpinned rather than guaranteed:
- ZeroBudgetSelectsEverything asserts set membership, not order. The ordering contract is pinned by PriorityOrderingPreservedWithinBudget, LimitTruncatesAfterOrdering, and the two PreferHashes cases; a backend that ordered correctly only when a budget was present would not be caught.
- Every case names its queues explicitly, so empty DequeueOpts.Queues — "all queues" — is never exercised. It cannot be, while the suite supports backends that share one store across subtests: an all-queues claim would take other cases' jobs.
- Requirements are built with resource.Set literals or nil, so a non-nil empty Set is never round-tripped through a backend here.
Run RunDequeueSuite with `go test -race`. ClaimIsAtomicUnderConcurrency is the case that proves the fit predicate did not cost the claim its atomicity, and without -race it is only probabilistic: a realistic mutant that drops atomicity (select-then-update as two statements instead of one) was caught in just 6 of 20 runs under `go test` alone. -race is the configuration where the interleaving that double-claims a job reliably triggers the detector instead of getting lucky.
The package depends only on job, resource, id, and the root package. It must never import a store backend: the backends import this, not the reverse.
Index ¶
- Constants
- func Contains(jobs []*job.Job, jobID id.JobID) bool
- func PendingJob(name, queue string, ttl time.Duration) *job.Job
- func RunCheckpointOrderSuite(t *testing.T, s workflow.Store, ...)
- func RunClusterSuite(t *testing.T, newStore func(t *testing.T) cluster.Store)
- func RunCronSuite(t *testing.T, newStore func(t *testing.T) CronStore)
- func RunDLQReplaySuite(t *testing.T, newStore func(t *testing.T) DLQReplayStore)
- func RunDLQSuite(t *testing.T, newStore func(t *testing.T) DLQStore)
- func RunDequeueSuite(t *testing.T, newStore func(t *testing.T) job.Store)
- func RunLeaseSuite(t *testing.T, newStore func(t *testing.T) LeaseStore)
- func RunListSuite(t *testing.T, newStore func(t *testing.T) ListStore)
- func RunWorkflowSuite(t *testing.T, newStore func(t *testing.T) WorkflowStore)
- func RunningJob(name, queue string, ttl time.Duration) *job.Job
- type CronStore
- type DLQReplayStore
- type DLQStore
- type LeaseStore
- type ListStore
- type WorkflowStore
Constants ¶
const GiB = int64(1) << 30
GiB is one gibibyte in bytes, the unit the memory and disk cases use.
Variables ¶
This section is empty.
Functions ¶
func Contains ¶
Contains reports whether jobs includes the given ID.
Reclamation is not queue-scoped, so cases that exercise it must assert on the job they created rather than on the length of the returned slice — otherwise a shared store makes every such case depend on what its neighbours left behind.
func PendingJob ¶
PendingJob builds a job ready to be dequeued now, on the given queue and with the given lease TTL. A zero ttl leaves LeaseTTL unset, meaning the pool default.
The queue is a parameter because the suite may run against one shared store — spinning a fresh Postgres or Redis container per subtest would cost more than the coverage is worth — so each case dequeues from its own queue to stay isolated from its neighbours.
func RunCheckpointOrderSuite ¶
func RunCheckpointOrderSuite(t *testing.T, s workflow.Store, setTime func(context.Context, id.RunID, string, time.Time) error)
RunCheckpointOrderSuite checks the shared timestamp/ID order against actual persisted rows. setTime forces ties without depending on a backend's clock.
func RunClusterSuite ¶
RunClusterSuite pins the worker row contract the engine's heartbeat and the dashboard's worker list rely on: a registered row reads back whole, capacity included, a heartbeat moves LastSeen to now, and both reads and heartbeats on a row that does not exist say so with dispatch.ErrWorkerNotFound. The engine re-registers on exactly that sentinel, so a backend that returned nil or another error for a missing row would leave a live worker invisible after another instance's stale sweep removed it.
newStore may return a shared store. Each case registers its own worker IDs and never asserts on ListWorkers, and no case sets IsLeader, so the single-leader index some backends keep is never contended.
func RunCronSuite ¶
RunCronSuite pins the targeted cron writes: each changes only the fields it names. The load-bearing case is DisableSurvivesAFire, the regression that let a disabled cron come back when the scheduler wrote its whole stale row after firing.
newStore may return a shared store, so every case registers its own entry under a unique name and never lists or counts.
func RunDLQReplaySuite ¶
func RunDLQReplaySuite(t *testing.T, newStore func(t *testing.T) DLQReplayStore)
RunDLQReplaySuite pins the replay claim: the first claim wins and every later one is refused, a release only undoes its own claim, the job a replay created reads back from every path, and a push never overwrites.
Run it with `go test -race`. ClaimReplayConcurrentExactlyOneWinner is the case that proves the claim is atomic, and an unguarded read-then-write only loses that race reliably under the detector.
newStore may return a shared store, so every case works on entries it pushed itself under a queue nobody else uses, and never asserts a total.
func RunDLQSuite ¶
RunDLQSuite asserts the DLQ contract every backend has to satisfy.
newStore may return a shared store, so every case works on entries it created itself and never asserts on the total count.
func RunDequeueSuite ¶
RunDequeueSuite runs the resource-aware dequeue conformance suite against a backend.
newStore is called once per subtest and receives that subtest's *testing.T, so a backend that stands up a container or opens a database can register teardown on the T that owns it. Closing over the parent T instead would hold every subtest's resources open until the whole suite finished, and would turn a setup t.Fatalf into a FailNow on a parent test.
newStore may return the same underlying store on every call. Each case enqueues onto its own queue and asserts only on the jobs it created, so cases cannot interfere — which matters because starting a fresh Postgres, Mongo, or Redis container per subtest would dominate the runtime of the whole suite.
func RunLeaseSuite ¶
func RunLeaseSuite(t *testing.T, newStore func(t *testing.T) LeaseStore)
RunLeaseSuite runs the lease conformance suite against a backend.
newStore is called once per subtest. It may return the same underlying store every time: each case enqueues onto its own queue and asserts on the jobs it created, so cases do not interfere. That matters because starting a fresh Postgres or Redis container per subtest would dominate the runtime of the whole suite.
The lease is GRANTED by job.Store.DequeueJobs, through job.DequeueOpts.WorkerID and LeaseUntil — there is no separate leased dequeue. That is why every case below claims with DequeueJobs: a backend cannot pass this suite with a grant path that skips the fit predicate, because there is only one path.
func RunListSuite ¶
RunListSuite pins the paged list contract: newest first by ID, a cursor that is the last ID returned, filters that are exact except a literal case-sensitive name prefix, and an empty scope that means every tenant.
newStore may return a shared store, so each case isolates itself with a queue, name prefix or scope nobody else uses, and asserts on the identity of what comes back, never on a total.
func RunWorkflowSuite ¶
func RunWorkflowSuite(t *testing.T, newStore func(t *testing.T) WorkflowStore)
RunWorkflowSuite pins ReopenRun, the claim that makes replay-from-step safe, and the parent link child runs are found by.
Run it with `go test -race`. ReopenRunConcurrentExactlyOneWinner proves the claim is atomic, and an unguarded read-then-write only loses that race reliably under the detector.
newStore may return a shared store, so every case creates its own runs and never lists or counts anything but its own parent's children.
Types ¶
type CronStore ¶
type CronStore interface {
cron.Store
cron.TargetedUpdater
}
CronStore is what the cron suite requires: the base cron store plus the targeted writes the scheduler and the operator actions use.
type DLQReplayStore ¶
type DLQReplayStore interface {
dlq.Store
dlq.ReplayClaimer
}
DLQReplayStore is what the replay suite requires: the base DLQ store plus the claim capability operator replays and retries are built on.
type LeaseStore ¶
type LeaseStore interface {
job.Store
job.LeaseStore
}
LeaseStore is what the lease suite requires of a backend: the base job store plus the opt-in lease capability.