UNPKG

proto-coverage-reporter

Version:
160 lines (157 loc) 6.73 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs"); const path = require("path"); const github_1 = require("@actions/github"); const const_1 = require("../const"); const logs_1 = require("../logs"); const proto_1 = require("./proto"); const Table = require('cli-table'); // eslint-disable-line @typescript-eslint/no-var-requires const chalk = require('chalk'); // eslint-disable-line @typescript-eslint/no-var-requires class ProtoCoverageReporter { globalConfig; options; protoSpec = {}; constructor(globalConfig, options) { this.globalConfig = globalConfig; this.options = options; const { coverageFrom } = options; // proto解析 if (!coverageFrom || !coverageFrom.length) { throw new Error('[proto-coverage-reporter]: coverageFrom option is required'); } for (const data of coverageFrom) { const { packageName, serviceProtoPath } = data; if (!packageName || !serviceProtoPath) { throw new Error('[proto-coverage-reporter]: packageName and serviceProtoPath are required'); } const serviceProtoAbsolutePath = this.getServiceProtoAbsolutePath(serviceProtoPath); const methodSpec = (0, proto_1.parseMethodSpec)(serviceProtoAbsolutePath, packageName); this.protoSpec[packageName] = methodSpec; } fs.mkdirSync(const_1.logsDirPath, { recursive: true }); } async onRunComplete(testContexts, originalResults) { const logsMap = await (0, logs_1.readLogsMap)(); const parsed = this.parseResult(logsMap); this.stdoutCoverage(parsed); this.createPRComment(parsed); this.removeLogsDir(); } parseResult(logsMap) { const parsed = Object.entries(this.protoSpec).reduce((acc, [packageName, methods]) => { acc[packageName] = Object.entries(methods).reduce((acc, [methodName, spec]) => { const desiredStatusCodes = spec.status_codes; const statusCodes = logsMap[packageName]?.[methodName]?.status_codes || {}; const uncheckedStatusCodes = desiredStatusCodes.filter(statusCode => !statusCodes[const_1.Status[statusCode]]); const uncehckedPercentage = uncheckedStatusCodes.length / desiredStatusCodes.length; const coverage = Math.round(100 - uncehckedPercentage * 100); acc[methodName] = { status_codes: { expected: desiredStatusCodes, unchecked: uncheckedStatusCodes, coverage, }, }; return acc; }, {}); return acc; }, {}); return parsed; } stdoutCoverage(result) { const table = new Table({ style: { head: ['white'] }, head: ['Package', 'Method', 'Coverage', 'Unchecked Status'], }); for (const [packageName, methods] of Object.entries(result)) { for (const [methodName, { status_codes }] of Object.entries(methods)) { const { coverage, unchecked } = status_codes; table.push([ packageName, coverage === 100 ? chalk.green(methodName) : chalk.red(methodName), coverage === 100 ? chalk.green(`${coverage}%`) : chalk.red(`${coverage}%`), chalk.red(unchecked.join(', ')), ]); } } console.log(table.toString()); } async createPRComment(result) { try { if (process.env.CI !== 'true' || typeof process.env.GITHUB_TOKEN !== 'string' || !process.env.GITHUB_TOKEN) return; const { eventName, repo: { owner, repo }, sha } = github_1.context; console.log('eventName', eventName); if (!eventName || !['push'].includes(eventName)) return; const octokit = (0, github_1.getOctokit)(process.env.GITHUB_TOKEN); const { data: prs } = await octokit.rest.pulls.list({ owner, repo, head: sha, state: 'open', }); if (!prs || !prs.length) return; const targetPr = prs.find(pr => pr.head.sha === sha); if (!targetPr) return; const { number: issue_number } = targetPr; // remove existing comment const { data: comments } = await octokit.rest.issues.listComments({ owner, repo, issue_number }); const targetComment = comments.find(comment => comment.body?.includes('__Proto_Coverage_Result__')); if (targetComment) { await octokit.rest.issues.deleteComment({ owner, repo, comment_id: targetComment.id }); } const coverages = []; const formattedLogs = []; for (const [packageName, methods] of Object.entries(result)) { for (const [methodName, { status_codes }] of Object.entries(methods)) { const { coverage, unchecked } = status_codes; coverages.push(coverage); formattedLogs.push(`| ${packageName} | ${methodName} | ${coverage}% | ${unchecked.join(', ')} |`); } } const totalCoverage = Math.round(coverages.reduce((acc, cur) => acc + cur, 0) / coverages.length); await octokit.rest.issues.createComment({ owner, repo, issue_number, body: ` ![__Proto_Coverage_Result__](https://img.shields.io/badge/Proto_Coverage-${totalCoverage}%25-${totalCoverage === 100 ? 'brightgreen' : 'red'}) <details open> <summary>Coverage Report</summary> | Package | Method | Coverage | Unchecked Status | | --- | --- | --- | --- | ${formattedLogs.join('\n')} </details> `.trim() }); } catch (e) { console.error(e); console.error('Failed to create PR comment'); } } getServiceProtoAbsolutePath(serviceProtoPath) { if (serviceProtoPath.startsWith('<rootDir>')) { return path.join(this.globalConfig.rootDir, serviceProtoPath.slice('<rootDir>'.length)); } else { return serviceProtoPath; } } removeLogsDir() { fs.rmSync(const_1.logsDirPath, { recursive: true }); } } exports.default = ProtoCoverageReporter;