Skip to content

Commit 59c69f6

Browse files
authored
ci: enable batched integration tests + fix concurrency collisions (#1407)
Here's the trimmed body to paste into PR 1407: ## Summary - Fix concurrency-group collision: manual PR dispatches all shared `workflow-refs/heads/main` and cancelled each other. Group is now target-aware (PR number → `pr_numbers` → `git_ref` → ref). - Replace single `pr_number` input with `pr_numbers` — accepts one PR or comma-separated list for batching (`100,200,300`). - New `prepare` job parses inputs into a JSON target array; the three test jobs fan out via `strategy.matrix.target` with `max-parallel: 2` and `fail-fast: false`. - Artifact names suffixed with the PR id to avoid collisions in batched runs. - Adopt the `setup-python-deps` cache pattern from `main.yml` so matrix cells restore the warm deps cache instead of re-resolving against JFrog.
1 parent b1047b5 commit 59c69f6

1 file changed

Lines changed: 115 additions & 46 deletions

File tree

.github/workflows/integration.yml

Lines changed: 115 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
#
33
# This workflow runs integration tests that require Databricks secrets.
44
#
5-
# For testing external contributions (PRs from forks):
5+
# For testing external contributions (PRs from forks) or batching multiple PRs:
66
# 1. Go to Actions tab -> Integration Tests -> Run workflow
7-
# 2. Enter the PR number in the 'pr_number' field
7+
# 2. Enter one PR number (e.g. "100") OR a comma-separated list (e.g. "100,200,300") in 'pr_numbers'
8+
# - For batch dispatches, matrix max-parallel caps in-run parallelism.
89
# 3. Click "Run workflow"
910
#
1011
# This approach is secure because:
@@ -24,33 +25,93 @@ on:
2425
- ".github/workflows/stale.yml"
2526

2627
workflow_dispatch:
27-
# Manual triggering for external contributions and ad-hoc testing
28+
# Manual triggering for external contributions and ad-hoc / batch testing
2829
inputs:
29-
pr_number:
30-
description: "PR number to test (for external contributions)"
30+
pr_numbers:
31+
description: "PR number(s) to test — single PR or comma-separated for batch (e.g. '100' or '100,200,300')"
3132
required: false
3233
type: string
3334
git_ref:
34-
description: "Git ref (branch/tag/commit) to test"
35+
description: "Git ref (branch/tag/commit) to test — used only when pr_numbers is empty"
3536
required: false
3637
type: string
3738

3839
permissions:
3940
id-token: write
4041
contents: read
4142

43+
# Target-aware concurrency:
44+
# - Different PRs / batches don't cancel each other.
45+
# - Re-push to the same PR (or re-dispatch of the same batch) cancels the stale run.
4246
concurrency:
43-
group: ${{ github.workflow }}-${{ github.ref }}
47+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.inputs.pr_numbers || github.event.inputs.git_ref || github.ref }}
4448
cancel-in-progress: true
4549

