Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
94ca9ec
Add array-api-strict test backend and array-escape triage tooling
mwcraig Jul 7, 2026
585418a
Do not cancel remaining CI jobs when one fails
mwcraig Jul 7, 2026
b51f903
Use continue-on-error for the strict array-API job instead of fail-fast
mwcraig Jul 7, 2026
8eddeb8
Move the strict array-API job into its own CI matrix
mwcraig Jul 7, 2026
cc32380
Address review comments
mwcraig Jul 7, 2026
b5bf86e
Enable the escape-triage summary in the strict tox environment
mwcraig Jul 7, 2026
fdf651e
Anchor escape-triage frame detection on the package directory
mwcraig Jul 7, 2026
a9313a6
Fix escape-logger bugs and apply agreed review changes
mwcraig Jul 8, 2026
cf4f4b7
Mark tests failing on the jax backend as backend_xfail
mwcraig Jul 8, 2026
30126fb
Add local-only escapes tox factor for dask and jax runs
mwcraig Jul 8, 2026
eed1fb0
Add array-API escape baseline ratchet and dedup escape-log summary
mwcraig Jul 9, 2026
74574cc
Keep workflow green when the expected-failures strict job fails
mwcraig Jul 11, 2026
b564834
Report strict-job status via neutral check instead of failing the rollup
mwcraig Jul 12, 2026
b00a459
Guard escape-baseline write/enforce modes against a disabled logger
mwcraig Jul 14, 2026
f3d583d
Harden the strict-status reporting pipeline
mwcraig Jul 14, 2026
115475d
Document the escape-baseline ratchet and fix changelog entries
mwcraig Jul 14, 2026
591a83b
Restore jax xfail on test_generator_ccds_without_unit
mwcraig Jul 14, 2026
98a6ec1
Document triage-tooling limitations and guard the ratchet against xdist
mwcraig Jul 14, 2026
d329ac5
Address inline review comments: comments, docstrings, width computation
mwcraig Jul 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .github/workflows/ci_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ jobs:
python: '3.12'
tox_env: 'py312-alldeps-dask'

# Regression gate: fail if a new array-API "escape" (a silent numpy
# coercion of a dask array in library code) appears that is not in
# the checked-in baseline, ccdproc/tests/array_escape_baseline.txt.
# No bottleneck here (as with the dask job above) so the numpy
# fallback paths are exercised.
- name: 'ubuntu-py312-dask-escape-baseline'
os: ubuntu-latest
python: '3.12'
tox_env: 'py312-alldeps-dask-enforce'

- name: 'windows-py312'
os: windows-latest
python: '3.12'
Expand Down Expand Up @@ -108,3 +118,77 @@ jobs:
- name: Upload coverage to codecov
if: "endsWith(matrix.tox_env, '-cov')"
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2

# The strict array-API job is in its own matrix so that its failures --
# expected until the remaining array-API bugs are fixed -- stay visible
# without cancelling the main test matrix above. The test step uses
# continue-on-error so the job (and the PR checks rollup) stays green;
# the real outcome is reported as a warning annotation, in the step
# summary, and via a per-matrix-entry strict-job-outcome-* artifact that
# strict_status.yml turns into a check run on the PR.
ci-tests-expected-failures:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
if: "!(contains(github.event.head_commit.message, '[skip ci]') || contains(github.event.head_commit.message, '[ci skip]'))"
strategy:
matrix:
include:
- name: 'ubuntu-py313-strict'
os: ubuntu-latest
python: '3.13'
tox_env: 'py313-strict'

steps:
- name: Check out repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
fetch-depth: 0
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: ${{ matrix.python }}
- name: Install base dependencies
run: |
python -m pip install --upgrade pip
python -m pip install tox wheel
- name: Print Python env
run: |
python --version
python -m pip list
- name: Run tests
id: tests
continue-on-error: true
run: |
tox -e ${{ matrix.tox_env }} -- ${{ matrix.toxposargs }}
# always() so an outcome file is recorded even when an earlier step
# failed; a skipped/cancelled tests step is recorded as "error" (an
# infrastructure failure) rather than being conflated with an expected
# test failure. The JSON schema here is the contract with
# strict_status.yml: {"name": "<matrix name>", "state": "success" |
# "failure" | "error"}.
- name: Record test outcome
if: always()
run: |
case "${{ steps.tests.outcome }}" in
success) state='success' ;;
failure) state='failure' ;;
*) state='error' ;;
esac
printf '{"name": "%s", "state": "%s"}\n' '${{ matrix.name }}' "$state" > strict-outcome.json
if [ "$state" = "failure" ]; then
echo "::warning::${{ matrix.tox_env }} tests failed (expected until the remaining array-API bugs are fixed)"
echo ":warning: **${{ matrix.name }}**: tests **failed** (expected until the remaining array-API bugs are fixed)" >> "$GITHUB_STEP_SUMMARY"
elif [ "$state" = "success" ]; then
echo ":tada: **${{ matrix.name }}**: tests **passed** -- the expected-failures carve-out for this job can be retired" >> "$GITHUB_STEP_SUMMARY"
else
echo "::warning::${{ matrix.name }} job hit an infrastructure error before the tests step completed (outcome: ${{ steps.tests.outcome }})"
echo ":x: **${{ matrix.name }}**: job hit an **infrastructure error** before the tests step completed (outcome: ${{ steps.tests.outcome }})" >> "$GITHUB_STEP_SUMMARY"
fi
- name: Upload test outcome
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # 6.0.0
with:
# Include the matrix entry name so the artifact names stay unique if
# this matrix grows (upload-artifact v4+ errors on duplicate names).
name: strict-job-outcome-${{ matrix.name }}
path: strict-outcome.json
121 changes: 121 additions & 0 deletions .github/workflows/strict_status.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Turn the outcome of the expected-failures strict array-API jobs into
# check runs on the PR. This runs as a separate workflow_run workflow because
# pull_request runs for fork PRs get a read-only GITHUB_TOKEN and cannot
# create check runs themselves. Each check uses a stable name (the matrix
# entry name); the outcome is carried by the conclusion and output instead.
# These checks are informational only (an expected test failure is reported
# as neutral, so the PR rollup stays green) and are not intended to be
# required checks while the failures are expected.
#
# Note: workflow_run workflows execute from the default branch, so changes to
# this file only take effect once merged.
name: Strict array API status

