Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Testing Guide

This guide covers how to write and run tests for the Torvyn project. Torvyn uses a multi-layered testing strategy: unit tests for individual functions and types, integration tests for cross-module behavior, benchmark tests for performance verification, property tests for invariant validation, and fuzz tests for input boundary exploration.

Test Infrastructure Overview

Tests live in two places:

  • Inline unit tests in #[cfg(test)] modules at the bottom of source files. Use these for testing private functions and implementation details.
  • Integration tests in crates/<crate-name>/tests/. Use these for testing the crate’s public API as an external consumer would use it.

Some integration tests depend on pre-compiled Wasm test components (fixtures) located in examples/test-components/. Build these before running the full test suite:

cd examples/test-components
cargo component build --release
cd ../..

How to Write Unit Tests

Unit tests for Torvyn crates follow the Arrange-Act-Assert pattern and the naming convention described in the Coding Standards.

Testing Identity Types

Identity types in torvyn-types should be tested for: construction, equality, hashing, Copy semantics, display formatting, and debug formatting.

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_flow_id_equality() {
        let a = FlowId::new(42);
        let b = FlowId::new(42);
        let c = FlowId::new(43);
        assert_eq!(a, b);
        assert_ne!(a, c);
    }

    #[test]
    fn test_flow_id_display() {
        let id = FlowId::new(7);
        assert_eq!(format!("{}", id), "flow-7");
    }
}
}

Testing State Machines

The FlowState and ResourceState machines must be tested for every valid transition and every invalid transition. The existing tests in torvyn-types/tests/state_machine_tests.rs demonstrate the pattern:

#![allow(unused)]
fn main() {
#[test]
fn test_flow_state_full_happy_path() {
    let state = FlowState::Created;
    let state = state.transition_to(FlowState::Validated).unwrap();
    let state = state.transition_to(FlowState::Instantiated).unwrap();
    let state = state.transition_to(FlowState::Running).unwrap();
    let state = state.transition_to(FlowState::Draining).unwrap();
    let state = state.transition_to(FlowState::Completed).unwrap();
    assert!(state.is_terminal());
}

#[test]
fn test_flow_state_invalid_transition_returns_error() {
    let state = FlowState::Created;
    let result = state.transition_to(FlowState::Running);
    assert!(result.is_err());
}
}

Testing Error Types

Every error variant should have a test verifying: construction, display output, and conversion to TorvynError.

#![allow(unused)]
fn main() {
#[test]
fn test_process_error_converts_to_torvyn_error() {
    let process_err = ProcessError::Fatal("disk full".into());
    let torvyn_err: TorvynError = process_err.into();
    let display = format!("{torvyn_err}");
    assert!(display.contains("FATAL"));
    assert!(display.contains("disk full"));
}
}

How to Write Integration Tests

Integration tests verify cross-module behavior by using the crate’s public API as an external consumer would. Place these in crates/<crate-name>/tests/.

For crates that depend on the Wasm engine (e.g., torvyn-engine, torvyn-host), integration tests may need compiled Wasm test components. These test fixtures live in examples/test-components/ and are compiled as part of the test setup.

#![allow(unused)]
fn main() {
// crates/torvyn-engine/tests/compilation_test.rs
use torvyn_engine::{WasmtimeEngine, WasmtimeEngineConfig, WasmEngine};

#[test]
fn test_compile_valid_component_succeeds() {
    let engine = WasmtimeEngine::new(WasmtimeEngineConfig::default()).unwrap();
    let wasm_bytes = std::fs::read("../../examples/test-components/target/wasm32-wasip2/release/passthrough.wasm").unwrap();
    let compiled = engine.compile(&wasm_bytes);
    assert!(compiled.is_ok());
}
}

How to Write Benchmark Tests

Torvyn uses Criterion for benchmark tests. Benchmarks live in benches/ directories within each crate.

