fix: Windows CI compat for palace lock tests and path normalization

Addresses the two actionable Copilot comments from the 2nd review pass.

tests/test_palace_locks.py (#7, #8)
  multiprocessing.get_context("fork") is unavailable on Windows, so the
  cross-process tests would crash the Windows CI runner. Added
  `_get_mp_context()` that picks "spawn" on Windows and "fork" elsewhere.
  Spawn re-imports the module in the child; it inherits os.environ
  (including the monkeypatched HOME), which is all these tests need.

mempalace/palace.py (#10)
  The per-palace lock key was computed from os.path.abspath(palace_path).
  On Windows the filesystem is case-insensitive, so `C:\\Palace` and
  `c:\\palace` would hash to different keys and two concurrent mines
  could touch the same on-disk palace. Switched to
  `os.path.normcase(os.path.realpath(...))` so:
    * realpath resolves symlinks and `..` segments
    * normcase folds case on Windows (no-op on POSIX)

Testing
  pytest tests/test_palace_locks.py tests/test_hooks_cli.py
         tests/test_backends.py tests/test_cli.py
  → 98 passed, 0 failed.
This commit is contained in:
Felipe Truman
2026-04-17 16:26:11 -03:00
committed by Igor Lins e Silva
parent 99b820cb42
commit 1998aede66
2 changed files with 23 additions and 4 deletions
+9 -2
View File
@@ -329,14 +329,21 @@ def mine_palace_lock(palace_path: str):
*different* palaces can still run in parallel — we only serialize
writes into the same palace, which is the correctness boundary.
The key is derived from a fully normalized form of the path:
`realpath` resolves symlinks and `..` segments, and `normcase` folds
case on Windows (which has a case-insensitive filesystem). Without
normcase, `C:\\Palace` and `c:\\palace` would hash to different keys
on Windows and let two concurrent mines touch the same on-disk palace.
Non-blocking: if another `mine` is already writing to this palace,
raise MineAlreadyRunning so the caller can exit cleanly instead of
piling up as a waiting worker.
"""
lock_dir = os.path.join(os.path.expanduser("~"), ".mempalace", "locks")
os.makedirs(lock_dir, exist_ok=True)
resolved = os.path.abspath(os.path.expanduser(palace_path))
palace_key = hashlib.sha256(resolved.encode()).hexdigest()[:16]
resolved = os.path.realpath(os.path.expanduser(palace_path))
lock_key_source = os.path.normcase(resolved)
palace_key = hashlib.sha256(lock_key_source.encode()).hexdigest()[:16]
lock_path = os.path.join(lock_dir, f"mine_palace_{palace_key}.lock")
lf = open(lock_path, "w")