on:
workflow_run:
workflows: ["CI"]
types: [completed]

permissions:
checks: write
actions: read

jobs:
report:
# Only report on pull_request runs. CI also triggers on push (all
# branches) and schedule, so without this gate a same-repo PR branch gets
# two completed CI runs per push and this workflow would post duplicate
# (possibly contradictory) checks on the same head SHA.
if: github.event.workflow_run.event == 'pull_request'
runs-on: ubuntu-latest
steps:
- name: Download strict job outcomes
id: download
# The artifacts are missing for CI runs from before the record/upload
# steps became `if: always()` (or when the whole CI run was skipped);
# tolerate that and skip the check runs below. Runs after that change
# should always upload an outcome artifact per matrix entry.
continue-on-error: true
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # 7.0.0
with:
pattern: strict-job-outcome-*
merge-multiple: false
path: strict-outcomes
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Create check runs
if: steps.download.outcome == 'success'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
script: |
const fs = require('fs');
const path = require('path');
const run = context.payload.workflow_run;
const root = 'strict-outcomes';
// One directory per artifact, i.e. per expected-failures matrix
// entry. Each contains a strict-outcome.json written by ci_tests.yml
// with the schema {"name": "<matrix name>",
// "state": "success" | "failure" | "error"}.
let dirs = [];
try {
dirs = fs.readdirSync(root).filter((d) => d.startsWith('strict-job-outcome-'));
} catch (err) {
core.info(`No outcome artifacts downloaded (${err.message}); nothing to report.`);
return;
}
if (dirs.length === 0) {
core.info('No strict-job-outcome-* artifacts found; nothing to report.');
return;
}
for (const dir of dirs) {
// Fall back to the artifact name and an error state if the JSON
// is missing or malformed, so a broken upload still surfaces.
let name = dir.replace('strict-job-outcome-', '');
let state = 'error';
try {
const data = JSON.parse(fs.readFileSync(path.join(root, dir, 'strict-outcome.json'), 'utf8'));
if (typeof data.name === 'string' && data.name) {
name = data.name;
}
if (['success', 'failure', 'error'].includes(data.state)) {
state = data.state;
} else {
core.warning(`Unrecognized state ${JSON.stringify(data.state)} in ${dir}; reporting an error state.`);
}
} catch (err) {
core.warning(`Could not read outcome JSON in ${dir} (${err.message}); reporting an error state.`);
}
let conclusion;
let title;
let summary;
if (state === 'success') {
conclusion = 'success';
title = 'Strict array-API tests passed';
summary = `The ${name} job in [this CI run](${run.html_url}) passed. ` +
'The expected-failures carve-out for it can be retired.';
} else if (state === 'failure') {
conclusion = 'neutral';
title = 'Strict array-API tests failed (expected)';
summary = `The ${name} job in [this CI run](${run.html_url}) failed. ` +
'This is expected until the remaining array-API bugs are fixed; ' +
'see the run log for details.';
} else {
conclusion = 'failure';
title = 'Strict array-API job broke (infra error)';
summary = `The ${name} job in [this CI run](${run.html_url}) did not ` +
'complete its tests step. This is an infrastructure error in the ' +
'job itself, not a test failure; see the run log for details.';
}
// Stable check name (just the matrix entry name); the outcome
// lives in the conclusion and output so the checks UI does not
// accumulate differently-named checks per outcome.
await github.rest.checks.create({
owner: context.repo.owner,
repo: context.repo.repo,
name,
head_sha: run.head_sha,
status: 'completed',
conclusion,
output: { title, summary },
});
}
13 changes: 13 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
2.6.0 (unreleased)
------------------

New Features
^^^^^^^^^^^^

- Add ``array-api-strict`` (on a non-default device) as a test backend, a
CPU-only proxy for CuPy device behavior. [#942]
- Add triage tooling for array-API backend failures: ``backend_xfail`` /
``backend_skip`` markers, an escape-site report
(``CCDPROC_TRIAGE_ESCAPES=1``), and an escape logger
(``CCDPROC_LOG_ARRAY_ESCAPES=1``). [#942]

2.5.1 (2025-07-05)
------------------

Expand Down
Loading
Loading