- Add test result artifacts storage with multiple formats (JUnit, JSON, HTML) - Configure GitHub Actions to upload and preserve test outputs - Add PR comment integration with test summaries - Create benchmark comparison workflow for PR performance tracking - Add detailed test report generation scripts - Configure artifact retention policies (30 days for tests, 90 for combined) - Set up test metadata collection for better debugging This completes all remaining test infrastructure tasks and provides comprehensive visibility into test results across CI/CD pipeline.
167 lines
5.5 KiB
JavaScript
167 lines
5.5 KiB
JavaScript
#!/usr/bin/env node
|
|
import { readFileSync, existsSync } from 'fs';
|
|
import { resolve } from 'path';
|
|
|
|
/**
|
|
* Generate a markdown summary of test results for PR comments
|
|
*/
|
|
function generateTestSummary() {
|
|
const results = {
|
|
tests: null,
|
|
coverage: null,
|
|
benchmarks: null,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
// Read test results
|
|
const testResultPath = resolve(process.cwd(), 'test-results/results.json');
|
|
if (existsSync(testResultPath)) {
|
|
try {
|
|
const testData = JSON.parse(readFileSync(testResultPath, 'utf-8'));
|
|
const totalTests = testData.numTotalTests || 0;
|
|
const passedTests = testData.numPassedTests || 0;
|
|
const failedTests = testData.numFailedTests || 0;
|
|
const skippedTests = testData.numSkippedTests || 0;
|
|
const duration = testData.duration || 0;
|
|
|
|
results.tests = {
|
|
total: totalTests,
|
|
passed: passedTests,
|
|
failed: failedTests,
|
|
skipped: skippedTests,
|
|
duration: duration,
|
|
success: failedTests === 0
|
|
};
|
|
} catch (error) {
|
|
console.error('Error reading test results:', error);
|
|
}
|
|
}
|
|
|
|
// Read coverage results
|
|
const coveragePath = resolve(process.cwd(), 'coverage/coverage-summary.json');
|
|
if (existsSync(coveragePath)) {
|
|
try {
|
|
const coverageData = JSON.parse(readFileSync(coveragePath, 'utf-8'));
|
|
const total = coverageData.total;
|
|
|
|
results.coverage = {
|
|
lines: total.lines.pct,
|
|
statements: total.statements.pct,
|
|
functions: total.functions.pct,
|
|
branches: total.branches.pct
|
|
};
|
|
} catch (error) {
|
|
console.error('Error reading coverage results:', error);
|
|
}
|
|
}
|
|
|
|
// Read benchmark results
|
|
const benchmarkPath = resolve(process.cwd(), 'benchmark-results.json');
|
|
if (existsSync(benchmarkPath)) {
|
|
try {
|
|
const benchmarkData = JSON.parse(readFileSync(benchmarkPath, 'utf-8'));
|
|
const benchmarks = [];
|
|
|
|
for (const file of benchmarkData.files || []) {
|
|
for (const group of file.groups || []) {
|
|
for (const benchmark of group.benchmarks || []) {
|
|
benchmarks.push({
|
|
name: `${group.name} - ${benchmark.name}`,
|
|
mean: benchmark.result.mean,
|
|
ops: benchmark.result.hz
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
results.benchmarks = benchmarks;
|
|
} catch (error) {
|
|
console.error('Error reading benchmark results:', error);
|
|
}
|
|
}
|
|
|
|
// Generate markdown summary
|
|
let summary = '## Test Results Summary\n\n';
|
|
|
|
// Test results
|
|
if (results.tests) {
|
|
const { total, passed, failed, skipped, duration, success } = results.tests;
|
|
const emoji = success ? '✅' : '❌';
|
|
const status = success ? 'PASSED' : 'FAILED';
|
|
|
|
summary += `### ${emoji} Tests ${status}\n\n`;
|
|
summary += `| Metric | Value |\n`;
|
|
summary += `|--------|-------|\n`;
|
|
summary += `| Total Tests | ${total} |\n`;
|
|
summary += `| Passed | ${passed} |\n`;
|
|
summary += `| Failed | ${failed} |\n`;
|
|
summary += `| Skipped | ${skipped} |\n`;
|
|
summary += `| Duration | ${(duration / 1000).toFixed(2)}s |\n\n`;
|
|
}
|
|
|
|
// Coverage results
|
|
if (results.coverage) {
|
|
const { lines, statements, functions, branches } = results.coverage;
|
|
const avgCoverage = (lines + statements + functions + branches) / 4;
|
|
const emoji = avgCoverage >= 80 ? '✅' : avgCoverage >= 60 ? '⚠️' : '❌';
|
|
|
|
summary += `### ${emoji} Coverage Report\n\n`;
|
|
summary += `| Type | Coverage |\n`;
|
|
summary += `|------|----------|\n`;
|
|
summary += `| Lines | ${lines.toFixed(2)}% |\n`;
|
|
summary += `| Statements | ${statements.toFixed(2)}% |\n`;
|
|
summary += `| Functions | ${functions.toFixed(2)}% |\n`;
|
|
summary += `| Branches | ${branches.toFixed(2)}% |\n`;
|
|
summary += `| **Average** | **${avgCoverage.toFixed(2)}%** |\n\n`;
|
|
}
|
|
|
|
// Benchmark results
|
|
if (results.benchmarks && results.benchmarks.length > 0) {
|
|
summary += `### ⚡ Benchmark Results\n\n`;
|
|
summary += `| Benchmark | Ops/sec | Mean (ms) |\n`;
|
|
summary += `|-----------|---------|------------|\n`;
|
|
|
|
for (const bench of results.benchmarks.slice(0, 10)) { // Show top 10
|
|
const opsFormatted = bench.ops.toLocaleString('en-US', { maximumFractionDigits: 0 });
|
|
const meanFormatted = (bench.mean * 1000).toFixed(3);
|
|
summary += `| ${bench.name} | ${opsFormatted} | ${meanFormatted} |\n`;
|
|
}
|
|
|
|
if (results.benchmarks.length > 10) {
|
|
summary += `\n*...and ${results.benchmarks.length - 10} more benchmarks*\n`;
|
|
}
|
|
summary += '\n';
|
|
}
|
|
|
|
// Links to artifacts
|
|
const runId = process.env.GITHUB_RUN_ID;
|
|
const runNumber = process.env.GITHUB_RUN_NUMBER;
|
|
const sha = process.env.GITHUB_SHA;
|
|
|
|
if (runId) {
|
|
summary += `### 📊 Artifacts\n\n`;
|
|
summary += `- 📄 [Test Results](https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${runId})\n`;
|
|
summary += `- 📊 [Coverage Report](https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${runId})\n`;
|
|
summary += `- ⚡ [Benchmark Results](https://github.com/${process.env.GITHUB_REPOSITORY}/actions/runs/${runId})\n\n`;
|
|
}
|
|
|
|
// Metadata
|
|
summary += `---\n`;
|
|
summary += `*Generated at ${new Date().toUTCString()}*\n`;
|
|
if (sha) {
|
|
summary += `*Commit: ${sha.substring(0, 7)}*\n`;
|
|
}
|
|
if (runNumber) {
|
|
summary += `*Run: #${runNumber}*\n`;
|
|
}
|
|
|
|
return summary;
|
|
}
|
|
|
|
// Generate and output summary
|
|
const summary = generateTestSummary();
|
|
console.log(summary);
|
|
|
|
// Also write to file for artifact
|
|
import { writeFileSync } from 'fs';
|
|
writeFileSync('test-summary.md', summary); |