Skip to content

Commit 441e9cf

Browse files
zlavclaude
andcommitted
CI: Parallelize integration tests, cache ghcup, fix tag resolution
1. Split integration tests into build + 3 parallel matrix jobs (jvm, compiled, scripting-and-other). Build job populates caches, test jobs restore them. Adds concurrency group to cancel stale runs. 2. Cache ~/.ghcup on macOS/Windows in build-all.yml to skip the ~500 MB GHC download on cache hit. 3. Fix release tag resolution in dist-newstyle cache keys. git describe needs reachable history (fails with fetch-depth: 2). Switch to git tag --sort=-v:refname which lists fetched tag refs regardless of shallow clone depth. Also upgrades actions/cache v4 to v5 (node20 -> node22 runtime). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f190d48 commit 441e9cf

3 files changed

Lines changed: 170 additions & 25 deletions

File tree

.github/workflows/bench.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ jobs:
2020

2121
- name: Get latest release tag
2222
id: latest-tag
23-
run: echo "tag=$(git describe --tags --abbrev=0 2>/dev/null || echo none)" >> $GITHUB_OUTPUT
23+
run: echo "tag=$(git tag --sort=-v:refname | head -1 2>/dev/null || echo none)" >> $GITHUB_OUTPUT
2424

2525
# Adding the "git config ..." line ensures git doesn't fail during our build.
2626
- name: Configure Git
@@ -55,15 +55,15 @@ jobs:
5555

5656
# Benchmarks build at a different optimization level, so they use
5757
# their own cache prefix to avoid collisions with the main build.
58-
- uses: actions/cache@v4
58+
- uses: actions/cache@v5
5959
name: Cache cabal store
6060
with:
6161
path: ${{ steps.setup-haskell.outputs.cabal-store || '~/.local/state/cabal' }}
6262
key: ${{ runner.os }}-${{ env.GHC_VERSION }}-benchmarks-cabal-cache-${{ hashFiles('spectrometer.cabal', 'cabal.project.ci.linux', 'cabal.project.common') }}
6363
restore-keys: |
6464
${{ runner.os }}-${{ env.GHC_VERSION }}-benchmarks-cabal-cache-
6565
66-
- uses: actions/cache@v4
66+
- uses: actions/cache@v5
6767
name: Cache dist-newstyle
6868
with:
6969
path: ${{ github.workspace }}/dist-newstyle

.github/workflows/build-all.yml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,25 @@ jobs:
6262

6363
- name: Get latest release tag
6464
id: latest-tag
65-
run: echo "tag=$(git describe --tags --abbrev=0 2>/dev/null || echo none)" >> $GITHUB_OUTPUT
65+
run: echo "tag=$(git tag --sort=-v:refname | head -1 2>/dev/null || echo none)" >> $GITHUB_OUTPUT
6666

6767
- name: Install MacOS binary dependencies
6868
if: ${{ contains(matrix.os, 'macos') }}
6969
run: |
7070
brew install jq
7171
7272
# Set up Haskell.
73+
# Cache ghcup installations so the setup action skips downloading GHC (~500 MB).
74+
- uses: actions/cache@v5
75+
if: ${{ !contains(matrix.os-name, 'Linux') }}
76+
name: Cache ghcup
77+
with:
78+
path: |
79+
~/.ghcup/bin
80+
~/.ghcup/ghc/${{ matrix.ghc }}
81+
~/.ghcup/cache
82+
key: ${{ matrix.os-name }}-ghcup-${{ matrix.ghc }}-3.10.3.0
83+
7384
- uses: haskell-actions/setup@v2
7485
id: setup-haskell
7586
name: Setup ghc/cabal (non-alpine)
@@ -129,15 +140,15 @@ jobs:
129140
# The home directory inside a github container run during a step is different than one run outside of it.
130141
# This is why there is special logic for 'LinuxARM'.
131142
# Its builds run inside a container but are cached by an action outside of it.
132-
- uses: actions/cache@v4
143+
- uses: actions/cache@v5
133144
name: Cache cabal store
134145
with:
135146
path: ${{ steps.setup-haskell.outputs.cabal-store || ( matrix.os == 'LinuxARM' && format('{0}/_github_home/.local/state/cabal', runner.temp) || '~/.local/state/cabal') }}
136147
key: ${{ matrix.os-name }}-${{ matrix.ghc }}-cabal-cache-${{ hashFiles('spectrometer.cabal', matrix.project-file, 'cabal.project.common') }}
137148
restore-keys: |
138149
${{ matrix.os-name }}-${{ matrix.ghc }}-cabal-cache-
139150
140-
- uses: actions/cache@v4
151+
- uses: actions/cache@v5
141152
name: Cache dist-newstyle
142153
with:
143154
path: ${{ github.workspace }}/dist-newstyle

.github/workflows/integrations-test.yml

Lines changed: 153 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,36 @@
11
name: Integration Tests
22

