Files
spacedrive/docs/core/testing.mdx
T
Jamie Pineandcapy-ai-staging[bot] f69c1fee89 Add to Library core: defaults and overrides, store placement, volume registration, portable identity, removal keeps the catalog (#3121)
* Add to Library defaults: library settings with per-add overrides

Every entry point used to carry its own notion of what a source starts as:
volumes.track defaulted external drives to unfiltered, folder tracking to
filtered, and the Explorer sent unfiltered: false by hand. The library now
holds the defaults under settings.adding (store placement in library, an
offline copy kept, filtered capture, content identified), config.library.get
reports them and config.library.update changes them for later adds only.

sources.track and volumes.track take the same AddOverrides on top; an absent
field is the default and an override never writes back. The effective
settings are the source's SourceConfig, persisted with the registration and
returned in the output together with the saved name and the store path.
A name, settings or volume row that fails to persist now fails the add
instead of reporting a tracked source. A source added without content
identification gets its walk and nothing after. volumes.track keeps the
archival behavior by passing an explicit unfiltered override for an external
drive unless the caller set one.

The CLI gains --on-source, --in-library, --keep-offline-copy and
--no-identify on sd source track. Generated TypeScript types regenerated.

Co-authored-by: capy-ai-staging[bot] <305915985+capy-ai-staging[bot]@users.noreply.github.com>

* Resolve store placement per library in the source layer

Placement is intent recorded in each source's config column; the serving
device resolves it. VolumeIndex::store_dir is the one resolver: in library
under <data>/sources/<id>, on source under <root>/.spacedrive/sources/<id>,
following the source's current root so an on-source store tracks its volume
across remounts and resolves to nothing while the drive is away. Writer
opens, read-only opens, freezes, the status surface and sources.list all go
through it, and SourceStore::open takes the resolved stores directory rather
than the data-directory layout. On source is refused for network and cloud
volumes, whose WAL database cannot live on the share.

The volume index also gains register_source_with, which registers under an
identity a store on disk already carries, and forget_source, which drops a
registration and retires its writer and reader handles without touching the
directory.

Co-authored-by: capy-ai-staging[bot] <305915985+capy-ai-staging[bot]@users.noreply.github.com>

* Adding a path tracks its volume

ensure_volume_in_db wrote the row but skipped the sync publication and the
VolumeTracked event that track_volume emits, and the add logged its failure
and carried on. It now syncs and emits on insert, the add fails when the row
cannot be written, and the volume index maps the drive at the same moment so
the mount point check and the detached flag apply to the new source. A second
folder on the same drive finds the row already there.

Co-authored-by: capy-ai-staging[bot] <305915985+capy-ai-staging[bot]@users.noreply.github.com>

* Portable source identity and managed-directory exclusions

Every store now carries source.json beside data.db: a versioned descriptor
with the source id, the library that wrote it, its volume anchor and path
within the volume, and its settings. Adding a scope first looks for a store
whose descriptor binds to this library, this volume and this path, and
registers under that identity, so a removed folder re-added or a drive
plugged in with its catalog on it reopens the catalog instead of minting a
fresh source over an empty one. A descriptor from another library or another
drive is ignored and a new source starts beside it.

The own-data-directory check becomes crate::config::is_managed: the data
directory under every spelling, any .spacedrive directory and what is under
it, and the volume identity marker. It is a path-component check rather than
a registry lookup, so it holds for an unfiltered source whose rules are off,
for an enclosing source that was never told about the inner store, for a
store on a drive nobody here tracks, and across remounts and aliases. The
walk, the watcher and the writer all use it.

Co-authored-by: capy-ai-staging[bot] <305915985+capy-ai-staging[bot]@users.noreply.github.com>

* Removing a source keeps its catalog unless asked to delete it

sources.delete used to delete the store directory through the adapter engine
before unregistering, which would have turned removal of an on-source store
into destruction of the portable catalog, assertions included. It now
retires the source's handles, drops the registration and leaves the store
where it is; delete_catalog is an explicit second choice. The volume row is
never touched: removing the last folder on a drive does not forget the
hardware. sd source untrack wraps the action.

Co-authored-by: capy-ai-staging[bot] <305915985+capy-ai-staging[bot]@users.noreply.github.com>

* Add to Library acceptance suite and status

One Core over temporary directories tracks a folder with defaults and one
with overrides, proves one volume row for both, finds the on-source store
excluded from its own and an enclosing source, removes a source and sees
its catalog and record identities survive the re-add, and sees deletion
start over. The suite joins the acceptance group of xtask test-core;
docs/core/acceptance/add-to-library.md maps the plan's cases to tests and
names the rows the core half cannot prove. PROJECT_STATUS and the plan's
status line record what landed and what the modal thread builds next.

Co-authored-by: capy-ai-staging[bot] <305915985+capy-ai-staging[bot]@users.noreply.github.com>

* Review fixes: placement-aware reads and backup, re-track keeps settings, adoption across placements

The library's per-source read queries (sources.list_items, list_records,
media_listing) opened the store through the engine's in-library layout, so
an on-source catalog was unreachable from the explorer; they now resolve a
registered filesystem source through the volume index and fall through to
the engine only for adapter sources. libraries.backup reads each store from
where placement put it. A re-track carries the previous value of every
setting the add did not name instead of the library's current defaults,
and never changes placement. portable_identity searches both placements so
a drive arriving with its catalog is adopted whatever placement the add
asked for, and the adopted catalog keeps the placement it was found under.
SourceConfig::default agrees with its serde defaults.

