From 7d5bb2311f324a0a1683cf8f4a3642fdbf2b062e Mon Sep 17 00:00:00 2001 From: yong203 Date: Wed, 24 Jun 2026 15:53:15 +0900 Subject: [PATCH 1/2] =?UTF-8?q?:wrench:=20chore:=20Codecov=20=EB=B0=8F=20P?= =?UTF-8?q?R=20CI=20=EB=A6=AC=ED=8F=AC=ED=8A=B8=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/backend-ci.yml | 134 +++++++++++++++++++++++++++++++ codecov.yml | 12 ++- docs/status.md | 1 + docs/testing.md | 11 +++ 4 files changed, 157 insertions(+), 1 deletion(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 1dc664b..45a1b28 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -66,6 +66,12 @@ jobs: runs-on: ubuntu-latest needs: test if: always() + outputs: + line_coverage: ${{ steps.coverage-summary.outputs.line_coverage }} + covered_lines: ${{ steps.coverage-summary.outputs.covered_lines }} + total_lines: ${{ steps.coverage-summary.outputs.total_lines }} + coverage_report_outcome: ${{ steps.download-coverage.outcome }} + codecov_outcome: ${{ steps.codecov.outcome }} steps: - name: Checkout @@ -79,6 +85,42 @@ jobs: name: backend-coverage-report path: build/reports/jacoco/test + - name: Calculate line coverage + id: coverage-summary + if: steps.download-coverage.outcome == 'success' + shell: python + run: | + import os + from pathlib import Path + import xml.etree.ElementTree as ET + + report = Path("build/reports/jacoco/test/jacocoTestReport.xml") + output = Path(os.environ["GITHUB_OUTPUT"]) + + line_coverage = "N/A" + covered_lines = "0" + total_lines = "0" + + if report.exists(): + root = ET.parse(report).getroot() + counter = next( + (item for item in root.findall("counter") if item.get("type") == "LINE"), + None, + ) + if counter is not None: + covered = int(counter.get("covered", "0")) + missed = int(counter.get("missed", "0")) + total = covered + missed + covered_lines = str(covered) + total_lines = str(total) + if total > 0: + line_coverage = f"{covered / total * 100:.2f}%" + + with output.open("a", encoding="utf-8") as stream: + stream.write(f"line_coverage={line_coverage}\n") + stream.write(f"covered_lines={covered_lines}\n") + stream.write(f"total_lines={total_lines}\n") + - name: Upload coverage to Codecov id: codecov if: steps.download-coverage.outcome == 'success' @@ -102,3 +144,95 @@ jobs: echo "| Codecov upload | ${{ steps.codecov.outcome }} |" echo "| Commit | \`${{ github.sha }}\` |" } >> "$GITHUB_STEP_SUMMARY" + + pr-report: + name: PR report + runs-on: ubuntu-latest + needs: + - test + - coverage + if: >- + always() && + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository + permissions: + contents: read + pull-requests: write + + steps: + - name: Update PR CI report comment + uses: actions/github-script@v9 + env: + TEST_RESULT: ${{ needs.test.result }} + LINE_COVERAGE: ${{ needs.coverage.outputs.line_coverage }} + COVERED_LINES: ${{ needs.coverage.outputs.covered_lines }} + TOTAL_LINES: ${{ needs.coverage.outputs.total_lines }} + COVERAGE_REPORT_OUTCOME: ${{ needs.coverage.outputs.coverage_report_outcome }} + CODECOV_OUTCOME: ${{ needs.coverage.outputs.codecov_outcome }} + WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + SUNBURST_URL: https://codecov.io/gh/${{ github.repository }}/pull/${{ github.event.pull_request.number }}/graphs/sunburst.svg + with: + script: | + const marker = ''; + + const jobResult = (value) => ({ + success: '✅ Passed', + failure: '❌ Failed', + cancelled: '⚪ Cancelled', + skipped: '⚪ Skipped', + })[value] ?? '⚪ Unknown'; + + const stepResult = (value, successLabel) => ({ + success: `✅ ${successLabel}`, + failure: '⚠️ Failed', + cancelled: '⚪ Cancelled', + skipped: '⚪ Skipped', + })[value] ?? '⚪ Not available'; + + const lineCoverage = process.env.LINE_COVERAGE || 'N/A'; + const coveredLines = process.env.COVERED_LINES || '0'; + const totalLines = process.env.TOTAL_LINES || '0'; + + const body = [ + marker, + '## Backend CI Report', + '', + '| Item | Result |', + '| --- | --- |', + `| Tests | ${jobResult(process.env.TEST_RESULT)} |`, + `| JaCoCo line coverage | ${lineCoverage} (${coveredLines}/${totalLines} lines) |`, + `| Coverage report | ${stepResult(process.env.COVERAGE_REPORT_OUTCOME, 'Generated')} |`, + `| Codecov upload | ${stepResult(process.env.CODECOV_OUTCOME, 'Uploaded')} |`, + '', + `[View workflow run](${process.env.WORKFLOW_URL})`, + '', + '### Codecov Sunburst', + '', + `![Codecov PR Sunburst](${process.env.SUNBURST_URL})`, + ].join('\n'); + + const comments = await github.paginate(github.rest.issues.listComments, { + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + per_page: 100, + }); + const existing = comments.find((comment) => + comment.user?.login === 'github-actions[bot]' && comment.body?.includes(marker) + ); + + if (existing) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: existing.id, + body, + }); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body, + }); + } diff --git a/codecov.yml b/codecov.yml index b4f2865..f4e05b4 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,6 +1,16 @@ +coverage: + status: + project: + default: + informational: true + patch: + default: + informational: true + comment: - layout: "condensed_header, diff, files" + layout: "condensed_header, condensed_files, condensed_footer" behavior: default require_changes: false require_base: false require_head: true + hide_project_coverage: false diff --git a/docs/status.md b/docs/status.md index 4a65b09..515d7d5 100644 --- a/docs/status.md +++ b/docs/status.md @@ -36,6 +36,7 @@ API 계약과 API별 상태는 `docs/api/README.md`와 `docs/api/` 하위 도메 | Issue | Scope | Status | Notes | |---|---|---|---| | [#16](https://github.com/Rewrite-Team/Rewrite-BE/issues/16) | Backend 문서 구조 및 개발 규칙 정리 | Open | 현재 문서 구조와 작업 규칙 정리 범위 | +| [#46](https://github.com/Rewrite-Team/Rewrite-BE/issues/46) | Codecov 및 PR CI 리포트 개선 | Open | Codecov informational status, JaCoCo 수치와 PR별 sunburst를 포함한 고정 CI 댓글 구성 | ## Current Recommended Next Work diff --git a/docs/testing.md b/docs/testing.md index ad72f9b..dff6394 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -59,3 +59,14 @@ Jacoco가 설정되어 있으며, 테스트 후 `jacocoTestReport`가 실행된 build/reports/jacoco/test/html/index.html build/reports/jacoco/test/jacocoTestReport.xml ``` + +## Pull Request CI Report + +- `test` job 실패는 기존과 동일하게 PR check를 실패 처리한다. +- Codecov project/patch status는 informational로 사용하며 커버리지 수치만 제공한다. +- 커버리지 감소나 Codecov 업로드 실패만으로 PR 병합을 차단하지 않는다. +- `coverage` job은 JaCoCo XML의 전체 line coverage와 Codecov 업로드 결과를 output으로 제공한다. +- `pr-report` job은 테스트 결과, line coverage, Codecov 업로드 상태와 PR별 sunburst를 고정 댓글로 표시한다. +- 고정 댓글은 새 workflow 실행마다 기존 `github-actions[bot]` 댓글을 갱신한다. +- PR 코드 실행 job에는 쓰기 권한을 주지 않고, checkout을 수행하지 않는 `pr-report` job에만 `pull-requests: write`를 부여한다. +- 외부 fork PR에서는 쓰기 토큰 제한을 고려해 고정 댓글 작성을 건너뛴다. From 9e45329390cbcf8fb193d5407b944cfc8462b045 Mon Sep 17 00:00:00 2001 From: yong203 Date: Wed, 24 Jun 2026 16:16:30 +0900 Subject: [PATCH 2/2] =?UTF-8?q?:bug:=20fix:=20PR=20=EC=BB=A4=EB=B2=84?= =?UTF-8?q?=EB=A6=AC=EC=A7=80=20=EA=B7=B8=EB=9E=98=ED=94=84=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 색상 정보가 없는 Codecov Sunburst 이미지를 제거하고 JaCoCo 수치와 Codecov 상세 링크를 제공한다. --- .github/workflows/backend-ci.yml | 8 ++------ docs/status.md | 2 +- docs/testing.md | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 45a1b28..c3176b7 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -170,7 +170,7 @@ jobs: COVERAGE_REPORT_OUTCOME: ${{ needs.coverage.outputs.coverage_report_outcome }} CODECOV_OUTCOME: ${{ needs.coverage.outputs.codecov_outcome }} WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - SUNBURST_URL: https://codecov.io/gh/${{ github.repository }}/pull/${{ github.event.pull_request.number }}/graphs/sunburst.svg + CODECOV_PR_URL: https://app.codecov.io/gh/${{ github.repository }}/pull/${{ github.event.pull_request.number }} with: script: | const marker = ''; @@ -204,11 +204,7 @@ jobs: `| Coverage report | ${stepResult(process.env.COVERAGE_REPORT_OUTCOME, 'Generated')} |`, `| Codecov upload | ${stepResult(process.env.CODECOV_OUTCOME, 'Uploaded')} |`, '', - `[View workflow run](${process.env.WORKFLOW_URL})`, - '', - '### Codecov Sunburst', - '', - `![Codecov PR Sunburst](${process.env.SUNBURST_URL})`, + `[View workflow run](${process.env.WORKFLOW_URL}) | [View coverage details](${process.env.CODECOV_PR_URL})`, ].join('\n'); const comments = await github.paginate(github.rest.issues.listComments, { diff --git a/docs/status.md b/docs/status.md index 515d7d5..ec56a58 100644 --- a/docs/status.md +++ b/docs/status.md @@ -36,7 +36,7 @@ API 계약과 API별 상태는 `docs/api/README.md`와 `docs/api/` 하위 도메 | Issue | Scope | Status | Notes | |---|---|---|---| | [#16](https://github.com/Rewrite-Team/Rewrite-BE/issues/16) | Backend 문서 구조 및 개발 규칙 정리 | Open | 현재 문서 구조와 작업 규칙 정리 범위 | -| [#46](https://github.com/Rewrite-Team/Rewrite-BE/issues/46) | Codecov 및 PR CI 리포트 개선 | Open | Codecov informational status, JaCoCo 수치와 PR별 sunburst를 포함한 고정 CI 댓글 구성 | +| [#46](https://github.com/Rewrite-Team/Rewrite-BE/issues/46) | Codecov 및 PR CI 리포트 개선 | Open | Codecov informational status, JaCoCo 수치와 Codecov 상세 링크를 포함한 고정 CI 댓글 구성 | ## Current Recommended Next Work diff --git a/docs/testing.md b/docs/testing.md index dff6394..521d83d 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -66,7 +66,7 @@ build/reports/jacoco/test/jacocoTestReport.xml - Codecov project/patch status는 informational로 사용하며 커버리지 수치만 제공한다. - 커버리지 감소나 Codecov 업로드 실패만으로 PR 병합을 차단하지 않는다. - `coverage` job은 JaCoCo XML의 전체 line coverage와 Codecov 업로드 결과를 output으로 제공한다. -- `pr-report` job은 테스트 결과, line coverage, Codecov 업로드 상태와 PR별 sunburst를 고정 댓글로 표시한다. +- `pr-report` job은 테스트 결과, line coverage, Codecov 업로드 상태와 Codecov PR 상세 링크를 고정 댓글로 표시한다. - 고정 댓글은 새 workflow 실행마다 기존 `github-actions[bot]` 댓글을 갱신한다. - PR 코드 실행 job에는 쓰기 권한을 주지 않고, checkout을 수행하지 않는 `pr-report` job에만 `pull-requests: write`를 부여한다. - 외부 fork PR에서는 쓰기 토큰 제한을 고려해 고정 댓글 작성을 건너뛴다.