3.7 KiB
fa2json Test Plan
Overview
A Node.js test runner (test/test.mjs) that exercises fa2json against a
temporary ext4 filesystem on a loop device. The test produces a single
pass/fail result and cleans up after itself unconditionally.
Requires root (fanotify FID reporting and mount both need CAP_SYS_ADMIN).
Files
| File | Purpose |
|---|---|
test/test.mjs |
Test runner |
Makefile |
make test target calls sudo node test/test.mjs |
Setup
- Create a temporary image file (
mktemp) dd10M of zeros into itmkfs.ext4the imagelosetup --find --showto attach it as a loop devicemountthe loop device to a temporary directory (mktemp -d)- Spawn
fa2json <mountpoint>as a child process - Attach a
readlineinterface to its stdout; parse each line as JSON and push into an event buffer
Teardown
Runs unconditionally in a finally block:
- Kill the
fa2jsonchild process umount <mountpoint>losetup -d <loopdev>rmthe image filermdirthe mount directory
Event Collection and the Marker Pattern
fa2json runs continuously for the entire test. To associate events with
specific operations, a marker file is used as a synchronisation barrier:
- Perform a filesystem operation
- Immediately
touch <mountpoint>/.marker_N(where N is a counter) - Wait until the event stream contains a CREATE event for
.marker_N - Collect all events since the previous marker — this batch belongs to the current operation
- Assert on the batch, then advance the counter
If a marker event never arrives the test hangs, which indicates a failure at the fa2json level itself.
Path Handling
fa2json emits full paths including the mount prefix
(e.g. /tmp/fa2json-mnt-XXXXX/dir_a/file.txt). The runner strips this prefix
so assertions work against a virtual root:
/tmp/fa2json-mnt-XXXXX/dir_a/file.txt → /dir_a/file.txt
Fanotify Mask Constants
Relevant flags (bitwise, check with mask & FLAG):
| Constant | Value | Meaning |
|---|---|---|
FAN_ATTRIB |
0x4 |
Metadata/attribute change |
FAN_CLOSE_WRITE |
0x8 |
File closed after writing |
FAN_CREATE |
0x100 |
File or directory created |
FAN_DELETE |
0x200 |
File or directory deleted |
FAN_RENAME |
0x10000000 |
Rename (has old and new fields) |
FAN_ONDIR |
0x40000000 |
Event subject is a directory |
Operations and Expected Events
Each row is one doOp() call. Events are matched by presence (not exact list)
— extra events from ext4 internals are ignored.
| Operation | Expected event(s) |
|---|---|
mkdir /dir_a |
CREATE | ONDIR, name /dir_a |
touch /file_a.txt |
CREATE, name /file_a.txt |
echo "content" >> /file_a.txt |
CLOSE_WRITE, name /file_a.txt |
mkdir /dir_b |
CREATE | ONDIR, name /dir_b |
touch /dir_b/nested.txt |
CREATE, name /dir_b/nested.txt |
mv /file_a.txt /file_b.txt |
RENAME, old /file_a.txt, new /file_b.txt |
mv /dir_b /dir_a/dir_b_moved |
RENAME | ONDIR, old /dir_b, new /dir_a/dir_b_moved |
chmod 600 /file_b.txt |
ATTRIB, name /file_b.txt |
touch -m /file_b.txt |
ATTRIB, name /file_b.txt |
chmod 755 /dir_a |
ATTRIB | ONDIR, name /dir_a |
rm /file_b.txt |
DELETE, name /file_b.txt |
rm /dir_a/dir_b_moved/nested.txt |
DELETE, name /dir_a/dir_b_moved/nested.txt |
rmdir /dir_a/dir_b_moved |
DELETE | ONDIR, name /dir_a/dir_b_moved |
rmdir /dir_a |
DELETE | ONDIR, name /dir_a |
Pass / Fail
- All assertions pass → print summary,
process.exit(0) - Any assertion throws → print the failing operation, the expected event, and
the actual batch received, then
process.exit(1)