Skip to main content

Module conformance

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

  1. Depend on atune_core with the conformance feature 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"] }
  2. 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.

  3. 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_trial honours its TrialTemplate instead 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_error records 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_transition on the same trial, exactly one caller wins and nobody errors.
check_finished_trials_are_immutable
Finished trials are immutable: every write against Complete, Pruned or Failed is an Error::Conflict, and the record is left alone.
check_heartbeat_records_the_supplied_timestamp
heartbeat stores 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_trial calls yield every number exactly once: no gaps, no duplicates.
check_set_values_matches_the_direction_count
set_values accepts 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::sync returns, with an honest more flag.
check_sync_returns_deltas_after_the_cursor
sync returns 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_state is 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_params is 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
true if the concurrent checks have a thread implementation on this target and feature set.