Repository navigation
Conversation
test_file kept a NamedTemporaryFile handle open while passing its path to r.kappa as the output file. Windows rejects a second process opening a file while the creating process still holds the handle; POSIX allows it. Use grass.script.core.tempfile() to get an unopened path instead, the same fix already applied to r.mfilter's testsuite in OSGeo#7878, and drop the now-unneeded @xfail_windows marker. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Windows CI ran pytest single-worker with a fixed test order, unlike Linux CI's pytest-xdist + needs_solo_run split. Under a fixed order, whichever test happens to run first in a given worker never changes, so an order-dependent bug that only breaks the first call to a C-based GRASS tool/library in a fresh worker process can stay hidden indefinitely. Install pytest-xdist and pytest-randomly and mirror Linux CI's parallel / needs_solo_run split so this class of bug surfaces instead of hiding. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Do not merge. Adds a workflow_dispatch-only job that runs the same compile + pytest-xdist step as the permanent osgeo4w.yml change, as a 15-way parallel matrix, to fish for order-dependent Windows failures across many independent pytest-randomly seeds at once. Delete this file once the investigation concludes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Do not merge. Adds workflow_dispatch and a matrix "attempt" dimension (1 on normal push/PR events, 1-15 on manual dispatch) to fish for Windows order-dependent failures across many independent pytest-randomly seeds at once, reusing the existing compile job instead of a new (undispatchable) workflow file. Skips the gunittest run, the thorough test pass, and the Codecov upload on dispatched attempts, since only the randomized xdist step is relevant here. Revert this commit once the investigation concludes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Do not merge. The CI job manually prepends GISBASE's lib/bin dirs to PATH before invoking pytest, which real local usage never does (that priming exists only in this CI step, not in how a user actually runs pytest, nor in how grass.py's own startup works). Confirmed with the user that their local repro of the hypothesis runs pytest without any such priming, relying only on gs.setup.init() per test. Skip the priming on the workflow_dispatch fishing attempts so they match that setup instead of pre-empting the race being investigated. Revert before merging. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
discover_modules() always sorts directories and files alphabetically during discovery, so python3 -m grass.gunittest.main (invoked once as a single process for the whole testsuite tree, e.g. by test_thorough.sh) always runs tests in the same fixed order. If some test's success secretly depends on state left behind by whichever test happens to run first in a directory (a plausible source of hidden, order-dependent failures, particularly on Windows where native library search state does not always propagate the way an environment variable change would suggest), a fixed order means that dependency never gets exercised differently and the bug stays invisible. Add an opt-in GRASS_GUNITTEST_RANDOM_ORDER environment variable that shuffles discovery order instead of sorting it, left off by default until validated, mirroring how pytest-randomly is an opt-in plugin rather than a default behavior change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Do not merge. Re-enables the "Run tests" (test_thorough.bat) step on the fishing dispatch attempts, setting GRASS_GUNITTEST_RANDOM_ORDER=1 only for those attempts, since this is where pygrass's direct ctypes calls into compiled GRASS libraries are actually exercised (the pytest-xdist suite mostly calls tools via subprocess, which the code already handles correctly across process boundaries). Gives each attempt's HTML report artifact a distinct name so the 15 parallel uploads don't collide. Revert before merging. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reverts the temporary workflow_dispatch trigger, the "attempt" matrix dimension, and the various skip conditions used to fish for order-dependent Windows failures. Across 15 parallel attempts with genuinely different pytest-randomly seeds and grass.gunittest discovery orders, results were byte-for-byte identical every time (the same 8 pre-existing, order-independent gunittest failures, already catalogued elsewhere) — no order-dependent failure was found. Keeps the permanent improvements this investigation validated as safe: pytest-xdist and pytest-randomly for the pytest suite, and (matching the same "run tests in random order" goal, now for the mechanism that actually turned out to fit it) GRASS_GUNITTEST_RANDOM_ORDER=1 enabled by default for the gunittest thorough-test run, so this class of bug cannot hide behind a fixed test order going forward even though this particular investigation didn't surface one today. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
3 of 6 tasks
echoix
marked this pull request as ready for review
September 4, 2026 17:37
3 of 6 tasks
Owner
Author
|
Split the Generated by Claude Code |
The NamedTemporaryFile fix for test_file was bundled into this PR together with unrelated Windows CI changes. Split it into its own PR (#694) so it can be reviewed and merged independently; revert it here to avoid landing it twice. This reverts commit 124fe83. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
echoix
force-pushed
the
claude/windows-test-isolation-suv8k1
branch
from
September 6, 2026 12:53
5756390 to
aef4a43
Compare
echoix
commented
Sep 6, 2026
echoix
left a comment
Owner
Author
There was a problem hiding this comment.
Is there a cleaner way to have the random order in gunittest? Like a real option, with the possibility of giving the same seed again? Also, if tests are run not in order, can the generated test report be kept in order though, so it can be compared more easily between runs?
6 of 8 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Investigated a hypothesis: on Windows, the first call to a C-based GRASS
tool/library within a given process can fail while later calls in the same
process succeed, and a fixed test order can hide this indefinitely because
whichever test happens to run first never changes.
Two real fixes, plus infrastructure to guard against this bug class going
forward:
r.kappa'stest_filetestsuite test: it held aNamedTemporaryFilehandle open while passing its path to
r.kappaas the output file, whichWindows rejects (a second process can't open a file the creator still holds
open; POSIX allows it). Fixed the same way as
r.mfilter's testsuite fix inr.mfilter: Avoid reopening NamedTemporaryFile on Windows in testsuite OSGeo/grass#7878 (
grass.script.core.tempfile()instead), and removed thenow-unneeded
@xfail_windowsmarker.osgeo4w.yml) ran the pytest suite single-worker with a fixedorder, unlike Linux CI's
pytest-xdist+needs_solo_runsplit. Addedpytest-xdistandpytest-randomly, mirroring Linux CI's structure.GRASS_GUNITTEST_RANDOM_ORDERenvironment variable tograss.gunittest.loader.discover_modules()(it always sorted directoriesand files alphabetically before), and enabled it by default for the
Windows thorough-test run.
test_thorough.sh/.batrun the entiregunittest testsuite tree as a single long-lived process in a fixed
traversal order — a more precise structural match for "fixed order hides
an order-dependent bug" than xdist workers, since no parallelism is even
needed for the mechanism to apply.
Investigation result: across roughly 56 Windows CI job runs (30 with
randomized pytest-xdist order, 15 with randomized gunittest discovery order,
plus verification runs), results were byte-for-byte identical regardless of
order — the same 8 pre-existing gunittest failures every time, all of which
match a set already catalogued by #682 as pre-existing, non-order-related
issues (network dependency, missing OpenGL on a headless runner, a numpy
doctest version difference, an md5sum mismatch, etc.), not something this
investigation should chase further here. No order-dependent failure was
found. That's a real (negative) result, not a dead end: the
pytest-randomlyand
GRASS_GUNITTEST_RANDOM_ORDERadditions stay as permanent, validated(15 real CI runs, no new noise introduced) protection against this bug class
recurring, even though it didn't reproduce today.
One genuine bug surfaced along the way —
python/grass/pygrass/raster/testsuite/test_raster_region.pyfails with aself-contained
FERROR: Input window changed while maps are open for readinside
raster2numpy()handling, unrelated to test order — will be filed andfixed as a separate, independent PR to keep it out of this one.
Motivation and context
doc/development/testing.mddocuments that xdist workers are long-livedprocesses handling many test files sequentially ("several [tests] per
process"), so state and initialization order can leak between unrelated
tests sharing a worker. Windows CI wasn't exercising randomized order at all
in either test runner, so this class of bug had no way to surface, whether
or not one is currently present.
How has this been tested?
r.kappafix mirrors an already-merged, already-verified fix(
r.mfilter, r.mfilter: Avoid reopening NamedTemporaryFile on Windows in testsuite OSGeo/grass#7878) for the identical anti-pattern.grass.gunittest.loaderrandomization werevalidated across dozens of real Windows CI runs during the investigation
(see above) before being kept as permanent defaults; the final state on
this PR has also been run through normal Linux (
pytest.yml) and Windows(
osgeo4w.yml) CI.Windows CI timing: xdist vs. single worker
Compared Windows CI (
osgeo4w.yml) job data with pytest-xdist enabled (55job records from 13 runs across this investigation's branches) against
single-worker pytest (118 job records spanning 11 months of
mainhistory,2025-07-25 to 2026-06-21).
pytest step timing. xdist splits the old single step into two — a
parallel step plus a new
needs_solo_runserial step, each paying its owntest-collection overhead — compared here against the combined total:
main, before)* GitHub's job-log/step retention (roughly 3-4 months) limits step-level
"before" data to the 5 most recent
mainruns; treat as indicative, notdefinitive.
** Only the 8 full-scope, PR-triggered jobs ran both steps; 45
workflow_dispatchfishing-attempt jobs used earlier in this investigationintentionally skipped the solo/gunittest/thorough steps to isolate the
parallel step, so they only contribute to the parallel-step-alone row.
Net: the pytest step is about 20% faster with xdist, even after paying for
a second collection pass.
Whole-job duration (compile + all test steps; not expected to move,
since pytest is a small slice of it):
main, before)No meaningful difference — the job is dominated by MSYS2 setup, compiling
GRASS, and the (unchanged) single-worker gunittest thorough-test step.
Timeouts / hangs. 0 GitHub Actions
timed_outconclusions in eithergroup (0/53 xdist, 0/118 single-worker). A handful of
cancelledjobs inboth groups (3/55 xdist, 4/118 single-worker) were checked individually and
are routine
cancel-in-progresssupersession from newer pushes to the samebranch, not hangs or timeouts.
Types of changes
Checklist
pre-defined prefixes
of this project.
Developed with the help of an AI coding agent (Claude Code), under human
review and direction throughout, including a live back-and-forth on how to
interpret the CI results and where to take the investigation next.
🤖 Generated with Claude Code