Testing strategy
Calimero Core is tested in layers, each trading scope for speed. Fast in-process unit tests cover application and CRDT logic; a deterministic simulator covers the sync protocol without WASM or a network; merobox workflows cover the real multi-node system end to end. This page maps the layers and gives the exact commands CI enforces, so you can reproduce the gate before opening a PR.
| Layer | Where | What it proves | Speed |
|---|---|---|---|
App unit tests (TestHost) | crates/sdk/src/testing.rs | Your app’s call / view / event / migrate logic, in isolation | Milliseconds |
| Storage convergence | crates/storage/src/testing.rs | A Mergeable / #[app::state] type converges under concurrent, reordered ops | Sub-second |
| Sync simulator | crates/node/tests/sync_sim/ | The sync protocol converges under loss, latency, reordering, and partitions | Fast, deterministic |
| Integration / e2e (merobox) | workflows/, e2e-tests/ | The real multi-node system: install, call, sync, govern, migrate | Slow (Docker) |
App unit tests with TestHost
Section titled “App unit tests with TestHost”TestHost
lets you exercise an #[app::state] type as ordinary Rust — no wasm32 build, no
node, no containers. State lives in an in-memory mock store; events and logs are
captured; the executor / context identity is configurable. You drive mutations
with call, reads with view, and inspect events() / logs(). Migrations are
supported too: TestHost::migrate, plus assert_migrate_converges and
assert_absorb_replay_converges for the determinism properties the migration
model rests on.
let mut app = TestHost::new(|| MyApp::init());app.call(|s| s.increment("a".into())).unwrap();let value = app.view(|s| s.get("a".into())).unwrap();assert_eq!(value, 1);assert_eq!(app.events().len(), 1);This is the same harness app developers use; the app-author-facing guide is at /build/testing/.
Storage convergence (property tests)
Section titled “Storage convergence (property tests)”crates/storage/src/testing.rs
provides converge / converge_app: assert in a single #[test] that a state
type converges under concurrent, reordered operations. It drives N in-memory
replicas (each with its own store and executor identity), applies the registered
ops on every replica in a per-replica shuffled order, gossips the resulting
StorageDeltas in shuffled order, and asserts every replica lands on the same
Merkle root hash.
converge_app(TeamMetricsApp::init) .replicas(3) .ops(|s| { let _ = s.record_win("liverpool".into()); }) .assert_all_replicas_equal();The deterministic sync simulator
Section titled “The deterministic sync simulator”crates/node/tests/sync_sim/
is a discrete-event framework for the sync protocol. It reuses the real
calimero-storage Index / Interface::apply_action and Merkle tree, the real
hash computation, and the real select_protocol() — so hash propagation and
protocol selection behave identically to production — while replacing WASM
execution, network I/O, time, and concurrency with a NetworkRouter (with fault
injection), a discrete SimClock, seeded RNG, and sequential event processing.
Seeds make every failure reproducible.
let mut rt = SimRuntime::with_seed(42);Read crates/node/tests/sync_sim/AGENT_GUIDE.md before touching the framework.
The framework itself lives under sync_sim/ (change with review); add new
protocol-behavior tests under sync_scenarios/, and compliance tests under
sync_compliance/.
Integration and e2e (merobox)
Section titled “Integration and e2e (merobox)”The widest layer runs real merod nodes in Docker via
merobox, driven by declarative
YAML workflows. The repo’s workflows/ directory holds the scenario files
(grouped under app-migration/, sync-tests/, fuzzy-tests/); each describes
a node topology and a sequence of install / call / assert steps. These are the
tests that exercise everything TestHost and the simulator leave out — real
networking, cross-context calls, governance across peers, late joiners,
partitions, and multi-node migrations. They run in CI under the
e2e-rust-apps.yml, app-migration-e2e.yml, and sync-regression.yml
workflows, building a local merod:local image plus the WASM apps under
apps/.
The pre-PR gate
Section titled “The pre-PR gate”These are the exact checks CI enforces in .github/workflows/ci-checks.yml. Run
them locally before pushing — they are the same commands, in the same order.
-
Build the apps, then the workspace. Several workspace tests load compiled WASM artifacts, so the apps build first.
Terminal window ./scripts/build-all-apps.shcargo build --workspace --all-targets --tests -
Check formatting.
Terminal window cargo fmt --check -
Lint with the no-warnings gate. The
--features calimero-storage/testingflag pulls the storagetestingharness into the same single pass so it is linted too.Terminal window cargo clippy --workspace --all-targets --features calimero-storage/testing -- -D warnings -
Run the tests. CI runs with output shown and a
TEST_HOSTSenv var (some networking tests bind real interfaces); set it locally if host-dependent tests fail.Terminal window TEST_HOSTS=127.0.0.1,0.0.0.0 cargo test -- --nocapture -
Run the narrow target checks CI keeps green: the no-chain context-config build and the local-only
merodinit binary.Terminal window cargo check -p calimero-context-configcargo check -p merod --no-default-features -
Check the dependency policy — only needed if you changed dependencies. CI pins
cargo-denyto0.16.3for toolchain compatibility.Terminal window cargo deny check licenses sources