Repository navigation
AI: Add Regression Police executable review #165
Workflow file for this run
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: PR Code Coverage | |
| on: | |
| pull_request: | |
| branches: | |
| - dev | |
| jobs: | |
| coverage-report: | |
| runs-on: ubuntu-latest | |
| # The ADO build (definition 2195) that publishes the coverage artifact takes | |
| # ~1 hour. The poll windows below wait up to ~65 min for it; bound the whole | |
| # job at 90 min (well under GitHub's 6-hour hosted-runner job execution cap). | |
| timeout-minutes: 90 | |
| permissions: | |
| pull-requests: write | |
| contents: read | |
| steps: | |
| - name: Checkout repo | |
| uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup git for diff-cover | |
| run: | | |
| git fetch origin dev:dev | |
| echo "Available branches:" | |
| git branch -a | |
| git show-ref --verify refs/heads/dev || echo "Warning: dev branch not found" | |
| - name: Wait for ADO build to start | |
| run: | | |
| PR_NUMBER=${{ github.event.pull_request.number }} | |
| PR_SHA=${{ github.event.pull_request.head.sha }} | |
| API_URL="https://dev.azure.com/sqlclientdrivers/public/_apis/build/builds?definitions=2195&queryOrder=queueTimeDescending&%24top=30&api-version=7.1-preview.7" | |
| echo "Waiting for Azure DevOps build to start for PR #$PR_NUMBER (SHA: $PR_SHA)..." | |
| for i in {1..30}; do | |
| echo "Attempt $i/30: Checking if build has started..." | |
| API_RESPONSE=$(curl --fail --silent --show-error "$API_URL") | |
| if ! echo "$API_RESPONSE" | jq . >/dev/null 2>&1; then | |
| echo "❌ Invalid JSON response from Azure DevOps API" | |
| echo "Response received: $API_RESPONSE" | |
| exit 1 | |
| fi | |
| # Match on both PR number and source SHA for accuracy | |
| BUILD_INFO=$(echo "$API_RESPONSE" | jq -c --arg PR "$PR_NUMBER" --arg SHA "$PR_SHA" \ | |
| '[.value[]? | select(.triggerInfo["pr.number"]?==$PR and (.triggerInfo["pr.sourceSha"]?==$SHA or .sourceVersion?==$SHA))] | .[0] // empty' 2>/dev/null) | |
| # Fall back to PR number only if SHA match fails (e.g. ADO hasn't updated triggerInfo yet) | |
| if [[ -z "$BUILD_INFO" || "$BUILD_INFO" == "null" || "$BUILD_INFO" == "empty" ]]; then | |
| BUILD_INFO=$(echo "$API_RESPONSE" | jq -c --arg PR "$PR_NUMBER" \ | |
| '[.value[]? | select(.triggerInfo["pr.number"]?==$PR)] | sort_by(.id) | reverse | .[0] // empty' 2>/dev/null) | |
| fi | |
| if [[ -n "$BUILD_INFO" && "$BUILD_INFO" != "null" && "$BUILD_INFO" != "empty" ]]; 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"') | |
| 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 | |
| if [[ "$STATUS" == "completed" && "$RESULT" == "failed" ]]; then | |
| echo "❌ Azure DevOps build $BUILD_ID failed early" | |
| exit 1 | |
| fi | |
| echo "🚀 Build has started, proceeding to poll for coverage artifacts..." | |
| break | |
| else | |
| echo "⏳ No build found for PR #$PR_NUMBER yet... (attempt $i/30)" | |
| fi | |
| if [[ $i -eq 30 ]]; then | |
| echo "❌ Timeout: No build found for PR #$PR_NUMBER after 30 attempts" | |
| exit 1 | |
| fi | |
| sleep 10 | |
| done | |
| - name: Download and parse coverage report | |
| run: | | |
| 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 "📥 Polling for coverage artifacts for build $BUILD_ID..." | |
| COVERAGE_ARTIFACT="" | |
| # The ADO build takes ~1 hour to publish the coverage artifact, so poll | |
| # up to ~65 min (130 * 30s) instead of giving up after 30 min. | |
| for i in {1..130}; do | |
| echo "Attempt $i/130: Checking for coverage artifacts..." | |
| ARTIFACTS_RESPONSE=$(curl --fail --silent --show-error "$ARTIFACTS_URL") | |
| if ! echo "$ARTIFACTS_RESPONSE" | jq . >/dev/null 2>&1; then | |
| echo "⚠️ Invalid JSON response from artifacts API (attempt $i/130)" | |
| if [[ $i -eq 130 ]]; then | |
| echo "❌ Persistent API issues after 130 attempts" | |
| exit 1 | |
| fi | |
| sleep 30 | |
| continue | |
| fi | |
| echo "🔍 Available artifacts:" | |
| echo "$ARTIFACTS_RESPONSE" | jq -r '.value[]?.name // "No artifacts found"' | |
| COVERAGE_ARTIFACT=$(echo "$ARTIFACTS_RESPONSE" | jq -r '.value[]? | select(.name | test("Code Coverage Report")) | .resource.downloadUrl // empty' 2>/dev/null) | |
| if [[ -n "$COVERAGE_ARTIFACT" && "$COVERAGE_ARTIFACT" != "null" && "$COVERAGE_ARTIFACT" != "empty" ]]; then | |
| echo "✅ Found coverage artifact on attempt $i!" | |
| break | |
| else | |
| echo "⏳ Coverage report not ready yet (attempt $i/130)..." | |
| if [[ $i -eq 130 ]]; then | |
| echo "❌ Timeout: Coverage report artifact not found after 130 attempts" | |
| exit 1 | |
| fi | |
| sleep 30 | |
| fi | |
| done | |
| echo "📊 Downloading coverage report..." | |
| if ! curl -L "$COVERAGE_ARTIFACT" -o coverage-report.zip --fail --silent; then | |
| echo "❌ Failed to download coverage report from Azure DevOps" | |
| exit 1 | |
| fi | |
| if ! unzip -o -q coverage-report.zip; then | |
| echo "❌ Failed to extract coverage artifacts" | |
| exit 1 | |
| fi | |
| # Find coverage.xml (Cobertura format from coverage.py) | |
| COVERAGE_FILE=$(find . -name "coverage.xml" -path "*/Code Coverage Report*" | head -1) | |
| if [[ -z "$COVERAGE_FILE" ]]; then | |
| COVERAGE_FILE=$(find . -name "*coverage*.xml" -type f | head -1) | |
| fi | |
| if [[ -z "$COVERAGE_FILE" ]]; then | |
| echo "❌ No coverage XML file found in artifacts" | |
| find . -type f | head -20 | |
| exit 1 | |
| fi | |
| echo "✅ Found coverage file: $COVERAGE_FILE" | |
| cp "$COVERAGE_FILE" ./coverage.xml | |
| # Parse Cobertura XML directly (much simpler than HTML parsing) | |
| python3 .github/scripts/parse_coverage.py | |
| echo "✅ Coverage data extracted successfully" | |
| - name: Generate patch coverage report | |
| run: | | |
| pip install diff-cover | |
| if [[ ! -f coverage.xml ]]; then | |
| echo "❌ coverage.xml not found" | |
| exit 1 | |
| fi | |
| echo "✅ coverage.xml found, size: $(wc -c < coverage.xml) bytes" | |
| # Generate diff coverage against dev branch | |
| diff-cover coverage.xml \ | |
| --compare-branch=dev \ | |
| --json-report patch-coverage.json \ | |
| --markdown-report patch-coverage.md || { | |
| echo "⚠️ diff-cover failed, continuing with empty patch coverage" | |
| } | |
| if [[ -f patch-coverage.json ]]; then | |
| PATCH_COVERAGE=$(jq -r '.total_percent_covered // "N/A"' patch-coverage.json) | |
| echo "✅ Patch coverage: ${PATCH_COVERAGE}%" | |
| echo "PATCH_COVERAGE_PCT=${PATCH_COVERAGE}%" >> $GITHUB_ENV | |
| else | |
| echo "PATCH_COVERAGE_PCT=N/A" >> $GITHUB_ENV | |
| fi | |
| if [[ -f patch-coverage.md ]]; then | |
| DELIM="DELIM_$(uuidgen | tr -d '-')" | |
| { | |
| echo "PATCH_COVERAGE_SUMMARY<<${DELIM}" | |
| cat patch-coverage.md | |
| echo "${DELIM}" | |
| } >> $GITHUB_ENV | |
| else | |
| 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 pr_number "${{ github.event.pull_request.number }}" \ | |
| --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 patch_coverage_summary "$PATCH_COVERAGE_SUMMARY" \ | |
| --arg ado_url "${{ env.ADO_URL }}" \ | |
| '{ | |
| pr_number: $pr_number, | |
| coverage_percentage: $coverage_percentage, | |
| covered_lines: $covered_lines, | |
| total_lines: $total_lines, | |
| patch_coverage_pct: $patch_coverage_pct, | |
| low_coverage_files: $low_coverage_files, | |
| patch_coverage_summary: $patch_coverage_summary, | |
| ado_url: $ado_url | |
| }' > coverage-comment-data/pr-info.json | |
| echo "✅ JSON validation:" | |
| jq . coverage-comment-data/pr-info.json > /dev/null || { | |
| echo "❌ Invalid JSON generated" | |
| cat coverage-comment-data/pr-info.json | |
| exit 1 | |
| } | |
| cat coverage-comment-data/pr-info.json | |
| - name: Upload coverage comment data | |
| 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 | |
| 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 }} |