Test
When to read this: You want to run the right part of the suite for the change you made, understand why CI runs tests the way it does, or add a test in the place the next reader will look.
Goal
Know which make target or cargo test invocation covers your change, and be able to update a characterization golden deliberately rather than by accident.
Prerequisites
- Build and run:
make setupdone. make wasm-moduleshas been run (scripts/build-wasm-fixtures.sh). It stages every fixture and theechoandbenfordplugins undertarget/wasm-fixtures/<name>/, which is git-ignored; the integration and conformance suites fail with a missing-file error without it.
How the suite is organised
| Where | What it covers | Run with |
|---|---|---|
#[cfg(test)] mod tests beside the code, in every crate | Unit tests. Most logic is in chatty-core, so most tests are too | make test-fast (cargo test -p chatty-core --lib), or cargo test -p <crate> |
crates/chatty-core/tests/integration.rs | The public API from an external consumer’s point of view: settings model CRUD lifecycle, ConversationsStore operations and ordering, provider capability propagation into ModelConfig, Azure provider configuration filtering, JSON serialization round-trips, token budget snapshot calculations. No display server | cargo test -p chatty-core --test integration |
crates/chatty-core/tests/browser_live.rs | End to end against a real Chrome, #[ignore]d because the first run may download the pinned build | cargo test -p chatty-core --features browser --test browser_live -- --ignored --test-threads=1 |
crates/chatty-gpui/tests/core_integration.rs | chatty-core types compiled with the gpui-globals feature: every impl Global still behaves, cross-crate interactions, token budget snapshots. Links GPUI but needs no live display | make test-gpui |
crates/chatty-protocol-gateway/tests/ (gateway_tests.rs, echo_plugin_e2e.rs) | The gateway’s routes, and the echo plugin over MCP (a plugin has no OpenAI or A2A route) | make test-gateway |
crates/chatty-protocol-gateway/tests/conformance/ | Plugin evaluation plan S3, rows 3.5–3.14 (3.1–3.4, 3.8 and 3.9 went with chat, PL-U3): MCP against echo, benford and the fixtures, and A2A against a participant, including real clients (rmcp, the MCP Inspector via npx on Node ≥ 22.19, chatty-core’s A2aClient). A missing npx prints SKIP and passes; CI installs Node. Rows red today are #[ignore = "known defect: …"] | cargo test -p chatty-protocol-gateway --test conformance; add -- --ignored for the red rows |
gemini_compat_tests in crates/chatty-core/src/tools/mod.rs | Every tool schema converts to a Gemini Schema without empty type strings | cargo test -p chatty-core --lib gemini_compat |
crates/chatty-core/src/repositories/store_conformance.rs | Every RepositoryRegistry family (13, token_tracking the newest) plus ConversationRepository against one load/save/round-trip contract, run against the JSON/SQLite and in-memory backends; exported under the test-support feature so hive’s store runs the identical suite (AGE-280) | cargo test -p chatty-core --lib conformance |
scripts/team-smoke.sh | The coder-reviewer test-fixture team end to end in a container against a local Ollama, scored by a verifier — the one check for a change to the broker, --team or the worker tree. Not in CI | bash scripts/team-smoke.sh; see Team smoke test |
| Characterization goldens (three directories, below) | The event sequence each frontend produces for a scripted stream | see below |
crates/hive-billing-sdk/tests/ | Standalone SDK with its own Cargo.lock; not a workspace member. .cargo/config.toml defaults the crate to wasm32-wasip2, which has no test-harness support, so tests need an explicit host target | cd crates/hive-billing-sdk && cargo test --target x86_64-unknown-linux-gnu |
modules/benford/src/lib.rs unit tests | Standalone plugin crate, also outside the workspace and defaulted to wasm32-wasip2 for the same reason | cd modules/benford && cargo test --target x86_64-unknown-linux-gnu |
crates/chatty-module-sdk/ | Standalone SDK crate consumed by every module; no tests of its own, so CI holds it to a clean wasm32-wasip2 clippy pass instead | cd crates/chatty-module-sdk && cargo clippy --target wasm32-wasip2 -- -D warnings |
crates/chatty-wasm-runtime/tests/sandbox.rs | Plugin evaluation plan S1, rows 1.1–1.14: fuel, wall-clock, memory, traps, file/config/billing imports, WASI surface, against real fixtures. Doubles as AGE-600’s trip-wire: its non-ignored rows (fuel exhaustion, wit-0.1/core-module load failures, trap-then-reuse, …) already run in cargo test --all-features, so a sandbox regression fails CI without a separate test. Rows red today are #[ignore = "known defect: …"] | cargo test -p chatty-wasm-runtime --features test-support --test sandbox; add -- --ignored for the red rows |
Steps
1. Inner loop
make test-fast # cargo test -p chatty-core --lib
cargo test -p chatty-core --lib my_module:: # one module
2. The crate you touched
make test-tui # cargo test -p chatty-tui
make test-gpui # cargo test -p chatty-gpui
make test-gateway # cargo test -p chatty-protocol-gateway
3. Everything, the way CI runs it
make test # cargo test --all-features
--all-features matters: the excel, docx, pdf, pptx, math-render, mermaid, duckdb and browser tool groups only compile with their feature on, so a plain cargo test never sees them. make ci adds formatting, clippy and the repository check scripts; see Make targets & CI workflows.
Note
Tests run in parallel. CI used to pass
--test-threads=1becausechatty-coretests intermittently died with SIGTRAP. The cause was pdfium, which is not thread-safe, being used from several test threads at once;create_pdfium()now hands out aPdfiumHandlethat serialises every use (AGE-176). If a SIGTRAP, SIGSEGV or SIGABRT comes back undercargo test, look for a new pdfium call that bypassescreate_pdfium()rather than re-adding the flag.
4. Characterization goldens
Turn orchestration — which chunk produces which event, which chunks end a turn, that cancellation produces a cancelled outcome, that sub-agent progress is drained ahead of stream chunks — is pinned by golden files rather than by assertions scattered across tests.
The scripted streams live in chatty_core::services::stream_fixtures (scenarios(), clarification_scenario(), scripted_stream, assert_golden). The module is compiled under #[cfg(any(test, feature = "test-support"))]; chatty-tui and chatty-gpui enable chatty-core/test-support from their dev-dependencies only, so it never reaches a release build.
Both frontends and the core loop drive the same scenarios and record their own event type, so a divergence between the two UIs shows up as a difference between two golden directories:
| Golden directory | Recorded by | Event type |
|---|---|---|
crates/chatty-core/src/services/goldens/stream_loop/ | loop_callback_sequence_matches_goldens in stream_processor.rs | StreamChunkHandler callbacks |
crates/chatty-core/src/session/goldens/ | session/tests.rs | SessionEvent |
crates/chatty-tui/src/engine/goldens/ | engine/characterization.rs | AppEvent |
crates/chatty-gpui/src/chatty/controllers/app_controller/goldens/ | session_characterization.rs | StreamManagerEvent |
Each directory holds one file per scenario: text_only, tool_call_then_result, tool_error, approval_granted, approval_denied, clarification_requested, provider_error_mid_stream, cancelled_mid_stream, sub_agent_progress, token_usage_on_done.
When a golden fails, the assertion prints both sequences. If the change is deliberate, rewrite the files and explain the diff in the PR:
UPDATE_GOLDENS=1 cargo test -p chatty-core --lib loop_callback_sequence
UPDATE_GOLDENS=1 cargo test -p chatty-core session::
UPDATE_GOLDENS=1 cargo test -p chatty-tui characterization
UPDATE_GOLDENS=1 cargo test -p chatty-gpui characterization
Deliberately not pinned: the real-time interleaving of progress events with chunks (racy in production, so scenarios queue progress before the chunks it accompanies), and anything wall-clock based — the stall watchdog has its own tests in stream_processor.rs.
5. Where mocks live
crates/chatty-core/src/tools/test_helpers.rs—MockMcpRepositoryandMockA2aRepository, in-memory repositories with injectable load/save errors,#[cfg(test)]only. Used by the MCP and A2A tool tests.- Providers are not mocked; the scripted streams above stand in for a provider whenever a test needs “what the model sent”.
- The desktop characterization test talks to real entities (a
ChatViewin a headless test window, a liveStreamManager), not stubs, so what it records is what the UI receives. browser_live.rsbuilds a throwaway workspace withtempfilerather than fixtures on disk.
Verify
make test-fastis green after a core change; the crate target is green after a frontend change.make test(full, serialised) is green before you push.- A golden you changed on purpose has its diff explained in the PR.
Checklist
-
make wasm-moduleshas been run on this checkout - New logic in
chatty-corehas a unit test beside it - A new tool has a
gemini_compatguard (see Your first change) - Turn-lifecycle changes re-recorded all three golden directories, not one
-
make testpasses
Common mistakes
| Mistake | Do this instead |
|---|---|
| Deleting a golden file to make a test pass | UPDATE_GOLDENS=1 and explain the diff |
| Updating the TUI goldens but not the desktop ones (or the reverse) | Re-run all three; the point is that they agree |
cargo test without --all-features before pushing | make test |
A signal (SIGTRAP/SIGSEGV/SIGABRT) under cargo test | Find the pdfium call that bypasses create_pdfium(); do not add --test-threads=1 |
Running browser_live in CI | It is #[ignore]d on purpose; run it locally |
Enabling test-support in a normal [dependencies] block | Dev-dependency only |