chore: initial release v0.1.0
Runtime-agnostic oneshot broadcast channel for Rust.
This commit is contained in:
64 files changed
+23966
No files matched your search
+396
@@ -0,0 +1,396 @@
|
||||
# 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).
|
||||
Reference in new issue
Block a user