Skip to content

backend: the test suite is portable to Windows (2 product fixes + 15 test fixes) - #148

Open
kai-openswarm wants to merge 4 commits into
openswarm-ai:devfrom
kai-openswarm:b8/windows-pytest
Open

backend: the test suite is portable to Windows (2 product fixes + 15 test fixes)#148
kai-openswarm wants to merge 4 commits into
openswarm-ai:devfrom
kai-openswarm:b8/windows-pytest

Conversation

@kai-openswarm

Copy link
Copy Markdown

Running backend/tests on windows-latest against dev fails 19 tests and stalls at the end (the stall is a Linux problem too and is fixed in #147). This PR makes the suite green on Windows, and two of the failures were product bugs on Windows, not test bugs.

Product fixes

  • compose_turn_system_prompt: the <current_time> pin never rendered on Windows. It was built with strftime('%-d') / strftime('%-I'), which are glibc extensions; on Windows the C runtime raises ValueError, the surrounding except swallowed it, and the pin was silently dropped, so on Windows the agent had no notion of "now" — the very thing the block exists to provide. Formatted without the non-portable codes; test_system_prompt now asserts the pin carries a real date and time.
  • swarm skill export wrote non-portable bundle keys. Supporting-file keys came from os.path.relpath, so a bundle exported on Windows carried scripts\go.py, and the importer (which joins the key onto the destination) would recreate a file literally named that on macOS/Linux. Same class of fix in skills.py (folder-skill listing) and outputs/publish_capability.py (API-facing relative paths). Normalised to /.

Test fixes (behaviour on Linux/macOS unchanged)

Test Why it failed on Windows Fix
test_browser_cookies_windows asserted the POSIX branch while IS_WIN was true pin IS_WIN=False for the POSIX case
test_browser_login_handoff "unwritable" path was writable on Windows a path under a regular file is unwritable everywhere
test_browser_provider_agnostic, test_skill_replay_vs_send_script open() in the locale codec (charmap) on UTF-8 fixtures encoding="utf-8"
test_credential_store::test_written_db_is_owner_only, test_reap_ghost_runtimes (SIGCONT), test_shutdown_fuse_disarms (SIGALRM) POSIX-only semantics skipif os.name == "nt" with the reason
test_app_export_no_stale_files fixture wrote in text mode → CRLF bytes vs LF expectation newline="\n"
test_bundled_extracted_modules .bin/vite symlink with a forward-slash relative target does not resolve on Windows plain file (what npm writes there; the check is exists())
test_disk_resilience::…fsyncs_directory… guard was hasattr(os, "O_RDONLY"), which Windows has; json_store skips the dir fsync on Windows by design skip on nt
test_browser_metrics::…secrets_are_scrubbed… asserted POSIX mode bits assert on POSIX only
test_service::test_install_id_persisted fixture redirected SETTINGS_FILE but not DATA_DIR; the atomic writer's temp file was on another drive than tmp_pathWinError 17 on os.replace, swallowed by p_get_install_id redirect DATA_DIR too (also stops the suite dropping temp files into backend/data)
test_skills_folders, test_system_prompt the two product bugs above

Proof

Full suite on hosted windows-latest (Python 3.13 from backend/requirements.lock) with these commits: 2951 passed / 15 skipped / 0 failed, every collected test ran (collected=2966 ran=2966), 9 min 14 s. ubuntu-latest on the same tree: 2951 passed / 15 skipped, unchanged from dev.

Follow-up

Once this and #147 land, a windows-latest leg for backend-tests.yml is a three-line matrix change (os: [ubuntu-latest, windows-latest], shell: bash on the run steps).

The suite had never run on Windows in CI, and a first run there failed twelve
tests for reasons that have nothing to do with the code under test:

- charmap: three tests read fixture/source files with the platform default
  encoding (test_browser_provider_agnostic x2, test_skill_replay_vs_send_script)
  -> read as utf-8, which is what the files are
- POSIX-only mechanisms asserted as facts: the shutdown fuse (a documented no-op
  on Windows), SIGCONT in the ghost reaper, st_mode bits in the credential store
  -> skipif os.name == 'nt' with the reason spelled out
- test_browser_login_handoff pointed at '/nonexistent-dir-xyz' to get an
  unwritable path; Windows can create it -> a path under a regular file, which
  is unwritable everywhere
- test_the_mac_branch_does_not_try_gcm trusted the host's IS_WIN; on Windows the
  GCM branch runs (correctly) -> the test pins the flag it means to test

Two of the failures were real portability defects in the code, not the tests:
list_skill_files and check_publish_capability reported os.path.relpath paths, so
on Windows the API answered backslash paths that the frontend joins with '/'
and that the SKILL.md-first ordering compares as strings. Both now report posix
relative paths on every OS.

Ubuntu behaviour is unchanged (the touched tests and the consumers of the two
code paths: 72 passed).
compose_turn_system_prompt built the <current_time> block with strftime('%-d')
and strftime('%-I'). Those are glibc extensions; on Windows the C runtime raises
ValueError("Invalid format string"), the surrounding except swallowed it, and
the pin was silently dropped, so on Windows the agent had no notion of "now"
(the very thing the block exists to provide). Format the day/hour without the
non-portable codes. test_system_prompt now asserts the pin carries a real date
and time, which is what fails on Windows without this.
Named by running the suite on windows-latest with the first batch applied:

- swarm skill export (backend/apps/swarm/entities/skills.py): supporting-file
  keys are built with os.path.relpath, so a bundle exported on Windows carried
  "scripts\go.py" and the importer, which joins the key onto the destination,
  would recreate a file literally named that on macOS/Linux. Normalise to "/";
  test_skills_folders asserted exactly this.
- test_app_export_no_stale_files: the fixture wrote the workspace files in text
  mode, so on Windows the export bytes were CRLF and the byte comparison
  failed; write with newline="\n".
- test_bundled_extracted_modules: the .bin/vite launch shim was a symlink with a
  forward-slash relative target, which Windows cannot resolve (exists() is
  False). A plain file is what npm writes there and is what the check reads.
- test_disk_resilience: the directory-fsync test guarded on hasattr(os,
  "O_RDONLY"), which Windows has; json_store skips the directory fsync on
  Windows by design, so skip the test there.
- test_browser_metrics: POSIX mode bits are asserted on POSIX only.
…file

The autouse fixture pointed SETTINGS_FILE at tmp_path but left the store's
DATA_DIR at the real data dir. atomic_write_settings stages its temp file in
DATA_DIR and os.replace()s it onto SETTINGS_FILE, so on a runner where the
repo checkout and the temp dir sit on different drives the replace fails with
"[WinError 17] The system cannot move the file to a different disk drive";
p_get_install_id swallows that and falls back to an unpersisted id, and
test_install_id_persisted fails on the KeyError. Redirect DATA_DIR alongside
SETTINGS_FILE, as production keeps them, which also stops the suite from
dropping temp files into backend/data.
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