3-
# Only run workflow manually
4-
# Refer to https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows#workflow_dispatch
53
on:
64
push:
75
workflow_dispatch:
86
schedule:
97
- cron: "0 5 * * *" # At 05:00 on every day.
108

9+
# Cancel in-progress runs for the same branch so stale runs don't block the runner.
10+
concurrency:
11+
group: integration-${{ github.ref }}
12+
cancel-in-progress: true
13+
14+
# All match patterns for integration test groups. Each group runs a subset;
15+
# the build job validates that every test belongs to exactly one group.
16+
env:
17+
TEST_GROUPS: >-
18+
Analysis.Maven Analysis.Gradle Analysis.Scala Analysis.Clojure
19+
Analysis.Go Analysis.Rust Analysis.Erlang Analysis.Elixir
20+
Analysis.Python Analysis.Ruby Analysis.Swift Analysis.Cocoapods
21+
Analysis.Pnpm Analysis.Nuget Analysis.Carthage Analysis.LicenseScanner
22+
Container.Analysis Reachability.Upload
23+
1124
jobs:
12-
integration-test:
13-
name: integration-test
25+
# Build once, then run test groups in parallel.
26+
# The build job populates the cabal and dist-newstyle caches.
27+
# Each test job restores those caches so it doesn't need to rebuild.
28+
build:
29+
name: build
1430
runs-on: "fossa-cli-integration-runner"
15-
# Be sure to update the env below too
1631
container: fossa/haskell-static-alpine:ghc-9.8.4
17-
32+
outputs:
33+
latest-tag: ${{ steps.latest-tag.outputs.tag }}
1834
env:
1935
GHC_VERSION: '9.8.4'
2036

@@ -29,13 +45,11 @@ jobs:
2945

3046
- name: Get latest release tag
3147
id: latest-tag
32-
run: echo "tag=$(git describe --tags --abbrev=0 2>/dev/null || echo none)" >> $GITHUB_OUTPUT
48+
run: echo "tag=$(git tag --sort=-v:refname | head -1 2>/dev/null || echo none)" >> $GITHUB_OUTPUT
3349

3450
- name: Ensures git ownership check does not lead to compile error (we run git during compile for version tagging, etc.)
3551
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
3652

37-
# adduser cannot add users to group: https://unix.stackexchange.com/a/397733
38-
# so we edit /etc/group directly
3953
- name: Create nixbuild users/group
4054
run: |
4155
addgroup nixbld
@@ -60,19 +74,15 @@ jobs:
6074
6175
- uses: Swatinem/rust-cache@v2
6276

