Files
swaits e8e55b7676 chore: initial release v0.1.0
Runtime-agnostic oneshot broadcast channel for Rust.
2025-10-13 19:36:37 -06:00

397 lines
11 KiB
Markdown

# Test Documentation Standard
This document defines the standard format for documenting tests in the `announcement` crate. Every test in this codebase MUST have a properly formatted header following these conventions.
## Table of Contents
1. [Header Format](#header-format)
2. [Category Definitions](#category-definitions)
3. [Writing Guidelines](#writing-guidelines)
4. [Examples](#examples)
5. [Maintenance](#maintenance)
## Header Format
### Standard Test Header
```rust
/// Test: [One-line summary of what is tested]
///
/// Verifies: [What behavior/invariant is verified]
/// Category: [Type: unit/integration/property/stress/timing/etc]
/// Coverage: [What code paths/scenarios are exercised]
#[test]
fn test_name() {
// test body
}
```
### Property Test Header
```rust
/// Property: [Invariant being tested]
///
/// Verifies: [What this property guarantees]
/// Strategy: [What input generation strategy is used]
proptest! {
#[test]
fn prop_name(inputs...) {
// property test body
}
}
```
### Async Test Header
```rust
/// Test: [One-line summary]
///
/// Verifies: [Behavior being verified]
/// Runtime: tokio/smol
/// Category: [Type]
#[tokio::test]
async fn test_name() {
// async test body
}
```
### Test Header with Notes
For tests with special conditions or important context:
```rust
/// Test: [Summary]
///
/// Verifies: [Behavior]
/// Category: [Type]
/// Note: [Any important context - e.g., "Runs 1000 iterations to catch race conditions"]
#[test]
fn test_name() {
// test body
}
```
**Historical Note**: Previously, timing-sensitive tests used `#[cfg(not(tarpaulin))]` to exclude them from coverage due to `cargo-tarpaulin`'s high instrumentation overhead. With the migration to `llvm-cov`, all tests now run during coverage analysis.
## Field Definitions
### Test / Property
**Required:** Yes
**Format:** One sentence, imperative mood
**Purpose:** Immediately tells the reader what this test does
**Examples:**
- "Test: Basic async listen operation"
- "Test: Multiple listeners receive same value"
- "Property: All listeners receive announced value"
### Verifies
**Required:** Yes
**Format:** One sentence describing the guarantee or behavior
**Purpose:** Explains what invariant or behavior this test validates
**Examples:**
- "Verifies: Listeners can wait asynchronously and receive values"
- "Verifies: Broadcast semantics work correctly"
- "Verifies: All N listeners receive the same announced value regardless of N"
### Category
**Required:** Yes
**Format:** One of the defined categories (see below)
**Purpose:** Classifies the test for filtering and organization
**Examples:**
- "Category: integration"
- "Category: property"
- "Category: stress"
### Coverage / Strategy
**Optional:** Use "Coverage" for regular tests, "Strategy" for property tests
**Format:** Brief description of scope or approach
**Purpose:** Explains what code paths or input strategies are used
**Examples:**
- "Coverage: Async listen path, event notification, value cloning"
- "Strategy: Generates 1-100 listeners with random values"
### Runtime
**Optional:** For async tests only
**Format:** tokio/smol/both
**Purpose:** Specifies which async runtime(s) are tested
**Example:** "Runtime: tokio"
### Excluded
**Optional:** Only for tests excluded from coverage or CI
**Format:** "Reason: explanation"
**Purpose:** Explains why test is excluded
**Example:** "Excluded: Coverage: timing-sensitive"
### Note
**Optional:** For additional context
**Format:** Any relevant information
**Purpose:** Provides context that doesn't fit other fields
**Example:** "Note: Runs 1000 iterations to catch race conditions"
## Category Definitions
### unit
Tests of individual functions or methods in isolation. Should be fast and deterministic.
### integration
End-to-end tests combining multiple components. Tests public API behavior.
### property
Property-based tests using `proptest` or `quickcheck`. Validates invariants across many generated inputs.
### stress
High-volume tests with many iterations or large inputs. May take longer to run.
### timing
Tests dependent on specific timing or delays. Often excluded from coverage due to flakiness.
### race
Tests for race conditions, TOCTOU bugs, or concurrency edge cases.
### concurrency
Multi-threaded tests verifying thread safety and concurrent access patterns.
### panic
Tests verifying correct behavior when panics occur. Tests panic safety and resource cleanup.
### edge
Tests of boundary conditions, extreme values, and unusual inputs.
### type
Tests verifying correct behavior with different Rust types.
### memory
Tests verifying memory safety, leak prevention, or Arc refcounting.
### mutation
Tests designed to detect semantic bugs through mutation testing.
## Writing Guidelines
### Be Concise
Test headers should be scannable. Aim for clarity over completeness.
**Good:**
```rust
/// Test: Listener receives value after announce
///
/// Verifies: Basic async listen operation works correctly
/// Category: integration
```
**Bad:**
```rust
/// Test: This test creates an announcement channel with an announcer and an announcement,
/// then creates a listener from the announcement, spawns an async task that announces a value,
/// and verifies that the listener can successfully receive that value through the async
/// listen() method...
///
/// [excessive verbosity]
```
### Be Specific
Say exactly what's being tested, not vague generalities.
**Good:**
```rust
/// Property: Arc refcount equals 1 + N listeners after announce
///
/// Verifies: No reference leaks in Arc-wrapped values
/// Strategy: Tests with 0-50 listeners
```
**Bad:**
```rust
/// Property: Arc stuff works
///
/// Verifies: Reference counting
/// Strategy: Random inputs
```
### Use Imperative Mood
Start test summaries with verbs.
**Good:**
- "Test: Multiple listeners receive same value"
- "Test: Panic in Drop doesn't leak memory"
**Bad:**
- "Test: When multiple listeners are created..."
- "Test: The case where Drop panics..."
### Keep Categories Consistent
Use defined categories. Don't invent new ones without updating this document.
### Document the Why
The "Verifies" field should explain WHY the test exists, not just WHAT it does.
**Good:**
```rust
/// Verifies: Channel cleanup happens correctly when announcer drops without announcing
```
**Bad:**
```rust
/// Verifies: Test passes
```
### Note Special Conditions
If a test has unusual requirements, exclusions, or setup, note it.
```rust
/// Test: Timeout behavior under load
///
/// Verifies: Timeouts work correctly even with many concurrent operations
/// Category: stress
/// Excluded: Coverage: takes too long (5+ seconds)
/// Note: Runs 10,000 iterations to ensure timing is reliable
```
## Examples
### Example 1: Simple Unit Test
```rust
/// Test: is_announced returns false before announce, true after
///
/// Verifies: State transition from unannounced to announced
/// Category: unit
#[test]
fn test_is_announced() {
let (announcer, announcement) = Announcement::<i32>::new();
assert!(!announcement.is_announced());
announcer.announce(42).unwrap();
assert!(announcement.is_announced());
}
```
### Example 2: Integration Test
```rust
/// Test: Multiple async listeners receive broadcast value
///
/// Verifies: Broadcast semantics work across multiple async tasks
/// Runtime: tokio
/// Category: integration
/// Coverage: Async listen, concurrent task spawning, value cloning
#[tokio::test]
async fn test_multiple_async_listeners() {
let (announcer, announcement) = Announcement::new();
// test body...
}
```
### Example 3: Property Test
```rust
/// Property: All N listeners receive the same announced value
///
/// Verifies: Broadcast semantics hold for arbitrary listener counts
/// Strategy: Generates 1-100 listeners with random i32 values
proptest! {
#[test]
fn prop_all_listeners_receive_value(value: i32, listener_count in 1usize..100) {
// property test body...
}
}
```
### Example 4: Stress Test
```rust
/// Test: TOCTOU protection under extreme contention
///
/// Verifies: Double-check pattern prevents races even with many threads
/// Category: race
/// Note: Runs 1000 iterations with tight timing windows to catch race conditions
#[test]
fn test_toctou_extreme_contention() {
// stress test body...
}
```
### Example 5: Panic Safety Test
```rust
/// Test: Arc refcount remains correct after panic
///
/// Verifies: Reference counting is panic-safe
/// Category: panic
/// Coverage: Arc refcount tracking, panic recovery, Drop behavior
#[test]
fn test_arc_refcount_with_panic() {
// panic safety test body...
}
```
## Maintenance
### Adding New Tests
When adding a new test:
1. **Choose the right category** - Review [Category Definitions](#category-definitions)
2. **Write the header first** - Document what you're testing before writing the test
3. **Keep it concise** - One-line summaries, focused "Verifies" statements
4. **Be consistent** - Follow existing patterns in the same test file
### Updating Existing Tests
When modifying a test:
1. **Update the header** - If the test behavior changes, update the documentation
2. **Maintain format** - Keep the same structure as other tests
3. **Check categories** - Ensure the category is still accurate
### Reviewing Test Documentation
When reviewing PRs:
1. **Check for headers** - Every test must have a header
2. **Verify clarity** - Test purpose should be immediately obvious
3. **Validate categories** - Categories should match the definitions
4. **Ensure consistency** - Format should match examples in this document
### Cross-Reference with TEST_DOCUMENTATION.md
This document defines the **in-code** header format. The file `TEST_DOCUMENTATION.md` provides a **high-level overview** of the entire test suite. When adding tests:
1. Update in-code headers (this document)
2. Update TEST_DOCUMENTATION.md if adding new test categories or files
3. Keep both documents in sync
## Rationale
### Why Use Headers?
1. **Immediate Clarity** - Know what a test does without reading the entire body
2. **Easy Scanning** - Quickly find tests by category or purpose
3. **Better Maintenance** - Clear documentation helps future developers
4. **Professional Polish** - Shows attention to detail and code quality
5. **Onboarding** - New contributors understand tests faster
### Why This Format?
1. **Rust Conventions** - Uses standard `///` doc comments
2. **Concise** - Respects the codebase's preference for simplicity
3. **Structured** - Fields provide consistent organization
4. **Scannable** - Visual consistency makes scanning fast
5. **Flexible** - Optional fields allow customization without clutter
### Why Mandatory?
Test documentation is not optional in this codebase. Every test MUST have a header because:
1. **Code Quality** - Documentation is part of code quality
2. **Maintainability** - Undocumented tests are technical debt
3. **Consistency** - Partial documentation is worse than none
4. **Professional Standards** - We hold ourselves to high standards
---
**Last Updated**: 2025-10-13
**Test Count**: ~628 tests across 24 files
**Compliance**: 100% (in progress)
For high-level test suite overview, see [TEST_DOCUMENTATION.md](../TEST_DOCUMENTATION.md).