Documents
Testing Standards and Tooling
Testing Standards and Tooling
Type
Document
Status
Published
Created
Oct 31, 2025
Updated
Mar 9, 2026
Updated by
Dosu Bot

Testing Strategy and Tooling#

DaemonEye employs a comprehensive, multi-layered testing strategy to ensure reliability, maintainability, and production readiness across all components. The approach combines unit, integration, property-based, and performance testing, with a strong emphasis on coverage, error handling, and cross-platform compatibility.

Snapshot Testing with Insta#

The project uses the insta crate for snapshot testing of CLI outputs and error conditions. Snapshot tests compare actual outputs (such as help messages, version info, error messages, and configuration responses) against previously approved snapshots, making it easy to detect regressions or unintended changes in user-facing behavior. Tests use assert_snapshot! and insta::with_settings! macros to assert outputs, with normalization filters applied to handle platform-specific or unstable output such as executable names or temporary directory paths (example).

Example CLI test using insta:

use assert_cmd::prelude::*;
use insta::assert_snapshot;
use std::process::Command;
use tempfile::TempDir;

#[test]
fn shows_help() -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::cargo_bin("procmond")?;
    cmd.arg("--help");
    let output = cmd.output()?;
    assert!(output.status.success());
    let stdout = String::from_utf8_lossy(&output.stdout);

    insta::with_settings!({
        filters => vec![
            (r"\bprocmond\.exe\b", "procmond"),
        ]
    }, {
        assert_snapshot!("procmond_help", stdout.as_ref());
    });

    Ok(())
}

Test Structure#

Tests are organized by crate and component, with dedicated directories for unit, integration, property-based, and benchmark tests. CLI tests are written as Rust #[test] functions, using assert_cmd to invoke binaries and tempfile to isolate filesystem side effects (example). IPC integration tests use async #[tokio::test] functions to validate client-server communication, error handling, concurrency, and cross-platform endpoint creation (example). Alerting and configuration tests follow similar patterns, focusing on builder-pattern config validation and exhaustive scenario coverage.

Workspace-level organization:

tests/
├── integration/
│ ├── cross_component.rs
│ ├── cli_integration.rs
│ └── snapshots/
├── benchmarks/
│ ├── procmond_bench.rs
│ ├── agent_bench.rs
│ └── cli_bench.rs
├── property/
│ ├── config_validation.rs
│ ├── data_structures.rs
│ └── edge_cases.rs
└── fixtures/
    ├── test_configs/
    ├── mock_data/
    └── performance_baselines/

Per-component test directories:

{procmond,daemoneye-agent,daemoneye-cli}/tests/
├── unit/
├── integration/
└── benchmarks/

Coverage Reporting Integration#

Coverage reporting is integrated into the CI workflow using cargo-llvm-cov to generate coverage reports, which are uploaded to Codecov and Qlty for analysis (CI example). Tests are orchestrated with cargo-nextest for efficient execution and cross-platform support. The CI matrix runs tests on Linux, macOS, and Windows, and includes code quality checks with rustfmt and clippy before running tests.

Best Practices for Reliable Tests#

CLI Components#

  • Use insta for snapshot testing of outputs and error conditions.
  • Normalize unstable output (e.g., executable names, temp paths) in snapshots for cross-platform consistency.
  • Isolate filesystem side effects with tempfile to avoid polluting user data.
  • Explicitly test error paths and invalid argument handling.

IPC Components#

  • Structure integration tests as async #[tokio::test] functions to validate concurrency and communication.
  • Use temporary directories for endpoint isolation, supporting both Unix sockets and Windows named pipes.
  • Include retry logic and cleanup steps to handle transient failures and ensure reliability.
  • Assert expected outcomes (success, error messages, correct task IDs) and simulate server-side failures to verify client robustness (example).

Alerting Components#

  • Write unit tests for individual alert sinks, configuration parsing, error condition handling, and authentication mechanisms.
  • Use integration tests with mock endpoints (e.g., mock HTTP servers for webhooks, local syslog daemons, SMTP mock servers for email) and simulate network failures.
  • Cover concurrent alert delivery, memory usage under load, and network timeout handling.
  • Implement reliability patterns such as circuit breakers, retries, and dead letter queues in tests (discussion).

Configuration Components#

  • Use builder-pattern tests to validate flexible configuration scenarios.
  • Employ property-based testing (e.g., with proptest) to discover edge cases and validate parsing logic.
  • Test configuration acceptance, precedence, and error handling for invalid or missing values.

General Practices#

  • Achieve and maintain high code coverage (>85%) across all components.
  • Keep test execution time under 5 minutes for the full suite.
  • Maintain a flaky test rate below 1% by isolating side effects and using robust assertions.
  • Use shared fixtures and test utilities to reduce duplication and improve maintainability.
  • Integrate performance benchmarks (e.g., with criterion) for critical paths and monitor for regressions in CI.

Automated Performance Benchmarking#

Performance validation is available through a dedicated .github/workflows/benchmarks.yml workflow. This workflow is triggered manually via workflow_dispatch, allowing developers to run benchmarks on-demand rather than on every commit.

The benchmarking infrastructure consists of two separate jobs:

Benchmarks job:

  • Runs Criterion-based performance benchmarks for the procmond package
  • Supports running all benchmarks or specific benchmark suites (e.g., performance_benchmarks, process_collector_benchmarks)
  • Compares results against cached baseline measurements from the main branch
  • Detects performance regressions and logs them as warnings (does not fail the build)
  • Caches baseline results between runs for accurate comparison over time
  • Uploads benchmark results as CI artifacts (retained for 30 days)

Load tests job:

  • Runs load tests independently to validate system behavior under stress
  • Uploads load test results as separate CI artifacts (retained for 30 days)

Baseline benchmarks are updated when benchmarks run on the main branch, ensuring comparisons remain accurate as the codebase evolves. Developers can manually trigger the workflow to validate performance before merging changes.

References#

This strategy provides developers with confidence in code changes, rapid feedback, and robust documentation through test examples, supporting the ongoing reliability and maintainability of DaemonEye.