63-
# Cache cabal store and dist-newstyle keyed on spectrometer.cabal + project file.
64-
# cabal build handles staleness internally — if a transitive dep changed,
65-
# it recompiles only what's needed. This avoids the 8-min cabal update +
66-
# dry-run solver step that the plan-hash approach requires.
67-
- uses: actions/cache@v4
77+
- uses: actions/cache@v5
6878
name: Cache cabal store
6979
with:
7080
path: ${{ steps.setup-haskell.outputs.cabal-store || '~/.local/state/cabal' }}
7181
key: ${{ runner.os }}-${{ env.GHC_VERSION }}-cabal-cache-${{ hashFiles('spectrometer.cabal', 'cabal.project.ci.linux', 'cabal.project.common') }}
7282
restore-keys: |
7383
${{ runner.os }}-${{ env.GHC_VERSION }}-cabal-cache-
7484
75-
- uses: actions/cache@v4
85+
- uses: actions/cache@v5
7686
name: Cache dist-newstyle
7787
with:
7888
path: ${{ github.workspace }}/dist-newstyle
@@ -98,8 +108,132 @@ jobs:
98108
cabal update
99109
$RUN_CMD || $RUN_CMD
100110
101-
# This is set up to run integration tests in parallel.
102-
# If that becomes a problem disable it by removing the "+RTS -N -RTS" test options.
103-
- name: Run all integration tests
111+
# Verify every integration test is covered by at least one group pattern.
112+
# Counts tests from an unfiltered --dry-run vs the union of all --match
113+
# patterns. Fails if any tests would be silently skipped.
114+
- name: Validate test groups cover all tests
104115
run: |
105-
cabal test --project-file=cabal.project.ci.linux --test-show-details=direct --test-option=--times integration-tests --test-options="+RTS -N -RTS"
116+
TOTAL=$(cabal test --project-file=cabal.project.ci.linux \
117+
--test-show-details=direct \
118+
integration-tests \
119+
--test-option=--dry-run 2>&1 | grep -c "^ " || echo 0)
120+
121+
MATCH_ARGS=""
122+
for pattern in $TEST_GROUPS; do
123+
MATCH_ARGS="$MATCH_ARGS --test-option=--match --test-option=$pattern"
124+
done
125+
MATCHED=$(cabal test --project-file=cabal.project.ci.linux \
126+
--test-show-details=direct \
127+
$MATCH_ARGS \
128+
integration-tests \
129+
--test-option=--dry-run 2>&1 | grep -c "^ " || echo 0)
130+
131+
echo "Total tests: $TOTAL"
132+
echo "Matched tests: $MATCHED"
133+
if [ "$TOTAL" -ne "$MATCHED" ]; then
134+
echo "ERROR: $((TOTAL - MATCHED)) integration tests are not covered by any group pattern."
135+
echo "Update TEST_GROUPS in integrations-test.yml to include the missing patterns."
136+
exit 1
137+
fi
138+
139+
# Upload artifacts that test jobs need so they don't have to rebuild
140+
# Rust or re-download vendor binaries.
141+
- uses: actions/upload-artifact@v4
142+
with:
143+
name: vendor-bins
144+
path: vendor-bins/
145+
146+
- uses: actions/upload-artifact@v4
147+
with:
148+
name: rust-release-bins
149+
path: |
150+
target/release/berkeleydb
151+
target/release/millhone
152+
153+
integration-test:
154+
name: test-${{ matrix.group }}
155+
needs: build
156+
runs-on: "fossa-cli-integration-runner"
157+
container: fossa/haskell-static-alpine:ghc-9.8.4
158+
env:
159+
GHC_VERSION: '9.8.4'
160+
161+
strategy:
162+
fail-fast: false
163+
matrix:
164+
include:
165+
- group: jvm
166+
matches: "Analysis.Maven Analysis.Gradle Analysis.Scala Analysis.Clojure"
167+
168+
- group: compiled
169+
matches: "Analysis.Go Analysis.Rust Analysis.Erlang Analysis.Elixir"
170+
171+
- group: scripting-and-other
172+
matches: "Analysis.Python Analysis.Ruby Analysis.Swift Analysis.Cocoapods Analysis.Pnpm Analysis.Nuget Analysis.Carthage Analysis.LicenseScanner Container.Analysis Reachability.Upload"
173+
174+
steps:
175+
- uses: actions/checkout@v4
176+
with:
177+
lfs: true
178+
fetch-depth: 2
179+
180+
- name: Ensures git ownership check does not lead to compile error
181+
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
182+
183+
- name: Create nixbuild users/group
184+
run: |
185+
addgroup nixbld
186+
adduser -D nixbld-1
187+
adduser -D nixbld-2
188+
adduser -D nixbld-3
189+
sed 's/nixbld:x:\([[:digit:]]*\):$/nixbld:x:\1:nixbld-1,nixbld-2,nixbld-3/' /etc/group > group-changed
190+
mv group-changed /etc/group
191+
192+
- uses: cachix/install-nix-action@v25
193+
with:
194+
nix_path: nixpkgs=channel:nixos-25.05
195+
extra_nix_config: "build-users-group = nixbld"
196+
197+
# Restore caches populated by the build job.
198+
- uses: actions/cache@v5
199+
name: Cache cabal store
200+
with:
201+
path: ${{ steps.setup-haskell.outputs.cabal-store || '~/.local/state/cabal' }}
202+
key: ${{ runner.os }}-${{ env.GHC_VERSION }}-cabal-cache-${{ hashFiles('spectrometer.cabal', 'cabal.project.ci.linux', 'cabal.project.common') }}
203+
restore-keys: |
204+
${{ runner.os }}-${{ env.GHC_VERSION }}-cabal-cache-
205+
206+
- uses: actions/cache@v5
207+
name: Cache dist-newstyle
208+
with:
209+
path: ${{ github.workspace }}/dist-newstyle
210+
key: ${{ runner.os }}-${{ env.GHC_VERSION }}-dist-newstyle-${{ needs.build.outputs.latest-tag }}-${{ hashFiles('spectrometer.cabal', 'cabal.project.ci.linux', 'cabal.project.common') }}
211+
restore-keys: |
212+
${{ runner.os }}-${{ env.GHC_VERSION }}-dist-newstyle-${{ needs.build.outputs.latest-tag }}-
213+
${{ runner.os }}-${{ env.GHC_VERSION }}-dist-newstyle-
214+
215+
# extra-source-files (vendor-bins/*, target/release/*) must exist on
216+
# disk or cabal recompiles EmbeddedBinary.hs (fails under -Werror).
217+
- uses: actions/download-artifact@v4
218+
with:
219+
name: vendor-bins
220+
path: vendor-bins/
221+
222+
- uses: actions/download-artifact@v4
223+
with:
224+
name: rust-release-bins
225+
path: target/release/
226+
227+
- name: Run integration tests (${{ matrix.group }})
228+
run: |
229+
cabal update
230+
MATCH_ARGS=""
231+
for pattern in ${{ matrix.matches }}; do
232+
MATCH_ARGS="$MATCH_ARGS --test-option=--match --test-option=$pattern"
233+
done
234+
cabal test --project-file=cabal.project.ci.linux \
235+
--test-show-details=direct \
236+
--test-option=--times \
237+
$MATCH_ARGS \
238+
integration-tests \
239+
--test-options="+RTS -N -RTS"

0 commit comments

Comments
 (0)