# fs-windows-test-harness.toml — fs-windows-test-harness consumer config # for ext4-win-driver. # # The harness scripts + Rust runner live at ../fs-windows-test-harness/, # a sibling checkout of # https://github.com/antimatter-studios/fs-windows-test-harness at the # HARNESS_REF pinned in chores.yml. Run `chore siblings` after cloning. [runner] # WinFsp mounts are serialised: the WinFsp host object is a per-process # singleton and cannot be shared across concurrent mount operations. # max_parallel = 1 enforces sequential execution (replaces the old # serialize_mounts = true). max_parallel = 1 [groups] # smoke — fast host-only scenarios for pre-flight wiring checks. # Covers: Alpine VM build, 9p share, host-side ls/cat/stat/tree/parts. # Run with: bash scripts/run-matrix.sh smoke smoke = [ "basic-ro-cat", "basic-ro-list", "basic-stat-test-txt", "basic-tree-hash", "basic-info-volume", ] [project] name = "ext4-win-driver" binary = "target/release/ext4.exe" matrix_path = "test-matrix.json" [vm] # Machine-local values (VM address, SSH key, workdir, image dir) are not # committed here — set them in the gitignored `.test-env` file instead: # # VM_HOST = user@host # SSH_KEY = /path/to/private/key # VM_WORKDIR = C:/path/on/windows/vm # HOST_IMAGE_DIR = /path/for/disk/images (default: diskimages/) # # The runner expands `${VAR}` and `${VAR:-default}` references directly # from `.test-env`. Run `bash ../fs-windows-test-harness/scripts/setup-local.sh` # to generate `.test-env` interactively on first setup. host = "${VM_HOST}" ssh_key = "${SSH_KEY}" workdir = "${VM_WORKDIR}" image_dir = "${HOST_IMAGE_DIR:-diskimages}" rust_toolchain = "stable-aarch64-pc-windows-gnullvm" # winfsp + LLVM-MinGW + LLVM (libclang) are prerequisites; see README's # "WinFsp build prerequisites" section. setup-windows-vm.ps1 installs # everything below via winget. WinFsp's default install is runtime-only # — bindgen against winfsp.h needs ADDLOCAL=F.Core,F.Developer for # headers + .lib, passed through via winget --override (harness v3.5.0+). packages = [ "MartinStorsjo.LLVM-MinGW.UCRT", "LLVM.LLVM", { id = "WinFsp.WinFsp", custom_args = "ADDLOCAL=F.Core,F.Developer" }, ] # bindgen needs libclang to parse winfsp.h. env_prefix = "$env:LIBCLANG_PATH = 'C:\\Program Files\\LLVM\\bin';" [tools] # Optional. fsck.ext4 isn't on the Windows VM today; post-verify hooks # referencing {tools.fsck} are stubbed out via [post_verify] until we # wire a Mac-side post-pull fsck into run-tests.sh. # fsck = "fsck.ext4 -fn" [run] # Host-side image dir for v2 op-defs. The canonical test images are # build artefacts produced by `build-test-disks.sh` in the rust-fs-ext4 # sibling project — they're not tracked in git, so a fresh sibling # checkout's ../rust-fs-ext4/test-disks/ has none. Set HOST_IMAGE_DIR in # .test-env to point at your local copy; defaults to diskimages/. image_dir = "${HOST_IMAGE_DIR:-diskimages}" # Build the Windows binary on the VM (cross-compilation is not set up # on the Mac side). The harness ships the source tree first, then runs # this command from [vm].workdir via SSH. vm_build_command = "cargo build --release --features mount" [ops] # v1 ops (bare command string -> implicit `host = "vm"`). Consumed by # the legacy `run-scenario.ps1` driver. Kept until every scenario has # migrated to v2 recipes; then we drop these and `run-scenario.ps1`. # # {extra} carries per-op CLI flags (e.g. `--part 1` for whole-disk # images). Empty by default; set per scenario via the op's `extra` # field. ls = "{binary} ls {image} {path} {extra}" cat = "{binary} cat {image} {path} {extra}" stat = "{binary} stat {image} {path} {extra}" tree = "{binary} tree {image} {extra}" parts = "{binary} parts {image}" info = "{binary} info {image}" # v2 ops (structured tables — host/command/expect_exit). Each is # dispatched per-recipe-step; `host = "host"` runs on the orchestrator # (Mac), where pure-CLI ops belong (image lives on host, no SSH needed). # # Verifiers are HARNESS-SHIPPED (v3.3.0+): ../fs-windows-test-harness/ # scripts/host/verify-*.sh. Each is filesystem-agnostic, parameterised # via `--binary `. The {binary} flat token resolves to # `target/release/ext4` on host (cross-platform fallback strips the # `.exe` suffix declared in [project].binary for Windows builds). # ── Image lifecycle ───────────────────────────────────────────────────────── # # Mirrors the ntfs pattern: # ntfs: init-image (dd blank) → mac-format (rust-ntfs binary on Mac) # ext4: build-ext4-image (Alpine VM via SSH — mkfs.ext4 needs Linux) # # The Alpine VM is started by scripts/run-matrix.sh before the harness # runs and stopped on exit. ${EXT4_BUILDER_PORT} and ${EXT4_BUILDER_KEY} # are written to .vm-cache/server.env and exported into the environment # by run-matrix.sh so the runner expands them from .test-env. [ops.build-ext4-image] # SSH into the Alpine builder VM and invoke _vm-builder.sh for this # scenario's image type. The image is written to /host/{run_id}/ inside # the VM, which maps to {image_dir}/{run_id}/ on the host via 9p share. # Uses scripts/builder-ssh.sh (not raw ssh) so it never goes through the # Windows VM SSH mux. host = "host" command = "bash scripts/builder-ssh.sh \"mkdir -p /host/{run_id} && sh /host/_vm-builder.sh /host/{run_id} {scenario.image_type}\"" expect_exit = 0 # ── Host-side verifiers ────────────────────────────────────────────────────── # Scripts are harness-shipped (../fs-windows-test-harness/scripts/host/). # All ops reference {image_dir}/{run_id}/{scenario.image} — the image # is created fresh per-run by build-ext4-image (first recipe step). [ops.verify-ls] host = "host" command = "bash ../fs-windows-test-harness/scripts/host/verify-ls.sh --binary {binary} {image_dir}/{run_id}/{scenario.image} {step.path} {step.expect_args}" expect_exit = 0 [ops.verify-cat] host = "host" command = "bash ../fs-windows-test-harness/scripts/host/verify-cat.sh --binary {binary} {image_dir}/{run_id}/{scenario.image} {step.path} {step.expect_args}" expect_exit = 0 [ops.verify-stat] host = "host" command = "bash ../fs-windows-test-harness/scripts/host/verify-stat.sh --binary {binary} {image_dir}/{run_id}/{scenario.image} {step.path} {step.expect_args}" expect_exit = 0 [ops.verify-tree] host = "host" command = "bash ../fs-windows-test-harness/scripts/host/verify-tree.sh --binary {binary} {image_dir}/{run_id}/{scenario.image} {step.expect_args}" expect_exit = 0 [ops.verify-parts] host = "host" command = "bash ../fs-windows-test-harness/scripts/host/verify-parts.sh --binary {binary} {image_dir}/{run_id}/{scenario.image} {step.expect_args}" expect_exit = 0 # Negative-path variant for images with no partition table. [ops.verify-parts-fails] host = "host" command = "bash ../fs-windows-test-harness/scripts/host/verify-parts.sh --binary {binary} {image_dir}/{run_id}/{scenario.image} {step.expect_args}" expect_exit = 1 [ops.verify-info] host = "host" command = "bash ../fs-windows-test-harness/scripts/host/verify-info.sh --binary {binary} {image_dir}/{run_id}/{scenario.image} {step.expect_args}" expect_exit = 0 [ops.verify-getxattr] host = "host" command = "bash ../fs-windows-test-harness/scripts/host/verify-getxattr.sh --binary {binary} {image_dir}/{run_id}/{scenario.image} {step.path} {step.name} {step.expect_args}" expect_exit = 0 [ops.verify-readlink] host = "host" command = "bash ../fs-windows-test-harness/scripts/host/verify-readlink.sh --binary {binary} {image_dir}/{run_id}/{scenario.image} {step.path} {step.expect_args}" expect_exit = 0 # ============================================================ # Win-side (vm) ops — Phase B # ============================================================ # # Each vm-side op script is SELF-CONTAINED: it mounts via -BinaryCmd, # does the op, unmounts. This is necessary because the v2 dispatcher # uses one SSH session per step and user-mode mount processes (WinFsp # + an FS driver binary) die when the spawning SSH session ends — # cross-step persistent mounts don't survive without serious detach # plumbing. Self-contained ops trade per-step mount overhead for # reliability + zero cross-step state. # # Common args every op takes: # -BinaryCmd " mount --drive : []" # -ReadyLine regex; "ext4 mounted at" for ext4-win-driver # -Drive drive letter (single char, no colon) # # Per-op args (Path, Content, From/To, ExpectContent/Size/Sha256, etc.) # are scenario-specific. # # Each consumer customises just the BinaryCmd template + ReadyLine in # its op-defs; the scripts themselves live in fs-windows-test-harness/scripts/vm/ # and are filesystem-agnostic. # Shared mount template — every vm-side op-def reuses this BinaryCmd # shape. ext4-specific bits (binary path, ready-line) are baked in. # Recipe steps fill {vm.workdir}/{scenario.image}, {step.drive}, {step.rw_flag?}. [ops.win-write] host = "vm" command = "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File '{vm.harness_root}/scripts/vm/win-write.ps1' -BinaryCmd '{vm.workdir}/target/release/ext4.exe mount {vm.workdir}/{scenario.image} --drive {step.drive}: {step.rw_flag?} {step.extra?}' -ReadyLine 'ext4 mounted at' -Drive '{step.drive}' -Path '{step.path}' -Content '{step.content?}'" expect_exit = 0 [ops.win-mkdir] host = "vm" command = "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File '{vm.harness_root}/scripts/vm/win-mkdir.ps1' -BinaryCmd '{vm.workdir}/target/release/ext4.exe mount {vm.workdir}/{scenario.image} --drive {step.drive}: {step.rw_flag?} {step.extra?}' -ReadyLine 'ext4 mounted at' -Drive '{step.drive}' -Path '{step.path}'" expect_exit = 0 [ops.win-cat-via-mount] host = "vm" command = "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File '{vm.harness_root}/scripts/vm/win-cat-via-mount.ps1' -BinaryCmd '{vm.workdir}/target/release/ext4.exe mount {vm.workdir}/{scenario.image} --drive {step.drive}: {step.rw_flag?} {step.extra?}' -ReadyLine 'ext4 mounted at' -Drive '{step.drive}' -Path '{step.path}' -ExpectContent '{step.expect_content?}' -ExpectSize '{step.expect_size?}' -ExpectSha256 '{step.expect_sha256?}'" expect_exit = 0 [ops.win-ls-via-mount] host = "vm" command = "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File '{vm.harness_root}/scripts/vm/win-ls-via-mount.ps1' -BinaryCmd '{vm.workdir}/target/release/ext4.exe mount {vm.workdir}/{scenario.image} --drive {step.drive}: {step.rw_flag?} {step.extra?}' -ReadyLine 'ext4 mounted at' -Drive '{step.drive}' -Path '{step.path}' -ExpectNames '{step.expect_names_csv?}' -ExpectCount '{step.expect_count?}'" expect_exit = 0 [ops.win-rename] host = "vm" command = "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File '{vm.harness_root}/scripts/vm/win-rename.ps1' -BinaryCmd '{vm.workdir}/target/release/ext4.exe mount {vm.workdir}/{scenario.image} --drive {step.drive}: {step.rw_flag?} {step.extra?}' -ReadyLine 'ext4 mounted at' -Drive '{step.drive}' -From '{step.from}' -To '{step.to}'" expect_exit = 0 [ops.win-unlink] host = "vm" command = "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File '{vm.harness_root}/scripts/vm/win-unlink.ps1' -BinaryCmd '{vm.workdir}/target/release/ext4.exe mount {vm.workdir}/{scenario.image} --drive {step.drive}: {step.rw_flag?} {step.extra?}' -ReadyLine 'ext4 mounted at' -Drive '{step.drive}' -Path '{step.path}'" expect_exit = 0 [ops.win-rmdir] host = "vm" command = "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File '{vm.harness_root}/scripts/vm/win-rmdir.ps1' -BinaryCmd '{vm.workdir}/target/release/ext4.exe mount {vm.workdir}/{scenario.image} --drive {step.drive}: {step.rw_flag?} {step.extra?}' -ReadyLine 'ext4 mounted at' -Drive '{step.drive}' -Path '{step.path}'" expect_exit = 0 [mount] # WinFsp mount, foreground. The runner kills the background process on # scenario teardown; ready_line tells the harness when to start running # scenario ops against the live mount. command = "{binary} mount {image} --drive {drive} {extra}" ready_line = "ext4 mounted at" rw_extra = "--rw" [post_verify] # Default post-verify: read-only fsck-style audit after any passing # scenario. RW scenarios in particular benefit — confirms the writes # didn't break link counts, leave dangling entries, etc. Scenarios can # override or set `post_verify = null` if they intentionally produce # anomalies. See docs/post-verify-hook.md. command = "{binary} audit {image}" expect_exit = 0