Test Helpers
Shared utilities for integration tests to reduce duplication and improve maintainability.
Modules
event_collector.rs - Event Collection Utilities
Shared event collector for capturing and analyzing events from the event bus during tests.
Key Components:
EventCollector
Collects all events emitted during test execution for analysis and verification.
Basic Usage (Statistics Only):
use helpers::EventCollector;
// Create collector for statistics
let mut collector = EventCollector::new(&harness.core.events);
// Spawn and collect
let collection_handle = tokio::spawn(async move {
collector.collect_events(Duration::from_secs(10)).await;
collector
});
// Analyze statistics
let collector = collection_handle.await.unwrap();
let stats = collector.analyze().await;
stats.print();
Advanced Usage (Full Event Capture for Debugging):
// Create collector with full event data capture
let mut collector = EventCollector::with_capture(&harness.core.events);
// ... collect events ...
let collector = collection_handle.await.unwrap();
// Print statistics
let stats = collector.analyze().await;
stats.print();
// Print full event details to stderr
collector.print_events().await;
// Write events to JSON file
collector.write_to_file(&snapshot_dir.join("events.json")).await?;
// Filter specific events
let file_events = collector.get_resource_batch_events("file").await;
let indexing_events = collector.get_events_by_type("IndexingCompleted").await;
Methods:
new()- Create collector for statistics only (lightweight)with_capture()- Create collector that captures full event datacollect_events(duration)- Collect events for specified durationanalyze()- Generate statistics from collected eventsprint_events()- Print detailed event breakdown to stderrwrite_to_file(path)- Write events to JSON fileget_events_by_type(type)- Filter events by variant nameget_resource_batch_events(resource_type)- Get ResourceChangedBatch for specific type
EventStats
Statistics about collected events with formatted output:
- ResourceChanged/ResourceChangedBatch events by resource type
- Indexing start/completion events
- Job lifecycle events (started/completed)
- Entry events (created/modified/deleted/moved)
Use Cases:
- Verifying watcher events during file operations
- Testing normalized cache updates
- Debugging event emission patterns
- Creating test fixtures with real event data
indexing_harness.rs - Indexing Test Utilities
Provides a comprehensive test harness for indexing integration tests, eliminating boilerplate and making it easy to test change detection.
Key Components:
IndexingHarnessBuilder
Builder for creating pre-configured indexing test environments.
let harness = IndexingHarnessBuilder::new("my_test")
.build()
.await?;
Automatically handles:
- Creating test directories
- Initializing tracing
- Setting up core and library
- Registering device
IndexingHarness
The test harness with convenient methods:
// Create test location
let location = harness.create_test_location("my_location").await?;
location.write_file("test.txt", "content").await?;
location.create_filtered_files().await?;
// Index the location
let handle = location.index("My Location", IndexMode::Deep).await?;
// Verify results
assert_eq!(handle.count_files().await?, 1);
handle.verify_no_filtered_entries().await?;
handle.verify_inode_tracking().await?;
// Make changes and re-index
handle.write_file("new.txt", "new").await?;
handle.modify_file("test.txt", "updated").await?;
handle.delete_file("old.txt").await?;
handle.move_file("from.txt", "to.txt").await?;
handle.reindex().await?;
Helper Classes
TestLocation - Builder for test locations:
write_file()- Create filescreate_dir()- Create directoriescreate_filtered_files()- Create files that should be filteredindex()- Index the location
LocationHandle - Handle to indexed location:
count_files(),count_directories(),count_entries()get_all_entries()- Get all indexed entriesverify_no_filtered_entries()- Assert filtering workedverify_inode_tracking()- Assert inodes are trackedwrite_file(),modify_file(),delete_file(),move_file()- Make changesreindex()- Re-index and wait for completion
sync_harness.rs - Two-Device Sync Test Utilities
Provides a comprehensive test harness for sync integration tests that eliminates ~200 lines of boilerplate per test.
Key Components:
TwoDeviceHarnessBuilder
Builder for creating pre-configured two-device test environments.
let harness = TwoDeviceHarnessBuilder::new("my_test")
.await?
.collect_events(true) // Optional: collect event logs
.collect_sync_events(true) // Optional: collect sync events
.start_in_ready_state(true) // Optional: skip backfill (default)
.build()
.await?;
Automatically handles:
- Creating test directories
- Initializing tracing to files
- Setting up cores and libraries
- Registering pre-paired devices
- Configuring mock transports
- Starting sync services
- Setting sync state
TwoDeviceHarness
The resulting test harness with convenient methods:
// Add locations
harness.add_and_index_location_alice("/path", "Name").await?;
harness.add_and_index_location_bob("/path", "Name").await?;
// Wait for sync (sophisticated algorithm)
harness.wait_for_sync(Duration::from_secs(60)).await?;
// Capture comprehensive snapshot
harness.capture_snapshot("final_state").await?;
// Access all internals
harness.library_alice;
harness.device_alice_id;
harness.transport_alice;
Helper Functions
Configuration:
TestConfigBuilder- Build test configs with custom filtersinit_test_tracing()- Standard tracing setup
Device Setup:
register_device()- Register a device in a libraryset_all_devices_synced()- Mark devices as synced (prevent auto-backfill)
Waiting:
wait_for_indexing()- Wait for indexing job completionwait_for_sync()- Sophisticated sync completion detection
Operations:
add_and_index_location()- Create and index a location
Snapshots:
create_snapshot_dir()- Create timestamped snapshot directorySnapshotCapture- Utilities for capturing databases, logs, events
sync_transport.rs - Mock Network Transport
Mock implementation of NetworkTransport for testing sync without real networking.
Key Features:
- Immediate message delivery (like production)
- Request/response handling for backfill
- Device blocking/unblocking (simulate offline)
- Message history tracking
- Queue inspection
Usage:
// Single device
let transport = MockTransport::new_single(device_id);
// Paired devices (most common)
let (transport_a, transport_b) = MockTransport::new_pair(device_a, device_b);
// Block/unblock
transport.block_device(device_id).await;
transport.unblock_device(device_id).await;
// Inspect
let queue_size = transport.queue_size(device_id).await;
let total = transport.total_message_count().await;
test_volumes.rs - Volume Test Utilities
Helper functions for creating mock volumes in tests (used by sync_backfill_test.rs).
Benefits of Using Shared Utilities
Code Reduction
- ~200 lines of boilerplate eliminated per test
- ~2887 lines saved across 6 sync tests (65% reduction)
- One source of truth for test patterns
Consistency
- Same tracing setup everywhere
- Same config creation
- Same device registration
- Same snapshot format
- Same waiting algorithms
Maintainability
- Fix bugs in one place
- Add features once, benefit everywhere
- Clear upgrade path for tests
- Easier code reviews
Reliability
- Battle-tested algorithms
- Sophisticated sync detection
- Comprehensive snapshot capture
- Proper cleanup
Migration Path
To migrate an existing sync test:
- Replace custom harness with
TwoDeviceHarnessBuilder - Remove duplicated code (config, registration, waiting)
- Use shared methods (add_and_index_location_alice/bob)
- Test thoroughly to ensure behavior unchanged
See REFACTORING_EXAMPLE.md for a detailed before/after comparison.
When NOT to Use the Shared Harness
The shared harness is optimized for two-device real-time sync tests. Consider custom setup for:
- Single-device tests (use
MockTransport::new_single()) - N-device tests (N > 2)
- Very specialized scenarios (custom transport behavior)
- Non-sync tests (obviously!)
Even in these cases, you can still use the individual helper functions.
Writing New Tests
For new two-device sync tests:
use helpers::TwoDeviceHarnessBuilder;
#[tokio::test]
async fn test_my_new_scenario() -> anyhow::Result<()> {
let harness = TwoDeviceHarnessBuilder::new("my_scenario")
.await?
.build()
.await?;
// Your test logic here
harness.capture_snapshot("final").await?;
Ok(())
}
For other test types: Use individual helper functions as needed.
Contributing
When adding new shared utilities:
- Add to
sync_harness.rsif it's sync-specific - Add to appropriate module otherwise
- Update this README with usage examples
- Update
SYNC_HARNESS_USAGE.mdif user-facing - Export from
mod.rs
Keep utilities:
- Generic - Useful for multiple tests
- Well-documented - Clear purpose and usage
- Battle-tested - Used by actual tests
- Simple - Easy to understand and maintain
Questions?
- See
SYNC_HARNESS_USAGE.mdfor detailed usage examples - See
REFACTORING_EXAMPLE.mdfor migration examples - See
SYNC_TESTS.mdfor test suite overview - Check the source code - it's well-commented!