Resolved conflict in core/src/infra/sync/hlc.rs by adopting the main branch's improved test implementation that properly uses FakeTimeSource for deterministic time control in tests.
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!