Fix replay deduplication for wrapped messages #1149
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: Coverage | |
| on: | |
| push: | |
| branches: [master] | |
| pull_request: | |
| branches: [master] | |
| workflow_dispatch: | |
| inputs: | |
| baseline_coverage: | |
| description: "Manual baseline coverage % (for testing)" | |
| required: false | |
| default: "0" | |
| concurrency: | |
| group: coverage-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_TERM_COLOR: always | |
| jobs: | |
| # ── Change Detection ──────────────────────────────────────────────── | |
| changes: | |
| name: Detect Changed Paths | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| rust: ${{ steps.filter.outputs.rust }} | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Detect changes | |
| id: filter | |
| uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1 | |
| with: | |
| filters: | | |
| rust: | |
| - '**/*.rs' | |
| - '**/Cargo.toml' | |
| - 'Cargo.lock' | |
| coverage: | |
| needs: changes | |
| if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' || needs.changes.outputs.rust == 'true' | |
| runs-on: ubuntu-latest | |
| name: Test Coverage | |
| permissions: | |
| contents: read | |
| pull-requests: write # Required for PR comments | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| - name: Setup Rust (stable) | |
| uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 # pinned from master | |
| with: | |
| toolchain: stable | |
| - name: Cache Rust dependencies | |
| uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| key: coverage | |
| - name: Install system dependencies | |
| run: sudo apt-get update && sudo apt-get install -y pkg-config | |
| - name: Install cargo-llvm-cov | |
| uses: taiki-e/install-action@65851e10cd6c377f11a60e600abc07cb08643468 # v2.79.3 | |
| with: | |
| tool: cargo-llvm-cov | |
| - name: Generate coverage | |
| run: | | |
| echo "=== Generating Coverage Report ===" | |
| mkdir -p coverage | |
| # Run tests with coverage for all feature combinations | |
| cargo llvm-cov --all-features --workspace --no-report | |
| cargo llvm-cov --no-default-features --workspace --no-report | |
| cargo llvm-cov --no-default-features --features mip04 --workspace --no-report | |
| # Generate all report formats from the merged coverage data | |
| cargo llvm-cov report --lcov --output-path coverage/lcov.info | |
| cargo llvm-cov report --html --output-dir coverage/html | |
| cargo llvm-cov report | tee coverage/summary.txt | |
| echo "" | |
| echo "=== Coverage Summary ===" | |
| cat coverage/summary.txt | |
| - name: Extract coverage percentage | |
| id: coverage | |
| run: | | |
| chmod +x scripts/extract-coverage.sh | |
| COVERAGE=$(./scripts/extract-coverage.sh coverage/lcov.info) | |
| echo "percentage=$COVERAGE" >> "$GITHUB_OUTPUT" | |
| echo "Current coverage: $COVERAGE%" | |
| - name: Get master branch coverage (for PRs) | |
| if: github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' | |
| id: master_coverage | |
| env: | |
| BASELINE_INPUT: ${{ github.event.inputs.baseline_coverage }} | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| # Numeric percentage: integer or decimal, no leading sign. Anything else | |
| # is rejected so it can't flow into downstream expression expansions | |
| # (Compare coverage / github-script) as a shell or JS payload. | |
| NUMERIC_RE='^[0-9]+(\.[0-9]+)?$' | |
| # Check if manual baseline provided (for testing). | |
| # The workflow_dispatch input defaults to "0", which is a sentinel meaning | |
| # "not provided" — fall through to the master-artifact path in that case. | |
| if [ -n "$BASELINE_INPUT" ] && [ "$BASELINE_INPUT" != "0" ]; then | |
| if ! [[ "$BASELINE_INPUT" =~ $NUMERIC_RE ]]; then | |
| echo "::error::baseline_coverage must be a numeric percentage (got: $BASELINE_INPUT)" | |
| exit 1 | |
| fi | |
| MASTER_COV="$BASELINE_INPUT" | |
| echo "baseline=$MASTER_COV" >> "$GITHUB_OUTPUT" | |
| echo "Using manual baseline: $MASTER_COV%" | |
| else | |
| echo "🔍 Searching for baseline from master branch..." | |
| # Get the latest completed coverage workflow run ID from master | |
| RUN_ID=$(gh run list \ | |
| --repo ${{ github.repository }} \ | |
| --workflow=coverage.yml \ | |
| --branch master \ | |
| --status completed \ | |
| --limit 1 \ | |
| --json databaseId \ | |
| --jq '.[0].databaseId') | |
| if [ -z "$RUN_ID" ] || [ "$RUN_ID" = "null" ]; then | |
| echo "baseline=0" >> "$GITHUB_OUTPUT" | |
| echo "⚠️ No completed coverage workflow run found on master" | |
| exit 0 | |
| fi | |
| echo "Found workflow run ID: $RUN_ID" | |
| # Download the baseline artifact from that specific run | |
| if gh run download "$RUN_ID" \ | |
| --repo ${{ github.repository }} \ | |
| --name coverage-baseline \ | |
| --dir master-coverage; then | |
| if [ -f master-coverage/coverage-baseline.txt ]; then | |
| MASTER_COV=$(cat master-coverage/coverage-baseline.txt) | |
| if ! [[ "$MASTER_COV" =~ $NUMERIC_RE ]]; then | |
| echo "baseline=0" >> "$GITHUB_OUTPUT" | |
| echo "⚠️ Baseline artifact contained non-numeric data, ignoring" | |
| exit 0 | |
| fi | |
| echo "baseline=$MASTER_COV" >> "$GITHUB_OUTPUT" | |
| echo "✓ Master branch coverage: $MASTER_COV%" | |
| else | |
| echo "baseline=0" >> "$GITHUB_OUTPUT" | |
| echo "⚠️ Baseline file not found in artifact" | |
| fi | |
| else | |
| echo "baseline=0" >> "$GITHUB_OUTPUT" | |
| echo "⚠️ Failed to download baseline artifact from run $RUN_ID" | |
| fi | |
| fi | |
| - name: Compare coverage | |
| if: (github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch') && steps.master_coverage.outputs.baseline != '0' | |
| run: | | |
| CURRENT=${{ steps.coverage.outputs.percentage }} | |
| BASELINE=${{ steps.master_coverage.outputs.baseline }} | |
| echo "=== Coverage Comparison ===" | |
| echo "Master branch: $BASELINE%" | |
| echo "This PR: $CURRENT%" | |
| DIFF=$(awk "BEGIN {printf \"%.2f\", $CURRENT - $BASELINE}") | |
| if (( $(awk "BEGIN {print ($CURRENT < $BASELINE)}") )); then | |
| echo "❌ Coverage decreased by $DIFF%" | |
| echo "::error::Coverage regression detected. Coverage decreased from $BASELINE% to $CURRENT% (-$DIFF%)" | |
| exit 1 | |
| elif (( $(awk "BEGIN {print ($CURRENT > $BASELINE)}") )); then | |
| echo "✅ Coverage improved by $DIFF%" | |
| else | |
| echo "✅ Coverage maintained at $BASELINE%" | |
| fi | |
| - name: Comment PR with coverage change | |
| if: always() && github.event_name == 'pull_request' && steps.master_coverage.outputs.baseline != '0' | |
| uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 | |
| with: | |
| script: | | |
| const current = parseFloat('${{ steps.coverage.outputs.percentage }}'); | |
| const baseline = parseFloat('${{ steps.master_coverage.outputs.baseline }}'); | |
| const diff = (current - baseline).toFixed(2); | |
| let emoji = current > baseline ? '✅' : current < baseline ? '❌' : '⚠️'; | |
| const commentMarker = '<!-- coverage-comment -->'; | |
| // Build the message (with markdown header for comments, without for logs) | |
| const commentBody = `${commentMarker}\n## ${emoji} Coverage: ${baseline}% → ${current}% (${diff > 0 ? '+' : ''}${diff}%)`; | |
| const logMessage = `${emoji} Coverage: ${baseline}% → ${current}% (${diff > 0 ? '+' : ''}${diff}%)`; | |
| // Skip commenting on PRs from forks (they don't have write permissions) | |
| const isFork = context.payload.pull_request.head.repo.full_name !== context.payload.repository.full_name; | |
| if (isFork) { | |
| console.log('Skipping PR comment: PR is from a fork and does not have write permissions'); | |
| console.log('Coverage result:', logMessage); | |
| return; | |
| } | |
| try { | |
| const { data: comments } = await github.rest.issues.listComments({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo | |
| }); | |
| const existing = comments.find(c => | |
| c.body.includes(commentMarker) && c.user.type === 'Bot' | |
| ); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ | |
| comment_id: existing.id, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: commentBody | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: commentBody | |
| }); | |
| } | |
| } catch (error) { | |
| // Gracefully handle permission errors (e.g., from forks) | |
| if (error.status === 403) { | |
| console.log('Unable to comment on PR (likely from fork):', error.message); | |
| console.log('Coverage result:', logMessage); | |
| } else { | |
| throw error; | |
| } | |
| } | |
| - name: Save coverage baseline (master only) | |
| if: github.ref == 'refs/heads/master' && github.event_name == 'push' | |
| run: | | |
| mkdir -p coverage-baseline | |
| echo "${{ steps.coverage.outputs.percentage }}" > coverage-baseline/coverage-baseline.txt | |
| echo "Saved baseline: ${{ steps.coverage.outputs.percentage }}%" | |
| - name: Upload coverage baseline (master only) | |
| if: github.ref == 'refs/heads/master' && github.event_name == 'push' | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: coverage-baseline | |
| path: coverage-baseline/coverage-baseline.txt | |
| retention-days: 90 | |
| - name: Upload HTML coverage report | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: coverage-report-html | |
| path: coverage/html/ | |
| retention-days: 90 | |
| - name: Upload lcov coverage report | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: coverage-report-lcov | |
| path: coverage/lcov.info | |
| retention-days: 90 | |
| - name: Upload coverage summary | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: coverage-summary | |
| path: coverage/summary.txt | |
| retention-days: 90 |