Skip to content

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.

LayerWhereWhat it provesSpeed
App unit tests (TestHost)crates/sdk/src/testing.rsYour app’s call / view / event / migrate logic, in isolationMilliseconds
Storage convergencecrates/storage/src/testing.rsA Mergeable / #[app::state] type converges under concurrent, reordered opsSub-second
Sync simulatorcrates/node/tests/sync_sim/The sync protocol converges under loss, latency, reordering, and partitionsFast, deterministic
Integration / e2e (merobox)workflows/, e2e-tests/The real multi-node system: install, call, sync, govern, migrateSlow (Docker)

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/.

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();

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/.

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/.

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.

  1. Build the apps, then the workspace. Several workspace tests load compiled WASM artifacts, so the apps build first.

    Terminal window
    ./scripts/build-all-apps.sh
    cargo build --workspace --all-targets --tests
  2. Check formatting.

    Terminal window
    cargo fmt --check
  3. Lint with the no-warnings gate. The --features calimero-storage/testing flag pulls the storage testing harness into the same single pass so it is linted too.

    Terminal window
    cargo clippy --workspace --all-targets --features calimero-storage/testing -- -D warnings
  4. Run the tests. CI runs with output shown and a TEST_HOSTS env 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
  5. Run the narrow target checks CI keeps green: the no-chain context-config build and the local-only merod init binary.

    Terminal window
    cargo check -p calimero-context-config
    cargo check -p merod --no-default-features
  6. Check the dependency policy — only needed if you changed dependencies. CI pins cargo-deny to 0.16.3 for toolchain compatibility.

    Terminal window
    cargo deny check licenses sources