Skip to content

Commit 252993d

Browse files
authored
Merge pull request #350 from open-edge-platform/refactor/coverage-script-cleanup
Refactor: Coverage Script Cleanup & Auto-Ratchet
2 parents 1d18f88 + 53b2cde commit 252993d

4 files changed

Lines changed: 230 additions & 267 deletions

File tree

.coverage-threshold

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
64.2
Lines changed: 146 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -1,227 +1,200 @@
1-
name: Unit and Coverage
1+
name: Unit Tests and Coverage
22

33
on:
44
pull_request:
5-
branches: [ main ] # Gate PRs into main
5+
branches: [main]
66
push:
7-
branches: [ main ] # also run on direct pushes
8-
workflow_dispatch: # Manual runs
7+
branches: [main] # Only on main to avoid duplicate runs with pull_request
8+
workflow_dispatch:
99
inputs:
1010
ref:
11-
description: "Branch or SHA to test (e.g. feature/x or a1b2c3)"
11+
description: "Branch or SHA to test"
1212
required: false
1313
cov_threshold:
14-
description: "Override threshold (percent) for this manual run only"
14+
description: "Override coverage threshold (%)"
1515
required: false
1616

1717
concurrency:
18-
group: earthly-tests-${{ github.ref }}
18+
group: unit-tests-${{ github.ref }}
1919
cancel-in-progress: true
2020

2121
permissions:
22-
contents: read
22+
contents: write # Needed to push threshold updates
2323

2424
jobs:
25-
run-earthly-tests:
26-
name: Run earthly +test (coverage gate)
25+
test:
26+
name: Unit Tests & Coverage Gate
2727
runs-on: ubuntu-latest
2828
timeout-minutes: 30
2929

3030
steps:
31-
- name: Checkout repository
31+
- name: Checkout
3232
uses: actions/checkout@v4
3333
with:
3434
fetch-depth: 0
3535
persist-credentials: false
36-
# Use input ref if provided; else PR head SHA; else current SHA
37-
ref: ${{ inputs.ref != '' && inputs.ref || (github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha) }}
36+
ref: ${{ inputs.ref || github.event.pull_request.head.sha || github.sha }}
3837

3938
- name: Setup Earthly
4039
uses: earthly/actions/setup-earthly@v1
4140
with:
4241
version: "latest"
4342

