Improved bootloader parsing (#356) (#358) #979
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: Unit Tests and Coverage | |
| on: | |
| pull_request: | |
| branches: [main] | |
| push: | |
| branches: [main] # Only on main to avoid duplicate runs with pull_request | |
| workflow_dispatch: | |
| inputs: | |
| ref: | |
| description: "Branch or SHA to test" | |
| required: false | |
| cov_threshold: | |
| description: "Override coverage threshold (%)" | |
| required: false | |
| concurrency: | |
| group: unit-tests-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: write # Needed to push threshold updates | |
| jobs: | |
| test: | |
| name: Unit Tests & Coverage Gate | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| ref: ${{ inputs.ref || github.event.pull_request.head.sha || github.sha }} | |
| - name: Setup Earthly | |
| uses: earthly/actions/setup-earthly@v1 | |
| with: | |
| version: "latest" | |
| - name: Configure test parameters | |
| id: config | |
| env: | |
| INPUT_COV_THRESHOLD: ${{ inputs.cov_threshold }} | |
| run: | | |
| # Read threshold from file, allow manual override | |
| # Use fallback 65.0 if file is missing, empty, or unreadable | |
| FILE_THRESHOLD=$(cat .coverage-threshold 2>/dev/null | tr -d '[:space:]') | |
| FILE_THRESHOLD="${FILE_THRESHOLD:-65.0}" | |
| COV_THRESHOLD="${INPUT_COV_THRESHOLD:-$FILE_THRESHOLD}" | |
| # Validate it's a number, otherwise use fallback | |
| if ! [[ "$COV_THRESHOLD" =~ ^[0-9]+\.?[0-9]*$ ]]; then | |
| echo "::warning::Invalid threshold '$COV_THRESHOLD', using 65.0" | |
| COV_THRESHOLD="65.0" | |
| fi | |
| echo "cov_threshold=${COV_THRESHOLD}" >> "$GITHUB_OUTPUT" | |
| echo "build_id=${GITHUB_RUN_ID}" >> "$GITHUB_OUTPUT" | |
| if [[ -n "${INPUT_COV_THRESHOLD}" ]]; then | |
| echo "::notice::Coverage threshold: ${COV_THRESHOLD}% (from manual override)" | |
| else | |
| echo "::notice::Coverage threshold: ${COV_THRESHOLD}% (from .coverage-threshold file)" | |
| fi | |
| - name: Run tests with coverage | |
| id: test | |
| env: | |
| COV_THRESHOLD: ${{ steps.config.outputs.cov_threshold }} | |
| BUILD_ID: ${{ steps.config.outputs.build_id }} | |
| run: | | |
| earthly +test \ | |
| --COV_THRESHOLD="${COV_THRESHOLD}" \ | |
| --PRINT_TS="${BUILD_ID}" \ | |
| --FAIL_ON_NO_TESTS="false" | |
| - name: Upload coverage artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-${{ github.run_id }} | |
| path: | | |
| coverage.out | |
| coverage_report.txt | |
| retention-days: 30 | |
| - name: Generate coverage summary | |
| if: always() | |
| run: | | |
| { | |
| echo "## 📊 Test Coverage Report" | |
| echo "" | |
| # Extract overall coverage | |
| if [[ -f coverage_report.txt ]]; then | |
| # Extract numeric values using simpler patterns | |
| OVERALL=$(grep "Overall Coverage:" coverage_report.txt | sed 's/[^0-9.]*\([0-9.]\+\)%.*/\1/')% | |
| THRESHOLD=$(grep "Threshold:" coverage_report.txt | sed 's/[^0-9.]*\([0-9.]\+\)%.*/\1/')% | |
| # Get last word on the Status line (PASSED or FAILED) | |
| STATUS=$(grep "Status:" coverage_report.txt | awk '{print $NF}') | |
| # Status badge | |
| if [[ "$STATUS" == "PASSED" ]]; then | |
| echo "| Metric | Value | Status |" | |
| echo "|--------|-------|--------|" | |
| echo "| **Overall Coverage** | $OVERALL | ✅ PASSED |" | |
| echo "| **Threshold** | $THRESHOLD | - |" | |
| echo "| **Build** | #${{ github.run_id }} | - |" | |
| else | |
| echo "| Metric | Value | Status |" | |
| echo "|--------|-------|--------|" | |
| echo "| **Overall Coverage** | $OVERALL | ❌ FAILED |" | |
| echo "| **Threshold** | $THRESHOLD | - |" | |
| echo "| **Build** | #${{ github.run_id }} | - |" | |
| fi | |
| echo "" | |
| # Failed tests section | |
| if grep -q "Failed Tests:" coverage_report.txt; then | |
| echo "### ❌ Failed Tests" | |
| echo "" | |
| sed -n '/\*\*Failed Tests:\*\*/,/^\*\*Note/p' coverage_report.txt | grep "•" | head -20 | |
| echo "" | |
| fi | |
| # Directory results table | |
| if grep -q "| Directory" coverage_report.txt; then | |
| echo "### 📁 Directory Results" | |
| echo "" | |
| sed -n '/| Directory/,/^$/p' coverage_report.txt | head -50 | |
| echo "" | |
| fi | |
| else | |
| echo "⚠️ Coverage report not generated" | |
| echo "" | |
| echo "Check the workflow logs for details." | |
| fi | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| - name: Auto-update coverage threshold (ratchet) | |
| if: success() && github.ref != 'refs/heads/main' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| HEAD_REF: ${{ github.head_ref }} | |
| REF_NAME: ${{ github.ref_name }} | |
| run: | | |
| set -x # Debug: show commands | |
| # Determine branch name based on event type | |
| if [[ "${EVENT_NAME}" == "pull_request" ]]; then | |
| BRANCH="${HEAD_REF}" | |
| else | |
| # For workflow_dispatch or other events, use ref_name | |
| BRANCH="${REF_NAME}" | |
| fi | |
| echo "Target branch: ${BRANCH}" | |
| # Skip if on main branch (shouldn't happen due to condition, but safety check) | |
| if [[ "$BRANCH" == "main" ]]; then | |
| echo "Skipping ratchet on main branch" | |
| exit 0 | |
| fi | |
| # Get current coverage from report using simpler pattern | |
| CURRENT=$(grep "Overall Coverage:" coverage_report.txt | sed 's/[^0-9.]*\([0-9.]\+\)%.*/\1/') | |
| OLD_THRESHOLD=$(cat .coverage-threshold 2>/dev/null || echo "0") | |
| # Validate CURRENT is a valid number before proceeding | |
| if [[ -z "$CURRENT" ]] || ! [[ "$CURRENT" =~ ^[0-9]+\.?[0-9]*$ ]]; then | |
| echo "::error::Failed to parse coverage value from report (got: '$CURRENT')" | |
| exit 1 | |
| fi | |
| echo "Current coverage: ${CURRENT}%" | |
| echo "Current threshold: ${OLD_THRESHOLD}%" | |
| # Calculate new threshold (0.5% buffer below actual coverage) | |
| # Use printf to ensure consistent one-decimal-place formatting | |
| NEW_THRESHOLD=$(printf '%.1f' "$(echo "$CURRENT - 0.5" | bc -l)") | |
| echo "Proposed new threshold: ${NEW_THRESHOLD}%" | |
| # Only update if new threshold is higher than old threshold | |
| if (( $(echo "$NEW_THRESHOLD > $OLD_THRESHOLD" | bc -l) )); then | |
| echo "Updating threshold: ${OLD_THRESHOLD}% -> ${NEW_THRESHOLD}%" | |
| echo "${NEW_THRESHOLD}" > .coverage-threshold | |
| # Configure git | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Fetch and checkout the branch | |
| git fetch origin "${BRANCH}" | |
| git checkout "${BRANCH}" | |
| # Ensure branch is up to date in case it changed after the workflow started | |
| git pull --rebase origin "${BRANCH}" | |
| # Stage and commit | |
| git add .coverage-threshold | |
| git commit -m "chore: auto-update coverage threshold to ${NEW_THRESHOLD}% (was ${OLD_THRESHOLD}%)" | |
| git push origin "${BRANCH}" | |
| echo "::notice::Coverage threshold updated to ${NEW_THRESHOLD}% on branch ${BRANCH}" | |
| else | |
| echo "New threshold (${NEW_THRESHOLD}%) is not higher than current (${OLD_THRESHOLD}%), no update needed" | |
| fi |