Testing

rho uses a layered test strategy: unit tests within each source file, integration tests at the crate level, and end-to-end scenario tests that exercise the full agent loop.

Running tests

cargo xtask ci        # Full CI pipeline: fmt → lint → build → test
ctest                 # All tests via cargo-nextest (falls back to cargo test)
cargo xtask test -p rho-core -- --nocapture  # Single crate

Test structure

LayerLocationWhat it tests
Unit tests#[cfg(test)] mod tests inside each source fileIndividual functions, types, edge cases
Integration testsrho-core/tests/integration_tests.rsAgent loop, approval flow, context management
Security integration testsrho-core/tests/security_tests.rsSandbox enforcement, denylist, trust store
Session integration testsrho-core/tests/session_integration_tests.rsSession tree operations, persistence, branching
RPC integration testsrho/src/rpc.rs (#[cfg(test)] mod tests)Full JSON-RPC 2.0 protocol: command dispatch, event sequencing, approval round-trips, tool calls, errors, multi-turn sessions
Tool integration testsrho-tools/tests/tool_tests.rsTool execution, sandbox enforcement, denylist
Shell executor testsrho-tools/tests/shell_executor_tests.rsShell command execution, path normalization
Extension unit testsrho-ext/src/*.rs (#[cfg(test)] mod tests)Runtime spawning, manifest parsing, discovery, transpilation, DenoTool, DenoObserver, host ops, module loader

RPC integration tests live in rho/src/rpc.rs (not rho/tests/) because App's fields are pub(crate). They use TestProvider to wrap a MockChatClient as a Provider and construct App directly, bypassing the full CLI startup sequence. The RPC dispatch loop is decoupled from I/O via the Transport trait (rho/src/transport.rs); tests inject a StdioTransport wired to Cursor<Vec<u8>> readers and captured writers for both stdin and stdout.

Test helpers (rho-test-helpers)

rho-test-helpers provides the shared infrastructure that all test layers use:

Mocks

HelperPurpose
MockChatClientPre-programmed model responses for deterministic agent loop tests
MockShellExecutorPre-programmed command output for tool tests
TestProviderWraps MockChatClient as a Provider for tests needing ProviderRegistry
AutoApproveGateApproves all tool calls automatically
AutoDenyGateDenies all tool calls automatically

Builders

HelperPurpose
text_events(text)Create a streaming text response followed by Done
tool_call_events(id, name, args)Create a single tool call response
multi_tool_call_events(calls)Create a response with multiple tool calls
FixedResponseToolA tool that always returns the same output
FailingToolA tool that always returns an error
Fixtures and utilitiesPurpose
FileTestEnvTemporary directory with file-system operations for sandbox tests
in_memory_session(prompt)Create a session without disk persistence
empty_trust_store()Trust store with no trusted files
detect_shell()Find the available PowerShell on the test system
tempdir_with_sandbox()Create a temp directory with a SandboxRoot
assert_no_orphan_tool_results()Verify every tool call has a matching result in the session

Naming conventions

  • Test functions: descriptive_snake_case — e.g., system_message_is_always_retained
  • Test modules: match the function under test — e.g., tests::tool_result_success_has_no_details
  • Fixtures: tests/fixtures/*.json — externalised test data for deserialization tests

Coverage priorities

AreaCoverageNotes
Error handlingHighEvery RhoError variant has a test
DeserializationHighEvery JSON fixture has a round-trip test
Edge casesHighEmpty inputs, oversized inputs, invalid inputs
SandboxHighPath traversal, symlinks, non-existent paths
DenylistHighEach denied command and substring pattern
Context managementHighEviction, pinning, compaction rendering
IntegrationMediumAgent loop with realistic message sequences
ScenariosLowEnd-to-end validation against real model servers