forked from mikael-lovqvist/fa2json
Compare commits
1 Commits
add/makefi
...
850d2ceb75
| Author | SHA1 | Date | |
|---|---|---|---|
| 850d2ceb75 |
56
Makefile
56
Makefile
@@ -1,56 +0,0 @@
|
|||||||
SHELL := /bin/bash
|
|
||||||
CC := cc
|
|
||||||
CFLAGS := -Wall -O2
|
|
||||||
TARGET := fa2json
|
|
||||||
SRCS := fs-watcher.c json-writer.c
|
|
||||||
|
|
||||||
.PHONY: all test clean
|
|
||||||
|
|
||||||
all: $(TARGET)
|
|
||||||
|
|
||||||
$(TARGET): $(SRCS)
|
|
||||||
$(CC) $(CFLAGS) -o $@ $^
|
|
||||||
|
|
||||||
# Requires root (fanotify FID + mount needs CAP_SYS_ADMIN).
|
|
||||||
# Loop device and mount are cleaned up automatically on exit.
|
|
||||||
# If the test aborts unexpectedly, check: losetup -l and /proc/mounts
|
|
||||||
test: $(TARGET)
|
|
||||||
@set -euo pipefail; \
|
|
||||||
IMG=""; MNTDIR=""; LOOPDEV=""; FA2JSON_PID=""; \
|
|
||||||
cleanup() { \
|
|
||||||
[ -n "$$FA2JSON_PID" ] && { kill "$$FA2JSON_PID" 2>/dev/null; wait "$$FA2JSON_PID" 2>/dev/null || true; }; \
|
|
||||||
[ -n "$$MNTDIR" ] && umount "$$MNTDIR" 2>/dev/null || true; \
|
|
||||||
[ -n "$$LOOPDEV" ] && losetup -d "$$LOOPDEV" 2>/dev/null || true; \
|
|
||||||
[ -n "$$IMG" ] && rm -f "$$IMG"; \
|
|
||||||
[ -n "$$MNTDIR" ] && rmdir "$$MNTDIR" 2>/dev/null || true; \
|
|
||||||
}; \
|
|
||||||
trap cleanup EXIT; \
|
|
||||||
IMG=$$(mktemp /tmp/fa2json-test-XXXXXX.img); \
|
|
||||||
MNTDIR=$$(mktemp -d /tmp/fa2json-mnt-XXXXXX); \
|
|
||||||
echo "--- Creating 10M ext4 image ---"; \
|
|
||||||
dd if=/dev/zero of="$$IMG" bs=1M count=10 status=none; \
|
|
||||||
mkfs.ext4 -q "$$IMG"; \
|
|
||||||
LOOPDEV=$$(losetup --find --show "$$IMG"); \
|
|
||||||
mount "$$LOOPDEV" "$$MNTDIR"; \
|
|
||||||
echo "--- Starting fa2json on $$MNTDIR ---"; \
|
|
||||||
./$(TARGET) "$$MNTDIR" & FA2JSON_PID=$$!; \
|
|
||||||
sleep 0.3; \
|
|
||||||
echo "--- Filesystem operations ---"; \
|
|
||||||
mkdir "$$MNTDIR/dir_a"; \
|
|
||||||
touch "$$MNTDIR/file_a.txt"; \
|
|
||||||
echo "content" >> "$$MNTDIR/file_a.txt"; \
|
|
||||||
mkdir "$$MNTDIR/dir_b"; \
|
|
||||||
touch "$$MNTDIR/dir_b/nested.txt"; \
|
|
||||||
mv "$$MNTDIR/file_a.txt" "$$MNTDIR/file_b.txt"; \
|
|
||||||
mv "$$MNTDIR/dir_b" "$$MNTDIR/dir_a/dir_b_moved"; \
|
|
||||||
chmod 600 "$$MNTDIR/file_b.txt"; \
|
|
||||||
touch -m "$$MNTDIR/file_b.txt"; \
|
|
||||||
chmod 755 "$$MNTDIR/dir_a"; \
|
|
||||||
rm "$$MNTDIR/file_b.txt"; \
|
|
||||||
rm "$$MNTDIR/dir_a/dir_b_moved/nested.txt"; \
|
|
||||||
rm -rf "$$MNTDIR/dir_a"; \
|
|
||||||
sleep 0.3; \
|
|
||||||
echo "--- Done ---"
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -f $(TARGET)
|
|
||||||
119
test/PLAN.md
Normal file
119
test/PLAN.md
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
1. Create a temporary image file (`mktemp`)
|
||||||
|
2. `dd` 10M of zeros into it
|
||||||
|
3. `mkfs.ext4` the image
|
||||||
|
4. `losetup --find --show` to attach it as a loop device
|
||||||
|
5. `mount` the loop device to a temporary directory (`mktemp -d`)
|
||||||
|
6. Spawn `fa2json <mountpoint>` as a child process
|
||||||
|
7. Attach a `readline` interface to its stdout; parse each line as JSON and
|
||||||
|
push into an event buffer
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Teardown
|
||||||
|
|
||||||
|
Runs unconditionally in a `finally` block:
|
||||||
|
|
||||||
|
1. Kill the `fa2json` child process
|
||||||
|
2. `umount <mountpoint>`
|
||||||
|
3. `losetup -d <loopdev>`
|
||||||
|
4. `rm` the image file
|
||||||
|
5. `rmdir` the 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:
|
||||||
|
|
||||||
|
1. Perform a filesystem operation
|
||||||
|
2. Immediately `touch <mountpoint>/.marker_N` (where N is a counter)
|
||||||
|
3. Wait until the event stream contains a CREATE event for `.marker_N`
|
||||||
|
4. Collect all events since the previous marker — this batch belongs to the
|
||||||
|
current operation
|
||||||
|
5. 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)`
|
||||||
Reference in New Issue
Block a user