44-
- name: Show Earthly version
45-
run: earthly --version
46-
47-
- name: Resolve coverage threshold
48-
id: threshold
43+
- name: Configure test parameters
44+
id: config
4945
env:
50-
MANUAL_COV: ${{ inputs.cov_threshold }}
46+
INPUT_COV_THRESHOLD: ${{ inputs.cov_threshold }}
5147
run: |
52-
set -euo pipefail
53-
54-
# Default threshold (matches script default)
55-
DEFAULT="12.3"
56-
57-
# Use manual override if provided, otherwise use default
58-
if [[ -n "${MANUAL_COV:-}" ]]; then
59-
HEAD_VAL="${MANUAL_COV}"
60-
echo "Using manual threshold override: ${HEAD_VAL}%"
48+
# Read threshold from file, allow manual override
49+
FILE_THRESHOLD=$(cat .coverage-threshold 2>/dev/null || echo "65.0")
50+
COV_THRESHOLD="${INPUT_COV_THRESHOLD:-$FILE_THRESHOLD}"
51+
echo "cov_threshold=${COV_THRESHOLD}" >> "$GITHUB_OUTPUT"
52+
echo "build_id=${GITHUB_RUN_ID}" >> "$GITHUB_OUTPUT"
53+
if [[ -n "${INPUT_COV_THRESHOLD}" ]]; then
54+
echo "::notice::Coverage threshold: ${COV_THRESHOLD}% (from manual override)"
6155
else
62-
HEAD_VAL="$DEFAULT"
63-
echo "Using default threshold: ${HEAD_VAL}%"
56+
echo "::notice::Coverage threshold: ${COV_THRESHOLD}% (from .coverage-threshold file)"
6457
fi
6558
66-
# Set environment variables for subsequent steps
67-
echo "COV_THRESHOLD=${HEAD_VAL}" >> "$GITHUB_ENV"
68-
echo "PRINT_TS=${GITHUB_RUN_ID}" >> "$GITHUB_ENV"
69-
echo "FAIL_ON_NO_TESTS=false" >> "$GITHUB_ENV"
70-
71-
echo "Resolved threshold: ${HEAD_VAL}%"
72-
echo "Build ID: ${GITHUB_RUN_ID}"
73-
74-
# Run standard tests with coverage
75-
- name: Run Earthly +test with coverage threshold
59+
- name: Run tests with coverage
60+
id: test
61+
env:
62+
COV_THRESHOLD: ${{ steps.config.outputs.cov_threshold }}
63+
BUILD_ID: ${{ steps.config.outputs.build_id }}
7664
run: |
77-
earthly +test --COV_THRESHOLD="${COV_THRESHOLD}" --PRINT_TS="${PRINT_TS}" --FAIL_ON_NO_TESTS="${FAIL_ON_NO_TESTS}"
65+
earthly +test \
66+
--COV_THRESHOLD="${COV_THRESHOLD}" \
67+
--PRINT_TS="${BUILD_ID}" \
68+
--FAIL_ON_NO_TESTS="false"
7869
79-
# Upload main coverage artifacts (always generated by script)
80-
- name: Upload main coverage artifacts
70+
- name: Upload coverage artifacts
8171
if: always()
8272
uses: actions/upload-artifact@v4
8373
with:
84-
name: coverage-reports
74+
name: coverage-${{ github.run_id }}
8575
path: |
8676
coverage.out
87-
coverage_total.txt
88-
coverage_packages.txt
89-
test_raw.log
90-
91-
# Upload detailed per-directory artifacts for debugging
92-
- name: Upload per-directory coverage artifacts
93-
if: always()
94-
uses: actions/upload-artifact@v4
95-
with:
96-
name: per-directory-coverage
97-
path: |
98-
*_coverage.out
99-
*_test.log
100-
overall_coverage.out
101-
combined_coverage.out
102-
overall_test.log
103-
overall_test_with_failures.log
104-
if-no-files-found: ignore
105-
106-
# Legacy individual uploads for backward compatibility
107-
- name: Upload coverage.out (legacy)
108-
if: always()
109-
uses: actions/upload-artifact@v4
110-
with:
111-
name: coverage.out
112-
path: coverage.out
113-
if-no-files-found: warn
114-
115-
- name: Upload coverage_total.txt (legacy)
116-
if: always()
117-
uses: actions/upload-artifact@v4
118-
with:
119-
name: coverage_total.txt
120-
path: coverage_total.txt
121-
if-no-files-found: warn
77+
coverage_report.txt
78+
retention-days: 30
12279

