Skip to content

test: normalize path separators in 7 cross-platform assertions (#643)#711

Merged
memtomem merged 1 commit intomainfrom
fix/windows-ci-path-separator
May 2, 2026
Merged

test: normalize path separators in 7 cross-platform assertions (#643)#711
memtomem merged 1 commit intomainfrom
fix/windows-ci-path-separator

Conversation

@memtomem
Copy link
Copy Markdown
Owner

@memtomem memtomem commented May 2, 2026

Closes #643.

Summary

The Windows CI job runs as informational (continue-on-error: true at .github/workflows/ci.yml:45). On the latest main run it accumulated 117 test failures, falling into 6 distinct classes. This PR addresses the smallest, lowest-risk class — path-separator literals in test assertions — so the rest can be tackled cleanly in follow-up PRs.

7 tests across 3 files compared production output (a Path whose str() uses the platform separator) against hardcoded forward-slash literals. Wrapping the LHS in Path(...).as_posix() makes the comparison portable without changing what the test actually exercises.

Test-only PR. No production source touched (notably norm_path() in storage/sqlite_helpers.py:24-38 is left as-is — its str(p.resolve()) behaviour is on-disk-key-shaped and changing it would force a migration of every existing chunk row).

Cross-check table

Triage was done against gh run view 25244100712 --log-failed --job=74025071567. Of the 14 candidates flagged by exploration, only the 7 below were genuinely path-separator failures; the others were misclassified and belong to other failure classes (see Out of scope below).

Test Path-sep? Pattern Notes
test_config_overrides.py::test_env_var_wins_over_config_json_scalar 1 '\\from\\env.db' == '/from/env.db'
test_config_overrides.py::test_env_var_wins_over_config_json_list 1 (list) ['\\from\\env'] == ['/from/env']
test_config_overrides.py::test_env_and_config_coexist_on_different_fields 1 same scalar shape
test_config_overrides.py::test_regression_pr247_mcp_json_env_block 1 (×2) scalar + list, tilde-prefixed
test_config_overrides.py::test_config_d_env_wins_over_fragments 1 env-over-fragment
test_session_summary_rescue.py::TestBoostSourcesHelper::test_above_threshold_walks_chunk_links_to_source_files 1 (set comp) {'\\tmp\\src\\…'} == {'/tmp/src/…'}
test_storage.py::TestNormPathUnicode::test_norm_path_osError_fallback_still_normalizes 1 (NFC-preserving) '\\\\tmp\\\\내 드라이브\\\\file.md' == '/tmp/내 드라이브/file.md'

The Pattern-1 fix wraps the LHS in Path(...).as_posix(). For the norm_path test, the comparison is structured so the NFC property is still pinned (RHS goes through unicodedata.normalize("NFC", nfd)); as_posix() only flattens the platform-dependent separator that the OSError fallback's str(p) introduces.

Verification

  • uv run pytest packages/memtomem/tests/test_config_overrides.py packages/memtomem/tests/test_session_summary_rescue.py packages/memtomem/tests/test_storage.py -m "not ollama" -q60 passed (macOS regression check).
  • uv run ruff check + uv run ruff format --check on the three files → clean.

Windows CI impact

Measured on this branch's run (gh run view 25244446944 --log-failed --job=74026052967).

Metric Before (run 25244100712) After (run 25244446944)
Total Windows failures 117 113
Path-sep candidates fixed 7 of 7 7 of 7 (all gone from fail list)
Net reduction −4

The net (−4) understates the path-sep contribution (−7) because the symlink / git-status failure class is flaky on Windows. Two tests from that class disappeared between runs (test_stale_pin_when_history_rewritten, test_skip_symlinks) and five others appeared (test_behind_state_pin_below_head, test_cli_status_renders_states, test_stale_pin_when_wiki_absent, test_dirty_partial_subdir, test_skip_dotgit_dsstore_pycache) — all in the same flaky class, none caused by this PR. They will be addressed in the dedicated symlink/git-status follow-up.

macOS / Ubuntu / lint / typecheck / golden-path / notebooks / CLA all green on this branch.

Out of scope (separate follow-up PRs)

The other 110 Windows failures fall into classes that need different mechanics. Each has its own tracking issue:

The next-simplest follow-up is the POSIX-only skipif batch (file-mode + fcntl + signal = ~14 tests, single uniform change).

Test plan

  • Local pytest passes on touched files (macOS, 60 tests).
  • Ruff lint + format clean.
  • Windows CI failure count drops by 7 — all 7 targeted tests confirmed gone from the fail list (run 25244446944, net −4 due to symlink-class flake noise).
  • macOS / Ubuntu pass counts unchanged on this branch (both green; no regression).

🤖 Generated with Claude Code

The Windows CI job (currently `continue-on-error: true`) had 117 test
failures on the latest main (run 25244100712). 7 of those were
straightforward path-separator literals in test assertions: production
returns a `Path` whose `str()` uses the platform separator
(`\\from\\env.db` on Windows), but the tests compared against hardcoded
forward-slash literals (`/from/env.db`).

Wrap the LHS in `Path(...).as_posix()` so the comparison is portable
across POSIX and Windows without changing what the test exercises:

- 5 tests in `test_config_overrides.py` (env var precedence over
  config.json; both scalar and list `memory_dirs` cases plus the PR #247
  `.mcp.json` regression and the `config.d` env override).
- 1 test in `test_session_summary_rescue.py`
  (`test_above_threshold_walks_chunk_links_to_source_files`) — set
  comprehension over chunk source paths.
- 1 test in `test_storage.py`
  (`test_norm_path_osError_fallback_still_normalizes`) — the assertion
  about NFC normalization of the OSError fallback. The NFC property is
  still pinned because the RHS goes through `unicodedata.normalize`;
  `as_posix()` only flattens the platform-dependent separator that the
  fallback's `str(p)` introduces.

Why test-only, not production: `norm_path()` in
`storage/sqlite_helpers.py` uses `str(p.resolve())` on purpose. Changing
it to `.as_posix()` would alter every existing on-disk path key under
the unique-content index and require a migration. Tests should match
production behaviour, not the other way around.

Out of scope (other Windows CI failure classes — separate PRs):

- POSIX file-mode (`stat.S_IMODE`) — issue #637
- HOME monkeypatch reach (`Path.home()` vs `USERPROFILE`) — issue #644
- Path canonicalization in indexing — issue #647
- `.claude/projects` discovery / wizard backslash regex — issue #316
- Symlink + git-status detection
- `fcntl` / signal handling (POSIX-only)
- `'home'` fs alias / tilde expansion in web fs-list — issue #646
- `config_signature` NTFS mtime resolution — issue #645

Verification:

- `uv run pytest packages/memtomem/tests/test_config_overrides.py
   packages/memtomem/tests/test_session_summary_rescue.py
   packages/memtomem/tests/test_storage.py -m "not ollama" -q`
   → 60 passed (macOS regression check).
- `uv run ruff check + format --check` clean on the three files.
- Windows CI before/after counts to be appended to the PR body once the
  branch run finishes.

Closes #643.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@memtomem
Copy link
Copy Markdown
Owner Author

memtomem commented May 2, 2026

Windows CI delta (run 25244446944, job 74026052967)

Metric Before (main run 25244100712) After (this PR)
Total Windows failures 117 113
Net reduction −4

What actually changed

All 7 path-separator tests this PR targets are gone from the failure list ✅:

  • test_config_overrides.py::test_env_var_wins_over_config_json_scalar
  • test_config_overrides.py::test_env_var_wins_over_config_json_list
  • test_config_overrides.py::test_env_and_config_coexist_on_different_fields
  • test_config_overrides.py::test_regression_pr247_mcp_json_env_block
  • test_config_overrides.py::test_config_d_env_wins_over_fragments
  • test_session_summary_rescue.py::TestBoostSourcesHelper::test_above_threshold_walks_chunk_links_to_source_files
  • test_storage.py::TestNormPathUnicode::test_norm_path_osError_fallback_still_normalizes

The net is only −4 because the symlink / git-status failure class is flaky on Windows. Two tests from that class disappeared between runs (test_stale_pin_when_history_rewritten, test_skip_symlinks), but five others appeared (test_behind_state_pin_below_head, test_cli_status_renders_states, test_stale_pin_when_wiki_absent, test_dirty_partial_subdir, test_skip_dotgit_dsstore_pycache). All five are in the symlink/git-status class — not regressions from this PR. They'll be addressed in the dedicated follow-up PR for that class.

Negative pin (no regression on other OSes)

  • test (ubuntu-latest): SUCCESS
  • test (macos-latest): SUCCESS
  • lint, typecheck, golden-path, notebooks: SUCCESS
  • CLAAssistant: SUCCESS

✅ All required gates green; the Windows job is informational (continue-on-error: true).

@memtomem memtomem merged commit b04dcdb into main May 2, 2026
8 of 9 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators May 2, 2026
@memtomem memtomem deleted the fix/windows-ci-path-separator branch May 2, 2026 05:35
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: replace POSIX path-separator assertions in tests (/ vs \\)

2 participants