Testing Infrastructure
Spacedrive Core provides two primary testing approaches:- Standard Tests - For unit and single-core integration testing
- Subprocess Framework - For multi-device networking and distributed scenarios
Test Organization
Tests live in two locations:core/tests/- Integration tests that verify complete workflowscore/src/testing/- Test framework utilities and helpers
Standard Testing
For single-device tests, use Tokio’s async test framework:Integration Test Setup
TheIntegrationTestSetup utility provides isolated test environments:
- Isolated temporary directories per test
- Structured logging to
test_data/{test_name}/library/logs/ - Automatic cleanup on drop
- Configurable app settings
Multi-Device Testing
Spacedrive provides two approaches for testing multi-device scenarios:When to Use Subprocess Framework
UseCargoTestRunner subprocess framework when:
- Testing real networking with actual network discovery, NAT traversal, and connections
- Testing device pairing workflows that require independent network stacks
- Scenarios need true process isolation (separate memory spaces, different ports)
- You want to test network reconnection, timeout, and failure handling
- Testing cross-platform network behavior
When to Use Custom Transport/Harness
Use custom harness with mock transport when:- Testing sync logic without network overhead
- Fast iteration on data synchronization algorithms
- Testing deterministic scenarios without network timing issues
- Verifying database state and conflict resolution
- Need precise control over sync event ordering
Comparison
Subprocess Testing Framework
The subprocess framework spawns separatecargo test processes for each device role:
Writing Multi-Device Tests
Create separate test functions for each device role:Device scenario functions must be marked with
#[ignore] to prevent direct
execution. They only run when called by the subprocess framework.Process Coordination
Processes coordinate through:- Environment variables:
TEST_ROLEandTEST_DATA_DIR - Temporary files: Share data like pairing codes
- Output patterns: Success markers for the runner to detect
Common Test Patterns
Filesystem Watcher Testing
When testing filesystem watcher functionality, several critical setup steps are required:Enable Watcher in Test Config
The defaultTestConfigBuilder disables the filesystem watcher (for performance in sync tests). Tests that verify watcher events must explicitly enable it:
Use Home Directory Paths on macOS
macOS temp directories (/var/folders/...) don’t reliably deliver filesystem events. Use home directory paths instead:
Ephemeral Watching Requirements
Ephemeral paths must be indexed before watching:The
IndexerJob automatically calls watch_ephemeral() after successful
indexing, so manual registration is only needed when bypassing the indexer.Persistent Location Watching
For persistent locations, the watcher auto-loads locations at startup. New locations created during tests must be manually registered:IndexingHarness handles this automatically.
Event Collection Best Practices
Start collecting events after initialization to avoid library statistics noise:EventCollector automatically filters out:
- Library statistics updates (
LibraryStatisticsUpdated) - Library resource events (non-file/entry events)
Expected Event Types
Different handlers emit different event types:- Ephemeral handler: Individual
ResourceChangedevents per file (CREATE + MODIFY) - Persistent handler: Batched
ResourceChangedBatchevents
Event Monitoring
Waiting for Specific Events
Wait for specific Core events with timeouts:Collecting All Events for Analysis
For tests that need to verify event emission patterns (e.g., ResourceChanged events during operations), use the sharedEventCollector helper:
EventCollector tracks:
- ResourceChanged/ResourceChangedBatch events by resource type
- Indexing start/completion events
- Job lifecycle events (started/completed)
- Entry events (created/modified/deleted/moved)
with_capture()):
- Verifying watcher events during file operations
- Testing normalized cache updates
- Debugging event emission patterns
- Creating test fixtures with real event data
- Inspecting actual resource payloads in events
Database Verification
Query the database directly to verify state:Job Testing
Test job execution and resumption:Mock Transport for Sync Testing
Test synchronization without real networking:Test Data & Snapshot Conventions
Data Directory Requirements
All test data MUST be created in the system temp directory. Never persist data outside temp unless using the snapshot flag. Naming convention:spacedrive-test-{test_name}
Snapshot System
Snapshots preserve test state for post-mortem debugging. They are optional and controlled by an environment variable. Enable snapshots:- Debugging sync tests (database state, event logs)
- Complex indexing scenarios (closure table analysis)
- Multi-phase operations (capture state at each phase)
- Investigating flaky tests
- Simple unit tests
- Tests with assertion-only validation
- Tests where console output is sufficient
Fixture Generation
Some tests generate fixtures used by other test suites (e.g., TypeScript tests consuming Rust-generated event data). These fixtures follow the same conventions as snapshots: always write to temp, only copy to source when explicitly requested. Generate fixtures:- Fixtures written to temp directory
- Test validates generation works
- No modification of source tree
SD_REGENERATE_FIXTURES=1 is set:
- Fixtures generated in temp first (validation)
- Copied to source tree for commit
- Used by TypeScript tests
- Backend event format changes
- TypeScript types updated
- New query responses added
- Resource change events modified
Helper Abstractions
TestDataDir - Manages test data directories with automatic cleanup and snapshot support:TestDataDir):
Test Helpers
Common Utilities
The framework provides comprehensive test helpers incore/tests/helpers/:
Event Collection:
EventCollector- Collect and analyze all events from the event busEventStats- Statistics about collected events with formatted output
IndexingHarnessBuilder- Create isolated test environments with indexing supportTestLocation- Builder for test locations with filesLocationHandle- Handle to indexed locations with verification methods
TwoDeviceHarnessBuilder- Pre-configured two-device sync test environmentsMockTransport- Mock network transport for deterministic sync testingwait_for_sync()- Sophisticated sync completion detectionTestConfigBuilder- Custom test configurations
wait_for_event()- Wait for specific events with timeoutwait_for_indexing()- Wait for indexing job completionregister_device()- Register a device in a library
Test Volumes
For volume-related tests, use the test volume utilities:Core Integration Test Suite
Spacedrive maintains a curated suite of core integration tests that run in CI and during local development. These tests are defined in a single source of truth using thextask pattern.
Running the Core Test Suite
Thecargo xtask test-core command runs all core integration tests with progress tracking:
Single Source of Truth
All core integration tests are defined inxtask/src/test_core.rs in the CORE_TESTS constant:
- CI and local development use identical test definitions
- Add or remove tests in one place
- Automatic progress tracking and result summary
- Continues running even if some tests fail
CI Integration
The GitHub Actions workflow runs the core test suite on all platforms:- macOS (ARM64 self-hosted)
- Linux (Ubuntu 22.04)
- Windows (latest)
fail-fast: false, all platforms complete even if one fails.
Deterministic Test Data
Core integration tests use the Spacedrive source code itself as test data instead of user directories. This ensures:- Consistent results across all machines and CI
- No user data access required
- Cross-platform compatibility without setup
- Predictable file structure for test assertions
Adding Tests to the Suite
To add a new test to the core suite:- Create your test in
core/tests/your_test.rs - Add it to
CORE_TESTSinxtask/src/test_core.rs:
- Run in CI on all platforms
- Appear in
cargo xtask test-coreoutput - Show in progress tracking and summary
Core integration tests use
--test-threads=1 to avoid conflicts when
accessing the same locations or performing filesystem operations.Running Tests
All Tests
Core Integration Tests
Specific Test
Debug Subprocess Tests
With Logging
Best Practices
Test Structure
- Use descriptive names:
test_cross_device_file_transferovertest_transfer - One concern per test: Focus on a single feature or workflow
- Clean up resources: Use RAII patterns or explicit cleanup
- Use deterministic test data: Index Spacedrive source code instead of user directories
Test Data
- All test data in temp directory: Use
TestDataDirorTempDir(see Test Data & Snapshot Conventions) - Prefer project source code: Use
env!("CARGO_MANIFEST_DIR")to locate the Spacedrive repo for test indexing - Avoid user directories: Don’t hardcode paths like
$HOME/Desktopor$HOME/Downloads - Use subdirectories for multiple locations:
core/,apps/, etc. when testing multi-location scenarios - Cross-platform paths: Ensure test paths work on Linux, macOS, and Windows
Subprocess Tests
- Always use
#[ignore]on scenario functions - Check TEST_ROLE early: Return immediately if role doesn’t match
- Use clear success patterns: Print distinct markers for the runner
- Set appropriate timeouts: Balance between test speed and reliability
Debugging
Common debugging approaches:- Run with
--nocaptureto see all output - Check job logs in
test_data/{test_name}/library/job_logs/ - Run scenarios individually with manual environment variables
- Use
RUST_LOG=tracefor maximum verbosity
Performance
- Run tests in parallel: Use
cargo testdefault parallelism - Minimize sleeps: Use event waiting instead of fixed delays
- Share setup code: Extract common initialization into helpers
Writing New Tests
Single-Device Test Checklist
- Create test with
#[tokio::test] - Use
TestDataDiror harness for test data (never hardcode paths outside temp) - Use deterministic test data for indexing (project source code, not user directories)
- Wait for events instead of sleeping
- Verify both positive and negative cases
- Automatic cleanup via RAII pattern (no manual cleanup needed with helpers)
Multi-Device Test Checklist
- Create orchestrator function with
CargoTestRunner - Create scenario functions with
#[ignore] - Add TEST_ROLE guards to scenarios
- Define clear success patterns
- Handle process coordination properly
- Set reasonable timeouts
- Use deterministic test data for cross-platform compatibility
Core Integration Test Checklist
When adding a test to the core suite (cargo xtask test-core):
- Test uses deterministic data (Spacedrive source code)
- Test runs reliably on Linux, macOS, and Windows
- Test includes
--test-threads=1if accessing shared resources - Add test definition to
xtask/src/test_core.rs - Verify test runs successfully in CI workflow
TypeScript Integration Testing
Spacedrive provides a bridge infrastructure for running TypeScript tests against a real Rust daemon. This enables true end-to-end testing across the Rust backend and TypeScript frontend, verifying that cache updates, WebSocket events, and React hooks work correctly with real data.Architecture
The TypeScript bridge test pattern works as follows:- Rust test creates a daemon with indexed locations using
IndexingHarnessBuilder - Connection info (TCP socket address, library ID, paths) written to JSON config file
- Rust spawns
bun testwith specific TypeScript test file - TypeScript test reads config, connects to daemon via
SpacedriveClient.fromTcpSocket() - TypeScript test performs file operations and validates cache updates via React hooks
- Rust validates test exit code and cleans up
Writing Bridge Tests
Rust Side
Create a test incore/tests/ that spawns the daemon and TypeScript test:
Use
.enable_daemon() on IndexingHarnessBuilder to start the RPC server.
The daemon listens on a random TCP port returned by .daemon_socket_addr().TypeScript Side
Create a test inpackages/ts-client/tests/integration/:
TCP Transport
TypeScript tests connect to the daemon via TCP socket usingTcpSocketTransport. This transport is designed for Bun/Node.js environments and enables testing outside the browser.
- Uses JSON-RPC 2.0 over TCP
- Supports WebSocket-style subscriptions for events
- Automatically reconnects on connection loss
- Works in both Bun and Node.js runtimes
Testing Cache Updates
The primary use case for bridge tests is verifying thatuseNormalizedQuery cache updates work correctly when the daemon emits ResourceChanged or ResourceChangedBatch events.
Key patterns:
- Enable debug logging with
debug: trueinuseNormalizedQueryoptions - Wait for watcher delays (500ms buffer + processing time, typically 2-8 seconds)
- Collect events by wrapping the subscription manager to log all received events
- Verify cache state using React Testing Library’s
waitForand assertions
Running Bridge Tests
Common Scenarios
File moves between folders:- Tests that files removed from one directory appear in another
- Verifies UUID preservation (move detection vs delete+create)
- Tests that nested files update their paths correctly
- Verifies parent path updates propagate to descendants
- Tests 20+ file moves with mixed Physical/Content paths
- Verifies cache updates don’t miss files during batched events
- Uses
IndexMode::Contentto enable content identification - Tests that files with
alternate_pathsupdate correctly - Verifies metadata-only updates don’t add duplicate cache entries
Debugging Bridge Tests
Check Rust logs:[TS]prefixed log messages- Event payloads with
🔔emoji - Final event summary at test end
- TypeScript test times out: Increase watcher wait time (filesystem events can be slow)
- Cache not updating: Enable
debug: trueto see if events are received - Connection refused: Verify daemon started with
.enable_daemon() - Wrong library: Check that
client.setCurrentLibrary()uses correct ID from config
Examples
For complete examples, refer to: Core Test Infrastructure:xtask/src/test_core.rs- Single source of truth for all core integration tests.github/workflows/core_tests.yml- CI workflow using xtask test runner
tests/copy_action_test.rs- Event collection during file operations (persistent + ephemeral)tests/job_resumption_integration_test.rs- Job interruption handling
tests/device_pairing_test.rs- Device pairing with real network discovery
tests/sync_realtime_test.rs- Real-time sync testing with deterministic transport using Spacedrive source codetests/sync_backfill_test.rs- Backfill sync with deterministic test datatests/sync_backfill_race_test.rs- Race condition testing with concurrent operationstests/file_transfer_test.rs- Cross-device file operations
tests/typescript_bridge_test.rs- Rust harness that spawns TypeScript testspackages/ts-client/tests/integration/useNormalizedQuery.test.ts- File move cache updatespackages/ts-client/tests/integration/useNormalizedQuery.folder-rename.test.ts- Folder rename propagationpackages/ts-client/tests/integration/useNormalizedQuery.bulk-moves.test.ts- Bulk operations with content-addressed files