4650
jobs:
51+
prepare:
52+
runs-on:
53+
group: databricks-protected-runner-group
54+
labels: linux-ubuntu-latest
55+
# Only run on internal PRs or manual dispatch - skip external forks to avoid secret access failures.
56+
# Downstream jobs inherit this gate via `needs: prepare`.
57+
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
58+
outputs:
59+
targets: ${{ steps.parse.outputs.targets }}
60+
steps:
61+
- name: Parse targets
62+
id: parse
63+
shell: bash
64+
env:
65+
EVENT_NAME: ${{ github.event_name }}
66+
PR_NUMBER: ${{ github.event.pull_request.number }}
67+
PR_SHA: ${{ github.event.pull_request.head.sha }}
68+
INPUT_PR_NUMBERS: ${{ github.event.inputs.pr_numbers }}
69+
INPUT_GIT_REF: ${{ github.event.inputs.git_ref }}
70+
DEFAULT_REF: ${{ github.ref }}
71+
run: |
72+
set -euo pipefail
73+
entry() { printf '{"pr":"%s","ref":"%s"}' "$1" "$2"; }
74+
targets="["
75+
if [[ "$EVENT_NAME" == "pull_request" ]]; then
76+
targets+=$(entry "$PR_NUMBER" "$PR_SHA")
77+
elif [[ -n "${INPUT_PR_NUMBERS//[[:space:]]/}" ]]; then
78+
first=1
79+
IFS=',' read -ra prs <<< "$INPUT_PR_NUMBERS"
80+
for pr in "${prs[@]}"; do
81+
pr_trimmed="${pr//[[:space:]]/}"
82+
[[ -z "$pr_trimmed" ]] && continue
83+
if [[ ! "$pr_trimmed" =~ ^[0-9]+$ ]]; then
84+
echo "::error::Invalid PR number '$pr_trimmed' in pr_numbers='$INPUT_PR_NUMBERS' — expected digits, comma-separated."
85+
exit 1
86+
fi
87+
[[ $first -eq 0 ]] && targets+=","
88+
first=0
89+
targets+=$(entry "$pr_trimmed" "refs/pull/$pr_trimmed/head")
90+
done
91+
elif [[ -n "${INPUT_GIT_REF//[[:space:]]/}" ]]; then
92+
targets+=$(entry "manual" "$INPUT_GIT_REF")
93+
else
94+
targets+=$(entry "manual" "$DEFAULT_REF")
95+
fi
96+
targets+="]"
97+
echo "targets=$targets" >> "$GITHUB_OUTPUT"
98+
echo "Parsed targets: $targets"
99+
47100
run-uc-cluster-e2e-tests:
101+
# Do not add `if: always()` / `if: !cancelled()` here or on sibling test jobs —
102+
# `needs: prepare` propagates the external-fork skip cleanly, and forcing
103+
# evaluation would make `fromJSON(needs.prepare.outputs.targets)` fail on an
104+
# empty output. Matrix shape contract: {pr, ref} — defined in the `prepare` job.
105+
needs: prepare
106+
strategy:
107+
fail-fast: false
108+
max-parallel: 2
109+
matrix:
110+
target: ${{ fromJSON(needs.prepare.outputs.targets) }}
48111
runs-on:
49112
group: databricks-protected-runner-group
50113
labels: linux-ubuntu-latest
51114
environment: azure-prod
52-
# Only run on internal PRs or manual dispatch - skip external forks to avoid secret access failures
53-
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
54115
env:
55116
DBT_DATABRICKS_HOST_NAME: ${{ secrets.DATABRICKS_HOST }}
56117
DBT_DATABRICKS_CLIENT_ID: ${{ secrets.TEST_PECO_SP_ID }}
@@ -63,17 +124,15 @@ jobs:
63124
- name: Check out repository
64125
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
65126
with:
66-
# For pull_request: checkout the PR head commit
67-
# For workflow_dispatch with pr_number: checkout that PR's head
68-
# For workflow_dispatch with git_ref: checkout that ref
69-
# Otherwise: checkout current branch
70-
ref: ${{ github.event.pull_request.head.sha || (github.event.inputs.pr_number && format('refs/pull/{0}/head', github.event.inputs.pr_number)) || github.event.inputs.git_ref || github.ref }}
71-
# Fetch enough history for PR testing
72-
fetch-depth: 0
73-
74-
- name: Setup JFrog PyPI Proxy
75-
uses: ./.github/actions/setup-jfrog-pypi
127+
ref: ${{ matrix.target.ref }}
76128

129+
- name: Setup Python Dependencies
130+
id: deps
131+
uses: ./.github/actions/setup-python-deps
132+
133+
- name: Setup JFrog PyPI Proxy (fallback)
134+
if: steps.deps.outputs.cache-hit != 'true'
135+
uses: ./.github/actions/setup-jfrog-pypi
77136

78137
- name: Set up python
79138
id: setup-python
@@ -87,6 +146,8 @@ jobs:
87146

88147
- name: Install uv
89148
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
149+
with:
150+
cache-local-path: ~/.cache/uv
90151

91152
- name: Install Hatch
92153
id: install-dependencies
@@ -99,17 +160,21 @@ jobs:
99160
if: always()
100161
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
101162
with:
102-
name: uc-cluster-test-logs
163+
name: uc-cluster-test-logs-${{ matrix.target.pr }}
103164
path: logs/
104165
retention-days: 5
105166

