Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions .github/workflows/test-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,36 +46,33 @@ jobs:
- name: Install dependencies
run: pnpm install

- name: 🧪 Run tests
run: pnpm run test

- name: 📊 Generate test coverage
run: pnpm run ci

- name: 📤 Save coverage from PR
id: pr_coverage
id: pr-coverage
run: |
COVERAGE=$(node .scripts/get-summary.js)
echo "current coverage: $COVERAGE"
echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT

- name: 📥 Checkout base branch to get base coverage
id: base-coverage
run: |
git fetch origin ${{ steps.get-base-branch.outputs.BASE_BRANCH }}
git checkout origin/${{ steps.get-base-branch.outputs.BASE_BRANCH }} -- coverage/coverage-final.json

- name: 📤 Save coverage from base branch
id: base_coverage
run: |
COVERAGE=$(node .scripts/get-summary.js)
echo "base coverage: $COVERAGE"
echo "coverage=$COVERAGE" >> $GITHUB_OUTPUT

- name: 💬 Post coverage change to PR
- name: 📬 Post coverage change to PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const prCoverage = parseFloat('${{ steps.pr_coverage.outputs.coverage }}');
const baseCoverage = parseFloat('${{ steps.base_coverage.outputs.coverage }}');
const prCoverage = parseFloat('${{ steps.pr-coverage.outputs.coverage }}');
const baseCoverage = parseFloat('${{ steps.base-coverage.outputs.coverage }}');
const delta = (prCoverage - baseCoverage).toFixed(2);

let comment = '## Coverage Change\n\n This Comment is auto generated by GitHub Actions.\n\n';
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ node_modules

dist
coverage/**
!coverage/coverage-final.json
!coverage/coverage-summary.json

.vscode

Expand Down
45 changes: 45 additions & 0 deletions .scripts/_utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import fs from "fs";
import path from "path";

function loadCoverageData(filePath) {
if (!fs.existsSync(filePath)) {
console.error(`❌ ${filePath} 파일을 찾을 수 없습니다.`);
process.exit(1);
}
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}

function saveSvg(svg, filePath) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, svg, "utf8");
console.log(`✅ Coverage SVG badge saved to: ${filePath}`);
}

function getTotalCoverage(data) {
const {
total: { lines, statements, functions, branches, branchesTrue },
} = data;
const totalTotal = lines.total + statements.total + functions.total + branches.total + branchesTrue.total;
const totalCovered = lines.covered + statements.covered + functions.covered + branches.covered + branchesTrue.covered;
const totalSkipped = lines.skipped + statements.skipped + functions.skipped + branches.skipped + branchesTrue.skipped;
// 소수점 2자리
const totalPct = totalTotal === 0 ? 100 : Math.round((totalCovered / totalTotal) * 10000) / 100;

const total = {
total: totalTotal,
covered: totalCovered,
skipped: totalSkipped,
pct: totalPct,
};

return {
coverage: total,
lines,
statements,
functions,
branches,
branchesTrue,
};
}

export { getTotalCoverage, loadCoverageData, saveSvg };
69 changes: 5 additions & 64 deletions .scripts/coverage-badge.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,12 @@
import fs from "fs";
import path, { dirname } from "path";
import { fileURLToPath } from "url";

import { getTotalCoverage, loadCoverageData, saveSvg } from "./_utils.js";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const COVERAGE_PATH = path.resolve(__dirname, "../coverage/coverage-final.json");
const OUTPUT_PATH = path.resolve(__dirname, "../badges/coverage.svg");

function loadCoverageData(filePath) {
if (!fs.existsSync(filePath)) {
console.error("❌ coverage-final.json 파일을 찾을 수 없습니다.");
process.exit(1);
}
return JSON.parse(fs.readFileSync(filePath, "utf8"));
}

function getTotalCoverage(data) {
let statements = { covered: 0, total: 0 };
let branches = { covered: 0, total: 0 };
let functions = { covered: 0, total: 0 };
let lines = { covered: 0, total: 0 };

for (const file of Object.values(data)) {
for (const [k, v] of Object.entries(file.s || {})) {
statements.covered += v > 0 ? 1 : 0;
statements.total++;
}
for (const [k, v] of Object.entries(file.b || {})) {
branches.covered += v.reduce((acc, b) => acc + (b > 0 ? 1 : 0), 0);
branches.total += v.length;
}
for (const [k, v] of Object.entries(file.f || {})) {
functions.covered += v > 0 ? 1 : 0;
functions.total++;
}
// lines는 별도로 없으면 statements와 동일하게 처리
lines = statements;
}

const toPercent = ({ covered, total }) => (total === 0 ? 100 : Math.round((covered / total) * 100));

return {
statements: toPercent(statements),
branches: toPercent(branches),
functions: toPercent(functions),
lines: toPercent(lines),
};
}
const COVERAGE_PATH = path.resolve(__dirname, "../coverage/coverage-summary.json");

function getColor(percent) {
if (percent >= 90) return "#4c1";
Expand Down Expand Up @@ -80,34 +39,16 @@ function generateSvg(label, value, color) {
`.trim();
}

function saveSvg(svg, filePath) {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, svg, "utf8");
console.log(`✅ Coverage SVG badge saved to: ${filePath}`);
}

// 실행
const coverage = loadCoverageData(COVERAGE_PATH);
const summary = getTotalCoverage(coverage);

let total = 0;

for (const [key, value] of Object.entries(summary)) {
console.log(`\nCoverage ${key}: ${value}%`);
total += value;
console.log(`\nCoverage ${key}: ${value.pct}%`);

const percent = summary[key];
const percent = value.pct;
const color = getColor(percent);
const svg = generateSvg(key, percent, color);
const outputPath = path.resolve(__dirname, `../badges/${key}.svg`);
saveSvg(svg, outputPath);
}

const average = Math.round(total / Object.keys(summary).length);

console.log(`\nTotal Coverage: ${average}%`);

const color = getColor(average);
const svg = generateSvg("coverage", average, color);
const outputPath = path.resolve(__dirname, OUTPUT_PATH);
saveSvg(svg, outputPath);
21 changes: 5 additions & 16 deletions .scripts/get-summary.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,12 @@
import fs from "fs";
import path, { dirname } from "path";
import { fileURLToPath } from "url";

import { getTotalCoverage, loadCoverageData } from "./_utils.js";

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const file = path.resolve(__dirname, "../coverage/coverage-final.json");
const data = JSON.parse(fs.readFileSync(file, "utf-8"));

let covered = 0;
let total = 0;

for (const file of Object.values(data)) {
const s = file.s || {};
for (const hit of Object.values(s)) {
total++;
if (hit > 0) covered++;
}
}
const data = loadCoverageData(path.resolve(__dirname, "../coverage/coverage-summary.json"));
const summary = getTotalCoverage(data);

const percentage = total === 0 ? 100 : Math.round((covered / total) * 10000) / 100;
console.log(percentage);
console.log(summary.coverage.pct);
1 change: 1 addition & 0 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
![functions](./badges/functions.svg)
![lines](./badges/lines.svg)
![statements](./badges/statements.svg)
![branchesTrue](./badges/branchesTrue.svg)

</div>

Expand Down
18 changes: 18 additions & 0 deletions badges/branchesTrue.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion badges/coverage.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion badges/functions.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading