Skip to content

Commit e9e54fd

Browse files
lkdvosclaude
andauthored
Add TestGroups reusable workflow with auto-discovery (#1)
* Add TestGroups reusable workflow with auto-discovery of test groups Introduces TestGroups.yml: a reusable workflow that auto-discovers subdirectories under test/ and runs each as a parallel job across a configurable Julia version × OS matrix. Includes a stable ci-success gate job for branch protection rules, fast-path support for draft PRs, and coverage restricted to a single OS/version to avoid redundant uploads. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Improve job and step names for cleaner PR checks view Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Update actions to latest versions (checkout@v6, setup-julia@v3, cache@v3, codecov@v6) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4d94fb9 commit e9e54fd

2 files changed

Lines changed: 254 additions & 2 deletions

File tree

.github/workflows/TestGroups.yml

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
name: "Reusable Test Groups Workflow"
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
exclude:
7+
description: "JSON array of test/ subdirectories to exclude from auto-discovery"
8+
default: '["setup", "gpu"]'
9+
required: false
10+
type: string
11+
julia-version:
12+
description: "JSON array of Julia versions to test (e.g. '[\"min\", \"1\"]')"
13+
default: '["min", "1"]'
14+
required: false
15+
type: string
16+
os:
17+
description: "JSON array of runner operating systems"
18+
default: '["ubuntu-latest", "macos-latest", "windows-latest"]'
19+
required: false
20+
type: string
21+
fast:
22+
description: "Collapse matrix to ubuntu-latest + julia 1, and append --fast to test args"
23+
default: false
24+
required: false
25+
type: boolean
26+
nthreads:
27+
description: "Number of Julia threads"
28+
default: 2
29+
required: false
30+
type: number
31+
timeout-minutes:
32+
description: "Maximum time per job in minutes"
33+
default: 60
34+
required: false
35+
type: number
36+
localregistry:
37+
description: "Newline-separated list of local registry URLs to add before building"
38+
default: ""
39+
required: false
40+
type: string
41+
cache:
42+
description: "Enable julia-actions/cache"
43+
default: true
44+
required: false
45+
type: boolean
46+
buildpkg:
47+
description: "Enable julia-actions/julia-buildpkg"
48+
default: true
49+
required: false
50+
type: boolean
51+
coverage:
52+
description: "Collect and upload coverage (restricted to ubuntu-latest + julia 1)"
53+
default: true
54+
required: false
55+
type: boolean
56+
coverage-directories:
57+
description: "Comma-separated directories for julia-processcoverage"
58+
default: "src,ext"
59+
required: false
60+
type: string
61+
secrets:
62+
CODECOV_TOKEN:
63+
required: false
64+
65+
jobs:
66+
setup:
67+
name: "Discover test groups"
68+
runs-on: ubuntu-latest
69+
outputs:
70+
groups: ${{ steps.discover.outputs.groups }}
71+
versions: ${{ steps.discover.outputs.versions }}
72+
systems: ${{ steps.discover.outputs.systems }}
73+
steps:
74+
- name: Checkout
75+
uses: actions/checkout@v6
76+
77+
- name: Discover test groups
78+
id: discover
79+
shell: bash
80+
run: |
81+
GROUPS=$(find test -mindepth 1 -maxdepth 1 -type d -printf '%f\n' \
82+
| sort \
83+
| jq -R -s -c --argjson exc '${{ inputs.exclude }}' \
84+
'split("\n")[:-1] | map(select(. as $g | $exc | index($g) | not))')
85+
echo "groups=$GROUPS" >> "$GITHUB_OUTPUT"
86+
87+
if [[ "${{ inputs.fast }}" == "true" ]]; then
88+
echo 'versions=["1"]' >> "$GITHUB_OUTPUT"
89+
echo 'systems=["ubuntu-latest"]' >> "$GITHUB_OUTPUT"
90+
else
91+
echo 'versions=${{ inputs.julia-version }}' >> "$GITHUB_OUTPUT"
92+
echo 'systems=${{ inputs.os }}' >> "$GITHUB_OUTPUT"
93+
fi
94+
95+
test:
96+
needs: setup
97+
name: "${{ matrix.group }} (${{ matrix.version }}, ${{ matrix.os }})"
98+
runs-on: ${{ matrix.os }}
99+
timeout-minutes: ${{ inputs.timeout-minutes }}
100+
strategy:
101+
fail-fast: false
102+
matrix:
103+
group: ${{ fromJSON(needs.setup.outputs.groups) }}
104+
version: ${{ fromJSON(needs.setup.outputs.versions) }}
105+
os: ${{ fromJSON(needs.setup.outputs.systems) }}
106+
steps:
107+
- name: Checkout
108+
uses: actions/checkout@v6
109+
110+
- name: "Setup Julia ${{ matrix.version }}"
111+
uses: julia-actions/setup-julia@v3
112+
with:
113+
version: "${{ matrix.version }}"
114+
115+
- name: Restore cache
116+
uses: julia-actions/cache@v3
117+
if: inputs.cache
118+
with:
119+
token: ${{ secrets.GITHUB_TOKEN }}
120+
121+
- name: Build package
122+
uses: julia-actions/julia-buildpkg@v1
123+
if: inputs.buildpkg
124+
with:
125+
localregistry: "${{ inputs.localregistry }}"
126+
127+
- name: "Run ${{ matrix.group }} tests"
128+
uses: julia-actions/julia-runtest@v1
129+
with:
130+
test_args: "${{ matrix.group }}${{ inputs.fast && ' --fast' || '' }}"
131+
coverage: "${{ inputs.coverage && matrix.os == 'ubuntu-latest' && matrix.version == '1' }}"
132+
env:
133+
JULIA_NUM_THREADS: "${{ inputs.nthreads }}"
134+
135+
- name: Process coverage
136+
uses: julia-actions/julia-processcoverage@v1
137+
if: inputs.coverage && matrix.os == 'ubuntu-latest' && matrix.version == '1'
138+
with:
139+
directories: "${{ inputs.coverage-directories }}"
140+
141+
- name: Upload coverage to Codecov
142+
uses: codecov/codecov-action@v6
143+
if: inputs.coverage && matrix.os == 'ubuntu-latest' && matrix.version == '1'
144+
with:
145+
files: lcov.info
146+
token: ${{ secrets.CODECOV_TOKEN }}
147+
fail_ci_if_error: false
148+
149+
ci-success:
150+
name: ci-success
151+
needs: test
152+
if: always()
153+
runs-on: ubuntu-latest
154+
steps:
155+
- name: Check all tests passed
156+
run: |
157+
if [[ "${{ contains(needs.test.result, 'failure') || contains(needs.test.result, 'cancelled') }}" == "true" ]]; then
158+
exit 1
159+
fi

README.md

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,99 @@
1-
# ITensorActions
1+
# QuantumKitHubActions
22

3-
Shared workflows for the ITensors Julia packages
3+
Shared workflows for the QuantumKitHub Julia packages
4+
5+
## Test Groups
6+
7+
The TestGroups workflow auto-discovers test groups from subdirectories of `test/` and runs them in parallel across a matrix of Julia versions and operating systems. Each subdirectory becomes a separate parallel job; groups are passed to the test suite via `test_args` and dispatched by [ParallelTestRunner.jl](https://github.com/QuantumKitHub/ParallelTestRunner.jl).
8+
9+
### Test directory structure
10+
11+
Organize your `test/` directory with one subfolder per test group. Place shared setup code in a `test/setup/` directory (excluded by default):
12+
13+
```
14+
test/
15+
├── setup/ # shared utilities, excluded from test groups
16+
├── core/
17+
│ └── runtests.jl
18+
├── extensions/
19+
│ └── runtests.jl
20+
└── runtests.jl # ParallelTestRunner.jl entry point
21+
```
22+
23+
A minimal `test/runtests.jl` using ParallelTestRunner.jl:
24+
25+
```julia
26+
using ParallelTestRunner
27+
ParallelTestRunner.runtests()
28+
```
29+
30+
### Example workflow
31+
32+
```yaml
33+
name: "Tests"
34+
35+
on:
36+
push:
37+
branches:
38+
- main
39+
tags: '*'
40+
pull_request:
41+
workflow_dispatch:
42+
43+
concurrency:
44+
group: ${{ github.workflow }}-${{ github.ref }}
45+
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}
46+
47+
jobs:
48+
tests:
49+
uses: "QuantumKitHub/QuantumKitHubActions/.github/workflows/TestGroups.yml@main"
50+
with:
51+
fast: ${{ github.event.pull_request.draft == true }}
52+
secrets:
53+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
54+
```
55+
56+
### Branch protection
57+
58+
Add `ci-success` as a required status check in your repository's branch protection rules. This single stable check name reflects the combined result of all parallel test jobs regardless of how many groups exist.
59+
60+
### Inputs
61+
62+
| Input | Type | Default | Description |
63+
|---|---|---|---|
64+
| `exclude` | string | `'["setup", "gpu"]'` | JSON array of `test/` subdirectory names to exclude from discovery |
65+
| `julia-version` | string | `'["min", "1"]'` | JSON array of Julia versions to test |
66+
| `os` | string | `'["ubuntu-latest", "macos-latest", "windows-latest"]'` | JSON array of runner OSes |
67+
| `fast` | boolean | `false` | Collapse matrix to `ubuntu-latest` + julia `1`, append `--fast` to test args |
68+
| `nthreads` | number | `2` | Julia thread count per job |
69+
| `timeout-minutes` | number | `60` | Per-job timeout |
70+
| `localregistry` | string | `""` | Newline-separated local registry URLs |
71+
| `cache` | boolean | `true` | Enable `julia-actions/cache` |
72+
| `buildpkg` | boolean | `true` | Enable `julia-actions/julia-buildpkg` |
73+
| `coverage` | boolean | `true` | Collect and upload coverage (only on `ubuntu-latest` + julia `1`) |
74+
| `coverage-directories` | string | `"src,ext"` | Directories for `julia-processcoverage` |
75+
76+
**Secrets:** `CODECOV_TOKEN` (optional, only needed when `coverage: true`)
77+
78+
### Fast path
79+
80+
When `fast: true`, the matrix collapses to a single OS and Julia version and `--fast` is appended to each group's test args. Wire it to draft PR detection for quick feedback during development:
81+
82+
```yaml
83+
with:
84+
fast: ${{ github.event.pull_request.draft == true }}
85+
```
86+
87+
ParallelTestRunner.jl passes `--fast` through to individual test files, which can use it to skip slow or expensive tests.
88+
89+
### Excluding folders
90+
91+
Override `exclude` to control which subdirectories are skipped:
92+
93+
```yaml
94+
with:
95+
exclude: '["setup", "gpu", "cuda"]'
96+
```
497

598
## Tests
699

0 commit comments

Comments
 (0)