KeyPath Coverage #43
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: KeyPath Coverage | |
| on: | |
| schedule: | |
| - cron: '30 8 * * *' | |
| workflow_dispatch: | |
| concurrency: | |
| group: coverage-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| narrow-coverage: | |
| runs-on: [self-hosted, macOS, keypath, swiftpm-safe] | |
| timeout-minutes: 10 | |
| env: | |
| COVERAGE_BASELINE_PERCENT: "0.27" | |
| COVERAGE_TOLERANCE_PERCENT: "0.01" | |
| steps: | |
| - name: Clean stale git credentials | |
| run: | | |
| git config --global --unset-all http.https://github.com/.extraheader 2>/dev/null || true | |
| git config --global --unset-all url.https://github.com/.insteadof 2>/dev/null || true | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: true | |
| persist-credentials: false | |
| - name: Verify runner disk reserve | |
| run: ./Scripts/lab/host-disk-reserve | |
| - name: Add Homebrew to PATH | |
| run: echo "/opt/homebrew/bin" >> $GITHUB_PATH | |
| - name: Generate coverage | |
| timeout-minutes: 5 | |
| env: | |
| KP_SIGN_DRY_RUN: "1" | |
| CI_ENVIRONMENT: "true" | |
| SKIP_EVENT_TAP_TESTS: "1" | |
| run: | | |
| echo "Generating narrow-lane coverage..." | |
| swift test --enable-code-coverage --filter 'KeyPathErrorTests|PermissionOracleTests' | |
| chmod +x ./Scripts/generate-coverage.sh | |
| ./Scripts/generate-coverage.sh coverage | |
| - name: Enforce coverage non-regression | |
| run: | | |
| echo "Enforcing narrow-lane coverage floor (${COVERAGE_BASELINE_PERCENT}%)..." | |
| if [ ! -f "coverage/coverage-summary.txt" ]; then | |
| echo "Missing coverage/coverage-summary.txt" | |
| exit 1 | |
| fi | |
| ACTUAL_COVERAGE=$(awk '/^TOTAL/ {for(i=1;i<=NF;i++) if ($i ~ /%$/) {gsub("%","",$i); print $i; exit}}' coverage/coverage-summary.txt) | |
| if [ -z "${ACTUAL_COVERAGE}" ]; then | |
| echo "Could not parse TOTAL coverage from coverage/coverage-summary.txt" | |
| cat coverage/coverage-summary.txt | |
| exit 1 | |
| fi | |
| echo "Coverage (narrow lane): ${ACTUAL_COVERAGE}%" | |
| EFFECTIVE_FLOOR=$(awk -v baseline="${COVERAGE_BASELINE_PERCENT}" -v tol="${COVERAGE_TOLERANCE_PERCENT}" 'BEGIN {printf "%.2f", baseline - tol}') | |
| if awk -v actual="${ACTUAL_COVERAGE}" -v floor="${EFFECTIVE_FLOOR}" 'BEGIN {exit !(actual + 0 >= floor + 0)}'; then | |
| echo "Coverage is non-regressing (>= ${EFFECTIVE_FLOOR}% effective floor)" | |
| else | |
| echo "Coverage regression: ${ACTUAL_COVERAGE}% < ${EFFECTIVE_FLOOR}% effective floor" | |
| exit 1 | |
| fi | |
| - name: Upload coverage artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage | |
| path: coverage/ | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| - name: Generate coverage summary | |
| if: always() | |
| run: | | |
| echo "## Coverage (narrow lane)" >> $GITHUB_STEP_SUMMARY | |
| if [ -f "coverage/coverage-summary.txt" ]; then | |
| echo "- Summary: \`$(cat coverage/coverage-summary.txt)\`" >> $GITHUB_STEP_SUMMARY | |
| echo "- Enforced narrow-lane floor: \`${COVERAGE_BASELINE_PERCENT}%\`" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "- Coverage not generated." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| # Visibility-only: runs the WHOLE suite under coverage instrumentation and | |
| # reports real numbers (total + the core business-logic files). It does NOT | |
| # enforce a floor yet — the narrow lane above is the only gate. Once these | |
| # numbers are trusted, a realistic per-file floor can be added here. | |
| full-coverage: | |
| runs-on: [self-hosted, macOS, keypath, swiftpm-safe] | |
| # Sequence after the narrow lane: the two jobs share the self-hosted runner's | |
| # workspace/.build, so running them in parallel risks clobbering each other's | |
| # default.profdata (CLAUDE.md: avoid concurrent broad Swift builds). `needs` | |
| # serializes them; `if: always()` still surfaces full numbers when the narrow | |
| # floor regresses or a filtered test fails. | |
| needs: [narrow-coverage] | |
| if: always() | |
| timeout-minutes: 40 | |
| steps: | |
| - name: Clean stale git credentials | |
| run: | | |
| git config --global --unset-all http.https://github.com/.extraheader 2>/dev/null || true | |
| git config --global --unset-all url.https://github.com/.insteadof 2>/dev/null || true | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: true | |
| persist-credentials: false | |
| - name: Verify runner disk reserve | |
| run: ./Scripts/lab/host-disk-reserve | |
| - name: Add Homebrew to PATH | |
| run: echo "/opt/homebrew/bin" >> $GITHUB_PATH | |
| - name: Generate full-suite coverage | |
| timeout-minutes: 30 | |
| env: | |
| KP_SIGN_DRY_RUN: "1" | |
| CI_ENVIRONMENT: "true" | |
| SKIP_EVENT_TAP_TESTS: "1" | |
| run: | | |
| echo "Generating full-suite coverage (no filter)..." | |
| swift test --enable-code-coverage | |
| chmod +x ./Scripts/generate-coverage.sh | |
| ./Scripts/generate-coverage.sh coverage-full | |
| - name: Upload full coverage artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-full | |
| path: coverage-full/ | |
| if-no-files-found: ignore | |
| retention-days: 14 | |
| - name: Report full coverage | |
| if: always() | |
| run: | | |
| echo "## Coverage (full suite — visibility only, not enforced)" >> $GITHUB_STEP_SUMMARY | |
| if [ -f "coverage-full/coverage-summary.txt" ]; then | |
| echo "- Total: \`$(cat coverage-full/coverage-summary.txt)\`" >> $GITHUB_STEP_SUMMARY | |
| else | |
| echo "- Coverage not generated." >> $GITHUB_STEP_SUMMARY | |
| fi | |
| if [ -s "coverage-full/coverage-core.txt" ]; then | |
| { | |
| echo "" | |
| echo "### Core business-logic files" | |
| echo '```' | |
| cat "coverage-full/coverage-core.txt" | |
| echo '```' | |
| } >> $GITHUB_STEP_SUMMARY | |
| fi |