@@ -54,11 +54,21 @@ runs:
5454 fi
5555
5656 # Test with base dependencies
57- # Exit code 5 = no tests collected (e.g. only CLI test files changed);
58- # treat that as success since CLI tests run in a separate workflow.
57+ # Exit code 5 = no tests collected (e.g. only CLI test files changed,
58+ # or no xdist_safe-marked tests in the changed set); treat that as
59+ # success since CLI tests run in a separate workflow and the xdist
60+ # step legitimately collects nothing when no marked modules changed.
5961 #
60- # TODO: xdist is disabled on Windows because several tests
61- # crash workers. Fix and re-enable. Failing tests:
62+ # xdist is opt-in: tests run serially by default. Modules opt in by
63+ # adding `pytestmark = pytest.mark.xdist_safe`. On Linux/macOS the
64+ # serial step excludes xdist_safe and a second step runs the marked
65+ # tests under -n auto. On Windows, xdist is disabled entirely and
66+ # the serial step runs every test (marked tests included).
67+ #
68+ # Note: The following tests crashed workers on Windows under xdist.
69+ # If/when any of their modules are opted into xdist_safe, re-verify
70+ # behavior on Windows (Windows will still run them serially, but the
71+ # underlying issues may resurface on Linux/macOS parallel runs):
6272 # - tests/_islands/test_island_generator.py::test_build
6373 # - tests/_islands/test_island_generator.py::test_render
6474 # - tests/_islands/test_island_generator.py::test_render_multiline_markdown
@@ -68,13 +78,31 @@ runs:
6878 # - tests/_server/test_session_manager.py::test_create_session_absolute_url
6979 # - tests/_server/test_session_manager.py::test_create_session_with_script_config_overrides
7080 # - tests/_server/test_session_manager.py::test_recents_touch_called_on_session_create
71- - name : Test changed with base dependencies
81+ - name : Test changed with base dependencies (serial)
7282 if : ${{ inputs.dependencies == 'core' }}
7383 shell : bash
7484 run : |
7585 uv run --python ${{ inputs.python-version }} --group test pytest tests/ \
7686 -v \
77- ${{ inputs.os != 'windows-latest' && '-n auto' || '-p no:xdist' }} \
87+ -p no:xdist \
88+ ${{ inputs.os != 'windows-latest' && '-m "not xdist_safe"' || '' }} \
89+ -k "not test_cli" \
90+ --durations=10 \
91+ -p packages.pytest_changed \
92+ --changed-from=${{ steps.setup-flags.outputs.changed_from }} \
93+ --include-unchanged=${{ steps.setup-flags.outputs.include_unchanged }} \
94+ --picked=first \
95+ --inline-snapshot=disable \
96+ || { ec=$?; [ $ec -eq 5 ] && exit 0 || exit $ec; }
97+
98+ - name : Test changed with base dependencies (xdist)
99+ if : ${{ inputs.dependencies == 'core' && inputs.os != 'windows-latest' }}
100+ shell : bash
101+ run : |
102+ uv run --python ${{ inputs.python-version }} --group test pytest tests/ \
103+ -v \
104+ -n auto \
105+ -m xdist_safe \
78106 -k "not test_cli" \
79107 --durations=10 \
80108 -p packages.pytest_changed \
@@ -85,13 +113,31 @@ runs:
85113 || { ec=$?; [ $ec -eq 5 ] && exit 0 || exit $ec; }
86114
87115 # Test with optional dependencies
88- - name : Test changed with optional dependencies
116+ - name : Test changed with optional dependencies (serial)
89117 if : ${{ inputs.dependencies == 'core,optional' }}
90118 shell : bash
91119 run : |
92120 uv run --python ${{ inputs.python-version }} --group test-optional pytest tests/ \
93121 -v \
94- ${{ inputs.os != 'windows-latest' && '-n auto' || '-p no:xdist' }} \
122+ -p no:xdist \
123+ ${{ inputs.os != 'windows-latest' && '-m "not xdist_safe"' || '' }} \
124+ -k "not test_cli" \
125+ --durations=10 \
126+ -p packages.pytest_changed \
127+ --changed-from=${{ steps.setup-flags.outputs.changed_from }} \
128+ --include-unchanged=${{ steps.setup-flags.outputs.include_unchanged }} \
129+ --picked=first \
130+ --inline-snapshot=disable \
131+ || { ec=$?; [ $ec -eq 5 ] && exit 0 || exit $ec; }
132+
133+ - name : Test changed with optional dependencies (xdist)
134+ if : ${{ inputs.dependencies == 'core,optional' && inputs.os != 'windows-latest' }}
135+ shell : bash
136+ run : |
137+ uv run --python ${{ inputs.python-version }} --group test-optional pytest tests/ \
138+ -v \
139+ -n auto \
140+ -m xdist_safe \
95141 -k "not test_cli" \
96142 --durations=10 \
97143 -p packages.pytest_changed \
@@ -104,15 +150,35 @@ runs:
104150 # Test with minimal dependencies using lowest resolution
105151 # https://docs.astral.sh/uv/concepts/resolution/#lowest-resolution
106152 # https://docs.astral.sh/uv/reference/environment/#uv_resolution
107- - name : Test with minimal dependencies (lowest resolution)
153+ - name : Test with minimal dependencies (lowest resolution, serial )
108154 if : ${{ inputs.dependencies == 'minimal' }}
109155 shell : bash
110156 env :
111157 UV_RESOLUTION : lowest-direct
112158 run : |
113159 uv run --python ${{ inputs.python-version }} --group test pytest tests/ \
114160 -v \
115- ${{ inputs.os != 'windows-latest' && '-n auto' || '-p no:xdist' }} \
161+ -p no:xdist \
162+ ${{ inputs.os != 'windows-latest' && '-m "not xdist_safe"' || '' }} \
163+ -k "not test_cli" \
164+ --durations=10 \
165+ -p packages.pytest_changed \
166+ --changed-from=${{ steps.setup-flags.outputs.changed_from }} \
167+ --include-unchanged=${{ steps.setup-flags.outputs.include_unchanged }} \
168+ --picked=first \
169+ --inline-snapshot=disable \
170+ || { ec=$?; [ $ec -eq 5 ] && exit 0 || exit $ec; }
171+
172+ - name : Test with minimal dependencies (lowest resolution, xdist)
173+ if : ${{ inputs.dependencies == 'minimal' && inputs.os != 'windows-latest' }}
174+ shell : bash
175+ env :
176+ UV_RESOLUTION : lowest-direct
177+ run : |
178+ uv run --python ${{ inputs.python-version }} --group test pytest tests/ \
179+ -v \
180+ -n auto \
181+ -m xdist_safe \
116182 -k "not test_cli" \
117183 --durations=10 \
118184 -p packages.pytest_changed \
0 commit comments