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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
|
# This is a basic workflow that is manually triggered
name: regression_template
on:
workflow_call:
inputs:
install_script:
default: './scripts/install.sh'
required: false
type: string
build_script:
default: './scripts/build.sh'
required: false
type: string
test_script:
default: './scripts/test.sh'
required: false
type: string
cmake_path:
default: './test/cmake'
required: false
type: string
skip_test:
default: false
required: false
type: boolean
skip_coverage:
default: false
required: false
type: boolean
# The merged report, not one configuration's. Every configuration is
# instrumented now (TX_COVERAGE below) and coverage.sh --merge unions
# them; default_build_coverage was one build of five, and the code the
# other four select was absent from its denominator rather than uncovered
# in it.
coverage_name:
default: 'merged'
required: false
type: string
skip_deploy:
default: false
required: false
type: boolean
deploy_list:
default: ''
required: false
type: string
result_affix:
default: ''
required: false
type: string
# Extra options for the container the tests run in, empty for every job
# that does not ask for them.
#
# It exists for one measured case. NetX Duo's two interoperability suites
# build a veth pair per test and address it, which needs CAP_NET_ADMIN in
# the container's network namespace. A container started with the default
# capability set holds CAP_NET_RAW, so tcpdump and libpcap work, and does
# not hold CAP_NET_ADMIN, so "ip link add ... type veth" fails with
# "RTNETLINK answers: Operation not permitted" on the first test of every
# configuration -- measured, not predicted.
#
# A caller that needs it passes "--cap-add=NET_ADMIN" and nothing else
# changes. Granting it here for every component instead would widen the
# privileges of five suites that have no use for it.
container_options:
default: ''
required: false
type: string
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "linux_job"
run_tests:
if: ${{ !inputs.skip_test}}
permissions:
contents: read
issues: read
checks: write
pull-requests: write
# The type of runner that the job will run on. Pinned rather than "latest",
# for the same reason the container below is.
runs-on: ubuntu-24.04
# The build environment is pinned, because the coverage numbers this job
# produces are certification evidence and have to be reproducible from a
# recorded version rather than from whatever the hosted image carries today.
#
# The kernel sources declare cmake_minimum_required(VERSION 3.0.0), and CMake
# removed compatibility below 3.5 in 4.0. On ubuntu-latest with no pin, this
# branch would stop configuring the day GitHub moves that image to CMake 4.x,
# with no change here and no warning. Debian trixie carries CMake 3.31.6, so
# the distribution package settles it with no policy override and no edit to
# the sources.
#
# trixie is also GCC 14, and gcc-multilib with it: the coverage build is 32
# bit (-m32 in cmake/linux.cmake and in both test trees), so a compiler
# without multilib cannot build this suite at all.
#
# The tag is pinned by digest as well as by name. A tag is a pointer Debian
# moves on every point release, so a certificate that names one names an
# image that can change underneath it; a digest cannot move. This is the
# image the published coverage figures were measured on.
#
# It fixes the base and not the whole environment: the toolchain is
# installed from the Debian archive at run time, so gcc, CMake and Ninja
# still arrive at whatever version the archive carries. scripts/install.sh
# prints and asserts all three, which is what makes a drifting toolchain
# fail the job by name and number rather than quietly produce evidence built
# with something else.
container:
image: debian:trixie@sha256:f324c7ff54321e8d9c588493a20244965938ce0aa50bbd1022d38010e9ffc4b1
options: ${{ inputs.container_options }}
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
# Before the checkout, because actions/checkout needs git and the pinned
# image is a bare Debian. Without it the action silently falls back to
# downloading a tarball over the REST API, which leaves no .git behind -- and
# the commit this evidence was built from is part of the evidence.
- name: Install git
run: |
apt-get update
apt-get install -y --no-install-recommends ca-certificates git
- name: Check out the repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: true
- name: Install softwares
run: ${{ inputs.install_script }}
# TX_COVERAGE instruments every build configuration rather than only the one
# whose name ends in _coverage. It has to be set for the build as well as the
# test: the build is where -fprofile-arcs is decided, and the test run is
# where the reports are collected and merged.
- name: Build
env:
TX_COVERAGE: ${{ inputs.skip_coverage && 'OFF' || 'ON' }}
run: ${{ inputs.build_script }}
# CTEST_REPEAT_FAIL gives ctest --repeat until-pass:N, and the runner script
# defaults it to 2. One attempt is what this branch wants, because a second
# attempt reports an intermittent failure as a pass and these runs are
# certification evidence. Measured on this commit: over 112 configuration
# runs, four were not green on the first attempt -- two SMP tests that hung
# for the full 1000-second timeout and two that failed outright -- and all
# fourteen runs reported success. A retry is a reasonable default for
# day-to-day CI and cannot be one here, where a green run has to mean the
# suite passed rather than that it passed eventually. Reading it back out of
# the logs afterwards is not an equivalent: a timed-out attempt prints its
# own 1000-second line, so the run's total matches the sum of its test times
# and the discrepancy that would betray it does not exist.
- name: Test
env:
TX_COVERAGE: ${{ inputs.skip_coverage && 'OFF' || 'ON' }}
CTEST_REPEAT_FAIL: 1
run: ${{ inputs.test_script }}
- name: Publish Test Results
uses: EnricoMi/publish-unit-test-result-action@d0a4676d0e0b938bc201470d88276b7c74c712b3 # v2.24.0
if: always()
with:
check_name: Test Results ${{ inputs.result_affix }}
files: |
${{ inputs.cmake_path }}/build/*/*.xml
- name: Upload Test Results
if: success() || failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: test_reports ${{ inputs.result_affix }}
path: |
${{ inputs.cmake_path }}/build/*.txt
${{ inputs.cmake_path }}/build/*/Testing/**/*.xml
${{ inputs.cmake_path }}/build/**/regression/output_files/*.bin
- name: Configure GitHub Pages
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
# The coverage steps below run even when a test failed. cmake_bootstrap.sh
# now produces the report in that case, and the run whose behaviour changed
# is the one whose coverage is worth reading; previously a single flaky test
# suppressed the report for the whole run. !cancelled() rather than always(),
# so a cancelled run still stops promptly -- the same idiom the
# deploy_code_coverage job below already uses. The ${{ }} is required: a bare
# ! opens a YAML tag, and the expression will not parse without it.
- name: Generate Code Coverage Results Summary
if: ${{ !cancelled() && (!inputs.skip_coverage) }}
uses: irongut/CodeCoverageSummary@51cc3a756ddcd398d447c044c02cb6aa83fdae95 # v1.3.0
with:
filename: ${{ inputs.cmake_path }}/coverage_report/${{ inputs.coverage_name }}.xml
format: markdown
badge: true
hide_complexity: true
output: file
- name: Write Code Coverage Summary
if: ${{ !cancelled() && (!inputs.skip_coverage) }}
run: |
echo "## Coverage Report ${{ inputs.result_affix }}" >> $GITHUB_STEP_SUMMARY
cat code-coverage-results.md >> $GITHUB_STEP_SUMMARY
- name: Create CheckRun for Code Coverage
if: ${{ !cancelled() && ((github.event_name == 'push') || (github.event_name == 'workflow_dispatch') || (github.event.pull_request.head.repo.full_name == github.repository)) && (!inputs.skip_coverage) }}
uses: LouisBrunner/checks-action@937cbbcde3259005b50746dc91cde29098aac2ff # v3.1.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
name: Code Coverage ${{ inputs.result_affix }}
conclusion: ${{ job.status }}
output: |
{"summary":"Coverage Report"}
output_text_description_file: code-coverage-results.md
- name: Add Code Coverage PR Comment
if: ${{ !cancelled() && ((github.event_name == 'push') || (github.event.pull_request.head.repo.full_name == github.repository)) && (!inputs.skip_coverage) }}
uses: marocchino/sticky-pull-request-comment@5770ad5eb8f42dd2c4f34da00c94c5381e49af88 # v3.0.5
with:
header: Code Coverage ${{ inputs.result_affix }}
path: code-coverage-results.md
# No sudo: the steps above run as root inside the pinned container, and the
# image does not carry sudo at all.
- name: Prepare Coverage GitHub Pages
if: ${{ !cancelled() && (!inputs.skip_coverage) }}
run: >-
if [ "${{ inputs.result_affix }}" != "" ] && ${{ inputs.skip_deploy }}; then
mv ${{ inputs.cmake_path }}/coverage_report/${{ inputs.coverage_name }} \
${{ inputs.cmake_path }}/coverage_report/${{ inputs.result_affix }}
fi
# Every suite used to upload an artifact called coverage_report and rely on
# them merging. upload-artifact refuses a name another job has already used
# from v4 onwards, so the name is made unique here and the deploy job
# collects them by pattern.
- name: Coverage Report name
id: artifact
if: ${{ !cancelled() && (!inputs.skip_coverage) }}
run: echo "coverage_report=coverage_report-$(date +%s)" >> $GITHUB_OUTPUT
# per_configuration is excluded deliberately. The deploy job merges every
# coverage_report-* artifact into one tree, and each suite's per-configuration
# directories carry the same names, so ThreadX's would overwrite SMP's. The
# top level therefore holds only the suite directory and the merged XML,
# which is the shape the deploy already expects.
- name: Upload Code Coverage Artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() && (inputs.skip_deploy && !inputs.skip_coverage) }}
with:
name: ${{ steps.artifact.outputs.coverage_report }}
path: |
${{ inputs.cmake_path }}/coverage_report
!${{ inputs.cmake_path }}/coverage_report/per_configuration/**
# A day is the right life for a report that exists to answer "what moved
# in this run". On the certification branch the same report is evidence,
# and evidence an assessor cannot fetch from the run that produced it is
# evidence that has to be taken on trust. That branch therefore keeps
# them for the repository's own default of 90 days, which is already
# what the test results above get, and every other branch is unchanged.
retention-days: ${{ github.ref == 'refs/heads/v6.4.1_cert' && 90 || 1 }}
# The per-configuration reports, kept separately so they are downloadable
# when the merged number moves and the question is which configuration moved
# it. It is a distinct artifact name, so the deploy job never picks it up and
# it does not reach the published site.
- name: Upload Per-Configuration Coverage
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: ${{ !cancelled() && (!inputs.skip_coverage) }}
with:
name: coverage_detail ${{ inputs.result_affix }}
path: ${{ inputs.cmake_path }}/coverage_report/per_configuration
# Kept as long as the merged report above, and for the same reason: these
# are the tracefiles the union figure and the gap inventory are computed
# from, so they are the ones a figure is checked against.
retention-days: ${{ github.ref == 'refs/heads/v6.4.1_cert' && 90 || 1 }}
- name: Upload Code Coverage Pages
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
if: ${{ !cancelled() && (!inputs.skip_deploy && !inputs.skip_coverage) }}
with:
path: ${{ inputs.cmake_path }}/coverage_report/${{ inputs.coverage_name }}
deploy_code_coverage:
runs-on: ubuntu-24.04
if: ((github.event_name == 'push') || (github.event_name == 'workflow_dispatch')) && !inputs.skip_coverage && !inputs.skip_deploy && !failure() && !cancelled()
needs: run_tests
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
permissions:
pages: write
id-token: write
steps:
# By pattern, because the artifacts are now coverage_report-<epoch> and the
# step that names them runs in a different job, whose steps context does not
# reach here. merge-multiple puts both suites' contents directly into
# coverage_report rather than under a directory named for each artifact, so
# the published path holds the two suite directories and not a run timestamp.
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
if: ${{ inputs.skip_test }}
with:
pattern: coverage_report-*
merge-multiple: true
path: ${{ inputs.cmake_path }}/coverage_report
- name: Upload Code Coverage Pages
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
if: ${{ inputs.skip_test }}
with:
path: .
- name: Delete Duplicate Code Coverage Artifact
uses: geekyeggo/delete-artifact@176a747ab7e287e3ff4787bf8a148716375ca118 # v6.0.0
with:
name: coverage_report-*
- name: Deploy GitHub Pages site
id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
- name: Write Code Coverage Report URL
run: >-
if [ "${{ inputs.deploy_list }}" != "" ]; then
for i in ${{ inputs.deploy_list }}; do
echo 'Coverage report for ' $i ':${{ steps.deployment.outputs.page_url }}'$i >> $GITHUB_STEP_SUMMARY
done
else
echo 'Coverage report: ${{ steps.deployment.outputs.page_url }}' >> $GITHUB_STEP_SUMMARY
fi
|