Co-authored-by: capy-ai-staging[bot] <305915985+capy-ai-staging[bot]@users.noreply.github.com>

* Restore names an on-source catalog it cannot put back

The archive now holds a store backed up from its drive, but a restore lays
every store in the in-library layout, where an on-source registration does
not read. The manifest records each source's placement; a restore leaves an
on-source catalog where it landed, keeps it out of the restored count, and
names it in on_source_catalogs with its path, so the caller knows what was
not put back. Moving it onto the drive is relocation.

Co-authored-by: capy-ai-staging[bot] <305915985+capy-ai-staging[bot]@users.noreply.github.com>

* catalog_reused means an identity was kept, not that a file exists

The acceptance run on CI saw a fresh add report its catalog reused: the
content identity job of an enclosing source opened the new source's store
between registration and the file probe. The flag now answers from the
registration (it existed, or a descriptor-bound catalog was adopted), which
is what it claims.

Co-authored-by: capy-ai-staging[bot] <305915985+capy-ai-staging[bot]@users.noreply.github.com>

* Regenerate TypeScript types for the catalog_reused doc

Co-authored-by: capy-ai-staging[bot] <305915985+capy-ai-staging[bot]@users.noreply.github.com>

* Volume tracking test follows the one-library-per-path rule

volumes.track now surfaces a source setup failure instead of logging it,
so tracking a volume another open library holds is refused. The
multi-library test asserts the refusal, then the handover once the first
library untracks the drive and removes its source.

Co-authored-by: capy-ai-staging[bot] <305915985+capy-ai-staging[bot]@users.noreply.github.com>

---------

Co-authored-by: capy-ai-staging[bot] <305915985+capy-ai-staging[bot]@users.noreply.github.com>
2026-10-07 20:25:10 +00:00

210 lines
6.6 KiB
Plaintext

