Skip to content

Add mount lifecycle tests and CI mount-tests job#45

Merged
reinauer merged 5 commits into
reinauer:mainfrom
tbdye:phase7/mount-tests
May 2, 2026
Merged

Add mount lifecycle tests and CI mount-tests job#45
reinauer merged 5 commits into
reinauer:mainfrom
tbdye:phase7/mount-tests

Conversation

@tbdye

@tbdye tbdye commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Why

AmiFUSE has extensive test coverage at the unit level (pytest) and integration level (HandlerBridge tests, tools/amifuse_matrix.py), but none of these test the actual FUSE mount path. amifuse_matrix.py uses HandlerBridge directly — it bypasses FUSE entirely. There are zero tests anywhere that mount an image, browse it through the OS filesystem, and unmount it. This PR closes that gap.

The mount lifecycle is the critical path for users — if mount/unmount breaks, nothing else matters. On Windows specifically, WinFSP mounts have unique behaviors (drive-letter allocation, process-termination unmount, file handle cleanup) that need automated regression protection. With this PR, every code change touching the mount path can be validated end-to-end before merge.

How

FUSE backend strategy

Each platform needed a different approach to get FUSE working in CI:

  • macOS: macFUSE requires a kernel extension blocked by SIP on GitHub Actions runners. We use FUSE-T (a user-space implementation) instead. fusepy does not find FUSE-T's library natively (it is named libfuse-t.dylib, not libfuse.dylib), so the CI step creates a compatibility symlink and sets DYLD_LIBRARY_PATH. Validated in a throwaway spike workflow before implementation.
  • Linux: fusepy uses the fuse2 ABI (libfuse.so), not fuse3. Installing fuse3 alone is insufficient — libfuse-dev (the fuse2 compatibility package) must also be installed.
  • Windows: choco install winfsp -y works without reboot on GitHub Actions runners.

Test fixture design

The mount_image factory fixture handles the full mount lifecycle via subprocess:

  • Launches amifuse mount --interactive via subprocess.Popen (not subprocess.run — mount is long-running). --interactive prevents daemonization so PID tracking works for teardown.
  • On Windows, uses drive-letter mounts (not directory mounts) because os.path.ismount() returns False for directory-based WinFSP mounts.
  • Polls os.listdir() for mount readiness (more reliable than os.path.ismount() which can return True before the filesystem is initialized on Windows).
  • 3-stage teardown: amifuse unmountprocess.terminate()process.kill(), with pipe draining and mountpoint cleanup.
  • Post-job CI cleanup steps on all platforms as a safety net.

Marker strategy

Tests use @pytest.mark.fuse (not @pytest.mark.integration) to avoid the existing machine68k gating in pytest_collection_modifyitems. The fuse_available fixture handles its own skip logic independently.

fusermount3 fix

get_unmount_command() on Linux only checked for fusermount (fuse2). On fuse3-only systems (like Ubuntu CI runners), it fell back to umount -f which may require root. Added fusermount3 as a fallback between fusermount and umount.

Summary

  • Mount test fixtures (fuse_available, mount_image, pfs3_mount) with cross-platform support (drive-letter mounts on Windows, tmpdir on Unix, 3-stage teardown escalation)
  • 8 mount lifecycle tests covering mount/unmount cycle, status discovery, file hash verification, error paths, and BW7 regression
  • CI mount-tests job with per-platform FUSE backend install: FUSE-T (macOS), fuse3+libfuse-dev (Linux), WinFSP (Windows)
  • fusermount3 fallback in platform.py for fuse3-only Linux systems
  • Post-job cleanup steps to prevent stale mounts on CI runners

Test plan

Local validation (Windows, WinFSP)

  • 7 of 7 applicable tests pass
  • Mount creates a working drive letter mount detectable by os.path.ismount()
  • amifuse status --json discovers the mount with correct image path and PID
  • Mounted volume root is listable via os.listdir()
  • amifuse unmount cleanly terminates the mount process and releases the drive letter
  • Double unmount fails cleanly (non-zero exit, no traceback, no hang)
  • Invalid image mount fails with clean error message
  • BW7 regression verified: no stale file locks on image after unmount (Windows-specific)

CI spike validation (all 3 platforms)

A throwaway workflow tested fusepy + each FUSE backend before writing the mount tests:

  • fusepy + FUSE-T mount works on macOS (NullFS mount, ismount=True)
  • fusepy + fuse3 mount works on Linux
  • fusepy + WinFSP mount works on Windows (drive-letter mount)
  • Unit tests: 346 pass (including 2 new fusermount3 fallback tests)

CI validation (this PR)

  • Full mount lifecycle tests pass on all 3 platforms with real PFS3 images from AmiFUSE-testing
  • Post-job cleanup prevents stale mounts on runners
  • fusermount3 fallback works on Linux runners with fuse3
  • Unit tests pass (346 tests)

tbdye added 5 commits April 25, 2026 10:43
Adds fuse_available, mount_image, and pfs3_mount fixtures to
conftest.py with cross-platform support (drive-letter mounts on
Windows, tmpdir on Unix, 3-stage teardown escalation). Implements
10 tests covering mount/unmount lifecycle, status discovery, file
hash verification, error paths, and BW7 regression.
Adds mount-tests CI job with per-platform FUSE backend install
(FUSE-T on macOS, fuse3+libfuse-dev on Linux, WinFSP on Windows),
post-job cleanup steps, and fusermount3 fallback in platform.py
for Linux systems with only fuse3 installed.
Use os.listdir() as the authoritative mount readiness signal
instead of os.path.ismount(), which returns True on Windows
before the filesystem is fully initialized. Use the same
approach for unmount detection. Skip .info files and handle
PermissionError in file read tests for WinFSP read-only mounts.
Drop test_mount_missing_fuse_detected and test_winfsp_eject_behavior
which were always skipped and never ran. Remove unused Path import.
Add mount test layer (fuse marker) to pytest table and quick start.
Update CI table to show all four jobs including mount-tests, with
FUSE backend details per platform. Reflect that Windows now runs
integration and tools-smoke via machine68k-amifuse fork.
@tbdye tbdye marked this pull request as ready for review April 25, 2026 22:02
@reinauer reinauer merged commit 3be6603 into reinauer:main May 2, 2026
18 checks passed
@tbdye tbdye deleted the phase7/mount-tests branch June 29, 2026 18:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants