Repository navigation
566 lines (502 loc) · 27.9 KB
/
Copy pathpr-code-coverage.yml
File metadata and controls
566 lines (502 loc) · 27.9 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
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
name: PR Code Coverage
on:
pull_request:
branches:
- main
permissions:
contents: read
concurrency:
group: pr-code-coverage-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
coverage-report:
runs-on: ubuntu-latest
timeout-minutes: 245
permissions:
pull-requests: write
contents: read
steps:
- name: Checkout repo
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
fetch-depth: 0
persist-credentials: false
- name: Setup git for diff-cover
run: |
# Fetch the main branch for comparison
git fetch origin main:main
# Show available branches for debugging
echo "Available branches:"
git branch -a
# Verify main branch exists
git show-ref --verify refs/heads/main || echo "Warning: main branch not found"
git show-ref --verify refs/remotes/origin/main || echo "Warning: origin/main not found"
- name: Wait for ADO build to start
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
PR_BRANCH="refs/pull/$PR_NUMBER/merge"
API_URL="https://dev.azure.com/sqlclientdrivers/public/_apis/build/builds?definitions=2128&branchName=refs%2Fpull%2F${PR_NUMBER}%2Fmerge&queryOrder=queueTimeDescending&%24top=100&api-version=7.1-preview.7"
DEADLINE=$((SECONDS + 15 * 60))
API_FAILURES=0
BUILD_ID=""
echo "Waiting up to 15 minutes for Azure DevOps build for PR #$PR_NUMBER at $PR_HEAD_SHA..."
while (( SECONDS < DEADLINE )); do
REQUEST_TIMEOUT=$((DEADLINE - SECONDS))
if (( REQUEST_TIMEOUT <= 0 )); then break; fi
if (( REQUEST_TIMEOUT > 30 )); then REQUEST_TIMEOUT=30; fi
if API_RESPONSE=$(curl --fail --silent --show-error --connect-timeout 10 --max-time "$REQUEST_TIMEOUT" "$API_URL") &&
jq -e 'type == "object" and (.value | type == "array")' <<< "$API_RESPONSE" >/dev/null 2>&1; then
API_FAILURES=0
else
API_FAILURES=$((API_FAILURES + 1))
echo "⚠️ Build API HTTP/JSON error ($API_FAILURES/5)"
if (( API_FAILURES >= 5 )); then
echo "❌ Azure DevOps build API unavailable after 5 consecutive failures"
exit 1
fi
API_RESPONSE='{"value":[]}'
fi
# The merge ref is shared across revisions; match the actual PR head as well.
BUILD_INFO=$(jq -c --arg PR "$PR_NUMBER" --arg SHA "$PR_HEAD_SHA" --arg BRANCH "$PR_BRANCH" '
[.value[]? | select(
.definition.id == 2128 and .sourceBranch == $BRANCH and
(.triggerInfo["pr.number"] | tostring) == $PR and
.triggerInfo["pr.sourceSha"] == $SHA
)] | .[0] // empty' <<< "$API_RESPONSE")
if [[ -n "$BUILD_INFO" ]]; then
STATUS=$(echo "$BUILD_INFO" | jq -r '.status // "unknown"')
RESULT=$(echo "$BUILD_INFO" | jq -r '.result // "unknown"')
BUILD_ID=$(echo "$BUILD_INFO" | jq -r '.id // "unknown"')
WEB_URL=$(echo "$BUILD_INFO" | jq -r '._links.web.href // "unknown"')
[[ "$BUILD_ID" =~ ^[0-9]+$ ]] || {
echo "Invalid Azure DevOps build ID"
exit 1
}
if [[ ${#WEB_URL} -gt 500 || "$WEB_URL" == *$'\n'* || "$WEB_URL" == *$'\r'* ]]; then
echo "Invalid Azure DevOps build URL"
exit 1
fi
echo "✅ Found build: ID=$BUILD_ID, Status=$STATUS, Result=$RESULT"
echo "🔗 Build URL: $WEB_URL"
echo "ADO_URL=$WEB_URL" >> "$GITHUB_ENV"
echo "BUILD_ID=$BUILD_ID" >> "$GITHUB_ENV"
echo "PR_NUMBER=$PR_NUMBER" >> "$GITHUB_ENV"
echo "PR_HEAD_SHA=$PR_HEAD_SHA" >> "$GITHUB_ENV"
# A failed matrix leg does not invalidate a successful coverage artifact.
echo "🚀 Build found, proceeding to poll for coverage artifacts..."
break
fi
echo "⏳ No matching build found for PR #$PR_NUMBER at $PR_HEAD_SHA yet..."
SLEEP_SECONDS=$((DEADLINE - SECONDS))
if (( SLEEP_SECONDS > 30 )); then SLEEP_SECONDS=30; fi
if (( SLEEP_SECONDS > 0 )); then sleep "$SLEEP_SECONDS"; fi
done
if [[ -z "$BUILD_ID" ]]; then
echo "❌ Timeout: No build found for PR #$PR_NUMBER at $PR_HEAD_SHA within 15 minutes"
exit 1
fi
- name: Download and parse coverage report
run: |
BUILD_URL="https://dev.azure.com/SqlClientDrivers/public/_apis/build/builds/$BUILD_ID?api-version=7.1-preview.7"
ARTIFACTS_URL="https://dev.azure.com/SqlClientDrivers/public/_apis/build/builds/$BUILD_ID/artifacts?api-version=7.1-preview.5"
BUILDS_URL="https://dev.azure.com/sqlclientdrivers/public/_apis/build/builds?definitions=2128&branchName=refs%2Fpull%2F${PR_NUMBER}%2Fmerge&queryOrder=queueTimeDescending&%24top=100&api-version=7.1-preview.7"
PR_BRANCH="refs/pull/$PR_NUMBER/merge"
# Coverage may queue behind 160-minute benchmark jobs before its own run.
DEADLINE=$((SECONDS + 220 * 60))
COMPLETED_AT=-1
ARTIFACT_FAILURES=0
BUILD_FAILURES=0
COVERAGE_ARTIFACT=""
COVERAGE_ARTIFACT_APPROVED=false
echo "📥 Waiting up to 220 minutes for coverage artifacts for build $BUILD_ID..."
while (( SECONDS < DEADLINE )); do
COVERAGE_ARTIFACT=""
REQUEST_TIMEOUT=$((DEADLINE - SECONDS))
if (( REQUEST_TIMEOUT <= 0 )); then break; fi
if (( REQUEST_TIMEOUT > 30 )); then REQUEST_TIMEOUT=30; fi
if ARTIFACTS_RESPONSE=$(curl --fail --silent --show-error --connect-timeout 10 --max-time "$REQUEST_TIMEOUT" "$ARTIFACTS_URL") &&
jq -e 'type == "object" and (.value | type == "array")' <<< "$ARTIFACTS_RESPONSE" >/dev/null 2>&1; then
ARTIFACT_FAILURES=0
COVERAGE_ARTIFACT=$(jq -r '
[.value[]? | select(.name | test("Code Coverage Report")) |
.resource.downloadUrl | select(type == "string" and length > 0)] |
.[0] // empty' <<< "$ARTIFACTS_RESPONSE")
else
ARTIFACT_FAILURES=$((ARTIFACT_FAILURES + 1))
echo "⚠️ Artifacts API HTTP/JSON error ($ARTIFACT_FAILURES/5)"
if (( ARTIFACT_FAILURES >= 5 )); then
echo "❌ Azure DevOps artifacts API unavailable after 5 consecutive failures"
exit 1
fi
fi
# Inspect lifecycle, not aggregate result: independent matrix jobs can fail.
REQUEST_TIMEOUT=$((DEADLINE - SECONDS))
if (( REQUEST_TIMEOUT <= 0 )); then break; fi
if (( REQUEST_TIMEOUT > 30 )); then REQUEST_TIMEOUT=30; fi
if BUILD_RESPONSE=$(curl --fail --silent --show-error --connect-timeout 10 --max-time "$REQUEST_TIMEOUT" "$BUILD_URL") &&
jq -e --arg ID "$BUILD_ID" '(.id | tostring) == $ID and
(.status | type == "string")' <<< "$BUILD_RESPONSE" >/dev/null 2>&1; then
BUILD_FAILURES=0
STATUS=$(jq -r '.status' <<< "$BUILD_RESPONSE")
RESULT=$(jq -r '.result // "unknown"' <<< "$BUILD_RESPONSE")
if [[ "$STATUS" == "completed" && "$RESULT" == "canceled" ]]; then
if REPLACEMENTS=$(curl --fail --silent --show-error --connect-timeout 10 --max-time "$REQUEST_TIMEOUT" "$BUILDS_URL") &&
REPLACEMENT=$(jq -ce --arg PR "$PR_NUMBER" --arg SHA "$PR_HEAD_SHA" \
--arg BRANCH "$PR_BRANCH" --arg ID "$BUILD_ID" '
[.value[]? | select(
.definition.id == 2128 and .sourceBranch == $BRANCH and
(.triggerInfo["pr.number"] | tostring) == $PR and
.triggerInfo["pr.sourceSha"] == $SHA and
.id > ($ID | tonumber) and
(.status != "completed" or .result != "canceled")
)] | .[0]' <<< "$REPLACEMENTS"); then
BUILD_ID=$(jq -r '.id' <<< "$REPLACEMENT")
[[ "$BUILD_ID" =~ ^[0-9]+$ ]] || {
echo "Invalid replacement Azure DevOps build ID"
exit 1
}
BUILD_URL="https://dev.azure.com/SqlClientDrivers/public/_apis/build/builds/$BUILD_ID?api-version=7.1-preview.7"
ARTIFACTS_URL="https://dev.azure.com/SqlClientDrivers/public/_apis/build/builds/$BUILD_ID/artifacts?api-version=7.1-preview.5"
ADO_URL="https://dev.azure.com/sqlclientdrivers/public/_build/results?buildId=$BUILD_ID"
echo "BUILD_ID=$BUILD_ID" >> "$GITHUB_ENV"
echo "ADO_URL=$ADO_URL" >> "$GITHUB_ENV"
COMPLETED_AT=-1
ARTIFACT_FAILURES=0
BUILD_FAILURES=0
COVERAGE_ARTIFACT=""
COVERAGE_ARTIFACT_APPROVED=false
echo "Selected ADO run was canceled; continuing with replacement build $BUILD_ID"
continue
fi
ARTIFACT_FAILURES=0
echo "Canceled build $BUILD_ID has no replacement yet..."
SLEEP_SECONDS=$((DEADLINE - SECONDS))
if (( SLEEP_SECONDS > 30 )); then SLEEP_SECONDS=30; fi
if (( SLEEP_SECONDS > 0 )); then sleep "$SLEEP_SECONDS"; fi
continue
fi
if [[ -n "$COVERAGE_ARTIFACT" ]] &&
{ [[ "$STATUS" == "inProgress" ]] ||
{ [[ "$STATUS" == "completed" ]] &&
[[ "$RESULT" =~ ^(succeeded|partiallySucceeded|failed)$ ]]; }; }; then
COVERAGE_ARTIFACT_APPROVED=true
echo "✅ Found coverage artifact!"
break
fi
if [[ "$STATUS" == "completed" ]] && (( COMPLETED_AT < 0 )); then
COMPLETED_AT=$SECONDS
echo "Build completed ($RESULT); allowing 2 minutes for artifact propagation..."
fi
else
BUILD_FAILURES=$((BUILD_FAILURES + 1))
echo "⚠️ Build lifecycle API HTTP/JSON error ($BUILD_FAILURES/5)"
if (( BUILD_FAILURES >= 5 )); then
echo "❌ Azure DevOps build lifecycle API unavailable after 5 consecutive failures"
exit 1
fi
fi
if (( COMPLETED_AT >= 0 && SECONDS - COMPLETED_AT >= 120 )); then
echo "❌ Build $BUILD_ID completed but coverage artifact is still unavailable after propagation grace"
exit 1
fi
echo "⏳ Coverage report not ready yet..."
SLEEP_SECONDS=$((DEADLINE - SECONDS))
if (( SLEEP_SECONDS > 30 )); then SLEEP_SECONDS=30; fi
if (( SLEEP_SECONDS > 0 )); then sleep "$SLEEP_SECONDS"; fi
done
if [[ "$COVERAGE_ARTIFACT_APPROVED" != true || -z "$COVERAGE_ARTIFACT" ]]; then
echo "❌ Timeout: Coverage report artifact not found within 220 minutes"
exit 1
fi
if [[ -n "$COVERAGE_ARTIFACT" && "$COVERAGE_ARTIFACT" != "null" && "$COVERAGE_ARTIFACT" != "empty" ]]; then
echo "📊 Downloading coverage report..."
COVERAGE_ARCHIVE="$RUNNER_TEMP/coverage-report.zip"
if ! curl -L "$COVERAGE_ARTIFACT" -o "$COVERAGE_ARCHIVE" --fail --silent --show-error \
--connect-timeout 10 --max-time 60 --max-filesize 268435456 \
--retry 2 --retry-delay 5 --retry-max-time 180; then
echo "❌ Failed to download coverage report from Azure DevOps"
echo "This indicates the coverage artifacts may not be available or accessible"
exit 1
fi
INDEX_FILE="$RUNNER_TEMP/coverage-index.html"
if ! python .github/scripts/extract_coverage_artifact.py html "$COVERAGE_ARCHIVE" "$INDEX_FILE"; then
echo "❌ Failed to read the coverage HTML artifact"
exit 1
fi
if [[ -f "$INDEX_FILE" ]]; then
echo "🔍 Parsing coverage data from $INDEX_FILE..."
# Debug: Show relevant parts of the HTML
echo "Debug: Looking for coverage data..."
grep -n "cardpercentagebar\|Covered lines\|Coverable lines" "$INDEX_FILE" | head -10
# Extract coverage metrics using simpler, more reliable patterns
OVERALL_PERCENTAGE=$(grep -o 'cardpercentagebar[0-9]*">[0-9]*%' "$INDEX_FILE" | head -1 | grep -o '[0-9]*%')
COVERED_LINES=$(grep -A1 "Covered lines:" "$INDEX_FILE" | grep -o 'title="[0-9]*"' | head -1 | grep -o '[0-9]*')
TOTAL_LINES=$(grep -A1 "Coverable lines:" "$INDEX_FILE" | grep -o 'title="[0-9]*"' | head -1 | grep -o '[0-9]*')
# Fallback method if the above doesn't work
if [[ -z "$OVERALL_PERCENTAGE" ]]; then
echo "Trying alternative parsing method..."
OVERALL_PERCENTAGE=$(grep -o 'large.*">[0-9]*%' "$INDEX_FILE" | head -1 | grep -o '[0-9]*%')
fi
echo "Extracted values:"
echo "OVERALL_PERCENTAGE=$OVERALL_PERCENTAGE"
echo "COVERED_LINES=$COVERED_LINES"
echo "TOTAL_LINES=$TOTAL_LINES"
# Validate that we got the essential data
if [[ -z "$OVERALL_PERCENTAGE" ]]; then
echo "❌ Could not extract coverage percentage from the report"
echo "The coverage report format may have changed or be incomplete"
exit 1
fi
echo "COVERAGE_PERCENTAGE=$OVERALL_PERCENTAGE" >> $GITHUB_ENV
echo "COVERED_LINES=${COVERED_LINES:-N/A}" >> $GITHUB_ENV
echo "TOTAL_LINES=${TOTAL_LINES:-N/A}" >> $GITHUB_ENV
# Extract top files with low coverage - improved approach
echo "📋 Extracting file-level coverage..."
# Extract file coverage data more reliably
LOW_COVERAGE_FILES=$(grep -o '<td><a href="[^"]*">[^<]*</a></td><td class="right">[0-9]*</td><td class="right">[0-9]*</td><td class="right">[0-9]*</td><td class="right">[0-9]*</td><td title="[^"]*" class="right">[0-9]*\.[0-9]*%' "$INDEX_FILE" | \
sed 's/<td><a href="[^"]*">\([^<]*\)<\/a><\/td>.*class="right">\([0-9]*\.[0-9]*\)%/\1: \2%/' | \
sort -t: -k2 -n | head -10)
# Alternative method if above fails
if [[ -z "$LOW_COVERAGE_FILES" ]]; then
echo "Trying alternative file parsing..."
LOW_COVERAGE_FILES=$(grep -E "\.py.*[0-9]+\.[0-9]+%" "$INDEX_FILE" | \
grep -o "[^>]*\.py[^<]*</a>.*[0-9]*\.[0-9]*%" | \
sed 's/\([^<]*\)<\/a>.*\([0-9]*\.[0-9]*\)%/\1: \2%/' | \
sort -t: -k2 -n | head -10)
fi
LOW_COVERAGE_FILES="${LOW_COVERAGE_FILES:-No detailed file data available}"
LOW_COVERAGE_DELIMITER="coverage_$(openssl rand -hex 16)"
if grep -Fxq "$LOW_COVERAGE_DELIMITER" <<< "$LOW_COVERAGE_FILES"; then
echo "Generated delimiter unexpectedly occurs in coverage data"
exit 1
fi
{
echo "LOW_COVERAGE_FILES<<$LOW_COVERAGE_DELIMITER"
echo "$LOW_COVERAGE_FILES"
echo "$LOW_COVERAGE_DELIMITER"
} >> "$GITHUB_ENV"
echo "✅ Coverage data extracted successfully"
else
echo "❌ Could not find index.html in coverage report"
exit 1
fi
else
echo "❌ Could not find coverage report artifact"
echo "Available artifacts from the build:"
echo "$ARTIFACTS_RESPONSE" | jq -r '.value[]?.name // "No artifacts found"' 2>/dev/null || echo "Could not parse artifacts list"
echo "This indicates the Azure DevOps build may not have generated coverage reports"
exit 1
fi
- name: Download coverage XML from ADO
run: |
# Download the Cobertura XML directly from the CodeCoverageReport job
BUILD_ID=${{ env.BUILD_ID }}
ARTIFACTS_URL="https://dev.azure.com/SqlClientDrivers/public/_apis/build/builds/$BUILD_ID/artifacts?api-version=7.1-preview.5"
echo "📥 Fetching artifacts for build $BUILD_ID to find coverage files..."
for i in {1..5}; do
if ARTIFACTS_RESPONSE=$(curl --fail --silent --show-error --connect-timeout 10 --max-time 30 "$ARTIFACTS_URL") &&
jq -e 'type == "object" and (.value | type == "array")' <<< "$ARTIFACTS_RESPONSE" >/dev/null 2>&1; then
break
fi
echo "⚠️ Artifacts API HTTP/JSON error ($i/5)"
if [[ $i -eq 5 ]]; then
echo "❌ Azure DevOps artifacts API unavailable after 5 attempts"
exit 1
fi
sleep 5
done
echo "🔍 Available artifacts:"
echo "$ARTIFACTS_RESPONSE" | jq -r '.value[]?.name // "No artifacts found"'
# Look for the unified coverage artifact from CodeCoverageReport job
COVERAGE_XML_ARTIFACT=$(echo "$ARTIFACTS_RESPONSE" | jq -r '.value[]? | select(.name | test("unified-coverage|Code Coverage Report|coverage")) | .resource.downloadUrl // empty' 2>/dev/null | head -1)
if [[ -n "$COVERAGE_XML_ARTIFACT" && "$COVERAGE_XML_ARTIFACT" != "null" && "$COVERAGE_XML_ARTIFACT" != "empty" ]]; then
echo "📊 Downloading coverage artifact from: $COVERAGE_XML_ARTIFACT"
COVERAGE_XML_ARCHIVE="$RUNNER_TEMP/coverage-artifacts.zip"
if ! curl -L "$COVERAGE_XML_ARTIFACT" -o "$COVERAGE_XML_ARCHIVE" --fail --silent --show-error \
--connect-timeout 10 --max-time 60 --max-filesize 268435456 \
--retry 2 --retry-delay 5 --retry-max-time 180; then
echo "❌ Failed to download coverage artifacts"
exit 1
fi
COVERAGE_XML="$RUNNER_TEMP/coverage.xml"
if ! python .github/scripts/extract_coverage_artifact.py xml "$COVERAGE_XML_ARCHIVE" "$COVERAGE_XML"; then
echo "❌ Failed to read the coverage XML artifact"
exit 1
fi
echo "✅ Coverage XML file is ready at $COVERAGE_XML"
ls -la "$COVERAGE_XML"
else
echo "❌ Could not find coverage artifacts"
echo "This indicates the Azure DevOps CodeCoverageReport job may not have run successfully"
exit 1
fi
- name: Generate patch coverage report
env:
COVERAGE_XML: ${{ runner.temp }}/coverage.xml
run: |
# Install dependencies
pip install diff-cover jq
sudo apt-get update && sudo apt-get install -y libxml2-utils
# Verify coverage.xml exists before proceeding
if [[ ! -f "$COVERAGE_XML" ]]; then
echo "❌ coverage.xml not found in current directory"
echo "Available files:"
ls -la | head -20
exit 1
fi
echo "✅ coverage.xml found, size: $(wc -c < "$COVERAGE_XML") bytes"
echo "🔍 Coverage file preview (first 10 lines):"
head -10 "$COVERAGE_XML"
# Generate diff coverage report using the new command format
echo "🚀 Generating patch coverage report..."
# Debug: Show git status and branches before running diff-cover
echo "🔍 Git status before diff-cover:"
git status --porcelain || echo "Git status failed"
echo "Current branch: $(git branch --show-current)"
echo "Available branches:"
git branch -a
echo "Checking if main branch is accessible:"
git log --oneline -n 5 main || echo "Could not access main branch"
# Debug: Show what diff-cover will analyze
echo "🔍 Git diff analysis:"
echo "Files changed between main and current branch:"
git diff --name-only main || echo "Could not get diff"
echo "Detailed diff for Python files:"
git diff main -- "*.py" | head -50 || echo "Could not get Python diff"
# Debug: Check coverage.xml content for specific files
echo "🔍 Coverage.xml analysis:"
echo "Python files mentioned in coverage.xml:"
grep -o 'filename="[^"]*\.py"' "$COVERAGE_XML" | head -10 || echo "Could not extract filenames"
echo "Sample coverage data:"
head -20 "$COVERAGE_XML"
# Use the new format for diff-cover commands
echo "🚀 Running diff-cover..."
diff-cover "$COVERAGE_XML" \
--compare-branch=main \
--html-report patch-coverage.html \
--json-report patch-coverage.json \
--markdown-report patch-coverage.md || {
echo "❌ diff-cover failed with exit code $?"
echo "Checking if coverage.xml is valid XML..."
if ! xmllint --noout "$COVERAGE_XML" 2>/dev/null; then
echo "❌ coverage.xml is not valid XML"
echo "First 50 lines of coverage.xml:"
head -50 "$COVERAGE_XML"
else
echo "✅ coverage.xml is valid XML"
echo "🔍 diff-cover verbose output:"
diff-cover "$COVERAGE_XML" --compare-branch=main --markdown-report debug-patch-coverage.md -v || echo "Verbose diff-cover also failed"
fi
# Don't exit here, let's see what files were created
}
# Check what files were generated
echo "🔍 Files generated after diff-cover:"
ls -la patch-coverage.* || echo "No patch-coverage files found"
ls -la *.md *.html *.json | grep -E "(patch|coverage)" || echo "No coverage-related files found"
# Extract patch coverage percentage
if [[ -f patch-coverage.json ]]; then
echo "🔍 Patch coverage analysis from JSON:"
echo "Raw JSON content:"
cat patch-coverage.json | jq . || echo "Could not parse JSON"
PATCH_COVERAGE=$(jq -r '.total_percent_covered // "N/A"' patch-coverage.json)
TOTAL_STATEMENTS=$(jq -r '.total_num_lines // "N/A"' patch-coverage.json)
MISSING_STATEMENTS=$(jq -r '.total_num_missing // "N/A"' patch-coverage.json)
if [[ ! "$PATCH_COVERAGE" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
PATCH_COVERAGE="N/A"
fi
echo "✅ Patch coverage: ${PATCH_COVERAGE}%"
echo "📊 Total lines: $TOTAL_STATEMENTS, Missing: $MISSING_STATEMENTS"
# Debug: Show per-file breakdown
echo "📁 Per-file coverage breakdown:"
jq -r '.src_stats // {} | to_entries[] | "\(.key): \(.value.percent_covered)% (\(.value.num_lines) lines, \(.value.num_missing) missing)"' patch-coverage.json || echo "Could not extract per-file stats"
echo "PATCH_COVERAGE_PCT=${PATCH_COVERAGE}%" >> $GITHUB_ENV
elif [[ -f patch-coverage.md ]]; then
echo "🔍 Extracting patch coverage from markdown file:"
echo "Markdown content:"
cat patch-coverage.md
# Extract coverage percentage from markdown
PATCH_COVERAGE=$(grep -o "Coverage.*[0-9]*%" patch-coverage.md | grep -o "[0-9]*%" | head -1 | sed 's/%//')
TOTAL_LINES=$(grep -o "Total.*[0-9]* lines" patch-coverage.md | grep -o "[0-9]*" | head -1)
MISSING_LINES=$(grep -o "Missing.*[0-9]* lines" patch-coverage.md | grep -o "[0-9]*" | tail -1)
if [[ -n "$PATCH_COVERAGE" ]]; then
echo "✅ Extracted patch coverage: ${PATCH_COVERAGE}%"
echo "📊 Total lines: $TOTAL_LINES, Missing: $MISSING_LINES"
echo "PATCH_COVERAGE_PCT=${PATCH_COVERAGE}%" >> $GITHUB_ENV
else
echo "⚠️ Could not extract coverage percentage from markdown"
echo "PATCH_COVERAGE_PCT=Could not parse" >> $GITHUB_ENV
fi
else
echo "⚠️ No patch coverage files generated"
echo "🔍 Checking for other output files:"
ls -la *coverage* || echo "No coverage files found"
echo "PATCH_COVERAGE_PCT=Report not generated" >> $GITHUB_ENV
fi
# Extract summary for comment
if [[ -f patch-coverage.md ]]; then
PATCH_COVERAGE_DELIMITER="coverage_$(openssl rand -hex 16)"
if grep -Fxq "$PATCH_COVERAGE_DELIMITER" patch-coverage.md; then
echo "Generated delimiter unexpectedly occurs in patch coverage data"
exit 1
fi
{
echo "PATCH_COVERAGE_SUMMARY<<$PATCH_COVERAGE_DELIMITER"
cat patch-coverage.md
echo "$PATCH_COVERAGE_DELIMITER"
} >> "$GITHUB_ENV"
echo "✅ Patch coverage markdown summary ready"
else
echo "⚠️ patch-coverage.md not generated"
echo "PATCH_COVERAGE_SUMMARY=Patch coverage report could not be generated." >> $GITHUB_ENV
fi
- name: Save coverage data for comment
run: |
mkdir -p coverage-comment-data
jq -n \
--arg coverage_percentage "${{ env.COVERAGE_PERCENTAGE }}" \
--arg covered_lines "${{ env.COVERED_LINES }}" \
--arg total_lines "${{ env.TOTAL_LINES }}" \
--arg patch_coverage_pct "${{ env.PATCH_COVERAGE_PCT }}" \
--arg low_coverage_files "$LOW_COVERAGE_FILES" \
--arg ado_url "${{ env.ADO_URL }}" \
'{
coverage_percentage: $coverage_percentage,
covered_lines: $covered_lines,
total_lines: $total_lines,
patch_coverage_pct: $patch_coverage_pct,
low_coverage_files: $low_coverage_files,
ado_url: $ado_url
}' > coverage-comment-data/pr-info.json
# Validate JSON before uploading
echo "Validating generated JSON..."
jq . coverage-comment-data/pr-info.json > /dev/null || {
echo "❌ Invalid JSON generated"
cat coverage-comment-data/pr-info.json
exit 1
}
echo "✅ JSON validation successful"
cat coverage-comment-data/pr-info.json
- name: Upload coverage comment data
# Only upload artifact for forked PRs since same-repo PRs post comment directly
# This prevents unnecessary workflow_run triggers for same-repo PRs
if: github.event.pull_request.head.repo.full_name != github.repository
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: coverage-comment-data
path: coverage-comment-data/
retention-days: 7
- name: Comment coverage summary on PR
# Skip for forked PRs due to token permission restrictions
if: github.event.pull_request.head.repo.full_name == github.repository
uses: ./.github/actions/post-coverage-comment
with:
pr_number: ${{ github.event.pull_request.number }}
coverage_percentage: ${{ env.COVERAGE_PERCENTAGE }}
covered_lines: ${{ env.COVERED_LINES }}
total_lines: ${{ env.TOTAL_LINES }}
patch_coverage_pct: ${{ env.PATCH_COVERAGE_PCT }}
low_coverage_files: ${{ env.LOW_COVERAGE_FILES }}
patch_coverage_summary: ${{ env.PATCH_COVERAGE_SUMMARY }}
ado_url: ${{ env.ADO_URL }}