From 62a7f24bc23b722a6152c921512988d7b7b1a472 Mon Sep 17 00:00:00 2001 From: Vyas Ramasubramani Date: Thu, 8 Oct 2026 13:01:07 -0700 Subject: [PATCH 1/2] Allow callers to reuse resolved Python test matrix snapshots --- .github/actionlint.yaml | 4 + .../prepare-matrix-dispatch/action.yaml | 5 + .github/actions/prepare-matrix/compute.sh | 87 ++++++++++++ .github/workflows/compute-matrix.yaml | 100 +++----------- .github/workflows/conda-python-tests.yaml | 7 +- .../resolve-python-test-matrices.yaml | 46 +++++++ .github/workflows/wheels-test.yaml | 7 +- .pre-commit-config.yaml | 11 +- README.md | 15 +++ ci/test_compute_matrix.py | 124 ++++++++++++++++++ 10 files changed, 319 insertions(+), 87 deletions(-) create mode 100644 .github/actions/prepare-matrix/compute.sh create mode 100644 .github/workflows/resolve-python-test-matrices.yaml create mode 100644 ci/test_compute_matrix.py diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index 72389da6..46eb5a67 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -12,6 +12,10 @@ config-variables: - TELEMETRY_ENABLED paths: + .github/workflows/resolve-python-test-matrices.yaml: + ignore: + # https://github.com/rhysd/actionlint/issues/711 + - 'specifying action "\$/.github/actions/prepare-matrix-dispatch" in invalid format because ref is missing. available formats are "{owner}/{repo}@{ref}" or "{owner}/{repo}/{path}@{ref}"' .github/workflows/compute-matrix.yaml: ignore: # https://github.com/rhysd/actionlint/issues/711 diff --git a/.github/actions/prepare-matrix-dispatch/action.yaml b/.github/actions/prepare-matrix-dispatch/action.yaml index 0c85b9e5..32493ab6 100644 --- a/.github/actions/prepare-matrix-dispatch/action.yaml +++ b/.github/actions/prepare-matrix-dispatch/action.yaml @@ -8,6 +8,10 @@ inputs: action-ref: description: Action ref. required: true + resolved-matrix: + description: When supplied, only check out the implementation; the caller already resolved its matrix snapshot. + required: false + default: '' outputs: matrix: @@ -26,4 +30,5 @@ runs: persist-credentials: false - id: prepare-matrix name: Prepare Build Matrix + if: inputs.resolved-matrix == '' uses: ./shared-workflows/.github/actions/prepare-matrix diff --git a/.github/actions/prepare-matrix/compute.sh b/.github/actions/prepare-matrix/compute.sh new file mode 100644 index 00000000..c1e3cafe --- /dev/null +++ b/.github/actions/prepare-matrix/compute.sh @@ -0,0 +1,87 @@ +#!/bin/bash +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +set -euo pipefail + +RESOLVED_MATRIX=${RESOLVED_MATRIX:-} +MATRIX_FILTER=${MATRIX_FILTER:-.} +PURE_WHEEL=${PURE_WHEEL:-false} +PURE_CONDA=${PURE_CONDA:-false} + +case "$BUILD_TYPE" in + branch|nightly|pull-request|release-candidate) ;; + *) echo "::error::Invalid build_type: $BUILD_TYPE"; exit 1 ;; +esac + +if [[ -n "$RESOLVED_MATRIX" ]]; then + if ! jq -en ' + $ENV.RESOLVED_MATRIX | fromjson + | type == "object" and (keys == ["include"]) + and (.include | type == "array" and length > 0 + and all(.[]; type == "object")) + ' >/dev/null; then + echo '::error::resolved-matrix must be a nonempty {"include": [...]} matrix' + exit 1 + fi + BASE_MATRIX=$(jq -cn '$ENV.RESOLVED_MATRIX | fromjson | .include') +else + if ! jq -en '$ENV.MATRIX | fromjson | has($ENV.MATRIX_NAME)' >/dev/null; then + echo "::error::Invalid matrix_name: $MATRIX_NAME" + exit 1 + fi + if [[ "$MATRIX_TYPE" == auto ]]; then + case "$BUILD_TYPE" in + branch|release-candidate) MATRIX_TYPE=nightly ;; + *) MATRIX_TYPE=$BUILD_TYPE ;; + esac + fi + export MATRIX_TYPES + MATRIX_TYPES=$(jq -cn --arg types "$MATRIX_TYPE" '$types | split(",")') + if ! jq -en ' + ($ENV.MATRIX | fromjson)[$ENV.MATRIX_NAME] as $matrix + | $ENV.MATRIX_TYPES | fromjson + | length > 0 and all(.[]; . as $type | $matrix | has($type)) + ' >/dev/null; then + echo "::error::Invalid matrix_type: $MATRIX_TYPE" + exit 1 + fi + BASE_MATRIX=$(jq -cn ' + ($ENV.MATRIX | fromjson)[$ENV.MATRIX_NAME] as $matrix + | [$ENV.MATRIX_TYPES | fromjson | .[] as $type | $matrix[$type][]] + | reduce .[] as $entry ([]; if index($entry) == null then . + [$entry] else . end) + ') +fi +export BASE_MATRIX + +# Preserve the existing default selectors for build workflows. +if [[ "$MATRIX_FILTER" == . ]]; then + if [[ "$PURE_WHEEL" == true ]]; then + MATRIX_FILTER='map(select(.ARCH == "amd64")) | group_by(.CUDA_VER) | map(max_by(.PY_VER | split(".") | map(tonumber)))' + elif [[ "$PURE_CONDA" == true ]]; then + MATRIX_FILTER='map(select(.ARCH == "amd64")) | sort_by(.CUDA_VER, .PY_VER) | [last]' + elif [[ "$PURE_CONDA" == cuda_major ]]; then + MATRIX_FILTER='map(select(.ARCH == "amd64")) | group_by(.CUDA_VER|split(".")|map(tonumber)|.[0]) | map(max_by([(.PY_VER|split(".")|map(tonumber)), (.CUDA_VER|split(".")|map(tonumber))]))' + fi +fi + +if ! RESULT=$(jq -ecn " + \$ENV.BASE_MATRIX | fromjson | ${MATRIX_FILTER} + | if type == \"array\" and length > 0 and all(.[]; type == \"object\") + then {include: .} else error(\"Empty or invalid matrix\") end +"); then + echo '::error::Empty or invalid matrix' + exit 1 +fi + +if [[ "$MATRIX_NAME" == wheels-test || "$MATRIX_NAME" == conda-python-tests ]]; then + if ! jq -e ' + .include | all(.[]; + . as $entry | ["ARCH", "CUDA_VER", "PY_VER", "LINUX_VER", "GPU", "DRIVER", "DEPENDENCIES"] + | all(.[]; $entry[.] | type == "string" and length > 0)) + ' <<< "$RESULT" >/dev/null; then + echo '::error::Python test matrix entries must contain all runner and environment fields' + exit 1 + fi +fi + +echo "matrix=$RESULT" | tee --append "$GITHUB_OUTPUT" diff --git a/.github/workflows/compute-matrix.yaml b/.github/workflows/compute-matrix.yaml index eeeecba3..32c99d16 100644 --- a/.github/workflows/compute-matrix.yaml +++ b/.github/workflows/compute-matrix.yaml @@ -6,45 +6,35 @@ on: required: true type: string matrix_name: - description: "One of: [conda-cpp-build, conda-cpp-tests, conda-python-build, conda-python-tests, wheels-build, wheels-test]" + description: Name of the workflow matrix in prepare-matrix/matrix.yaml. required: true type: string matrix_type: - description: "One or more comma-separated matrix types defined for this workflow's matrix in .github/actions/prepare-matrix/matrix.yaml, e.g. 'pull-request' or 'nightly'. Defaults to 'auto', which must be used alone and means 'choose a value based on what's provided via build_type'." - required: false + description: Comma-separated matrix types, or auto to select by build_type. type: string - default: "auto" + default: auto matrix_filter: - description: | - jq expression which modifies the matrix. - For example, 'map(select(.ARCH == "amd64"))' to achieve "only run amd64 jobs". + description: jq expression which modifies the matrix entry array. type: string default: "." + resolved-matrix: + description: Optional nonempty JSON include matrix. Reuses the caller's snapshot instead of resolving current definitions; matrix_filter still applies. + type: string + default: '' pure_wheel: - description: "One of [true, false], true if the wheel is not dependent on operating system, Python minor version, or CPU architecture" - required: false + description: Whether the wheel is independent of OS, Python, and architecture. type: boolean default: false pure-conda: - required: false + description: "One of: [true, false, cuda_major]" type: string default: "false" - description: | - One of [true, false, cuda_major]. 'true' if the conda package is not - dependent on operating system, Python minor version, CPU architecture, - or CUDA version. 'cuda_major' if the package is not dependent on - operating system, Python minor version, or CPU architecture, but is - dependent on CUDA major version. outputs: matrix: value: ${{ jobs.compute-matrix.outputs.matrix }} jobs: compute-matrix: runs-on: ubuntu-latest - env: - BUILD_TYPE: ${{ inputs.build_type }} - MATRIX_NAME: ${{ inputs.matrix_name }} - MATRIX_TYPE: ${{ inputs.matrix_type }} outputs: matrix: ${{ steps.compute-matrix.outputs.matrix }} steps: @@ -54,74 +44,16 @@ jobs: with: action-repo: ${{ github.action_repository }} action-ref: ${{ github.action_ref }} - - name: Validate Inputs - env: - MATRIX: ${{ steps.prepare-matrix.outputs.matrix }} - run: | - if [[ "$BUILD_TYPE" != "branch" ]] && [[ "$BUILD_TYPE" != "nightly" ]] && [[ "$BUILD_TYPE" != "pull-request" ]] && [[ "$BUILD_TYPE" != "release-candidate" ]]; then - echo "::error::Invalid build_type! Must be one of 'branch', 'nightly', 'pull-request', or 'release-candidate'." - exit 1 - fi - if ! jq -n -e '$ENV.MATRIX | fromjson | has($ENV.MATRIX_NAME)'; then - echo "::error::Invalid matrix_name! Must be one of '$(jq -n -r "\$ENV.MATRIX | fromjson | keys | join(\"', '\")")'." - exit 1 - fi - if [[ "$MATRIX_TYPE" != "auto" ]] && ! jq -n -e '($ENV.MATRIX | fromjson)[$ENV.MATRIX_NAME] as $matrix | $ENV.MATRIX_TYPE | split(",") | length > 0 and all(. as $matrix_type | $matrix | has($matrix_type))'; then - echo "::error::Invalid matrix_type! Must be 'auto' or a comma-separated list of matrix types defined for '${MATRIX_NAME}': $(jq -n -r '($ENV.MATRIX | fromjson)[$ENV.MATRIX_NAME] | keys | join(", ")')." - exit 1 - fi + resolved-matrix: ${{ inputs.resolved-matrix }} - name: Compute Build Matrix id: compute-matrix env: + BUILD_TYPE: ${{ inputs.build_type }} + MATRIX_NAME: ${{ inputs.matrix_name }} + MATRIX_TYPE: ${{ inputs.matrix_type }} MATRIX: ${{ steps.prepare-matrix.outputs.matrix }} + RESOLVED_MATRIX: ${{ inputs.resolved-matrix }} MATRIX_FILTER: ${{ inputs.matrix_filter }} PURE_WHEEL: ${{ inputs.pure_wheel }} PURE_CONDA: ${{ inputs.pure-conda }} - run: | - set -eo pipefail - - # only overwrite MATRIX_TYPE if it was set to 'auto' - if [[ "${MATRIX_TYPE}" == "auto" ]]; then - if [[ "${BUILD_TYPE}" == "branch" || "${BUILD_TYPE}" == "release-candidate" ]]; then - # Use the nightly matrix for branch tests - MATRIX_TYPE="nightly" - else - MATRIX_TYPE="${BUILD_TYPE}" - fi - fi - MATRIX_TYPES="$(jq -cn --arg matrix_types "${MATRIX_TYPE}" '$matrix_types | split(",")')" - export MATRIX_TYPES - - # When pure-wheel is true and matrix_filter is default, override to build one wheel per CUDA version with amd64 and the latest PY_VER - if [ "${PURE_WHEEL}" = "true" ] && [ "${MATRIX_FILTER}" = "." ]; then - MATRIX_FILTER="map(select(.ARCH == \"amd64\")) | group_by(.CUDA_VER) | map(max_by(.PY_VER | split(\".\") | map(tonumber)))" - fi - - # When pure-conda is true and matrix_filter is default, override to build one conda package with amd64, latest CUDA_VER, and the latest PY_VER - if [ "${PURE_CONDA}" = "true" ] && [ "${MATRIX_FILTER}" = "." ]; then - MATRIX_FILTER="map(select(.ARCH == \"amd64\")) | sort_by(.CUDA_VER, .PY_VER) | [last]" - elif [ "${PURE_CONDA}" = "cuda_major" ] && [ "${MATRIX_FILTER}" = "." ]; then - MATRIX_FILTER="map(select(.ARCH == \"amd64\")) | group_by(.CUDA_VER|split(\".\")|map(tonumber)|.[0]) | map(max_by([(.PY_VER|split(\".\")|map(tonumber)), (.CUDA_VER|split(\".\")|map(tonumber))]))" - fi - - if ! MATRIX="$( - jq -n -c " - \$ENV.MATRIX | fromjson as \$matrix - | [ - \$ENV.MATRIX_TYPES | fromjson - | .[] as \$matrix_type - | \$matrix[\$ENV.MATRIX_NAME][\$matrix_type][] - ] - | reduce .[] as \$entry ( - []; - if index(\$entry) == null then . + [\$entry] else . end - ) - | ${MATRIX_FILTER} - | if (. | length) > 0 then {include: .} else \"\" | halt_error(1) end - " - )"; then - echo "::error::Empty matrix" - exit 1 - fi - - echo "matrix=${MATRIX}" | tee --append "${GITHUB_OUTPUT}" + run: bash shared-workflows/.github/actions/prepare-matrix/compute.sh diff --git a/.github/workflows/conda-python-tests.yaml b/.github/workflows/conda-python-tests.yaml index e16dd0f0..28b358e9 100644 --- a/.github/workflows/conda-python-tests.yaml +++ b/.github/workflows/conda-python-tests.yaml @@ -37,6 +37,10 @@ on: For example, 'map(select(.ARCH == "amd64"))' to achieve "only run amd64 jobs". type: string default: "." + resolved-matrix: + description: Optional JSON include matrix from a previously resolved snapshot. matrix_filter still applies. + type: string + default: '' continue-on-error: description: | If true, job failures do not result in workflow failures (useful for implementing @@ -196,6 +200,7 @@ jobs: matrix_name: conda-python-tests matrix_type: ${{ inputs.matrix_type }} matrix_filter: ${{ inputs.matrix_filter }} + resolved-matrix: ${{ inputs.resolved-matrix }} tests: name: ${{ matrix.CUDA_VER }}, ${{ matrix.PY_VER }}, ${{ matrix.ARCH }}, ${{ matrix.LINUX_VER }}, ${{ matrix.GPU }}, ${{ matrix.DRIVER }}-driver, ${{ matrix.DEPENDENCIES }}-deps needs: compute-matrix @@ -263,7 +268,7 @@ jobs: extra_attributes: "rapids.PACKAGER=conda,rapids.CUDA_VER=${{ matrix.CUDA_VER }},rapids.PY_VER=${{ matrix.PY_VER }},rapids.ARCH=${{ matrix.ARCH }},rapids.LINUX_VER=${{ matrix.LINUX_VER }},rapids.GPU=${{ matrix.GPU }},rapids.DRIVER=${{ matrix.DRIVER }},rapids.DEPENDENCIES=${{ matrix.DEPENDENCIES }}" - name: Setup proxy cache - uses: nv-gha-runners/setup-proxy-cache@main + uses: nv-gha-runners/setup-proxy-cache@eadc57871cde0c92a223d0f486ab669a2360f978 # main continue-on-error: true with: enable-apt: true diff --git a/.github/workflows/resolve-python-test-matrices.yaml b/.github/workflows/resolve-python-test-matrices.yaml new file mode 100644 index 00000000..37c73089 --- /dev/null +++ b/.github/workflows/resolve-python-test-matrices.yaml @@ -0,0 +1,46 @@ +name: Resolve Python test matrix snapshot +on: + workflow_call: + inputs: + build_type: + type: string + required: true + matrix_type: + type: string + default: auto + outputs: + wheels-test: + value: ${{ jobs.resolve.outputs.wheels-test }} + conda-python-tests: + value: ${{ jobs.resolve.outputs.conda-python-tests }} +permissions: + contents: read +jobs: + resolve: + runs-on: ubuntu-latest + outputs: + wheels-test: ${{ steps.wheels.outputs.matrix }} + conda-python-tests: ${{ steps.conda.outputs.matrix }} + steps: + - name: Prepare matrix definitions once + id: prepare + uses: $/.github/actions/prepare-matrix-dispatch + with: + action-repo: ${{ github.action_repository }} + action-ref: ${{ github.action_ref }} + - name: Resolve wheel test matrix + id: wheels + env: + BUILD_TYPE: ${{ inputs.build_type }} + MATRIX_TYPE: ${{ inputs.matrix_type }} + MATRIX_NAME: wheels-test + MATRIX: ${{ steps.prepare.outputs.matrix }} + run: bash shared-workflows/.github/actions/prepare-matrix/compute.sh + - name: Resolve Conda Python test matrix + id: conda + env: + BUILD_TYPE: ${{ inputs.build_type }} + MATRIX_TYPE: ${{ inputs.matrix_type }} + MATRIX_NAME: conda-python-tests + MATRIX: ${{ steps.prepare.outputs.matrix }} + run: bash shared-workflows/.github/actions/prepare-matrix/compute.sh diff --git a/.github/workflows/wheels-test.yaml b/.github/workflows/wheels-test.yaml index ace21b01..c483f5f2 100644 --- a/.github/workflows/wheels-test.yaml +++ b/.github/workflows/wheels-test.yaml @@ -36,6 +36,10 @@ on: For example, 'map(select(.ARCH == "amd64"))' to achieve "only run amd64 jobs". type: string default: "." + resolved-matrix: + description: Optional JSON include matrix from a previously resolved snapshot. matrix_filter still applies. + type: string + default: '' container-options: description: | Command-line arguments passed to 'docker run' when starting the container this workflow runs in. @@ -202,6 +206,7 @@ jobs: matrix_name: wheels-test matrix_type: ${{ inputs.matrix_type }} matrix_filter: ${{ inputs.matrix_filter }} + resolved-matrix: ${{ inputs.resolved-matrix }} test: name: ${{ matrix.CUDA_VER }}, ${{ matrix.PY_VER }}, ${{ matrix.ARCH }}, ${{ matrix.LINUX_VER }}, ${{ matrix.GPU }}, ${{ matrix.DRIVER }}-driver, ${{ matrix.DEPENDENCIES }}-deps needs: compute-matrix @@ -269,7 +274,7 @@ jobs: build_workflow_name: ${{ inputs.build_workflow_name }} - name: Setup proxy cache - uses: nv-gha-runners/setup-proxy-cache@main + uses: nv-gha-runners/setup-proxy-cache@eadc57871cde0c92a223d0f486ab669a2360f978 # main continue-on-error: true with: enable-apt: true diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f08de3b9..de5af49b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,6 +6,15 @@ ci: skip: [actionlint-docker] repos: + - repo: local + hooks: + - id: test-compute-matrix + name: Test shared matrix resolution + language: python + additional_dependencies: [PyYAML==6.0.3] + entry: python -m unittest discover -s ci -p test_compute_matrix.py + pass_filenames: false + files: ^(ci/test_compute_matrix\.py|\.github/actions/prepare-matrix/|\.github/workflows/(compute-matrix|resolve-python-test-matrices)\.yaml) - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: @@ -26,7 +35,7 @@ repos: rev: v0.11.0.1 hooks: - id: shellcheck - files: ^ci/maven-publish/.*\.sh$ + files: ^(ci/maven-publish/.*|\.github/actions/prepare-matrix/compute)\.sh$ - repo: https://github.com/rapidsai/pre-commit-hooks rev: v1.6.1 hooks: diff --git a/README.md b/README.md index 4c727db7..edb2df6d 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,21 @@ MATRIX="$( echo "${MATRIX}" | jq ``` +### Resolved Python test matrices + +`resolve-python-test-matrices.yaml` prepares the matrix definitions once and +returns `wheels-test` and `conda-python-tests` outputs, each a JSON +`{"include": [...]}` matrix. It accepts `build_type` and optional `matrix_type`, +using the same selection and validation as `compute-matrix.yaml`. + +Callers can partition that snapshot between related test suites and pass each +partition through the optional `resolved-matrix` input of `wheels-test.yaml` or +`conda-python-tests.yaml`. The supplied snapshot is validated and the existing +`matrix_filter` still applies; matrix definitions are not read again. Omitting +the input retains the existing matrix resolution behavior. + +The resolver and consumers should use the same shared-workflows revision. + ### Secrets Some workflows support passing in arbitrary secrets and making them available as environment variables for the `script:` input. diff --git a/ci/test_compute_matrix.py b/ci/test_compute_matrix.py new file mode 100644 index 00000000..50b2c6c0 --- /dev/null +++ b/ci/test_compute_matrix.py @@ -0,0 +1,124 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +"""Run with python -m unittest discover -s ci -p 'test_compute_matrix.py'.""" + +import json +import os +from pathlib import Path +import subprocess +import tempfile +import unittest + +import yaml + + +ROOT = Path(__file__).resolve().parents[1] +SCRIPT = ROOT / ".github/actions/prepare-matrix/compute.sh" +MATRICES = yaml.safe_load( + (ROOT / ".github/actions/prepare-matrix/matrix.yaml").read_text() +) + + +class ComputeMatrixTests(unittest.TestCase): + def compute(self, name="wheels-test", **overrides): + with tempfile.TemporaryDirectory() as directory: + output = Path(directory) / "output" + result = subprocess.run( + ["bash", str(SCRIPT)], + env={ + **os.environ, + "BUILD_TYPE": "pull-request", + "MATRIX_NAME": name, + "MATRIX_TYPE": "auto", + "MATRIX": json.dumps(MATRICES), + "MATRIX_FILTER": ".", + "PURE_WHEEL": "false", + "PURE_CONDA": "false", + "RESOLVED_MATRIX": "", + "GITHUB_OUTPUT": str(output), + **overrides, + }, + text=True, + capture_output=True, + ) + matrix = ( + json.loads(output.read_text().removeprefix("matrix=")) + if result.returncode == 0 else None + ) + return result, matrix + + def test_existing_default_callers(self): + for name, definitions in MATRICES.items(): + for build_type, matrix_type in ( + ("pull-request", "pull-request"), + ("nightly", "nightly"), + ("branch", "nightly"), + ("release-candidate", "nightly"), + ): + if matrix_type not in definitions: + continue + with self.subTest(name=name, build_type=build_type): + result, matrix = self.compute(name, BUILD_TYPE=build_type) + self.assertEqual(result.returncode, 0, result.stderr + result.stdout) + expected = [] + for entry in definitions[matrix_type]: + if entry not in expected: + expected.append(entry) + self.assertEqual(matrix, {"include": expected}) + + def test_snapshot_ignores_changed_definitions(self): + snapshot = {"include": MATRICES["wheels-test"]["pull-request"]} + result, matrix = self.compute( + RESOLVED_MATRIX=json.dumps(snapshot), MATRIX="not JSON" + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(matrix, snapshot) + + def test_snapshot_applies_filter(self): + entries = MATRICES["wheels-test"]["pull-request"] + result, matrix = self.compute( + RESOLVED_MATRIX=json.dumps({"include": entries}), + MATRIX_FILTER='map(select(.ARCH == "arm64"))', + ) + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(matrix["include"], [e for e in entries if e["ARCH"] == "arm64"]) + + def test_multiple_types_deduplicate(self): + result, matrix = self.compute(MATRIX_TYPE="pull-request,pull-request") + self.assertEqual(result.returncode, 0, result.stderr) + self.assertEqual(matrix["include"], MATRICES["wheels-test"]["pull-request"]) + + def test_pure_defaults_match_existing_filters(self): + cases = ( + ("PURE_WHEEL", "true", 'map(select(.ARCH == "amd64")) | group_by(.CUDA_VER) | map(max_by(.PY_VER | split(".") | map(tonumber)))'), + ("PURE_CONDA", "true", 'map(select(.ARCH == "amd64")) | sort_by(.CUDA_VER, .PY_VER) | [last]'), + ("PURE_CONDA", "cuda_major", 'map(select(.ARCH == "amd64")) | group_by(.CUDA_VER|split(".")|map(tonumber)|.[0]) | map(max_by([(.PY_VER|split(".")|map(tonumber)), (.CUDA_VER|split(".")|map(tonumber))]))'), + ) + for option, value, expression in cases: + with self.subTest(option=option, value=value): + default_result, default = self.compute("wheels-build", **{option: value}) + explicit_result, explicit = self.compute("wheels-build", MATRIX_FILTER=expression) + self.assertEqual(default_result.returncode, 0, default_result.stderr) + self.assertEqual(explicit_result.returncode, 0, explicit_result.stderr) + self.assertEqual(default, explicit) + + def test_rejects_invalid_inputs(self): + cases = [ + {"BUILD_TYPE": "invalid"}, + {"MATRIX_TYPE": "missing"}, + {"MATRIX_FILTER": "map(select(false))"}, + {"MATRIX_FILTER": "{}"}, + ] + cases.extend({"RESOLVED_MATRIX": snapshot} for snapshot in ( + "not JSON", "[]", '{"include": []}', '{"include": [1]}', + '{"include": [{}]}', '{"include": [{}], "exclude": []}', + )) + for overrides in cases: + with self.subTest(overrides=overrides): + result, matrix = self.compute(**overrides) + self.assertNotEqual(result.returncode, 0) + self.assertIsNone(matrix) + + +if __name__ == "__main__": + unittest.main() From 9b6de2348f47847386904ed74b8efd624216d7b1 Mon Sep 17 00:00:00 2001 From: Vyas Ramasubramani Date: Thu, 8 Oct 2026 13:06:36 -0700 Subject: [PATCH 2/2] Run matrix regression tests on a jq-equipped Actions runner --- .../workflows/test-python-test-matrices.yaml | 40 +++++++++++++++++++ .pre-commit-config.yaml | 3 +- 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test-python-test-matrices.yaml diff --git a/.github/workflows/test-python-test-matrices.yaml b/.github/workflows/test-python-test-matrices.yaml new file mode 100644 index 00000000..e694bf2e --- /dev/null +++ b/.github/workflows/test-python-test-matrices.yaml @@ -0,0 +1,40 @@ +name: Python test matrix resolution +on: + pull_request: + paths: + - .github/actions/prepare-matrix/** + - .github/actions/prepare-matrix-dispatch/** + - .github/workflows/compute-matrix.yaml + - .github/workflows/resolve-python-test-matrices.yaml + - .github/workflows/test-python-test-matrices.yaml + - .github/workflows/wheels-test.yaml + - .github/workflows/conda-python-tests.yaml + - .pre-commit-config.yaml + - ci/test_compute_matrix.py + push: + branches: [main] + paths: + - .github/actions/prepare-matrix/** + - .github/actions/prepare-matrix-dispatch/** + - .github/workflows/compute-matrix.yaml + - .github/workflows/resolve-python-test-matrices.yaml + - .github/workflows/test-python-test-matrices.yaml + - .github/workflows/wheels-test.yaml + - .github/workflows/conda-python-tests.yaml + - .pre-commit-config.yaml + - ci/test_compute_matrix.py +permissions: + contents: read +jobs: + matrix-tests: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - name: Test matrix resolution and snapshot reuse + run: | + python3 -m venv .matrix-test-env + .matrix-test-env/bin/python -m pip install PyYAML==6.0.3 + .matrix-test-env/bin/python -m unittest discover -s ci -p test_compute_matrix.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index de5af49b..4f14ac38 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,8 @@ ci: autoupdate_schedule: 'monthly' - skip: [actionlint-docker] + # pre-commit.ci lacks Docker and jq; matrix tests run in the Ubuntu Actions job. + skip: [actionlint-docker, test-compute-matrix] repos: - repo: local