This document provides comprehensive testing strategies and guidelines for DaemonEye, covering unit testing, integration testing, performance testing, and security testing.
Testing Philosophy#
DaemonEye follows a comprehensive testing strategy that ensures:
- Reliability: Robust error handling and edge case coverage
- Performance: Meets performance requirements under load
- Security: Validates security controls and prevents vulnerabilities
- Maintainability: Easy to understand and modify tests
- Coverage: High test coverage across all components
Testing Strategy#
Three-Tier Testing Architecture#
- Unit Tests: Test individual components in isolation
- Integration Tests: Test component interactions and data flow
- End-to-End Tests: Test complete workflows and user scenarios
Testing Pyramid#
┌─────────────────┐
│ E2E Tests │ Few, slow, expensive
│ (Manual) │
├─────────────────┤
│ Integration │ Some, medium speed
│ Tests │
├─────────────────┤
│ Unit Tests │ Many, fast, cheap
│ (Automated) │
└─────────────────┘
Unit Testing#
Core Testing Framework#
DaemonEye uses Rust's built-in testing framework with additional crates like insta for snapshot testing, tempfile for temporary directories, mockall for mocking, and proptest for property-based testing.
Key test patterns include:
- Process Collection Tests: Verify process enumeration works correctly
- Database Operation Tests: Test CRUD operations with in-memory SQLite
- Serialization Tests: Ensure ProcessInfo round-trips through serde correctly
Mocking and Test Doubles#
Use mockall for external dependencies. Mock the ProcessCollectionService trait to test the agent independently of actual system processes.
Property-Based Testing#
Use proptest for testing complex logic with random inputs, such as verifying serialization round-trips and SQL query validation.
Integration Testing#
Database Integration Tests#
Test database operations with real SQLite including schema creation, data insertion, retrieval, and query execution using TempDir for isolation.
IPC Integration Tests#
Test inter-process communication between server and client components using Unix domain sockets.
Alert Delivery Integration Tests#
Test alert delivery through multiple sinks (syslog, webhook) with realistic alert data.
End-to-End Testing#
CLI Testing#
Use assert_cmd and insta for snapshot testing CLI output:
#[test]
fn test_cli_help() {
let mut cmd = Command::cargo_bin("daemoneye-cli").unwrap();
cmd.assert()
.success()
.stdout(predicate::str::contains("DaemonEye CLI"));
}
#[test]
fn test_cli_query() {
let mut cmd = Command::cargo_bin("daemoneye-cli").unwrap();
cmd.args(&["query", "SELECT * FROM processes LIMIT 1"])
.assert()
.success();
}
Full System Testing#
Test complete system workflows by starting procmond and daemoneye-agent, then verifying CLI operations against the running system.
Performance Testing#
Load Testing with Criterion#
use criterion::{Criterion, black_box, criterion_group, criterion_main};
fn benchmark_process_collection(c: &mut Criterion) {
let mut group = c.benchmark_group("process_collection");
group.bench_function("collect_processes", |b| {
b.iter(|| {
let collector = ProcessCollector::new();
black_box(collector.collect_processes())
})
});
group.finish();
}
Memory Testing#
Verify that repeated operations do not leak memory by comparing RSS before and after 1000 collection cycles. Memory increase should stay under 10MB.
Stress Testing#
Run collection continuously for 60 seconds to verify system stability under sustained load.
Security Testing#
Fuzz Testing#
Use cargo-fuzz to test ProcessInfo deserialization and SQL query validation with random inputs.
SQL Injection Prevention Testing#
Test various SQL injection attempts against the database layer. Verify that malicious queries are either rejected or executed safely without causing damage.
Security Boundary Testing#
Test privilege dropping to verify the system correctly transitions from elevated to reduced privileges while maintaining core functionality.
Input Validation Testing#
Test both valid and invalid ProcessInfo structures to ensure the validation layer catches issues like empty names, negative CPU usage, and overly long command lines.
Test Configuration#
# test-config.yaml
app:
log_level: debug
scan_interval_ms: 1000
batch_size: 10
database:
path: ':memory:'
max_connections: 5
retention_days: 1
alerting:
enabled: false
testing:
enable_mocks: true
mock_external_services: true
test_data_dir: /tmp/daemoneye-test
cleanup_after_tests: true
Continuous Integration#
GitHub Actions Workflow#
name: Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
rust: [1.87, stable, beta]
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: ${{ matrix.rust }}
override: true
- name: Run tests
run: |
cargo test --verbose
cargo test --verbose --features integration-tests
- name: Run benchmarks
run: cargo bench --verbose
- name: Generate coverage report
run: |
cargo install cargo-tarpaulin
cargo tarpaulin --out Html --output-dir coverage
Test Organization#
tests/
├── unit/
│ ├── collector_tests.rs
│ ├── database_tests.rs
│ └── alert_tests.rs
├── integration/
│ ├── ipc_tests.rs
│ ├── database_tests.rs
│ └── alert_delivery_tests.rs
├── e2e/
│ ├── cli_tests.rs
│ └── system_tests.rs
└── common/
├── test_helpers.rs
└── test_data.rs
This testing documentation provides comprehensive guidance for testing DaemonEye. For additional testing information, consult the specific test files or contact the development team.