106167
run-sqlwarehouse-e2e-tests:
168+
needs: prepare
169+
strategy:
170+
fail-fast: false
171+
max-parallel: 2
172+
matrix:
173+
target: ${{ fromJSON(needs.prepare.outputs.targets) }}
107174
runs-on:
108175
group: databricks-protected-runner-group
109176
labels: linux-ubuntu-latest
110177
environment: azure-prod
111-
# Only run on internal PRs or manual dispatch - skip external forks to avoid secret access failures
112-
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
113178
env:
114179
DBT_DATABRICKS_HOST_NAME: ${{ secrets.DATABRICKS_HOST }}
115180
DBT_DATABRICKS_CLIENT_ID: ${{ secrets.TEST_PECO_SP_ID }}
@@ -123,17 +188,15 @@ jobs:
123188
- name: Check out repository
124189
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
125190
with:
126-
# For pull_request: checkout the PR head commit
127-
# For workflow_dispatch with pr_number: checkout that PR's head
128-
# For workflow_dispatch with git_ref: checkout that ref
129-
# Otherwise: checkout current branch
130-
ref: ${{ github.event.pull_request.head.sha || (github.event.inputs.pr_number && format('refs/pull/{0}/head', github.event.inputs.pr_number)) || github.event.inputs.git_ref || github.ref }}
131-
# Fetch enough history for PR testing
132-
fetch-depth: 0
133-
134-
- name: Setup JFrog PyPI Proxy
135-
uses: ./.github/actions/setup-jfrog-pypi
191+
ref: ${{ matrix.target.ref }}
136192

193+
- name: Setup Python Dependencies
194+
id: deps
195+
uses: ./.github/actions/setup-python-deps
196+
197+
- name: Setup JFrog PyPI Proxy (fallback)
198+
if: steps.deps.outputs.cache-hit != 'true'
199+
uses: ./.github/actions/setup-jfrog-pypi
137200

138201
- name: Set up python
139202
id: setup-python
@@ -147,6 +210,8 @@ jobs:
147210

148211
- name: Install uv
149212
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
213+
with:
214+
cache-local-path: ~/.cache/uv
150215

151216
- name: Install Hatch
152217
id: install-dependencies
@@ -159,17 +224,21 @@ jobs:
159224
if: always()
160225
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
161226
with:
162-
name: sql-endpoint-test-logs
227+
name: sql-endpoint-test-logs-${{ matrix.target.pr }}
163228
path: logs/
164229
retention-days: 5
165230

166231
run-cluster-e2e-tests:
232+
needs: prepare
233+
strategy:
234+
fail-fast: false
235+
max-parallel: 2
236+
matrix:
237+
target: ${{ fromJSON(needs.prepare.outputs.targets) }}
167238
runs-on:
168239
group: databricks-protected-runner-group
169240
labels: linux-ubuntu-latest
170241
environment: azure-prod
171-
# Only run on internal PRs or manual dispatch - skip external forks to avoid secret access failures
172-
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
173242
env:
174243
DBT_DATABRICKS_HOST_NAME: ${{ secrets.DATABRICKS_HOST }}
175244
DBT_DATABRICKS_CLIENT_ID: ${{ secrets.TEST_PECO_SP_ID }}
@@ -181,17 +250,15 @@ jobs:
181250
- name: Check out repository
182251
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
183252
with:
184-
# For pull_request: checkout the PR head commit
185-
# For workflow_dispatch with pr_number: checkout that PR's head
186-
# For workflow_dispatch with git_ref: checkout that ref
187-
# Otherwise: checkout current branch
188-
ref: ${{ github.event.pull_request.head.sha || (github.event.inputs.pr_number && format('refs/pull/{0}/head', github.event.inputs.pr_number)) || github.event.inputs.git_ref || github.ref }}
189-
# Fetch enough history for PR testing
190-
fetch-depth: 0
191-
192-
- name: Setup JFrog PyPI Proxy
193-
uses: ./.github/actions/setup-jfrog-pypi
253+
ref: ${{ matrix.target.ref }}
194254

255+
- name: Setup Python Dependencies
256+
id: deps
257+
uses: ./.github/actions/setup-python-deps
258+
259+
- name: Setup JFrog PyPI Proxy (fallback)
260+
if: steps.deps.outputs.cache-hit != 'true'
261+
uses: ./.github/actions/setup-jfrog-pypi
195262

196263
- name: Set up python
197264
id: setup-python
@@ -205,6 +272,8 @@ jobs:
205272

206273
- name: Install uv
207274
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
275+
with:
276+
cache-local-path: ~/.cache/uv
208277

209278
- name: Install Hatch
210279
id: install-dependencies
@@ -217,6 +286,6 @@ jobs:
217286
if: always()
218287
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
219288
with:
220-
name: cluster-test-logs
289+
name: cluster-test-logs-${{ matrix.target.pr }}
221290
path: logs/
222291
retention-days: 5

0 commit comments

Comments
 (0)