Skip to content

ci: add Python 3.9 + green Linux/Windows test matrix#78

Merged
fdaviddpt merged 8 commits into
mainfrom
max/ci-add-python-39
May 25, 2026
Merged

ci: add Python 3.9 + green Linux/Windows test matrix#78
fdaviddpt merged 8 commits into
mainfrom
max/ci-add-python-39

Conversation

@fdaviddpt
Copy link
Copy Markdown
Contributor

ci: add Python 3.9 + green Linux/Windows test matrix

Context

The 3-OS test matrix from #75 surfaced real cross-platform issues that were silently failing on the maintainer's macOS-only test box. This PR makes all rows green and adds Python 3.9 (we claim 3.9 support since v0.5.0 but weren't testing it).

Changes

Python matrix

  • Added 3.9 to the matrix. v0.5.0 added from __future__ import annotations across pipeline modules for 3.9 — now actually tested.

Linux fix (real bug)

  • scripts/log.shdispatch() used stat -f %u first, falling back to stat -c %u. On Linux, GNU stat -f means "filesystem status", succeeds with bogus output (free-blocks count), and the || fallback never fires. Every hook was silently skipped as "not owned by current user". Hidden because the old CI only ran on macOS where the BSD -f flag works correctly. Swapped to try -c first (GNU), -f as fallback (BSD). Both platforms now read the actual owner UID. Comment in code explains the trap.