---
title: Testing
description: Prove filesystem safety across restarts, platforms, and real data
sidebarTitle: Testing
---
Spacedrive tests behavior at four scales: pure logic, one daemon, several
processes, and real filesystems. A test suite that never touches real storage
cannot support the release claim.
## Commands
~~~bash
cargo test
cargo test <TEST_NAME>
cargo test --lib
cargo test -- --nocapture
cargo clippy
cargo fmt --check
~~~
Frontend changes also run the relevant package type checks and production
build. Public Rust type changes regenerate TypeScript before those checks.
## Test layers
### Unit tests
Keep pure rules beside their implementation:
- identity evidence ordering;
- capture-policy matching;
- content evidence promotion;
- sync conflict rules;
- path and URI parsing;
- permission decisions.
Unit tests should be deterministic and avoid global daemon state.
### Single-daemon integration tests
Use an isolated temporary data directory and real SQLite stores. Cover complete
operation paths, transaction behavior, jobs, events, and restart reconciliation.
Tests inspect public operations and durable state. They do not rely only on log
messages.
### Multi-process tests
Use independent daemon processes for pairing, peer transport, remote operations,
relay behavior, disconnects, and protocol compatibility. Separate processes
catch assumptions hidden by an in-memory harness.
Use a deterministic transport harness for merge algorithms and precise packet
ordering, then keep at least one real-network test for the protocol.
### Filesystem matrix
Run destructive and metadata-sensitive tests on every claimed platform and
filesystem. Include:
- macOS APFS;
- Windows NTFS;
- Linux ext4 and supported network filesystems;
- removable drives;
- case-sensitive and case-insensitive names;
- symlinks and permission denial;
- names that are not valid UTF-8;
- sparse, large, and changing files.
An emulated filesystem is useful for injection but cannot replace native
metadata verification.
## Source ingest regression suite
Every source ingest must prove:
- the daemon data directory is excluded;
- a failed batch salvages valid records and identifies the bad record;
- capture policy matches between the initial walk and watcher;
- final writes commit before job completion;
- interrupted walks restart from zero;
- incomplete evidence cannot sweep unseen records;
- a surprising mass sweep trips the safety fuse;
- volume-relative roots survive remount;
- unreadable content remains represented with a reason;
- reindex preserves assertions.
Compare final record counts with an independent filesystem walk. Sample stored
metadata against platform APIs.
## Content verification suite
Test:
- deterministic candidate identity;
- candidate collisions;
- full-byte confirmation;
- files changing during a read;
- final batch flush;
- permission and I/O failures;
- duplicate grouping across records;
- failure-domain counts across physical storage.
No test may treat a sampled candidate as authorization to delete bytes.
## Job recovery suite
Interrupt each job kind:
- before its first checkpoint;
- during an external write;
- after data commit but before progress update;
- during final output;
- during cancellation.
Restart the daemon and assert that every queued or running row becomes resumed,
failed with a reason, cancelled, or completed. No zombie state is acceptable.
## File operation suite
Copy, move, sync, archive, and delete tests inject:
- destination collision;
- source change after preview;
- short read and short write;
- disk full;
- network loss;
- provider throttling;
- process exit;
- checksum mismatch;
- permission revocation;
- cross-device rename fallback.
Assert both bytes and durable operation evidence. For destructive work, assert
that failed verification leaves the source copy intact.
## Sync and networking suite
Test local and relayed pairing, device revocation, scoped source grants, remote
query completeness, range reads, reconnect watermarks, source authority, and
multi-writer assertions.
Library sync, source sync, and file sync have separate assertions. A passing
metadata sync test does not prove that bytes were copied.
## Extension suite
Run a real WASM extension that:
- requests and is denied a capability;
- registers a file kind and preview;
- checkpoints a job;
- writes each supported model scope;
- migrates and rolls back;
- survives reindex;
- degrades safely after uninstall.
The host must reject database, filesystem, and network access outside the grant.
## Real-data qualification
Release candidates run against datasets large enough to expose queue races,
filesystem churn, and query-plan failures. Keep the origins backed up and
perform independent acceptance checks.
Record:
- source and independent counts;
- pending content work;
- unreadable objects and reasons;
- verification results;
- frozen store checks;
- restart and recovery observations.
Fixtures prove known cases. Real data discovers cases the suite does not yet
know to model.
## Test hygiene
- Use unique temporary directories.
- Never point destructive tests at a home directory or workspace root.
- Keep timeouts bounded.
- Assert structured output before logs.
- Preserve failed test artifacts when they contain useful evidence.
- Avoid sleeps when an event or state query can prove readiness.
- Add every production data-loss bug as a regression test.
## Acceptance matrices
A plan that ends in an acceptance table gets a matrix file under
`docs/core/acceptance/`, one row per scenario, each naming the test that
proves it and its status on the CI runner. A row no Linux test can prove says
why. A row whose test fails at the current commit keeps the test, marked
`#[ignore = "R8: <row> fails: <reason>"]`, and the matrix records the observed
behavior so the fix has a target.
- [Source runtime](https://github.com/spacedriveapp/spacedrive/blob/main/docs/core/acceptance/source-runtime.md):
the R8 table of the source runtime reliability plan.
- [Entries drop and file operations](https://github.com/spacedriveapp/spacedrive/blob/main/docs/core/acceptance/entries-drop-and-file-operations.md):
the FDA acceptance section of the entries final drop plan and the
acceptance cases of the action previews and file operations plans.
- [Volumes](https://github.com/spacedriveapp/spacedrive/blob/main/docs/core/acceptance/volumes.md):
the Linux rows of the locked volumes plan.
- [Add to Library](https://github.com/spacedriveapp/spacedrive/blob/main/docs/core/acceptance/add-to-library.md):
the acceptance cases of the Add to Library plan that the core half proves.
See [Jobs](/core/jobs), [Indexing](/core/indexing), and [Releases](/core/releases)
for the contracts these tests enforce.