Skip to main content
Testing in Spacedrive Core ensures reliability across single-device operations and multi-device networking scenarios. This guide covers the available frameworks, patterns, and best practices.

Testing Infrastructure

Spacedrive Core provides two primary testing approaches:
  1. Standard Tests - For unit and single-core integration testing
  2. Subprocess Framework - For multi-device networking and distributed scenarios

Test Organization

Tests live in two locations:
  • core/tests/ - Integration tests that verify complete workflows
  • core/src/testing/ - Test framework utilities and helpers

Standard Testing

For single-device tests, use Tokio’s async test framework:

Integration Test Setup

The IntegrationTestSetup utility provides isolated test environments:
Key features:
  • 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

Use CargoTestRunner 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
Examples: Device pairing, network discovery, connection management

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
Examples: Real-time sync, backfill, content identity linking, conflict resolution

Comparison

Subprocess Testing Framework

The subprocess framework spawns separate cargo 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_ROLE and TEST_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 default TestConfigBuilder 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:
The IndexingHarness handles this automatically.

Event Collection Best Practices

Start collecting events after initialization to avoid library statistics noise:
The 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 ResourceChanged events per file (CREATE + MODIFY)
  • Persistent handler: Batched ResourceChangedBatch events

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 shared EventCollector helper:
The EventCollector tracks:
  • ResourceChanged/ResourceChangedBatch events by resource type
  • Indexing start/completion events
  • Job lifecycle events (started/completed)
  • Entry events (created/modified/deleted/moved)
Statistics Output:
Detailed Event Output (with with_capture()):
Use Cases:
  • 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}
Standard structure:
Cleanup: Temp directories are automatically cleaned up after test completion using RAII pattern.

Snapshot System

Snapshots preserve test state for post-mortem debugging. They are optional and controlled by an environment variable. Enable snapshots:
Snapshot location (when enabled):
Structure:
When to use 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
Not needed for:
  • 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:
Fixture location (when enabled):
Default behavior:
  • Fixtures written to temp directory
  • Test validates generation works
  • No modification of source tree
When SD_REGENERATE_FIXTURES=1 is set:
  • Fixtures generated in temp first (validation)
  • Copied to source tree for commit
  • Used by TypeScript tests
Example fixture test:
When to regenerate fixtures:
  • 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:
SnapshotManager - Captures test snapshots (accessed via TestDataDir):
Integration with existing harnesses:

Test Helpers

Common Utilities

The framework provides comprehensive test helpers in core/tests/helpers/: Event Collection:
  • EventCollector - Collect and analyze all events from the event bus
  • EventStats - Statistics about collected events with formatted output
Indexing Tests:
  • IndexingHarnessBuilder - Create isolated test environments with indexing support
  • TestLocation - Builder for test locations with files
  • LocationHandle - Handle to indexed locations with verification methods
Sync Tests:
  • TwoDeviceHarnessBuilder - Pre-configured two-device sync test environments
  • MockTransport - Mock network transport for deterministic sync testing
  • wait_for_sync() - Sophisticated sync completion detection
  • TestConfigBuilder - Custom test configurations
Database & Jobs:
  • wait_for_event() - Wait for specific events with timeout
  • wait_for_indexing() - Wait for indexing job completion
  • register_device() - Register a device in a library
See core/tests/helpers/README.md for detailed documentation on all available helpers including usage examples and migration guides.

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 the xtask pattern.

Running the Core Test Suite

The cargo xtask test-core command runs all core integration tests with progress tracking:
Example output:

Single Source of Truth

All core integration tests are defined in xtask/src/test_core.rs in the CORE_TESTS constant:
Benefits:
  • 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:
Tests run in parallel on:
  • macOS (ARM64 self-hosted)
  • Linux (Ubuntu 22.04)
  • Windows (latest)
With 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
Tests that need multiple locations use different subdirectories:

Adding Tests to the Suite

To add a new test to the core suite:
  1. Create your test in core/tests/your_test.rs
  2. Add it to CORE_TESTS in xtask/src/test_core.rs:
The test will automatically:
  • Run in CI on all platforms
  • Appear in cargo xtask test-core output
  • 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

  1. Use descriptive names: test_cross_device_file_transfer over test_transfer
  2. One concern per test: Focus on a single feature or workflow
  3. Clean up resources: Use RAII patterns or explicit cleanup
  4. Use deterministic test data: Index Spacedrive source code instead of user directories

Test Data

  1. All test data in temp directory: Use TestDataDir or TempDir (see Test Data & Snapshot Conventions)
  2. Prefer project source code: Use env!("CARGO_MANIFEST_DIR") to locate the Spacedrive repo for test indexing
  3. Avoid user directories: Don’t hardcode paths like $HOME/Desktop or $HOME/Downloads
  4. Use subdirectories for multiple locations: core/, apps/, etc. when testing multi-location scenarios
  5. Cross-platform paths: Ensure test paths work on Linux, macOS, and Windows

