Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
159 changes: 159 additions & 0 deletions .github/workflows/TestGroups.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
name: "Reusable Test Groups Workflow"

on:
workflow_call:
inputs:
exclude:
description: "JSON array of test/ subdirectories to exclude from auto-discovery"
default: '["setup", "gpu"]'
required: false
type: string
julia-version:
description: "JSON array of Julia versions to test (e.g. '[\"min\", \"1\"]')"
default: '["min", "1"]'
required: false
type: string
os:
description: "JSON array of runner operating systems"
default: '["ubuntu-latest", "macos-latest", "windows-latest"]'
required: false
type: string
fast:
description: "Collapse matrix to ubuntu-latest + julia 1, and append --fast to test args"
default: false
required: false
type: boolean
nthreads:
description: "Number of Julia threads"
default: 2
required: false
type: number
timeout-minutes:
description: "Maximum time per job in minutes"
default: 60
required: false
type: number
localregistry:
description: "Newline-separated list of local registry URLs to add before building"
default: ""
required: false
type: string
cache:
description: "Enable julia-actions/cache"
default: true
required: false
type: boolean
buildpkg:
description: "Enable julia-actions/julia-buildpkg"
default: true
required: false
type: boolean
coverage:
description: "Collect and upload coverage (restricted to ubuntu-latest + julia 1)"
default: true
required: false
type: boolean
coverage-directories:
description: "Comma-separated directories for julia-processcoverage"
default: "src,ext"
required: false
type: string
secrets:
CODECOV_TOKEN:
required: false

jobs:
setup:
name: "Discover test groups"
runs-on: ubuntu-latest
outputs:
groups: ${{ steps.discover.outputs.groups }}
versions: ${{ steps.discover.outputs.versions }}
systems: ${{ steps.discover.outputs.systems }}
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Discover test groups
id: discover
shell: bash
run: |
GROUPS=$(find test -mindepth 1 -maxdepth 1 -type d -printf '%f\n' \
| sort \
| jq -R -s -c --argjson exc '${{ inputs.exclude }}' \
'split("\n")[:-1] | map(select(. as $g | $exc | index($g) | not))')
echo "groups=$GROUPS" >> "$GITHUB_OUTPUT"

if [[ "${{ inputs.fast }}" == "true" ]]; then
echo 'versions=["1"]' >> "$GITHUB_OUTPUT"
echo 'systems=["ubuntu-latest"]' >> "$GITHUB_OUTPUT"
else
echo 'versions=${{ inputs.julia-version }}' >> "$GITHUB_OUTPUT"
echo 'systems=${{ inputs.os }}' >> "$GITHUB_OUTPUT"
fi

test:
needs: setup
name: "${{ matrix.group }} (${{ matrix.version }}, ${{ matrix.os }})"
runs-on: ${{ matrix.os }}
timeout-minutes: ${{ inputs.timeout-minutes }}
strategy:
fail-fast: false
matrix:
group: ${{ fromJSON(needs.setup.outputs.groups) }}
version: ${{ fromJSON(needs.setup.outputs.versions) }}
os: ${{ fromJSON(needs.setup.outputs.systems) }}
steps:
- name: Checkout
uses: actions/checkout@v6

- name: "Setup Julia ${{ matrix.version }}"
uses: julia-actions/setup-julia@v3
with:
version: "${{ matrix.version }}"

- name: Restore cache
uses: julia-actions/cache@v3
if: inputs.cache
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Build package
uses: julia-actions/julia-buildpkg@v1
if: inputs.buildpkg
with:
localregistry: "${{ inputs.localregistry }}"

- name: "Run ${{ matrix.group }} tests"
uses: julia-actions/julia-runtest@v1
with:
test_args: "${{ matrix.group }}${{ inputs.fast && ' --fast' || '' }}"
coverage: "${{ inputs.coverage && matrix.os == 'ubuntu-latest' && matrix.version == '1' }}"
env:
JULIA_NUM_THREADS: "${{ inputs.nthreads }}"

- name: Process coverage
uses: julia-actions/julia-processcoverage@v1
if: inputs.coverage && matrix.os == 'ubuntu-latest' && matrix.version == '1'
with:
directories: "${{ inputs.coverage-directories }}"

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v6
if: inputs.coverage && matrix.os == 'ubuntu-latest' && matrix.version == '1'
with:
files: lcov.info
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false

ci-success:
name: ci-success
needs: test
if: always()
runs-on: ubuntu-latest
steps:
- name: Check all tests passed
run: |
if [[ "${{ contains(needs.test.result, 'failure') || contains(needs.test.result, 'cancelled') }}" == "true" ]]; then
exit 1
fi
97 changes: 95 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,99 @@
# ITensorActions
# QuantumKitHubActions

Shared workflows for the ITensors Julia packages
Shared workflows for the QuantumKitHub Julia packages

## Test Groups

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).

### Test directory structure

Organize your `test/` directory with one subfolder per test group. Place shared setup code in a `test/setup/` directory (excluded by default):

```
test/
├── setup/ # shared utilities, excluded from test groups
├── core/
│ └── runtests.jl
├── extensions/
│ └── runtests.jl
└── runtests.jl # ParallelTestRunner.jl entry point
```

A minimal `test/runtests.jl` using ParallelTestRunner.jl:

```julia
using ParallelTestRunner
ParallelTestRunner.runtests()
```

### Example workflow

```yaml
name: "Tests"

on:
push:
branches:
- main
tags: '*'
pull_request:
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}

jobs:
tests:
uses: "QuantumKitHub/QuantumKitHubActions/.github/workflows/TestGroups.yml@main"
with:
fast: ${{ github.event.pull_request.draft == true }}
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
```

### Branch protection

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.

### Inputs

| Input | Type | Default | Description |
|---|---|---|---|
| `exclude` | string | `'["setup", "gpu"]'` | JSON array of `test/` subdirectory names to exclude from discovery |
| `julia-version` | string | `'["min", "1"]'` | JSON array of Julia versions to test |
| `os` | string | `'["ubuntu-latest", "macos-latest", "windows-latest"]'` | JSON array of runner OSes |
| `fast` | boolean | `false` | Collapse matrix to `ubuntu-latest` + julia `1`, append `--fast` to test args |
| `nthreads` | number | `2` | Julia thread count per job |
| `timeout-minutes` | number | `60` | Per-job timeout |
| `localregistry` | string | `""` | Newline-separated local registry URLs |
| `cache` | boolean | `true` | Enable `julia-actions/cache` |
| `buildpkg` | boolean | `true` | Enable `julia-actions/julia-buildpkg` |
| `coverage` | boolean | `true` | Collect and upload coverage (only on `ubuntu-latest` + julia `1`) |
| `coverage-directories` | string | `"src,ext"` | Directories for `julia-processcoverage` |

**Secrets:** `CODECOV_TOKEN` (optional, only needed when `coverage: true`)

### Fast path

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:

```yaml
with:
fast: ${{ github.event.pull_request.draft == true }}
```

ParallelTestRunner.jl passes `--fast` through to individual test files, which can use it to skip slow or expensive tests.

### Excluding folders

Override `exclude` to control which subdirectories are skipped:

```yaml
with:
exclude: '["setup", "gpu", "cuda"]'
```

## Tests

Expand Down