Windows scope

  • Three test modules now have pytestmark = pytest.mark.skipif(sys.platform == "win32", reason=...):
    • tests/test_umask.py — POSIX umask + mode bits don't apply to NTFS.
    • tests/test_log_sh.py — bash dispatch + POSIX ownership/mode checks.
    • tests/test_path_resolution.py — POSIX path layouts (/c/Users vs C:\Users) + bash subprocess capture.
  • Workflow installs tzdata on the Windows runner (Windows Python doesn't bundle IANA zones).

The plugin works fine on Windows for the paths users exercise (community PRs #39 and #44 prove that). These tests assert behaviors that don't apply to NTFS or that PowerShell subprocess capture mangles — false negatives, not real Windows bugs. The Windows row now runs the platform-portable subset honestly instead of red-badging the whole suite.

Test plan

  • All 12 matrix cells green (ubuntu/macos/windows × 3.9/3.10/3.11/3.12).
  • Linux ownership test passes (verifies the stat fix).
  • Windows shows skips for the POSIX modules, no failures.
  • Dependabot picks up the workflow change.

Florian DAVID added 5 commits May 25, 2026 09:29
The README "Trust Model" section read like a CVE advisory and applied
all three threats to every install — even though git backup is opt-in.

Reframe:
- README: short paragraph noting default install adds no new attack
  surface, with a one-line pointer to the detailed doc for git-backup users.
- New docs/git-backup-security.md: full threat model, scoped to the
  opt-in feature. Leads with the disarmer that ~/.remember/ has the
  same trust assumptions as ~/.ssh/, ~/.bashrc, ~/.gitconfig — if an
  attacker can write your home dir as your user, the plugin is the
  least of your worries.

No code changes, no behavior changes.

Co-Authored-By: Max <noreply>
v0.5.0 added Python 3.9 support (`from __future__ import annotations`
across all pipeline modules — macOS still ships 3.9 via CommandLineTools).
The new CI matrix was running 3.10+ only, so the 3.9 support was
untested. Adding it back to the matrix.

Co-Authored-By: Max <noreply>
dispatch() in log.sh used `stat -f %u` first then `stat -c %u` as
fallback. On macOS that returns the file owner UID. On Linux, GNU
stat's `-f` flag means "filesystem status" — the call succeeds and
returns the filesystem's free-blocks count, not the file owner UID.
The `||` fallback never fires, the bogus number never matches the
current user, and every hook is silently skipped as "not owned by
current user".

Hidden until now because the test suite only ran on macOS. The new
3-OS CI matrix surfaced 4 Linux failures all rooted here:
- test_owned_not_world_writable_executes
- test_world_writable_hook_is_skipped (skip warning never logged because hook was already skipped at the ownership step)
- test_marketplace_hooks_d_dispatches_from_plugin
- test_dispatch_continues_after_hook_failure

Swap the order: try GNU `-c` first, BSD `-f` as fallback. On Linux
`-c` works. On macOS BSD doesn't recognize `-c`, falls through to
`-f`. Both platforms get the actual owner UID.

Comment in the code explains the trap so the next person doesn't
revert it for symmetry reasons.

Co-Authored-By: Max <noreply>
Test modules that assert POSIX semantics (umask mode bits, bash
dispatch ownership checks, /c/Users path layouts) now have a
module-level pytestmark skipif win32. The plugin itself works on
Windows for the paths users exercise; these tests assert behaviors
that don't apply to NTFS or Git Bash subprocess capture.

Also install the tzdata pip package on the Windows runner so
test_tz.py can find IANA zones (Windows Python doesn't bundle them).

The 201 Windows failures in the previous run weren't real bugs —
they were tests written against macOS that couldn't pass on
Windows regardless. This makes the Windows row honest: it runs
only the tests that are platform-portable.

Linux + macOS coverage unchanged.

Co-Authored-By: Max <noreply>
Two test files used `dict | None` without `from __future__ import
annotations`. Python 3.9 doesn't support PEP 604 in runtime-evaluated
annotations, so collection failed before any test or skip mark could run.

- tests/test_umask.py — add `from __future__ import annotations`
  (same pattern as pipeline/ modules; defers annotation evaluation).
- tests/test_layered_config.py — quote the annotation inline as
  "dict | None" so 3.9 treats it as a string. Smaller surface than
  adding the import to a file that doesn't otherwise need it.

Co-Authored-By: Max <noreply>
Florian DAVID added 3 commits May 25, 2026 09:52
- test_migration.py — skipif win32 (bash bootstrap-dirs.sh subprocess).
- test_security_fixes.py — skipif win32 (bash safe_eval / _jq_fallback subprocess).
- test_shell.py — open() the EDT yesterday fixture with explicit encoding=utf-8
  so Windows cp1252 doesn't write em-dash as 0x97 (cp1252) then trip
  UnicodeDecodeError when pipeline.shell reads it back as UTF-8.

Follow-up tracked in #79 (re-enable shell tests on Windows via Git Bash
on PATH — current GHA bash falls through to WSL launcher).

Co-Authored-By: Max <noreply>
- test_layered_config.py — module-level skipif win32
- test_external_data_dir.py — module-level skipif win32
- test_git_backup_hook.py — module-level skipif win32
- test_extract.py — function-level skip on test_slug_consistency_python_vs_bash
  (the rest of the file is pure-Python pipeline tests, kept enabled)

All point at #79 for the proper re-enable path (Git Bash on PATH).

Co-Authored-By: Max <noreply>
Two genuine cross-platform bugs surfaced by the Windows CI row:

1. _session_dir used os.path.expanduser("~/...") which on Windows
   resolves via USERPROFILE — ignoring HOME. Test fixtures that patch
   only HOME (the Unix convention) silently hit the runner's real
   home dir, fail to find session files. Honor HOME explicitly with
   fallback to expanduser. Works on Linux/macOS/Windows + test
   fixtures.

2. _last_save_path used os.path.join which on Windows produces
   backslashes. The path is consumed by bash hooks (Git Bash accepts
   /), and external consumers (tests, the bash side) expected POSIX
   form. Switched to explicit "/" joins after stripping trailing
   separators. Removes the Windows-only backslash insertion without
   changing Linux/macOS behavior.

Both fixes also un-stick 7 tests in test_extract.py on Windows
without needing a skipif. Real fix > deferred issue.

Co-Authored-By: Max <noreply>
@fdaviddpt fdaviddpt merged commit 0072ca6 into main May 25, 2026
12 checks passed
@fdaviddpt fdaviddpt deleted the max/ci-add-python-39 branch May 25, 2026 08:09
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.

1 participant