-
Notifications
You must be signed in to change notification settings - Fork 10
204 lines (194 loc) · 8.25 KB
/
Copy pathreusable-test.yml
File metadata and controls
204 lines (194 loc) · 8.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
name: Reusable Test Workflow
on:
workflow_call:
inputs:
test-type:
description: 'Type of tests to run (unit, integration, e2e, performance, providers, infrastructure)'
required: true
type: string
provider:
description: 'When test-type=providers, scope the run to a single provider subtree (e.g. aws, k8s).'
required: false
type: string
default: ''
python-version:
description: 'Python version to test with'
required: false
type: string
default-python-version:
description: 'Default Python version (fallback)'
required: true
type: string
os:
description: 'Operating system to run on'
required: false
type: string
default: 'ubuntu-latest'
workers:
description: 'pytest-xdist worker count (empty = leg default; "auto" = one per core, for larger runners)'
required: false
type: string
default: ''
continue-on-error:
description: 'Whether to continue on test failures'
required: false
type: boolean
default: false
upload-coverage:
description: 'Whether to upload coverage reports'
required: false
type: boolean
default: true
aws-region:
description: 'AWS region for testing'
required: false
type: string
default: 'us-east-1'
aws-access-key:
description: 'AWS access key for testing'
required: false
type: string
default: 'testing'
aws-secret-key:
description: 'AWS secret key for testing'
required: false
type: string
default: 'testing'
environment:
description: 'Environment name'
required: false
type: string
default: 'testing'
testing-flag:
description: 'Testing flag'
required: false
type: string
default: 'true'
cache-key:
description: >-
Pre-computed cache key from the caller's setup-cache step. When
provided (recommended) the job hits the warm cache primed by the
caller's cache-management job. When omitted the job falls back to
computing its own key (always cold — deps reinstalled).
required: false
type: string
default: ''
artifact-prefix:
description: >-
Caller-supplied prefix prepended to artifact names to prevent
collisions when multiple workflows produce the same test-type/os/py
combination (e.g. ci-tests vs test-matrix both running unit tests).
required: false
type: string
default: ''
secrets:
CODECOV_TOKEN:
required: false
env:
AWS_DEFAULT_REGION: ${{ inputs.aws-region }}
AWS_ACCESS_KEY_ID: ${{ inputs.aws-access-key }}
AWS_SECRET_ACCESS_KEY: ${{ inputs.aws-secret-key }}
ENVIRONMENT: ${{ inputs.environment }}
TESTING: ${{ inputs.testing-flag }}
jobs:
test:
name: ${{ inputs.test-type == 'unit' && 'Unit Tests' || inputs.test-type == 'integration' && 'Integration Tests' || inputs.test-type == 'e2e' && 'End-to-End Tests' || (inputs.test-type == 'providers' && inputs.provider != '') && format('Providers Tests ({0})', inputs.provider) || format('{0} Tests', inputs.test-type) }}
runs-on: ${{ inputs.os }}
timeout-minutes: 30
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Setup UV with cached environment
uses: ./.github/actions/setup-uv-cached
with:
# Use the caller-supplied cache key when available so this job hits the
# warm cache primed by the caller's cache-management job. Fallback
# mirrors the key format used by cache-management (dependencies type)
# so at least restore-keys can provide a partial hit.
cache-key: >-
${{
inputs.cache-key != ''
&& inputs.cache-key
|| format('test-deps-py{0}-{1}',
inputs.python-version || inputs.default-python-version,
hashFiles('.project.yml', 'pyproject.toml', 'uv.lock'))
}}
fail-on-cache-miss: false
- name: Run tests
id: run-tests
# Harden against shell-injection: pass caller inputs via env vars and
# reference them as shell variables, never interpolate ${{ }} into the
# run body. PYTEST_N_WORKERS is only set when the caller passes workers
# (e.g. "auto" on a larger runner); empty → make's default (-n 2).
env:
TEST_TYPE: ${{ inputs.test-type }}
PROVIDER: ${{ inputs.provider }}
WORKERS: ${{ inputs.workers }}
run: |
if [ -n "$WORKERS" ]; then
make "ci-tests-${TEST_TYPE}" PROVIDER="$PROVIDER" PYTEST_N_WORKERS="$WORKERS"
else
make "ci-tests-${TEST_TYPE}" PROVIDER="$PROVIDER"
fi
continue-on-error: ${{ inputs.continue-on-error }}
# When continue-on-error is true the overall job status stays green even
# if tests fail, which can hide regressions in the PR summary view.
# This step fires only on failure and writes a visible warning to the
# GitHub job summary so operators notice the failure without digging into
# the raw log.
- name: Annotate soft failure in job summary
if: steps.run-tests.outcome == 'failure'
env:
TEST_TYPE: ${{ inputs.test-type }}
run: |
echo "::warning::${TEST_TYPE} tests failed (continue-on-error=true). Review the test log for details."
{
echo "## :warning: ${TEST_TYPE} test failures"
echo ""
echo "The **${TEST_TYPE}** test suite reported failures."
echo "Overall job status is still green because \`continue-on-error: true\` is set,"
echo "but these failures should be investigated before merging."
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload test results
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
if: always()
with:
# Artifact name is namespaced by caller workflow (artifact-prefix) so
# ci-tests and test-matrix don't overwrite each other when they both
# produce unit tests on the same OS/Python combination.
name: >-
test-results-${{
inputs.artifact-prefix != ''
&& format('{0}-', inputs.artifact-prefix)
|| ''
}}${{ inputs.test-type }}${{
inputs.provider != '' && format('-{0}', inputs.provider) || ''
}}-${{ inputs.os }}-py${{ inputs.python-version || inputs.default-python-version }}
retention-days: 30
# Include raw .coverage data files so the coverage-combine fan-in job
# can merge them across legs and enforce the combined threshold.
# include-hidden-files is required: .coverage is a dotfile and
# upload-artifact@v4+ excludes hidden files by default.
include-hidden-files: true
path: |
junit-*.xml
coverage-*.xml
.coverage*
# NOTE: per-leg coverage is NOT uploaded to Codecov here. The .coverage
# data files are uploaded as artifacts (above) and the coverage-combine
# fan-in job in ci-tests.yml merges them and uploads ONE combined report
# to Codecov. A single upload gives Codecov one deterministic number
# (no mid-run partial-merge flapping) and auto-scales to any number of
# provider legs without a hardcoded after_n_builds count.
- name: Upload test results to Codecov
# Gated on upload-coverage: legs without pytest (e.g. ui-smoke) produce
# no junit-*.xml, so running test-results-action there yields a spurious
# "JUnit XML file not found" on the Codecov Test Analytics page.
if: ${{ inputs.upload-coverage && !cancelled() }}
continue-on-error: true
uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: junit-${{ inputs.test-type }}${{ inputs.provider != '' && format('-{0}', inputs.provider) || '' }}.xml