From 6a2eaec7256d369f4fc5c608bdb63a71331e093a Mon Sep 17 00:00:00 2001 From: Monkopedia Date: Wed, 29 Jul 2026 15:06:09 -0400 Subject: [PATCH] fix(ci): make samples/minimal test evidence loud, not silent (#198) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gradle's Kotlin/Native test task prints no per-test lines by default, and a test task with no matching sources succeeds silently — so the samples-minimal workflow could go green whether nativeTest ran 3 tests or zero, with nothing in the log to tell the difference. - build.gradle.kts: wire testLogging(passed/skipped/failed) onto every AbstractTestTask so per-test PASSED/FAILED lines show up in the log. - workflow: parse the JUnit XML nativeTest produces and fail the job outright if the parsed test count is 0 (or if failures/errors slipped through). Upload the XML as a workflow artifact for human review. - workflow: add timeout-minutes: 15 so a wedged run can't sit for the inherited 360-minute GitHub default. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01NLWsoRAe7f1RdQrYJ2ZXqm --- .github/workflows/samples-minimal.yml | 52 +++++++++++++++++++++++++++ samples/minimal/build.gradle.kts | 11 ++++++ 2 files changed, 63 insertions(+) diff --git a/.github/workflows/samples-minimal.yml b/.github/workflows/samples-minimal.yml index 62e7b8e..e587173 100644 --- a/.github/workflows/samples-minimal.yml +++ b/.github/workflows/samples-minimal.yml @@ -37,6 +37,9 @@ on: jobs: native-test: runs-on: ubuntu-latest + # The job normally takes ~2 minutes; without this it inherits GitHub's 360-minute + # cap, so a wedged run could sit for six hours before anyone notices (#198). + timeout-minutes: 15 steps: - uses: actions/checkout@v4 @@ -68,3 +71,52 @@ jobs: - name: samples/minimal :nativeTest working-directory: samples/minimal run: ./gradlew nativeTest --no-daemon + + # #198: Gradle's Kotlin/Native test task prints no per-test lines by default (fixed + # above via testLogging in build.gradle.kts) and a Test task with no matching + # sources succeeds silently — so a green step above is NOT proof anything ran. Parse + # the JUnit XML this task itself produced and fail loudly if the count is zero (or if + # any failure/error slipped through despite Gradle already having failed the build on + # those). This is the actual guarantee; the artifact upload below is just evidence for + # a human who wants to double check. + - name: Assert nativeTest actually ran tests + if: always() + working-directory: samples/minimal + run: | + set -euo pipefail + shopt -s nullglob globstar + xml_files=(build/test-results/**/*.xml) + if [ ${#xml_files[@]} -eq 0 ]; then + echo "::error::No JUnit XML found under samples/minimal/build/test-results — nativeTest produced no report at all, so it cannot have run anything." + exit 1 + fi + total_tests=0 + total_failures=0 + total_errors=0 + for f in "${xml_files[@]}"; do + t=$(grep -o 'tests="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*') || t=0 + fa=$(grep -o 'failures="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*') || fa=0 + er=$(grep -o 'errors="[0-9]*"' "$f" | head -1 | grep -o '[0-9]*') || er=0 + echo "$f: tests=$t failures=$fa errors=$er" + total_tests=$((total_tests + t)) + total_failures=$((total_failures + fa)) + total_errors=$((total_errors + er)) + done + echo "TOTAL across ${#xml_files[@]} report(s): tests=$total_tests failures=$total_failures errors=$total_errors" + if [ "$total_tests" -eq 0 ]; then + echo "::error::nativeTest reported ZERO tests. This job would otherwise be green having run nothing — failing it explicitly." + exit 1 + fi + if [ "$total_failures" -gt 0 ] || [ "$total_errors" -gt 0 ]; then + echo "::error::nativeTest reported failures/errors in the JUnit XML." + exit 1 + fi + + - name: Upload nativeTest results + if: always() + uses: actions/upload-artifact@v4 + with: + name: samples-minimal-native-test-results + path: samples/minimal/build/test-results/** + if-no-files-found: warn + retention-days: 14 diff --git a/samples/minimal/build.gradle.kts b/samples/minimal/build.gradle.kts index 036513b..584a3d4 100644 --- a/samples/minimal/build.gradle.kts +++ b/samples/minimal/build.gradle.kts @@ -164,3 +164,14 @@ tasks.withType().all { jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11) } } + +// #198: Kotlin/Native's test task (KotlinNativeTest, an AbstractTestTask) prints no +// per-test lines by default, so a green `nativeTest` in CI looks identical whether it +// ran 3 tests or zero. Make per-test outcomes show up in the log directly — the CI +// workflow (.github/workflows/samples-minimal.yml) additionally parses the JUnit XML +// under build/test-results/** and fails the job outright if the parsed count is 0. +tasks.withType().configureEach { + testLogging { + events("passed", "skipped", "failed") + } +}