123-
- name: Upload coverage_packages.txt (legacy)
124-
if: always()
125-
uses: actions/upload-artifact@v4
126-
with:
127-
name: coverage_packages.txt
128-
path: coverage_packages.txt
129-
if-no-files-found: warn
130-
131-
- name: Upload test_raw.log (legacy)
132-
if: always()
133-
uses: actions/upload-artifact@v4
134-
with:
135-
name: test_raw.log
136-
path: test_raw.log
137-
if-no-files-found: warn
138-
139-
- name: Publish coverage summary
80+
- name: Generate coverage summary
14081
if: always()
14182
run: |
14283
{
143-
echo "## Coverage Summary"
144-
if [[ -f coverage_total.txt ]]; then
145-
echo ""
146-
echo '```'
147-
cat coverage_total.txt
148-
echo '```'
149-
fi
150-
if [[ -f coverage_packages.txt ]]; then
151-
echo ""
152-
echo "Packages by coverage (lowest first):"
153-
echo '```'
154-
head -n 50 coverage_packages.txt || true
155-
echo '```'
156-
fi
84+
echo "## 📊 Test Coverage Report"
15785
echo ""
158-
echo "**Threshold used:** ${COV_THRESHOLD}%"
159-
echo "**Build ID:** ${PRINT_TS}"
160-
echo "**Test method:** Per-directory coverage with script-based execution"
16186
162-
# Add directory-level summary if available
163-
if [[ -f coverage_total.txt ]] && grep -q "| Directory" coverage_total.txt; then
87+
# Extract overall coverage
88+
if [[ -f coverage_report.txt ]]; then
89+
# Extract numeric values using simpler patterns
90+
OVERALL=$(grep "Overall Coverage:" coverage_report.txt | sed 's/[^0-9.]*\([0-9.]\+\)%.*/\1/')%
91+
THRESHOLD=$(grep "Threshold:" coverage_report.txt | sed 's/[^0-9.]*\([0-9.]\+\)%.*/\1/')%
92+
STATUS=$(grep "Status:" coverage_report.txt | sed 's/[^A-Z]*\([A-Z]\+\).*/\1/')
93+
94+
# Status badge
95+
if [[ "$STATUS" == "PASSED" ]]; then
96+
echo "| Metric | Value | Status |"
97+
echo "|--------|-------|--------|"
98+
echo "| **Overall Coverage** | $OVERALL | ✅ PASSED |"
99+
echo "| **Threshold** | $THRESHOLD | - |"
100+
echo "| **Build** | #${{ github.run_id }} | - |"
101+
else
102+
echo "| Metric | Value | Status |"
103+
echo "|--------|-------|--------|"
104+
echo "| **Overall Coverage** | $OVERALL | ❌ FAILED |"
105+
echo "| **Threshold** | $THRESHOLD | - |"
106+
echo "| **Build** | #${{ github.run_id }} | - |"
107+
fi
164108
echo ""
165-
echo "### Directory Test Results"
166-
echo '```'
167-
grep -A 100 "| Directory" coverage_total.txt | head -n 50 || true
168-
echo '```'
109+
110+
# Failed tests section
111+
if grep -q "Failed Tests:" coverage_report.txt; then
112+
echo "### ❌ Failed Tests"
113+
echo ""
114+
sed -n '/\*\*Failed Tests:\*\*/,/^\*\*Note/p' coverage_report.txt | grep "•" | head -20
115+
echo ""
116+
fi
117+
118+
# Directory results table
119+
if grep -q "| Directory" coverage_report.txt; then
120+
echo "### 📁 Directory Results"
121+
echo ""
122+
sed -n '/| Directory/,/^$/p' coverage_report.txt | head -50
123+
echo ""
124+
fi
125+
else
126+
echo "⚠️ Coverage report not generated"
127+
echo ""
128+
echo "Check the workflow logs for details."
169129
fi
170130
} >> "$GITHUB_STEP_SUMMARY"
171131
172-
# Debug job for troubleshooting (runs on manual trigger with failures)
173-
run-earthly-tests-debug:
174-
name: Run earthly +test-debug (enhanced debugging)
175-
runs-on: ubuntu-latest
176-
timeout-minutes: 45
177-
if: failure() && github.event_name == 'workflow_dispatch'
178-
needs: run-earthly-tests
179-
180-
steps:
181-
- name: Checkout repository
182-
uses: actions/checkout@v4
183-
with:
184-
fetch-depth: 0
185-
persist-credentials: false
186-
ref: ${{ inputs.ref != '' && inputs.ref || (github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha) }}
187-
188-
- name: Setup Earthly
189-
uses: earthly/actions/setup-earthly@v1
190-
with:
191-
version: "latest"
192-
193-
- name: Run Earthly +test-debug with enhanced output
132+
- name: Auto-update coverage threshold (ratchet)
133+
if: success() && github.ref != 'refs/heads/main'
194134
env:
195-
COV_THRESHOLD: ${{ inputs.cov_threshold || '12.3' }}
196-
PRINT_TS: ${{ github.run_id }}
197-
FAIL_ON_NO_TESTS: "false"
198-
run: |
199-
earthly +test-debug --COV_THRESHOLD="${COV_THRESHOLD}" --PRINT_TS="${PRINT_TS}" --FAIL_ON_NO_TESTS="${FAIL_ON_NO_TESTS}"
200-
201-
- name: Upload all debug artifacts
202-
if: always()
203-
uses: actions/upload-artifact@v4
204-
with:
205-
name: debug-coverage-artifacts
206-
path: |
207-
coverage.out
208-
coverage_total.txt
209-
coverage_packages.txt
210-
test_raw.log
211-
*_coverage.out
212-
*_test.log
213-
overall_coverage.out
214-
combined_coverage.out
215-
overall_test.log
216-
overall_test_with_failures.log
217-
if-no-files-found: ignore
218-
219-
- name: Show debug summary
220-
if: always()
135+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
136+
EVENT_NAME: ${{ github.event_name }}
137+
HEAD_REF: ${{ github.head_ref }}
138+
REF_NAME: ${{ github.ref_name }}
221139
run: |
222-
echo "=== Debug Coverage Analysis ==="
223-
echo "Files generated:"
224-
ls -la *.out *.txt *.log || true
225-
echo ""
226-
echo "Directory structure:"
227-
find . -name "*coverage*" -o -name "*test*.log" | head -20 || true
140+
set -x # Debug: show commands
141+
142+
# Determine branch name based on event type
143+
if [[ "${EVENT_NAME}" == "pull_request" ]]; then
144+
BRANCH="${HEAD_REF}"
145+
else
146+
# For workflow_dispatch or other events, use ref_name
147+
BRANCH="${REF_NAME}"
148+
fi
149+
150+
echo "Target branch: ${BRANCH}"
151+
152+
# Skip if on main branch (shouldn't happen due to condition, but safety check)
153+
if [[ "$BRANCH" == "main" ]]; then
154+
echo "Skipping ratchet on main branch"
155+
exit 0
156+
fi
157+
158+
# Get current coverage from report using simpler pattern
159+
CURRENT=$(grep "Overall Coverage:" coverage_report.txt | sed 's/[^0-9.]*\([0-9.]\+\)%.*/\1/')
160+
OLD_THRESHOLD=$(cat .coverage-threshold 2>/dev/null || echo "0")
161+
162+
# Validate CURRENT is a valid number before proceeding
163+
if [[ -z "$CURRENT" ]] || ! [[ "$CURRENT" =~ ^[0-9]+\.?[0-9]*$ ]]; then
164+
echo "::error::Failed to parse coverage value from report (got: '$CURRENT')"
165+
exit 1
166+
fi
167+
168+
echo "Current coverage: ${CURRENT}%"
169+
echo "Current threshold: ${OLD_THRESHOLD}%"
170+
171+
# Calculate new threshold (0.5% buffer below actual coverage)
172+
# Use printf to ensure consistent one-decimal-place formatting
173+
NEW_THRESHOLD=$(printf '%.1f' "$(echo "$CURRENT - 0.5" | bc -l)")
174+
175+
echo "Proposed new threshold: ${NEW_THRESHOLD}%"
176+
177+
# Only update if new threshold is higher than old threshold
178+
if (( $(echo "$NEW_THRESHOLD > $OLD_THRESHOLD" | bc -l) )); then
179+
echo "Updating threshold: ${OLD_THRESHOLD}% -> ${NEW_THRESHOLD}%"
180+
echo "${NEW_THRESHOLD}" > .coverage-threshold
181+
182+
# Configure git
183+
git config user.name "github-actions[bot]"
184+
git config user.email "github-actions[bot]@users.noreply.github.com"
185+
186+
# Fetch and checkout the branch
187+
git fetch origin "${BRANCH}"
188+
git checkout "${BRANCH}"
189+
# Ensure branch is up to date in case it changed after the workflow started
190+
git pull --rebase origin "${BRANCH}"
191+
192+
# Stage and commit
193+
git add .coverage-threshold
194+
git commit -m "chore: auto-update coverage threshold to ${NEW_THRESHOLD}% (was ${OLD_THRESHOLD}%)"
195+
git push origin "${BRANCH}"
196+
197+
echo "::notice::Coverage threshold updated to ${NEW_THRESHOLD}% on branch ${BRANCH}"
198+
else
199+
echo "New threshold (${NEW_THRESHOLD}%) is not higher than current (${OLD_THRESHOLD}%), no update needed"
200+
fi

0 commit comments

Comments
 (0)