Testing a backend
Test the trait without a kernel, then add live-mount and concurrency coverage.
Most backend bugs are visible without mounting anything. Call the trait methods directly, use a small directory sink, and keep a few live tests for the behaviour only a kernel can produce.
Test the trait directly
A trait method is an ordinary function. Import the trait and call it on the concrete type:
This covers lookup, metadata, error mapping and directory enumeration. A
VecSink with a small capacity also tests resumption:
Assert the exact bytes of names. Comparing name.to_vec() catches accidental lossy UTF-8
conversion, which is easy to introduce and hard to see in a mount.
What needs a live mount
Some behaviour belongs to the kernel or the platform extension:
- authorization status and the setup flow;
- mount and unmount lifecycle, including lease release on drop;
- permission enforcement when
kernel_permissionsis on; - path traversal from a real process,
dfoutput, rename and unlink races; - ProjFS virtualized roots, hydration, notifications and eviction;
- FSKit local-file opens from the sandboxed extension.
Keep those in a small, clearly marked set. Everything else should run in the normal test suite.
How the crate tests itself
Unit tests live inside fsk/src/*.rs and cover pure conversions: error mapping, statfs unit
conversion, rename-flag rejection, handle allocation, raw-mask composition, socket and resource
URL encoding, path normalization, WTF-8 round-tripping, hydration across short reads, and the
Swift IPC address encoder.
On macOS, RustBackendTests.swift exercises resource validation, packed directory names,
Unix-socket layout, local-file leases, duplicate opens sharing a lease, unreadable local files
falling back, and empty directory pages. It needs no signed extension and no mount.
That script runs cargo fmt --check, Clippy with -D warnings, the workspace tests,
xcodegen generate, and xcodebuild test with CODE_SIGNING_ALLOWED=NO.
CI runs that macOS job plus cargo test --workspace --all-targets and Clippy on Linux and
Windows. A separate live-mount workflow runs weekly and on changes under fsk/ or the network
examples. It installs fuse3, loads the FUSE module, asserts /dev/fuse exists, and mounts both
network examples through FUSE.
Live mount tests
Live tests are ignored by default because they need a real mount and credentials:
Both names contain live_mount, so the filter picks them up. They require FUSE on Linux. The
GitHub test mounts a repository and reads it through the mounted path; the Drive test reads a
fsk-ci/hello.txt fixture, overridable with FSK_DRIVE_TEST_PATH.
Use the same pattern for your own backend: mount, read through the mount path, assert, drop the
session. Do not put mount tests in the default suite. A CI image without /dev/fuse should fail
an ignored test only when someone explicitly asks for it.
Invariants worth asserting
- Cookies resume. Enumerate with a small sink, pass
next_cookieback, and check that no entry is skipped or duplicated. Every page before the last must advance the cookie. - Verifiers invalidate. Enumerate, mutate the directory, call again with the old verifier and
assert
Error::STALE, not a silently restarted page. - Namespace mutations agree. After
create,removeandrename, checklookupfrom the parent, the parent'smetadata().modified_ns, and the directory verifier. - Sparse I/O. Write past the end, read the hole, and assert zeroes plus the expected
size/allocated_sizerelationship. - Capacity is atomic. A failed growth must not partially consume the budget.
- Errors are deliberate. Assert the exact
Errorfor missing names, non-empty directories, read-only files and unsupported mutations.
The verifier test is short and catches a surprising number of bugs:
If the backend returns 0 for the verifier, this test fails. That is intended: Linux ignores the
value, but macOS and Windows use it to restart safely.
Concurrency
Adapters call methods concurrently, so a stress test that hammers several methods is worth keeping. Use a barrier to make the threads collide rather than run in sequence:
Run it under cargo test --release too. Debug builds hide some races, and a lock-ordering bug may
need more iterations to appear.
Share a fake backend
Put shared fixtures in tests/support/mod.rs and declare mod support; from each integration
test. Keep the fixture on the trait surface: a builder that creates a known tree, a VecSink
with configurable capacity, and helpers that assert a directory's exact bytes. That keeps tests
fast and makes failures point at a method instead of a kernel message.
The GitHub and Google Drive examples also run end-to-end tests against fake HTTP servers, so pagination and caching can be tested without credentials.
Next
- Directories and lookup for cookies and verifiers in detail.
- Examples for complete backends and their test layouts.
- Troubleshooting when a live mount disagrees with a unit test.