#![allow(unused)]
fn main() {
// crates/torvyn-resources/benches/pool_benchmark.rs
use criterion::{criterion_group, criterion_main, Criterion};
use torvyn_resources::{BufferPoolSet, TierConfig};

fn bench_allocate_and_release(c: &mut Criterion) {
    let pool = BufferPoolSet::new(TierConfig::default());
    c.bench_function("allocate_small_buffer", |b| {
        b.iter(|| {
            let handle = pool.allocate(256).unwrap();
            pool.release(handle);
        })
    });
}

criterion_group!(benches, bench_allocate_and_release);
criterion_main!(benches);
}

Run benchmarks:

cargo bench -p torvyn-resources

When submitting a PR that modifies hot-path code, include benchmark results showing the before and after. Use cargo bench -- --save-baseline before and cargo bench -- --baseline before to generate comparison reports.

How to Use Test Components

The examples/test-components/ directory contains minimal Wasm components designed for testing. These implement Torvyn’s WIT interfaces with simple, deterministic behavior:

  • passthrough — A processor that copies input to output without modification. Useful for measuring baseline overhead.
  • counter-source — A source that produces elements containing a monotonically increasing counter. Useful for testing flow lifecycle and ordering.
  • slow-sink — A sink that introduces configurable artificial delay. Useful for testing backpressure behavior.
  • failing-processor — A processor that returns ProcessError::Fatal after a configurable number of elements. Useful for testing error handling.

Build all test components:

cd examples/test-components
cargo component build --release

Running Specific Test Subsets

# All tests in a specific crate
cargo test -p torvyn-reactor

# Tests matching a name pattern
cargo test -p torvyn-types flow_state

# Only integration tests (not unit tests)
cargo test -p torvyn-engine --test '*'

# Only unit tests (not integration tests)
cargo test -p torvyn-types --lib

# Only doc tests
cargo test -p torvyn-types --doc

Measuring Code Coverage

Use cargo-llvm-cov for coverage measurement:

cargo install cargo-llvm-cov

# Generate coverage for the workspace
cargo llvm-cov --workspace --html

# Open the report
open target/llvm-cov/html/index.html

# Generate coverage for a single crate
cargo llvm-cov -p torvyn-types --html

There is no hard coverage target. Focus coverage on: all public API functions, all state machine transitions, all error paths, and all hot-path code.

Property Testing with proptest

Torvyn uses proptest for property-based testing, particularly for identity types, state machines, and serialization round-trips.

#![allow(unused)]
fn main() {
use proptest::prelude::*;

proptest! {
    #[test]
    fn test_resource_id_roundtrip(index in 0u32..u32::MAX, gen in 0u32..u32::MAX) {
        let id = ResourceId::new(index, gen);
        assert_eq!(id.index(), index);
        assert_eq!(id.generation(), gen);
    }

    #[test]
    fn test_flow_state_terminal_states_are_idempotent(
        terminal in prop_oneof![
            Just(FlowState::Completed),
            Just(FlowState::Failed),
        ]
    ) {
        // No transition from a terminal state should succeed
        for target in FlowState::all_variants() {
            if target != terminal {
                assert!(terminal.transition_to(target).is_err());
            }
        }
    }
}
}

Add proptest as a dev dependency in the crate’s Cargo.toml:

[dev-dependencies]
proptest = "1"

Fuzz Testing Strategy

Fuzz testing is planned for security-sensitive input parsing boundaries: WIT file parsing (torvyn-contracts), TOML configuration parsing (torvyn-config), and OCI artifact deserialization (torvyn-packaging).

Fuzz targets use cargo-fuzz with libFuzzer:

cargo install cargo-fuzz

# List available fuzz targets
cargo fuzz list

# Run a specific fuzz target for 60 seconds
cargo fuzz run wit_parser_fuzz -- -max_total_time=60

Fuzz targets live in fuzz/ directories within the relevant crates. When writing a new fuzz target, focus on functions that accept arbitrary byte slices or string input from untrusted sources.