A read-only synthetic filesystem for macOS, built on FSKit. Mount a JSON tree as a filesystem whose contents are made up on demand — either a fill character or deterministic pseudo-random bytes. Useful for testing software against arbitrarily large directory structures without spending the disk space.
Native Swift, no kernel extension, no fuse-t shim. macOS 15.4 or later
(FSKit V1 is the floor).
This is the macOS-native port of
sohonetlabs/test_json_fs
(Python + FUSE).
- Grab the latest
TestFS-X.Y.Z.dmgfrom Releases. - Open the DMG and drag
TestFS.appto theApplicationsshortcut. (The DMG also contains anExamples/folder of sample JSON trees — drag that to~/Documents/if you want them handy.) - Launch
/Applications/TestFS.app. The first time, a banner asks you to enable the FSKit extension — click Open System Settings…, then toggle TestFS on under General → Login Items & Extensions → File System Extensions. - Pick a JSON tree (the Examples ▾ button next to Choose…
opens the bundled set; File ▸ Try an example… does the same
thing through a file dialog), pick an empty directory as the
mountpoint, click Mount. Avoid Desktop / Documents /
Downloads / iCloud Drive / Pictures / Movies / Music — macOS
won't let
fskitdwrite to those, and the mount will fail with Operation not permitted. A subdir under your home folder root or/tmpworks.
The DMG is signed with a Developer ID Application certificate and notarized by Apple, so Gatekeeper accepts it without warnings.
Subsequent releases install themselves: TestFS embeds
Sparkle, checks
https://raw.githubusercontent.com/sohonetlabs/testfs/main/appcast.xml
in the background, and prompts when a new version is available. You
can also force a check from App ▸ Check for Updates….
git clone --recurse-submodules git@github.com:sohonetlabs/testfs.git
cd testfs
xcodebuild -project TestFS.xcodeproj -scheme TestFS \
-configuration Debug -destination 'platform=macOS' \
-allowProvisioningUpdates buildThe build phase rsyncs research/test_json_fs/example/ into
TestFS.app/Contents/Resources/Examples/, so the in-app picker works
straight from a Debug build.
If you don't have a Sohonet team signing identity, change
DEVELOPMENT_TEAM and the bundle IDs across both targets to your own.
Prerequisites: TestFS.app is installed (so the FSKit extension is
registered) and toggled on under General → Login Items & Extensions
→ File System Extensions. scripts/smoke.sh verifies both before
you bother trying to mount. Sample JSON trees live at
research/test_json_fs/example/ — clone with --recurse-submodules.
scripts/smoke.sh # check the install + extension registration
scripts/mount.sh # mounts test.json at /tmp/testfs
scripts/mount.sh path/to/tree.json # custom tree, default /tmp/testfs
scripts/mount.sh path/to/tree.json /mountpoint # custom tree + mountpoint
scripts/unmount.sh # unmount + detach the dummy diskRun as your normal user — not sudo. The mount(8) -F path that
FSKit V1 uses fails under sudo: fskitd checks the caller's audit
token uid against the dev node's owner, and the user-owned dev node
that hdiutil attached can't be opened by uid 0.
Mountpoint must be outside macOS's privacy-protected directories
(Desktop, Documents, Downloads, iCloud Drive, Pictures, Movies,
Music) — fskitd is denied access there and the mount fails with
Operation not permitted. The default /tmp/testfs and any fresh
subdir under your home folder root are fine.
TestFS.app
├── TestFS (SwiftUI host, unsandboxed*)
└── TestFSExtension.appex (FSKit V1 block-resource extension,
sandboxed, reads its sidecar JSON
from its own container)
The mounted volume is case-sensitive (matches the Python jsonfs.py
upstream). Foo.txt and foo.txt are independent entries; lookup is
byte-exact after unicode_normalization is applied (default NFD).
* The host is intentionally unsandboxed because mounting in-process
needs hdiutil access to attach a dummy raw disk image — a path that
DiskArbitration + the IOKit user client refuse from a sandboxed
process. The FSKit extension itself is sandboxed.
Sample JSON trees live in
research/test_json_fs/example/ (a
git submodule pointing at sohonetlabs/test_json_fs). The
release-built DMG includes a copy of the same set under
Examples/. Highlights:
| File | What it demonstrates |
|---|---|
test.json |
10-file basic demo |
bad_windows.json / bad_s3.json |
Names that break on those platforms |
big_list_of_naughty_strings_fs.json |
Unicode/edge-case fuzzing |
archive_torture_*.json |
Pathological inputs for archive tools |
imdbfslayout.json.zip |
460k-file IMDB-shaped layout (unzip first) |
The release pipeline produces a notarized, stapled DMG containing
TestFS.app, an Applications drop-link, and the Examples/ folder.
One-time setup on each release machine:
brew install create-dmg swiftlint- Developer ID Application certificate for team
H6XW263G62in the login keychain. - Notarization keychain profile:
xcrun notarytool store-credentials testFS \ --apple-id <your-apple-id> --team-id H6XW263G62 \ --password <app-specific-password> - Sparkle Ed25519 signing key. On the first machine, generate it once:
Back up the private key (e.g. via 1Password) — losing it means new installs would have to ship under a different bundle ID. To restore on a second machine,
$(ls -td ~/Library/Developer/Xcode/DerivedData/TestFS-*/SourcePackages/artifacts/sparkle/Sparkle/bin | head -1)/generate_keys --account testfsgenerate_keys --account testfs -f <key-file>.
Per release:
# Bump version
echo 0.1.4 > VERSION
git commit -am "Bump version to $(cat VERSION)"
# Build, lint-gate, sign, notarize, staple, sparkle-sign, update appcast
scripts/release.sh
# ⇒ build/TestFS-0.1.4.dmg + appcast.xml updated locally
# Tag, push code, push the new appcast, upload the DMG to a Release
git tag v$(cat VERSION)
git push --follow-tags
gh release create v$(cat VERSION) build/TestFS-$(cat VERSION).dmg \
--notes "Release notes here"
git add appcast.xml && git commit -m "Appcast: $(cat VERSION)" && git pushscripts/release.sh runs swiftlint --strict first (cheap fail-fast
gate), then archives in Release configuration, exports with
method=developer-id, builds the DMG via create-dmg, submits to
notarytool --wait, staples, mounts the DMG to assess the embedded
app via spctl, signs the DMG with Sparkle's Ed25519 key, and
prepends the new entry to appcast.xml. Wall time is 3–5 minutes
depending on Apple's notarization queue.
The Python original is vendored at research/test_json_fs/ as a git
submodule. See its README for the full feature list and CLI flags;
this Swift port aims for parity.
MIT. The port builds on Sohonet Labs' test_json_fs and
KhaosT's FSKitSample — see
LICENSE for the attribution notices.