test(gfql): make the polars lane see the 280 tests it was skipping everywhere - #1805
Conversation
…erywhere
polars is installed in exactly ONE CI lane (test-polars, driven by bin/test-polars.sh),
and that lane runs a hand-maintained file list. Every other lane collection-skips a
module-level `pytest.importorskip("polars")` and every polars/polars-gpu parameter. A
polars-gated test the list omits therefore runs NOWHERE while both lanes report green.
Measured on run 30311675717 (PR #1799): 280 tests executed locally with polars installed
and appear in ZERO job logs of that run, including six whole modules — four of them named
`test_engine_polars_*`. Two real defects were sitting in the blind spot:
- `test_explicit_polars_gpu_declines_indexed_helper_and_falls_back` executes
engine='polars-gpu' but only gates on `importorskip("polars")`, so it raises
ImportError wherever polars is present without the RAPIDS cudf_polars stack. It has
never once run: 9 SKIPPED, 0 executed, across the whole run.
- `test_case_regex_unicode_trick_matrix[toupper-eq-ss-fold]` and
`[tolower-turkish-dotted-i]` catch a genuine silent cross-engine divergence: under
pandas>=3 the pandas engine answers [9] where polars answers [8, 9] for
`toUpper(n.name) = 'STRASSE'`. Filed as #1802 with the repro; both rows are marked
xfail(strict=True) keyed on the pandas accessor's own behaviour, so the fix XPASSes
and retires the marks.
Changes:
- bin/test-polars.sh gains the six invisible modules plus the four whose polars
parameters were invisible.
- New `test_polars_lane_completeness.py` parses POLARS_TEST_FILES and fails when a
polars-mentioning test module is neither in the lane nor in a documented exemption,
when a module-level polars gate sits outside the lane, when a listed path is gone, or
when a polars-gated test in the `-k polars` phase is unselectable by that filter.
Mutation-checked: dropping one entry turns two of its assertions red.
- Split the polars-gpu test so the CPU half (helper must decline) runs wherever polars
does, and the execution half skips honestly without cudf_polars.
- Renamed the two test_lowering.py polars tests `-k polars` could not select.
Test-only; no product code touched, so no CHANGELOG entry.
Widened lane, py3.11 + polars 1.43.1 + pandas 3.0.5: 2726 passed, 142 skipped, 3 xfailed,
0 failed (was 1636 passed on the same box). ORDERING NOTE: #1797 records that the py3.12
coverage cell is already at its 10-minute ceiling and that adding a single file tipped it
into a timeout; the pending ci.yml split (comment on PR #1794) must land BEFORE this.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB
Replace the getattr/ast.AST pairing in the guard's two helpers with the concrete node types they are actually called with: _node_source takes ast.stmt | ast.expr and reads .lineno/.end_lineno directly (end_lineno is Optional[int], so the None case is handled rather than defaulted through getattr), and _selected_by_k_polars takes a FuncDef alias so .name and .decorator_list are checked attributes instead of getattr lookups with invented fallbacks. No behaviour change; guard still 8 passed, ruff clean, mypy clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB
…olars
`_engines()` appended `"polars-gpu"` when **`cudf`** imported. But `polars-gpu`
is the `cudf_polars` GPU collect target and raises
ImportError: GFQL engine='polars-gpu' requires the RAPIDS cudf_polars stack
without it. So any box with cuDF installed and `cudf_polars` absent — a common
developer configuration — got 20 failures indistinguishable from product
breakage, in a file that IS listed in `bin/test-polars.sh`. CI never caught it
because that lane installs neither package, so the parameter did not exist
there: a gate can only fabricate failures where it is never exercised.
Measured on a box with cuDF 25.10 and no `cudf_polars`:
before 20 failed, 116 passed
after 0 failed, 105 passed
SCOPE NOTE. This branch originally carried the whole CA4 allowlist change
(seven files added to `bin/test-polars.sh`). PR #1805
(`fix/gfql-polars-lane-invisible-tests`), opened concurrently, is a SUPERSET of
that list AND adds `test_polars_lane_completeness.py`, a guard that fails when a
polars-parametrized file is missing from the lane — which makes the fix
permanent rather than one-time. It also lands the same pandas-3 case-mapping
xfail, against the same issue. Reduced to the one hunk #1805 does not contain,
so the owner reviews the allowlist once instead of twice.
RUNTIME DELTA: zero. One test file's engine list and a CHANGELOG line; no
pyg-bench lane run required (CB5), checkable from the diff.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB
The first commit quoted a local 4-CPU-pinned A/B (326s -> 109s, 2.98x). CI has now run this branch and realizes less: the `test-polars (3.12)` cell goes 501s -> 322s and its script step 484s -> 297s (1.63x), with the five non-coverage cells at 222-318s -> 115-172s. Coverage tracing is per-worker CPU cost that does not parallelize away, and the ~8s serial second phase plus 4x interpreter startup are fixed, so the coverage cell gains less than the plain cells (which see ~2x). The CI figure is the one that decides whether #1805 fits, so it is the one the changelog quotes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB
|
The Context: this PR's #1814 parallelizes Verified there before relying on it: identical pass/skip node-id sets serial vs parallel (2417 ids, 2404 passed / 13 skipped), coverage a strict superset with zero lines lost, the Diffs are mechanically separate and rebase cleanly either way. #1814 touches only the block after the Suggested order: #1814, then this PR. |
The first commit quoted a local 4-CPU-pinned A/B (326s -> 109s, 2.98x). CI has now run this branch and realizes less: the `test-polars (3.12)` cell goes 501s -> 322s and its script step 484s -> 297s (1.63x), with the five non-coverage cells at 222-318s -> 115-172s. Coverage tracing is per-worker CPU cost that does not parallelize away, and the ~8s serial second phase plus 4x interpreter startup are fixed, so the coverage cell gains less than the plain cells (which see ~2x). The CI figure is the one that decides whether #1805 fits, so it is the one the changelog quotes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB
…olars
`_engines()` appended `"polars-gpu"` when **`cudf`** imported. But `polars-gpu`
is the `cudf_polars` GPU collect target and raises
ImportError: GFQL engine='polars-gpu' requires the RAPIDS cudf_polars stack
without it. So any box with cuDF installed and `cudf_polars` absent — a common
developer configuration — got 20 failures indistinguishable from product
breakage, in a file that IS listed in `bin/test-polars.sh`. CI never caught it
because that lane installs neither package, so the parameter did not exist
there: a gate can only fabricate failures where it is never exercised.
Measured on a box with cuDF 25.10 and no `cudf_polars`:
before 20 failed, 116 passed
after 0 failed, 105 passed
SCOPE NOTE. This branch originally carried the whole CA4 allowlist change
(seven files added to `bin/test-polars.sh`). PR #1805
(`fix/gfql-polars-lane-invisible-tests`), opened concurrently, is a SUPERSET of
that list AND adds `test_polars_lane_completeness.py`, a guard that fails when a
polars-parametrized file is missing from the lane — which makes the fix
permanent rather than one-time. It also lands the same pandas-3 case-mapping
xfail, against the same issue. Reduced to the one hunk #1805 does not contain,
so the owner reviews the allowlist once instead of twice.
RUNTIME DELTA: zero. One test file's engine list and a CHANGELOG line; no
pyg-bench lane run required (CB5), checkable from the diff.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB
…cing its 10-minute timeout (#1814) * ci(polars): parallelize the polars lane so the coverage cell stops racing its timeout `test-polars (3.12)` is the only cell that runs the coverage-instrumented pass plus the per-file coverage audit, and it had already been cancelled twice at its 10-minute budget (615s, 611s) with zero test failures — pure budget exhaustion, the same commit passing or failing depending on the runner it drew. Measured on the master run that produced this base commit, the script step alone is 484s of the cell's 501s. The lever that needs no workflow edit: pytest-xdist is already in the [test] extra, and test-gfql-core already runs `-n auto` under `--cov`, so coverage+xdist is established here. The main pytest phase now runs `-n auto --maxprocesses 4 --dist load`. Measured (py3.12, polars 1.43.1, pandas 3.0.5, POLARS_COV=1), pinned to 4 CPUs to stand in for a GitHub-hosted ubuntu-latest runner: 325.65s serial -> 109.34s parallel, 2.98x. Verified, not assumed: - pass/skip NODE-ID SETS (not counts) identical serial vs parallel: 2417 node ids, 2404 passed / 13 skipped, across 4-worker, 2-worker, --dist loadfile and 4-CPU-pinned runs - merged coverage is a strict superset of serial (28,480 vs 28,478 covered lines; zero lines lost, zero files dropped; the 2 extra are versioneer fallback lines in _version.py) - the second `--cov-append` invocation still appends into the xdist-produced data file (+1,791 lines, nothing lost) and the result is line-identical to the whole-script run - `bin/coverage_audit.py --profile gfql-polars` emits a byte-identical report (modulo its timestamp) from the parallel data, so `changed-line-coverage` sees no change `--dist load` rather than `loadfile` because one module (test_engine_polars_chain.py) is 69% of the lane's test time, which caps file-level distribution at ~1.4x; no test in this lane depends on execution order or cross-test module state. POLARS_XDIST=0 restores the serial path and POLARS_XDIST_DIST selects another distribution mode if that ever changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB * docs(changelog): quote the MEASURED CI number, not the local one The first commit quoted a local 4-CPU-pinned A/B (326s -> 109s, 2.98x). CI has now run this branch and realizes less: the `test-polars (3.12)` cell goes 501s -> 322s and its script step 484s -> 297s (1.63x), with the five non-coverage cells at 222-318s -> 115-172s. Coverage tracing is per-worker CPU cost that does not parallelize away, and the ~8s serial second phase plus 4x interpreter startup are fixed, so the coverage cell gains less than the plain cells (which see ~2x). The CI figure is the one that decides whether #1805 fits, so it is the one the changelog quotes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Resolves both conflicts and corrects two defects the conflict exposed. bin/test-polars.sh -- take master's version unchanged. This branch adds graphistry/tests/compute/gfql/test_rows_table_named_middle.py and test_rewrite_param_discard.py to POLARS_TEST_FILES, but #1805 (49db91c, this branch's own merge-base) already added both. Keeping the branch side would list each file twice, so the polars lane would run both modules twice -- and that lane's py3.12 cell is the coverage cell, currently 442s against a 600s cap. Master's version also carries #1814's xdist plumbing, which the branch side predates. CHANGELOG.md -- the entry claimed "neither file was in bin/test-polars.sh, so test_rows_table_named_middle.py ... ran in no CI lane at all". That is false at this branch's base: both files are listed there at lines 43 and 50. Rewritten to the residual that is true -- the suites were parametrized over pandas + polars only, so cuDF and polars-gpu never exercised the table guards; and the module-level pytest.importorskip("polars") skipped the whole file in test-gfql-core, so its pandas cases ran only in the polars lane. The measured claims (45 passed / 3 xfailed / 0 skipped on GB10; 24 mutation failures, 6 per engine) are unchanged. Also types engine_skip_reason, per review: the engine -> required-modules map is hoisted to a module-level Mapping[str, Tuple[str, ...]] constant rather than rebuilt inline per call, the signature is (engine: str, smoke: Callable[[], object]) -> Optional[str], and gpu_environment_reason's "str | None" string annotation is normalized to Optional[str] to match (this file is imported under py3.8 lanes). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YYZRXegrALuXd3NHH5evqx
The finding
A full local suite on py3.11 + polars reported 3 failures that reproduce identically on
origin/master(233b64c8). "Pre-existing, not mine" was the tempting read. It is the wrongone: all three fail because CI cannot see them, and one of them is a real product defect.
polars is installed in exactly one CI lane —
test-polars, driven bybin/test-polars.sh,which runs a hand-maintained file list. Every other lane collection-skips a module-level
pytest.importorskip("polars")and everypolars/polars-gpuparameter. So a polars-gatedtest the list omits runs nowhere, and both lanes report green about it.
Measured against every job log of run
30311675717 (PR #1799):
280 tests executed locally with polars installed and appear in ZERO CI job logs.
The three failures, and the cause of each
index/test_indexed_bindings.py::test_explicit_polars_gpu_declines_indexed_helper_and_falls_backImportError: GFQL engine='polars-gpu' requires the RAPIDS cudf_polars stack(chain.py:1005)engine='polars-gpu'but gates only onimportorskip("polars")test_viz_pipeline_conformance.py::test_case_regex_unicode_trick_matrix[toupper-eq-ss-fold]toupper-eq-ss-fold: hand pin disagrees with the pandas mirror; assert [9] == [8, 9]…::test_case_regex_unicode_trick_matrix[tolower-turkish-dotted-i]tolower-turkish-dotted-i: hand pin disagrees with the pandas mirror; assert [] == [7]#1 — evidence.
grepover all job logs: the test appears 9 times, every oneSKIPPED, zeroexecutions. It is collection-visible in the pandas lanes (
test-gfql-corecollectsgraphistry/tests/compute/gfql) but skips there for want of polars, and its file is not inbin/test-polars.sh, so the one lane that has polars never sees it. It has never run.#2/#3 — evidence. These are not environment noise. pandas>=3 makes the Arrow-backed
strdtype the default, and Arrow's
utf8_upper/utf8_lowerare simple per-codepoint mappings,where Python's (and pandas<3's object dtype) are full:
row/pipeline.py:1521implements CyphertoLower/toUpperwith that accessor, so:A silent cross-engine divergence: no decline, no NIE, just different rows — precisely what
these conformance suites exist to forbid. Filed as #1802 with the repro and a sizing of the
fix. CI's own
test-py3.12.lockpinspandas==3.0.3, so this is not an exotic localenvironment; it is the environment CI runs. The test that catches it simply never executes.
Sibling inventory — the cause is structural
280 tests, in 10 files, ran in no CI job of that run:
test_engine_polars_narrow_combine.pyindex/test_indexed_bindings.pytest_engine_polars_semi_key_dedup.pytest_viz_pipeline_conformance.pytest_engine_coercion.pytest_engine_polars_call_modality.pytest_rows_table_named_middle.pytest_reentry_caller_graph_immutability.pytest_rewrite_param_discard.pycypher/test_lowering.py-k polarsphase, but the filter cannot select themSix whole modules were invisible, four of them named
test_engine_polars_*. Also confirmedinvisible for a different reason and left alone here:
test_engine_polars_gpu.pyandindex/test_index_gpu_edge_match.pyare GPU-gated — a separate lane gap.What this changes
bin/test-polars.sh— adds the six invisible modules and the four whose polars parameterswere invisible. 277 of the 280 now execute in the lane; the other 3 are the two renamed
test_lowering.pytests (now selected by-k polars) and the polars-gpu test (now skippinghonestly, its CPU half split out and running).
test_polars_lane_completeness.py(new) — the durable part. It parsesPOLARS_TEST_FILESand fails when: a polars-mentioning test module is neither in the lane nor in a documented
exemption; a module-level polars gate sits outside the lane; a listed path no longer exists; a
polars-gated test in the
-k polarsphase is unselectable by that filter; or an exemption goesstale. Exemptions carry written reasons and are length-checked, so "" cannot re-open the hole.
Mutation-checked: removing one entry from the array turns two of its assertions red.
try_indexed_connected_bindings_statemustdecline for
Engine.POLARS_GPU) now runs wherever polars does; the execution half isskipif-gated oncudf_polars, mirroringchain.py's own check.xfail(strict=True), keyed on a probe of the pandas accessor's ownbehaviour — so they must still PASS on pandas<3, and the GFQL: pandas vs polars silently disagree on toUpper/toLower under pandas>=3 (simple vs full Unicode case mapping) #1802 fix turns them into an XPASS
that forces the marks to be retired. The hand pins stay at the Cypher-correct values.
test_lowering.pypolars tests that-k polarscould not select.Test-only. No product code touched, hence no CHANGELOG entry.
Verification — CI green, 77/77
test-polarsgreen on all six python versions, and the behaviour is version-discriminatingrather than suppressed:
pins correct, xfail condition correctly not applied.
test_explicit_polars_gpu_declines_indexed_helperPASSED; the execution halfSKIPPED for want of cudf_polars — instead of the ImportError it used to raise wherever it
was actually run.
-k polarsphase 51 passed (was 49). The polars per-file coverage audit passes unchanged.mutation-tested (dropping one array entry turns two of its assertions red), not just
observed green.
ruff checkclean;mypyreports no error in the new file.Ordering dependency — #1797's workflow patch should land first
It passed, but by three seconds. Measured job durations for
test-polars (3.12), the cellthat runs the suite under coverage:
233b64c8)timeout-minutes: 10timeout-minutes: 10Master was already at 84% of that budget before this change. #1797 records that adding a single
file previously tipped this cell into a timeout — which GitHub reports as
cancelled, with zerotest failures in the log. The pending
ci.ymlsplit in the comment on #1794 (a dedicatedtest-polars-coveragejob attimeout-minutes: 20) should land before this, or the nextpolars test — or a slightly slower runner — cancels the cell rather than failing it.
This PR changes no workflow file; that patch is untouched here.
🤖 Generated with Claude Code
https://claude.ai/code/session_015YsqAZQLbqjSDrYSFz2GoB