Subprocess Tests

  1. Always use #[ignore] on scenario functions
  2. Check TEST_ROLE early: Return immediately if role doesn’t match
  3. Use clear success patterns: Print distinct markers for the runner
  4. Set appropriate timeouts: Balance between test speed and reliability

Debugging

When tests fail, check the logs in test_data/{test_name}/library/logs/ for detailed information about what went wrong.
Common debugging approaches:
  • Run with --nocapture to see all output
  • Check job logs in test_data/{test_name}/library/job_logs/
  • Run scenarios individually with manual environment variables
  • Use RUST_LOG=trace for maximum verbosity

Performance

  1. Run tests in parallel: Use cargo test default parallelism
  2. Minimize sleeps: Use event waiting instead of fixed delays
  3. Share setup code: Extract common initialization into helpers

Writing New Tests

Single-Device Test Checklist

  • Create test with #[tokio::test]
  • Use TestDataDir or 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=1 if 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:
  1. Rust test creates a daemon with indexed locations using IndexingHarnessBuilder
  2. Connection info (TCP socket address, library ID, paths) written to JSON config file
  3. Rust spawns bun test with specific TypeScript test file
  4. TypeScript test reads config, connects to daemon via SpacedriveClient.fromTcpSocket()
  5. TypeScript test performs file operations and validates cache updates via React hooks
  6. Rust validates test exit code and cleans up
This pattern tests the entire stack: Rust daemon → RPC transport → TypeScript client → React hooks → cache updates.

Writing Bridge Tests

Rust Side

Create a test in core/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 in packages/ts-client/tests/integration/:

TCP Transport

TypeScript tests connect to the daemon via TCP socket using TcpSocketTransport. This transport is designed for Bun/Node.js environments and enables testing outside the browser.
The TCP transport:
  • 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 that useNormalizedQuery cache updates work correctly when the daemon emits ResourceChanged or ResourceChangedBatch events. Key patterns:
  1. Enable debug logging with debug: true in useNormalizedQuery options
  2. Wait for watcher delays (500ms buffer + processing time, typically 2-8 seconds)
  3. Collect events by wrapping the subscription manager to log all received events
  4. Verify cache state using React Testing Library’s waitFor and assertions

Running Bridge Tests

Use --nocapture to see TypeScript test output. The Rust test prints all stdout/stderr from the TypeScript test process.

Common Scenarios

File moves between folders:
  • Tests that files removed from one directory appear in another
  • Verifies UUID preservation (move detection vs delete+create)
Folder renames:
  • Tests that nested files update their paths correctly
  • Verifies parent path updates propagate to descendants
Bulk operations:
  • Tests 20+ file moves with mixed Physical/Content paths
  • Verifies cache updates don’t miss files during batched events
Content-addressed files:
  • Uses IndexMode::Content to enable content identification
  • Tests that files with alternate_paths update correctly
  • Verifies metadata-only updates don’t add duplicate cache entries

Debugging Bridge Tests

Check Rust logs:
Check TypeScript output: The Rust test prints all TypeScript stdout/stderr. Look for:
  • [TS] prefixed log messages
  • Event payloads with 🔔 emoji
  • Final event summary at test end
Verify daemon is running:
Check bridge config:
Common issues:
  • TypeScript test times out: Increase watcher wait time (filesystem events can be slow)
  • Cache not updating: Enable debug: true to 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
Single Device Tests:
  • tests/copy_action_test.rs - Event collection during file operations (persistent + ephemeral)
  • tests/job_resumption_integration_test.rs - Job interruption handling
Subprocess Framework (Real Networking):
  • tests/device_pairing_test.rs - Device pairing with real network discovery
Custom Harness (Mock Transport):
  • tests/sync_realtime_test.rs - Real-time sync testing with deterministic transport using Spacedrive source code
  • tests/sync_backfill_test.rs - Backfill sync with deterministic test data
  • tests/sync_backfill_race_test.rs - Race condition testing with concurrent operations
  • tests/file_transfer_test.rs - Cross-device file operations
TypeScript Bridge Tests:
  • tests/typescript_bridge_test.rs - Rust harness that spawns TypeScript tests
  • packages/ts-client/tests/integration/useNormalizedQuery.test.ts - File move cache updates
  • packages/ts-client/tests/integration/useNormalizedQuery.folder-rename.test.ts - Folder rename propagation
  • packages/ts-client/tests/integration/useNormalizedQuery.bulk-moves.test.ts - Bulk operations with content-addressed files