feat: support /api/v1/artifacts/:reference API #19
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: Code Coverage | ||
| on: | ||
| push: | ||
| branches: [main, release-*] | ||
| paths-ignore: ['**.md', '**.png', '**.jpg', '**.svg', '**/docs/**'] | ||
| pull_request: | ||
| branches: [main, release-*] | ||
| paths-ignore: ['**.md', '**.png', '**.jpg', '**.svg', '**/docs/**'] | ||
| # Only read-only permissions needed here. | ||
| # PR comments are posted by coverage-comment.yml via workflow_run | ||
| # so that fork PRs can also receive comments with write access. | ||
| permissions: | ||
| contents: read | ||
| env: | ||
| THRESHOLD_TOTAL: 70 # overall coverage threshold (%) | ||
| THRESHOLD_DIFF: 90 # changed-line coverage threshold (%) | ||
| jobs: | ||
| coverage: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 60 | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 # full history needed for diff coverage | ||
| - name: Set up Go | ||
| uses: actions/setup-go@v5 | ||
| with: | ||
| go-version: '1.24.2' | ||
| - name: Cache Go modules | ||
| uses: actions/cache@v4 | ||
| with: | ||
| path: | | ||
| ~/.cache/go-build | ||
| ~/go/pkg/mod | ||
| key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} | ||
| restore-keys: | | ||
| ${{ runner.os }}-go- | ||
| # ── Run tests ──────────────────────────────────────────────────────────── | ||
| - name: Run tests with coverage (excluding pkg/server) | ||
| run: make test-coverage-ci | ||
| # ── Convert coverage format for diff-cover ──────────────────────────── | ||
| - name: Install gocover-cobertura | ||
| run: go install github.com/t-yuki/gocover-cobertura@latest | ||
| - name: Convert to Cobertura XML | ||
| run: gocover-cobertura < coverage.out > coverage.xml | ||
| - name: Install diff-cover | ||
| run: pip install diff-cover | ||
| # ── Check total coverage threshold ─────────────────────────────────── | ||
| - name: Check total coverage (>= ${{ env.THRESHOLD_TOTAL }}%) | ||
| id: total_coverage | ||
| run: | | ||
| TOTAL=$(go tool cover -func=coverage.out | tail -1 | awk '{print $3}' | sed 's/%//') | ||
| echo "total=${TOTAL}" >> "$GITHUB_OUTPUT" | ||
| echo "Total coverage: ${TOTAL}% (threshold: ${{ env.THRESHOLD_TOTAL }}%)" | ||
| if awk "BEGIN { exit !( ${TOTAL} + 0 < ${{ env.THRESHOLD_TOTAL }}) }"; then | ||
| echo "status=fail" >> "$GITHUB_OUTPUT" | ||
| echo "❌ Total coverage ${TOTAL}% is below the required ${{ env.THRESHOLD_TOTAL }}%" | ||
| else | ||
| echo "status=pass" >> "$GITHUB_OUTPUT" | ||
| echo "✅ Total coverage ${TOTAL}% meets the threshold of ${{ env.THRESHOLD_TOTAL }}%" | ||
| fi | ||
| # ── Check diff (changed-line) coverage threshold ───────────────────── | ||
| - name: Fetch base branch for diff | ||
| if: github.event_name == 'pull_request' | ||
| run: git fetch origin ${{ github.base_ref }} --depth=1 | ||
| - name: Check diff coverage (>= ${{ env.THRESHOLD_DIFF }}%) | ||
| id: diff_coverage | ||
| if: github.event_name == 'pull_request' | ||
| run: | | ||
| BASE=origin/${{ github.base_ref }} | ||
| DIFF_OUTPUT=$(diff-cover coverage.xml \ | ||
| --compare-branch="${BASE}" \ | ||
| --format json:diff-cover.json \ | ||
| --fail-under=${{ env.THRESHOLD_DIFF }} \ | ||
| 2>&1) && DIFF_EXIT=0 || DIFF_EXIT=$? | ||
| if [ -f diff-cover.json ]; then | ||
| DIFF_PCT=$(python3 - <<'PY' | ||
| import json | ||
| with open('diff-cover.json', encoding='utf-8') as f: | ||
| report = json.load(f) | ||
| num_changed_lines = report.get('num_changed_lines', 0) | ||
| total_num_lines = report.get('total_num_lines', 0) | ||
| total_percent_covered = report.get('total_percent_covered', 'N/A') | ||
| if num_changed_lines > 0 and total_num_lines == 0: | ||
| print('N/A') | ||
| else: | ||
| print(total_percent_covered) | ||
| PY | ||
| ) | ||
| else | ||
| DIFF_PCT="N/A" | ||
| fi | ||
| if [ "$DIFF_PCT" = "N/A" ]; then | ||
| DIFF_DISPLAY="N/A" | ||
| else | ||
| DIFF_DISPLAY="${DIFF_PCT}%" | ||
| fi | ||
| echo "diff_pct=${DIFF_PCT}" >> "$GITHUB_OUTPUT" | ||
| echo "diff_display=${DIFF_DISPLAY}" >> "$GITHUB_OUTPUT" | ||
| echo "$DIFF_OUTPUT" | ||
| if [ "$DIFF_EXIT" -ne 0 ] || [ "$DIFF_PCT" = "N/A" ]; then | ||
| echo "diff_status=fail" >> "$GITHUB_OUTPUT" | ||
| if [ "$DIFF_PCT" = "N/A" ]; then | ||
| echo "❌ Changed-line coverage could not be computed because diff-cover found changed lines without matching coverage information." | ||
| fi | ||
| else | ||
| echo "diff_status=pass" >> "$GITHUB_OUTPUT" | ||
| fi | ||
| # ── Generate per-package coverage table ────────────────────────────── | ||
| - name: Generate coverage report markdown | ||
| if: always() | ||
| run: | | ||
| { | ||
| echo "## 📊 Code Coverage Report" | ||
| echo "" | ||
| TOTAL="${{ steps.total_coverage.outputs.total }}" | ||
| STATUS="${{ steps.total_coverage.outputs.status }}" | ||
| [ -n "$TOTAL" ] || TOTAL="N/A" | ||
| [ "$STATUS" = "pass" ] && ICON="✅" || ICON="❌" | ||
| echo "| Metric | Coverage | Threshold | Status |" | ||
| echo "|--------|----------|-----------|--------|" | ||
| echo "| Overall | **${TOTAL}%** | ${{ env.THRESHOLD_TOTAL }}% | ${ICON} |" | ||
| if [ "${{ github.event_name }}" = "pull_request" ]; then | ||
| DIFF_DISPLAY="${{ steps.diff_coverage.outputs.diff_display }}" | ||
| DIFF_STATUS="${{ steps.diff_coverage.outputs.diff_status }}" | ||
| [ -n "$DIFF_DISPLAY" ] || DIFF_DISPLAY="N/A" | ||
| [ "$DIFF_STATUS" = "pass" ] && DIFF_ICON="✅" || DIFF_ICON="❌" | ||
| echo "| Changed lines | **${DIFF_DISPLAY}** | ${{ env.THRESHOLD_DIFF }}% | ${DIFF_ICON} |" | ||
| fi | ||
| if [ -f coverage.out ]; then | ||
| echo "" | ||
| echo "<details>" | ||
| echo "<summary>📦 Per-package breakdown</summary>" | ||
| echo "" | ||
| echo '```' | ||
| go tool cover -func=coverage.out | grep -v "^total:" | \ | ||
| awk '{printf "%-80s %s\n", $1, $3}' | sort | ||
| echo "" | ||
| go tool cover -func=coverage.out | grep "^total:" | ||
| echo '```' | ||
| echo "" | ||
| echo "</details>" | ||
| else | ||
| echo "" | ||
| echo "Coverage artifacts were not generated because the workflow failed before coverage collection completed." | ||
| fi | ||
| } > coverage-report.md | ||
| # ── Write step summary ──────────────────────────────────────────────── | ||
| - name: Write job summary | ||
| if: always() | ||
| run: cat coverage-report.md >> "$GITHUB_STEP_SUMMARY" | ||
| # ── Save PR number so the comment workflow can find the right PR ────── | ||
| - name: Save PR number | ||
| if: always() && github.event_name == 'pull_request' | ||
| run: echo "${{ github.event.pull_request.number }}" > pr-number.txt | ||
| # ── Upload artifacts (report + pr-number for comment workflow) ──────── | ||
| - name: Upload coverage artifacts | ||
| if: always() | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: coverage-report | ||
| path: | | ||
| coverage.out | ||
| coverage.xml | ||
| diff-cover.json | ||
| coverage-report.md | ||
| pr-number.txt | ||
| if-no-files-found: warn | ||
| retention-days: 14 | ||
| - name: Upload coverage to Codecov | ||
| uses: codecov/codecov-action@v4 | ||
| with: | ||
| file: ./coverage.out | ||
| flags: unittests | ||
| name: codecov-umbrella | ||
| fail_ci_if_error: false | ||
| env: | ||
| CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} | ||
| # ── Enforce thresholds (after artifacts uploaded so report is always saved) ── | ||
| - name: Enforce total coverage threshold | ||
| if: steps.total_coverage.outputs.status == 'fail' | ||
| run: | | ||
| echo "❌ Total coverage ${{ steps.total_coverage.outputs.total }}% is below the required ${{ env.THRESHOLD_TOTAL }}%" | ||
| exit 1 | ||
| - name: Enforce diff coverage threshold | ||
| if: > | ||
| github.event_name == 'pull_request' && | ||
| steps.diff_coverage.outputs.diff_status == 'fail' | ||
| run: | | ||
| echo "❌ Changed-line coverage ${{ steps.diff_coverage.outputs.diff_display }} is below the required ${{ env.THRESHOLD_DIFF }}%" | ||
| exit 1 | ||