-
Notifications
You must be signed in to change notification settings - Fork 148
518 lines (470 loc) · 23.4 KB
/
Copy pathnightly-build-test-sophon.yml
File metadata and controls
518 lines (470 loc) · 23.4 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
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
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
name: Nightly Sophon Build and Test
on:
# Trigger daily at 2:00 AM Beijing Time (18:00 UTC previous day)
schedule:
- cron: '12 18 * * *'
# Allow manual trigger for testing purposes
workflow_dispatch:
permissions:
contents: read
jobs:
build-sophon:
runs-on: ubuntu-latest
timeout-minutes: 60
defaults:
run:
shell: bash
outputs:
candidate_guard_sha256: ${{ steps.candidate_guard_runtime.outputs.sha256 }}
candidate_tests_sha256: ${{ steps.candidate_test_binary.outputs.sha256 }}
env:
COSMO_MODEL_GUARD_BUILD_PROFILE: public-runtime
# Cargo defaults each HTTP request to 30 seconds and only three retries.
# The public rsproxy sparse index can exceed that from GitHub runners.
CARGO_HTTP_TIMEOUT: "120"
CARGO_NET_RETRY: "5"
# Specify the public container image for the build environment
container:
image: ghcr.io/cosmo-wander-ai/cosmo_edge-build-env_sophon:v1
steps:
- name: Checkout code
uses: actions/checkout@v7
with:
submodules: recursive # Recommended if the repository uses git submodules
# Enforce project standards: use the unified build script instead of cmake directly.
# A single configure builds the clean public-runtime package and cosmo-tests
# together, sharing one set of compiled OBJECT libraries; coverage is off
# by default (COSMO_ENABLE_COVERAGE), so the package binary stays clean.
- name: Build package and tests
run: |
chmod +x scripts/build.sh
./scripts/build.sh -T -c bm1688
# Upload the build artifacts for deployment or download
- name: Upload Artifacts
uses: actions/upload-artifact@v7
with:
name: sophon-build-package
# Path where the built binaries/packages are generated by scripts/build.sh
path: build/install/
overwrite: true
retention-days: 3
- name: Record Candidate Test Binary
id: candidate_test_binary
run: |
set -euo pipefail
binary="build/cosmo-tests"
if [ ! -f "$binary" ] || [ -L "$binary" ]; then
echo "Candidate test binary is not a regular file: $binary" >&2
exit 1
fi
digest="$(sha256sum "$binary" | awk '{print $1}')"
if [ "${#digest}" -ne 64 ] || [[ "$digest" == *[!0-9a-f]* ]]; then
echo "Invalid Candidate test binary SHA-256: $digest" >&2
exit 1
fi
echo "sha256=$digest" >> "$GITHUB_OUTPUT"
echo "Candidate test binary SHA-256: $digest"
- name: Upload Test Binary
uses: actions/upload-artifact@v7
with:
name: sophon-tests-binary
path: build/cosmo-tests
if-no-files-found: error
overwrite: true
retention-days: 1
- name: Record Candidate Guard Runtime
id: candidate_guard_runtime
run: |
set -euo pipefail
runtime="build/install/lib/libcosmo_model_guard.so.2.0.0"
if [ ! -f "$runtime" ] || [ -L "$runtime" ]; then
echo "Candidate Guard runtime is not a regular file: $runtime" >&2
exit 1
fi
digest="$(sha256sum "$runtime" | awk '{print $1}')"
if [ "${#digest}" -ne 64 ] || [[ "$digest" == *[!0-9a-f]* ]]; then
echo "Invalid Candidate Guard SHA-256: $digest" >&2
exit 1
fi
echo "sha256=$digest" >> "$GITHUB_OUTPUT"
echo "Candidate Guard SHA-256: $digest"
# Keep the candidate Guard runtime independent from the large package and
# test-binary cache. overwrite=true makes a full job re-run replace the
# artifact, while a failed-job re-run still downloads the build's artifact.
- name: Upload Candidate Guard Runtime
uses: actions/upload-artifact@v7
with:
name: sophon-model-guard-runtime
path: build/install/lib/libcosmo_model_guard.so.2.0.0
if-no-files-found: error
overwrite: true
retention-days: 1
test-sophon:
needs: build-sophon
runs-on: [self-hosted, hosted-sophon-device]
timeout-minutes: 45 # self-hosted 无默认超时,防挂死占用设备(分片+二分;绿跑仅数分钟,45min 是坏夜二分余量)
env:
COSMO_TEST_CASE_TIMEOUT_SECONDS: 120
COSMO_CATCH2_SHARDS: 12
COSMO_CATCH2_SHARD_TIMEOUT_SECONDS: 120
COSMO_CATCH2_RESULTS_DIR: test-results/catch2
COSMO_CATCH2_REPORTER: compact
COSMO_SOPHON_LD_LIBRARY_PATH: /appfs/cosmo_wander/cwai_data/lib:/data:/usr/lib
COSMO_CANDIDATE_RUNTIME_DIR: ${{ github.workspace }}/candidate-runtime
COSMO_CANDIDATE_GUARD_SHA256: ${{ needs.build-sophon.outputs.candidate_guard_sha256 }}
COSMO_CANDIDATE_TESTS_SHA256: ${{ needs.build-sophon.outputs.candidate_tests_sha256 }}
steps:
# The test binary may be restored from the persistent device cache, but
# the Guard library must always come from this build's current artifact.
- name: Prepare Candidate Guard Runtime Directory
run: |
set -euo pipefail
expected_dir="$GITHUB_WORKSPACE/candidate-runtime"
if [ "$COSMO_CANDIDATE_RUNTIME_DIR" != "$expected_dir" ]; then
echo "Refusing unexpected candidate runtime directory: $COSMO_CANDIDATE_RUNTIME_DIR" >&2
exit 1
fi
rm -rf -- "$COSMO_CANDIDATE_RUNTIME_DIR"
mkdir -p -- "$COSMO_CANDIDATE_RUNTIME_DIR"
- name: Download Candidate Guard Runtime
uses: actions/download-artifact@v8
with:
name: sophon-model-guard-runtime
path: candidate-runtime
- name: Verify Candidate Guard Runtime
run: |
set -euo pipefail
runtime="$COSMO_CANDIDATE_RUNTIME_DIR/libcosmo_model_guard.so.2.0.0"
if [ ! -f "$runtime" ] || [ -L "$runtime" ]; then
echo "Downloaded Candidate Guard runtime is not a regular file: $runtime" >&2
exit 1
fi
if [ "${#COSMO_CANDIDATE_GUARD_SHA256}" -ne 64 ] \
|| [[ "$COSMO_CANDIDATE_GUARD_SHA256" == *[!0-9a-f]* ]]; then
echo "Missing or invalid expected Candidate Guard SHA-256." >&2
exit 1
fi
actual_sha256="$(sha256sum "$runtime" | awk '{print $1}')"
if [ "$actual_sha256" != "$COSMO_CANDIDATE_GUARD_SHA256" ]; then
echo "Candidate Guard SHA-256 mismatch." >&2
echo "Expected: $COSMO_CANDIDATE_GUARD_SHA256" >&2
echo "Actual: $actual_sha256" >&2
exit 1
fi
ln -s -- "libcosmo_model_guard.so.2.0.0" \
"$COSMO_CANDIDATE_RUNTIME_DIR/libcosmo_model_guard.so.2"
ln -s -- "libcosmo_model_guard.so.2" \
"$COSMO_CANDIDATE_RUNTIME_DIR/libcosmo_model_guard.so"
test "$(readlink "$COSMO_CANDIDATE_RUNTIME_DIR/libcosmo_model_guard.so.2")" \
= "libcosmo_model_guard.so.2.0.0"
test "$(readlink "$COSMO_CANDIDATE_RUNTIME_DIR/libcosmo_model_guard.so")" \
= "libcosmo_model_guard.so.2"
echo "Candidate Guard runtime verified: $actual_sha256"
# The Download step below is the dominant wall-clock cost on this
# self-hosted device (fetching the artifact from GitHub over a slow link
# routinely takes 7-13 min, vs seconds-to-minutes for the tests). On
# "Re-run failed jobs" the whole job re-runs, so we cache the binary on
# the device's persistent filesystem keyed by both GITHUB_RUN_ID and the
# build-produced SHA-256. A full job re-run therefore cannot reuse a stale
# binary, while a failed-job re-run can still skip the large download.
- name: Restore Test Binary From Device Cache
id: binary-cache
run: |
set -euo pipefail
if [ "${#COSMO_CANDIDATE_TESTS_SHA256}" -ne 64 ] \
|| [[ "$COSMO_CANDIDATE_TESTS_SHA256" == *[!0-9a-f]* ]]; then
echo "Missing or invalid expected Candidate test binary SHA-256." >&2
exit 1
fi
# $HOME persists across runs and is outside the per-job workspace
# GitHub cleans, so a file saved here survives across attempts.
# Override the dir by setting COSMO_TEST_BINARY_CACHE_DIR in env.
cache_dir="${COSMO_TEST_BINARY_CACHE_DIR:-$HOME/cosmo-ci-cache}"
mkdir -p "$cache_dir"
cached="$cache_dir/cosmo-tests-${GITHUB_RUN_ID}-${COSMO_CANDIDATE_TESTS_SHA256}"
if [ -f "$cached" ] && [ ! -L "$cached" ]; then
cached_sha256="$(sha256sum "$cached" | awk '{print $1}')"
if [ "$cached_sha256" = "$COSMO_CANDIDATE_TESTS_SHA256" ]; then
cp -- "$cached" ./cosmo-tests
restored_sha256="$(sha256sum ./cosmo-tests | awk '{print $1}')"
if [ "$restored_sha256" != "$COSMO_CANDIDATE_TESTS_SHA256" ]; then
echo "Restored Candidate test binary SHA-256 mismatch." >&2
exit 1
fi
echo "cache-hit=true" >> "$GITHUB_OUTPUT"
echo "Restored verified cosmo-tests from $cached (skipping artifact download)."
else
echo "cache-hit=false" >> "$GITHUB_OUTPUT"
echo "Ignoring corrupt test-binary cache entry: $cached"
fi
else
echo "cache-hit=false" >> "$GITHUB_OUTPUT"
echo "No verified cache entry for run ${GITHUB_RUN_ID} and SHA ${COSMO_CANDIDATE_TESTS_SHA256}; will download."
fi
- name: Download Test Binary
if: steps.binary-cache.outputs.cache-hit != 'true' # skipped on device-cache hit
uses: actions/download-artifact@v8
with:
name: sophon-tests-binary
path: .
- name: Verify Candidate Test Binary
run: |
set -euo pipefail
if [ ! -f ./cosmo-tests ] || [ -L ./cosmo-tests ]; then
echo "Candidate test binary is not a regular file." >&2
exit 1
fi
actual_sha256="$(sha256sum ./cosmo-tests | awk '{print $1}')"
if [ "$actual_sha256" != "$COSMO_CANDIDATE_TESTS_SHA256" ]; then
echo "Candidate test binary SHA-256 mismatch." >&2
echo "Expected: $COSMO_CANDIDATE_TESTS_SHA256" >&2
echo "Actual: $actual_sha256" >&2
exit 1
fi
echo "Candidate test binary verified: $actual_sha256"
- name: Save Test Binary To Device Cache
if: steps.binary-cache.outputs.cache-hit != 'true' && success()
run: |
set -euo pipefail
cache_dir="${COSMO_TEST_BINARY_CACHE_DIR:-$HOME/cosmo-ci-cache}"
cached="$cache_dir/cosmo-tests-${GITHUB_RUN_ID}-${COSMO_CANDIDATE_TESTS_SHA256}"
pending="${cached}.pending-${GITHUB_RUN_ATTEMPT}"
cp --remove-destination -- ./cosmo-tests "$pending"
pending_sha256="$(sha256sum "$pending" | awk '{print $1}')"
if [ "$pending_sha256" != "$COSMO_CANDIDATE_TESTS_SHA256" ]; then
echo "Refusing to cache test binary with mismatched SHA-256." >&2
exit 1
fi
mv -f -- "$pending" "$cached"
# Prune to the 5 most-recent entries; never touch the current run.
# `|| true`: grep exits 1 when only the current run's file exists
# (nothing left after filtering), which under `set -o pipefail` would
# otherwise fail this step. Pruning is best-effort cleanup.
ls -t "$cache_dir"/cosmo-tests-* 2>/dev/null \
| grep -v -F -- "$cached" \
| tail -n +6 \
| xargs -r rm -f || true
echo "Saved cosmo-tests to $cached; cache pruned."
- name: Add Execution Permission
run: chmod +x ./cosmo-tests
- name: Verify Candidate Guard Resolution
run: |
set -euo pipefail
export LD_LIBRARY_PATH="$COSMO_CANDIDATE_RUNTIME_DIR:$COSMO_SOPHON_LD_LIBRARY_PATH:${LD_LIBRARY_PATH:-}"
if ! command -v ldd >/dev/null 2>&1; then
echo "Required command is missing: ldd" >&2
exit 1
fi
resolved="$(ldd ./cosmo-tests \
| awk '$1 == "libcosmo_model_guard.so.2" && $2 == "=>" {print $3; exit}')"
if [ -z "$resolved" ]; then
echo "cosmo-tests did not resolve libcosmo_model_guard.so.2." >&2
exit 1
fi
expected_real="$(readlink -f \
"$COSMO_CANDIDATE_RUNTIME_DIR/libcosmo_model_guard.so.2.0.0")"
resolved_real="$(readlink -f "$resolved")"
if [ "$resolved_real" != "$expected_real" ]; then
echo "cosmo-tests resolved a non-candidate Guard runtime: $resolved" >&2
exit 1
fi
echo "cosmo-tests resolves Candidate Guard runtime: $resolved_real"
- name: Run Catch2 Tests
run: |
set +e
set -uo pipefail
export LD_LIBRARY_PATH="$COSMO_CANDIDATE_RUNTIME_DIR:$COSMO_SOPHON_LD_LIBRARY_PATH:${LD_LIBRARY_PATH:-}"
shard_count="$COSMO_CATCH2_SHARDS"
shard_timeout_seconds="$COSMO_CATCH2_SHARD_TIMEOUT_SECONDS"
case_timeout_seconds="$COSMO_TEST_CASE_TIMEOUT_SECONDS"
reporter="$COSMO_CATCH2_REPORTER"
results_dir="$COSMO_CATCH2_RESULTS_DIR"
shards_dir="$results_dir/shards"
bisect_dir="$results_dir/bisect"
summary_file="$results_dir/summary.txt"
gcov_dir="${RUNNER_TEMP:-/tmp}/cosmo-gcov-${GITHUB_RUN_ID:-manual}-${GITHUB_RUN_ATTEMPT:-0}"
# Validate numeric knobs: a non-positive shard_count would skip every loop and
# the verdict below would pass 0/0 → green with zero tests run.
require_pos_int() {
case "$2" in
''|*[!0-9]*) echo "error: $1 must be a positive integer (got '$2')" >&2; exit 2 ;;
esac
[ "$2" -ge 1 ] || { echo "error: $1 must be >= 1 (got '$2')" >&2; exit 2; }
}
require_pos_int COSMO_CATCH2_SHARDS "$shard_count"
require_pos_int COSMO_CATCH2_SHARD_TIMEOUT_SECONDS "$shard_timeout_seconds"
require_pos_int COSMO_TEST_CASE_TIMEOUT_SECONDS "$case_timeout_seconds"
# --input-file parses each line as a Catch2 test specification. Escape
# grammar characters so punctuation in a literal test name (notably
# commas) cannot turn the generated filter into an invalid spec.
write_catch2_test_spec() {
local escaped="$1"
escaped="${escaped//\\/\\\\}"
escaped="${escaped//,/\\,}"
escaped="${escaped//\"/\\\"}"
printf '"%s"\n' "$escaped"
}
# self-hosted runner 工作目录跨 run 复用,历史 run 的 bisect/shards 文件会累积,
# 被 Upload step 整个目录打包上传,新旧混杂干扰诊断。每次 run 开始清空这两个目录。
rm -rf "$shards_dir" "$bisect_dir"
mkdir -p "$shards_dir" "$bisect_dir" "$gcov_dir"
: > "$summary_file"
# cosmo-tests is built WITHOUT --coverage by default; coverage is now
# opt-in via COSMO_ENABLE_COVERAGE. The GCOV_PREFIX exports below are a
# no-op in the default nightly build, but are kept so that if coverage
# is re-enabled, gcov writes redirect to a local writable dir instead
# of the GitHub /__w workspace path that does not exist on this device
# (libgcov would otherwise pollute stderr / fail process exit).
export GCOV_PREFIX="$gcov_dir"
export GCOV_PREFIX_STRIP=4
if ! command -v timeout >/dev/null 2>&1; then
echo "Required command is missing: timeout" >&2
exit 2
fi
# 1) Enumerate all test cases. `--verbosity quiet` makes --list-tests print one
# plain name per line (Catch2's listTestNamesOnly), which round-trips through
# --input-file. Wrap in timeout in case the binary deadlocks while loading.
list_file="$results_dir/test-list.txt"
raw_list_file="$results_dir/test-list.raw.txt"
list_stderr_file="$results_dir/test-list.stderr.log"
timeout 120s ./cosmo-tests --list-tests --verbosity quiet > "$raw_list_file" 2> "$list_stderr_file"
list_status=$?
if [ "$list_status" -ne 0 ]; then
echo "Failed to list Catch2 test cases, exit code: $list_status" >&2
cat "$raw_list_file"
cat "$list_stderr_file"
exit "$list_status"
fi
awk 'NF && $0 !~ /^profiling:/' "$raw_list_file" > "$list_file"
mapfile -t all_tests < "$list_file"
total=${#all_tests[@]}
if [ "$total" -eq 0 ]; then
echo "No Catch2 test cases found." >&2
exit 1
fi
# Don't create more shards than tests.
if [ "$total" -lt "$shard_count" ]; then
shard_count="$total"
fi
echo "Listed $total Catch2 test cases; splitting into $shard_count shards (${shard_timeout_seconds}s budget/shard, ${case_timeout_seconds}s/case on bisection)."
# 2) Round-robin each test name into its shard's --input-file.
shard_input=()
for ((s = 0; s < shard_count; s++)); do
shard_input[$s]="$(printf '%s/shards/%02d.input' "$results_dir" "$((s + 1))")"
: > "${shard_input[$s]}"
done
for i in "${!all_tests[@]}"; do
name="${all_tests[$i]}"
[ -n "$name" ] || continue
s=$((i % shard_count))
printf '%s\n' "$name" >> "${shard_input[$s]}"
done
# 3) Run each shard in its own process. Per-shard timeout bounds a hung test to
# the shard budget instead of the whole job; --warn UnmatchedTestSpec turns a
# partial spec match (shouldn't happen, but defensive) into a non-zero exit.
shards_run=0
shards_passed=0
problem_shards=0
bisect_targets=()
for ((s = 0; s < shard_count; s++)); do
input="${shard_input[$s]}"
# An empty --input-file means "no filter" and would run the WHOLE suite.
[ -s "$input" ] || continue
idx="$(printf '%02d' "$((s + 1))")"
log="$shards_dir/$idx.log"
mapfile -t shard_tests < "$input"
n=${#shard_tests[@]}
shards_run=$((shards_run + 1))
filter="$shards_dir/$idx.filter"
: > "$filter"
for name in "${shard_tests[@]}"; do
[ -n "$name" ] || continue
write_catch2_test_spec "$name" >> "$filter"
done
echo
echo "[$idx] shard with $n tests"
start_seconds="$(date +%s)"
timeout --kill-after=10s "${shard_timeout_seconds}s" \
./cosmo-tests --input-file "$filter" --reporter "$reporter" \
--warn UnmatchedTestSpec \
> "$log" 2>&1
status=$?
elapsed_seconds=$(( $(date +%s) - start_seconds ))
if [ "$status" -eq 0 ]; then
echo "SHARD PASS in ${elapsed_seconds}s: #$idx ($n tests)"
printf 'SHARD\tPASS\telapsed_seconds=%s\ttests=%s\tshard=%s\n' "$elapsed_seconds" "$n" "$idx" >> "$summary_file"
shards_passed=$((shards_passed + 1))
elif [ "$status" -eq 42 ]; then
# TestFailureExitCode: an assertion already failed and Catch2 named it.
echo "SHARD FAIL in ${elapsed_seconds}s: #$idx (assertion failure; last 40 log lines:)"
tail -n 40 "$log"
printf 'SHARD\tFAIL\telapsed_seconds=%s\ttests=%s\tshard=%s\n' "$elapsed_seconds" "$n" "$idx" >> "$summary_file"
problem_shards=$((problem_shards + 1))
elif [ "$status" -eq 124 ] || [ "$status" -eq 137 ]; then
echo "SHARD TIMEOUT after ${elapsed_seconds}s: #$idx (will bisect per-test)"
printf 'SHARD\tTIMEOUT\telapsed_seconds=%s\ttests=%s\tshard=%s\n' "$elapsed_seconds" "$n" "$idx" >> "$summary_file"
bisect_targets+=("$s")
problem_shards=$((problem_shards + 1))
else
echo "SHARD CRASH($status) in ${elapsed_seconds}s: #$idx (will bisect per-test)"
printf 'SHARD\tCRASH(%s)\telapsed_seconds=%s\ttests=%s\tshard=%s\n' "$status" "$elapsed_seconds" "$n" "$idx" >> "$summary_file"
bisect_targets+=("$s")
problem_shards=$((problem_shards + 1))
fi
done
# 4) For any shard that timed out or crashed, re-run its tests one-per-process to
# pinpoint the culprit. Use the same literal-name escaping as the shard run;
# --warn UnmatchedTestSpec still catches a no-match.
if [ "${#bisect_targets[@]}" -gt 0 ]; then
echo
echo "Bisecting ${#bisect_targets[@]} shard(s) per-test (${case_timeout_seconds}s/case)."
for s in "${bisect_targets[@]}"; do
idx="$(printf '%02d' "$((s + 1))")"
mapfile -t shard_tests < "${shard_input[$s]}"
echo
echo "[bisect #$idx] ${#shard_tests[@]} tests"
for j in "${!shard_tests[@]}"; do
name="${shard_tests[$j]}"
[ -n "$name" ] || continue
number=$((j + 1))
safe_name="$(printf '%s' "$name" | tr -cs '[:alnum:]_.-' '_' | cut -c1-120)"
safe_name="${safe_name:-test}"
log_file="$bisect_dir/${idx}_$(printf '%03d' "$number")_${safe_name}.log"
filter_file="$bisect_dir/${idx}_$(printf '%03d' "$number")_${safe_name}.filter"
write_catch2_test_spec "$name" > "$filter_file"
echo "[bisect #$idx $number/${#shard_tests[@]}] $name"
start_seconds="$(date +%s)"
timeout --kill-after=10s "${case_timeout_seconds}s" \
./cosmo-tests --input-file "$filter_file" --reporter "$reporter" \
--warn UnmatchedTestSpec \
> "$log_file" 2>&1
status=$?
elapsed_seconds=$(( $(date +%s) - start_seconds ))
if [ "$status" -eq 0 ]; then
printf 'BISECT\tPASS\tshard=%s\telapsed_seconds=%s\t%s\n' "$idx" "$elapsed_seconds" "$name" >> "$summary_file"
elif [ "$status" -eq 124 ] || [ "$status" -eq 137 ]; then
echo " -> TIMEOUT after ${elapsed_seconds}s: $name"
printf 'BISECT\tTIMEOUT\tshard=%s\telapsed_seconds=%s\t%s\n' "$idx" "$elapsed_seconds" "$name" >> "$summary_file"
else
echo " -> FAIL($status) in ${elapsed_seconds}s: $name"
printf 'BISECT\tFAIL(%s)\tshard=%s\telapsed_seconds=%s\t%s\n' "$status" "$idx" "$elapsed_seconds" "$name" >> "$summary_file"
fi
done
done
fi
# 5) Verdict.
echo
echo "Catch2 summary: shards_passed=$shards_passed/$shards_run problem_shards=$problem_shards total_tests=$total"
echo "Per-shard logs: $shards_dir ; bisect logs: $bisect_dir ; summary: $summary_file"
# Fail on any problem shard, OR if we somehow ran zero shards (e.g. bad
# config): a vacuous 0/0 must never pass as green.
if [ "$problem_shards" -ne 0 ] || [ "$shards_passed" -ne "$shards_run" ] || [ "$shards_run" -eq 0 ]; then
exit 1
fi
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v7
with:
name: sophon-catch2-test-results
path: test-results/catch2/
retention-days: 3