Skip to content

Commit 21ac529

Browse files
Merge pull request #20 from jeromekelleher/fs-test-hardening
Extend fs_tests with stress jobs and a liveness runner
2 parents 7bc575b + a7a590d commit 21ac529

6 files changed

Lines changed: 460 additions & 86 deletions

File tree

fs_tests/README.md

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ every PR.
1010
|---|---|---|
1111
| POSIX syscall semantics | native Python | ~10 s |
1212
| pjdfstest curated subset | external (`pjdfstest`) | ~30–60 s |
13-
| Read-pattern stress | external (`fio`) | ~3 min |
13+
| Read-pattern stress | external (`fio`) | ~4 min |
1414
| Read cross-validation | native Python (fsx-style) | ~30 s |
1515
| Filesystem stressor | external (`stress-ng`) | ~2 min |
1616
| Mount/unmount cycling | native Python | ~2 min |
17+
| Active under stress | external (`fio`) + native probes | ~30 s |
1718
| **Total** | | **~10–15 min** |
1819

1920
Every category mounts biofuse fresh in a subprocess (matching real-user
@@ -44,10 +45,11 @@ uv sync --group fs-tests
4445
Run from the repository root:
4546

4647
```bash
47-
uv run --group fs-tests python -m fs_tests.harness # all categories
48-
uv run --group fs-tests python -m fs_tests.harness --quick # smoke run (skips pjdfstest + large fio)
49-
uv run --group fs-tests python -m fs_tests.harness posix # one category
50-
uv run --group fs-tests python -m fs_tests.harness fio --large # fio with the 500 MB fixture
48+
uv run --group fs-tests python -m fs_tests.harness # all categories
49+
uv run --group fs-tests python -m fs_tests.harness --quick # smoke run (skips pjdfstest, fio, active-under-stress)
50+
uv run --group fs-tests python -m fs_tests.harness posix # one category
51+
uv run --group fs-tests python -m fs_tests.harness fio --large # fio with the 500 MB fixture
52+
uv run --group fs-tests python -m fs_tests.harness active-under-stress # liveness probes under background fio
5153
uv run --group fs-tests python -m fs_tests.harness --results /tmp/biofuse-results all
5254
```
5355

@@ -91,6 +93,28 @@ errors" reported by fio. Throughput numbers are recorded in `report.md`
9193
for tracking but do not gate on a floor — they depend heavily on the
9294
host (page cache, CPU, fixture size).
9395

96+
**fio gating: streaming vs. static targets.** The fio runner classifies
97+
each job in `harness/fio_runner.py`:
98+
99+
- Gated streaming jobs against `.bed`: `seq-read`, `rand-read`,
100+
`mmap-read`, `parallel-seq-read` (numjobs=4 sequential). Errors here
101+
fail the runner.
102+
- Informational streaming job: `multithread` (numjobs=16, random) is
103+
expected to time out under FUSE backpressure (the kernel returns
104+
EAGAIN); its errors are recorded in the detail line but do not gate
105+
the run. The concurrency-overlap check it drives is still gated.
106+
- Gated static-file jobs: `static-stress-bim` / `static-stress-fam`
107+
hammer the in-memory cached `.bim` and `.fam` files with
108+
`numjobs=16` random reads. These must always pass.
109+
110+
**Active under stress.** A separate runner (`active-under-stress`)
111+
spawns the multithread fio job in the background against `.bed`, then
112+
loops foreground probes — `readdir` plus 4 KB reads of `.bim` and
113+
`.fam` — with a per-probe timeout (default 5 s). It exists to confirm
114+
that mount-level operations and static-file reads stay responsive even
115+
while the streaming file is saturated. The streaming file itself is
116+
*not* probed: that is the load.
117+
94118
**fsx is read-only mode only.** Apple/LTP/xfstests fsx all assume a
95119
writable filesystem (they bootstrap the in-memory model by writing to
96120
the file under test). None of them run unmodified against a read-only

fs_tests/harness/cli.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
fio_runner,
1313
fsx_runner,
1414
lifecycle,
15+
liveness,
1516
pjdfstest,
1617
posix,
1718
report,
@@ -240,6 +241,25 @@ def lifecycle_cmd(ctx: click.Context, iterations: int) -> None:
240241
sys.exit(_emit(results_dir, [result]))
241242

242243

244+
@main.command("active-under-stress")
245+
@click.option(
246+
"--duration",
247+
type=float,
248+
default=liveness.DEFAULT_DURATION_S,
249+
help="Background fio stress duration in seconds.",
250+
)
251+
@click.pass_context
252+
def active_under_stress_cmd(ctx: click.Context, duration: float) -> None:
253+
"""Run mount-liveness probes while a background fio stressor is active."""
254+
results_dir = ctx.obj["results_dir"]
255+
log_dir = _runner_log_dir(results_dir, "active-under-stress")
256+
result = _run_with_banner(
257+
"active-under-stress",
258+
lambda: liveness.run(log_dir=log_dir, duration_s=duration),
259+
)
260+
sys.exit(_emit(results_dir, [result]))
261+
262+
243263
@main.command("all")
244264
@click.pass_context
245265
def all_cmd(ctx: click.Context) -> None:
@@ -254,6 +274,7 @@ def all_cmd(ctx: click.Context) -> None:
254274
fsx_log_dir = _runner_log_dir(results_dir, "fsx")
255275
stress_log_dir = _runner_log_dir(results_dir, "stress-ng")
256276
lifecycle_log_dir = _runner_log_dir(results_dir, "lifecycle")
277+
liveness_log_dir = _runner_log_dir(results_dir, "active-under-stress")
257278

258279
results: list[tools.RunnerResult] = [
259280
_run_with_banner("posix", lambda: posix.run(log_path=posix_log))
@@ -281,5 +302,12 @@ def all_cmd(ctx: click.Context) -> None:
281302
results.append(
282303
_run_with_banner("lifecycle", lambda: lifecycle.run(log_dir=lifecycle_log_dir))
283304
)
305+
if not quick:
306+
results.append(
307+
_run_with_banner(
308+
"active-under-stress",
309+
lambda: liveness.run(log_dir=liveness_log_dir),
310+
)
311+
)
284312

285313
sys.exit(_emit(results_dir, results))

0 commit comments

Comments
 (0)