Module conformance
Expand description
The shared Storage conformance suite — “write your own backend”.
Every storage backend in the ecosystem — the in-memory reference
(MemStorage), the journal (M2), sqlite (M4), remote
(M5), and anything you write out of tree — must uphold the same invariants
(writing your own backend).
Those invariants are subtle, mostly
concurrent, and easy to almost get right, so they are not re-tested per
backend: they are asserted once, here, against a factory.
§Running the suite against your backend
-
Depend on
atune_corewith theconformancefeature enabled — for a backend crate that is usually a dev-dependency, so the battery never ships in release builds:[dev-dependencies] atune_core = { version = "0.1", features = ["conformance"] } -
Write a factory: a closure returning a fresh, empty backend as a
Box<dyn Storage>. It is called once per check, so no check can be polluted by another’s state. -
Invoke
storage_conformance_tests!, which expands to one#[test]per check, each named after the invariant it pins. Put it in its own module: the macro defines items with fixed names.mod conformance { use atune_core::storage::{MemStorage, Storage}; atune_core::storage_conformance_tests!( || Box::new(MemStorage::new()) as Box<dyn Storage> ); }One test per check is the point: a failure names the invariant that broke instead of reporting “the storage suite failed”.
If you would rather have a single test — for a smoke check in a doc
example, say — call run_all instead.
§What a check is
A check is a pub fn check_*(new_storage: &dyn Fn() -> Box<dyn Storage>)
that panics with a message naming the invariant and the observed
violation. Checks use nothing but the Storage trait, so they are
genuinely backend-agnostic; they never read a clock, never sleep, and use
Barriers rather than timing to make the concurrent
ones deterministic and fast.
§Concurrency and portability
Four checks spawn threads:
check_race_free_trial_numbering,
check_exactly_one_thread_wins_a_transition and
check_sync_survives_interleaved_writers, plus
check_study_catalog_create_if_empty_is_atomic. They exist unconditionally so
that the macro’s expansion does not depend on your crate’s feature set, but
their bodies compile only with atune_core’s system feature on a
non-wasm target. Everywhere else — a wasm32 build, a
--no-default-features build — they compile to a no-op, because there are
no threads to race with.
§What the suite does not check
Durability, cross-process visibility and crash recovery: they are not expressible through the trait and are backend-specific (the journal backend brings its own). Nor does it require unique study names — the trait leaves that to the backend.
Functions§
- check_
absurd_ inputs_ do_ not_ panic - Absurd but well-typed inputs produce a
Result, never a panic. - check_
contiguous_ trial_ numbers - Trial numbers are contiguous from 0 within a study, and identifiers are unique.
- check_
create_ trial_ honours_ the_ template create_trialhonours itsTrialTemplateinstead of discarding it.- check_
distribution_ compatibility_ gate - The compatibility gate is study-wide and directional: numeric bound drift
is accepted, a changed categorical choice set (or kind, or log flag) is
Error::Incompatible. - check_
error_ text_ survives_ onto_ a_ failed_ trial - A failed trial’s error text survives:
set_errorrecords it on an unfinished trial, and it is still there after the trial is failed. - check_
exactly_ one_ thread_ wins_ a_ transition - Under concurrent
try_transitionon the same trial, exactly one caller wins and nobody errors. - check_
finished_ trials_ are_ immutable - Finished trials are immutable: every write against
Complete,PrunedorFailedis anError::Conflict, and the record is left alone. - check_
heartbeat_ records_ the_ supplied_ timestamp heartbeatstores the caller’s timestamp verbatim; storage never reads a clock of its own.- check_
intermediate_ values_ are_ ordered_ and_ keyed_ by_ step - Intermediate reports are kept in acceptance order and keyed by step: a repeated step overwrites rather than appending a second entry.
- check_
lost_ transitions_ are_ ok_ false - Losing a race is
Ok(false), never an error — including when the winner has already driven the trial into a terminal state. - check_
missing_ state_ is_ none_ not_ an_ error - Reading a scope that was never written is
Ok(None), not an error. - check_
per_ seed_ sub_ results_ round_ trip - A multi-seed trial’s per-seed sub-results round-trip through the backend.
- check_
pruned_ trials_ adopt_ their_ last_ intermediate - A pruned trial’s objective value is its last intermediate value — the rule successive-halving schedulers depend on — end to end through storage.
- check_
race_ free_ trial_ numbering - Concurrent
create_trialcalls yield every number exactly once: no gaps, no duplicates. - check_
set_ values_ matches_ the_ direction_ count set_valuesaccepts exactly as many values as the study declares directions.- check_
state_ blobs_ round_ trip_ and_ overwrite - State blobs are typed, scoped, independent per scope, and overwritten in place.
- check_
study_ catalog_ create_ if_ empty_ is_ atomic - The empty-catalog check and insert are one atomic operation: exactly one concurrent caller creates the study and all others observe Occupied.
- check_
study_ catalog_ create_ list_ and_ find - The opt-in catalog capability creates only into an empty backend, lists in stable study-id order, and distinguishes a missing name from a storage failure.
- check_
study_ catalog_ duplicate_ names_ are_ conflict - A catalog must not turn duplicate names into an arbitrary study identity.
- check_
study_ catalog_ unsupported_ is_ explicit - A backend without the optional catalog capability is rejected explicitly; callers never fall back to probing conventional numeric study ids.
- check_
study_ creation_ and_ config_ round_ trip - A study’s configuration survives the round trip through storage verbatim.
- check_
sync_ at_ head_ is_ empty_ and_ idempotent - A sync that has nothing new returns no deltas and the same cursor, however often it is repeated.
- check_
sync_ coalesces_ latest_ snapshot_ per_ changed_ trial - Sync coalesces all unseen mutations into one latest complete snapshot for each changed trial.
- check_
sync_ limited_ pages_ fold_ to_ one_ full_ sync - A bounded sync pages the same feed: fold the pages and you get exactly what
one unbounded
Storage::syncreturns, with an honestmoreflag. - check_
sync_ returns_ deltas_ after_ the_ cursor syncreturns exactly what happened after the cursor, and folding the increments reproduces a full re-read.- check_
sync_ survives_ interleaved_ writers - With several writers hammering one study, an incremental reader still ends up with exactly the study a full re-read reports.
- check_
sync_ tolerates_ a_ cursor_ from_ the_ future - A cursor from the future is tolerated: no deltas, no error, no backwards cursor.
- check_
transition_ matrix - The full 6x6 transition matrix agrees with
TrialState::can_transition_to, and terminal states admit nothing. - check_
trial_ numbers_ are_ per_ study - Numbering is per study: two studies both start at 0 and never interleave.
- check_
trial_ side_ resources_ are_ independently_ keyed - Retry, fan, constraint and checkpoint side-resources are four independent keys; overwriting one can never erase or reinterpret another.
- check_
typed_ trial_ interfaces_ preserve_ incompatible_ state - Public Study/Trial round trips for every typed trial resource, including
legacy reads and a same-backend resume. Incompatible fixtures are installed
through the lifecycle owner capability because raw
put_stateis correctly forbidden for managed trials. - check_
unknown_ ids_ are_ not_ found - Unknown study and trial identifiers produce
Error::NotFound, never a panic and never a silent success. - check_
user_ attrs_ round_ trip_ and_ reserve_ atune_ prefix - Study and trial user attributes preserve arbitrary nested JSON, replace by key, remain visible through sync, and reject the reserved interop namespace.
- check_
write_ params_ is_ atomic write_paramsis all-or-nothing: a batch containing one rejected entry writes none of them.- run_all
- Runs every check against
new_storage, panicking on the first violation. - runs_
concurrent_ checks trueif the concurrent checks have a thread implementation on this target and feature set.