mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-30 06:12:06 -07:00
fix: harden coverage lane cleanup
This commit is contained in:
@@ -1,7 +1,8 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
|
import { resolve } from 'node:path';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
|
|
||||||
import { mergeLcovReports } from './run-coverage-lane';
|
import { mergeLcovReports, resolveCoverageDir } from './run-coverage-lane';
|
||||||
|
|
||||||
test('mergeLcovReports combines duplicate source-file counters across shard outputs', () => {
|
test('mergeLcovReports combines duplicate source-file counters across shard outputs', () => {
|
||||||
const merged = mergeLcovReports([
|
const merged = mergeLcovReports([
|
||||||
@@ -59,3 +60,15 @@ test('mergeLcovReports keeps distinct source files as separate records', () => {
|
|||||||
assert.match(merged, /SF:src\/a\.ts[\s\S]*end_of_record/);
|
assert.match(merged, /SF:src\/a\.ts[\s\S]*end_of_record/);
|
||||||
assert.match(merged, /SF:src\/b\.ts[\s\S]*end_of_record/);
|
assert.match(merged, /SF:src\/b\.ts[\s\S]*end_of_record/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('resolveCoverageDir keeps coverage output inside the repository', () => {
|
||||||
|
const repoRoot = resolve('/tmp', 'subminer-repo-root');
|
||||||
|
|
||||||
|
assert.equal(resolveCoverageDir(repoRoot, []), resolve(repoRoot, 'coverage'));
|
||||||
|
assert.equal(
|
||||||
|
resolveCoverageDir(repoRoot, ['--coverage-dir', 'coverage/test-src']),
|
||||||
|
resolve(repoRoot, 'coverage/test-src'),
|
||||||
|
);
|
||||||
|
assert.throws(() => resolveCoverageDir(repoRoot, ['--coverage-dir', '../escape']));
|
||||||
|
assert.throws(() => resolveCoverageDir(repoRoot, ['--coverage-dir', '/tmp/escape']));
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
||||||
import { spawnSync } from 'node:child_process';
|
import { spawnSync } from 'node:child_process';
|
||||||
import { join, relative, resolve } from 'node:path';
|
import { isAbsolute, join, relative, resolve } from 'node:path';
|
||||||
|
|
||||||
type LaneConfig = {
|
type LaneConfig = {
|
||||||
roots: string[];
|
roots: string[];
|
||||||
@@ -85,6 +85,15 @@ function parseCoverageDirArg(argv: string[]): string {
|
|||||||
return 'coverage';
|
return 'coverage';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveCoverageDir(repoRootDir: string, argv: string[]): string {
|
||||||
|
const candidate = resolve(repoRootDir, parseCoverageDirArg(argv));
|
||||||
|
const rel = relative(repoRootDir, candidate);
|
||||||
|
if (isAbsolute(rel) || rel.startsWith('..')) {
|
||||||
|
throw new Error(`--coverage-dir must be within repository: ${candidate}`);
|
||||||
|
}
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
|
||||||
function parseLcovReport(report: string): LcovRecord[] {
|
function parseLcovReport(report: string): LcovRecord[] {
|
||||||
const records: LcovRecord[] = [];
|
const records: LcovRecord[] = [];
|
||||||
let current: LcovRecord | null = null;
|
let current: LcovRecord | null = null;
|
||||||
@@ -251,7 +260,7 @@ function runCoverageLane(): number {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const coverageDir = resolve(repoRoot, parseCoverageDirArg(process.argv.slice(3)));
|
const coverageDir = resolveCoverageDir(repoRoot, process.argv.slice(3));
|
||||||
const shardRoot = join(coverageDir, '.shards');
|
const shardRoot = join(coverageDir, '.shards');
|
||||||
mkdirSync(coverageDir, { recursive: true });
|
mkdirSync(coverageDir, { recursive: true });
|
||||||
rmSync(shardRoot, { recursive: true, force: true });
|
rmSync(shardRoot, { recursive: true, force: true });
|
||||||
@@ -260,37 +269,40 @@ function runCoverageLane(): number {
|
|||||||
const files = getLaneFiles(laneName);
|
const files = getLaneFiles(laneName);
|
||||||
const reports: string[] = [];
|
const reports: string[] = [];
|
||||||
|
|
||||||
for (const [index, file] of files.entries()) {
|
try {
|
||||||
const shardDir = join(shardRoot, `${String(index + 1).padStart(3, '0')}`);
|
for (const [index, file] of files.entries()) {
|
||||||
const result = spawnSync(
|
const shardDir = join(shardRoot, `${String(index + 1).padStart(3, '0')}`);
|
||||||
'bun',
|
const result = spawnSync(
|
||||||
['test', '--coverage', '--coverage-reporter=lcov', '--coverage-dir', shardDir, `./${file}`],
|
'bun',
|
||||||
{
|
['test', '--coverage', '--coverage-reporter=lcov', '--coverage-dir', shardDir, `./${file}`],
|
||||||
cwd: repoRoot,
|
{
|
||||||
stdio: 'inherit',
|
cwd: repoRoot,
|
||||||
},
|
stdio: 'inherit',
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (result.error) {
|
if (result.error) {
|
||||||
throw result.error;
|
throw result.error;
|
||||||
}
|
}
|
||||||
if ((result.status ?? 1) !== 0) {
|
if ((result.status ?? 1) !== 0) {
|
||||||
return result.status ?? 1;
|
return result.status ?? 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const lcovPath = join(shardDir, 'lcov.info');
|
||||||
|
if (!existsSync(lcovPath)) {
|
||||||
|
process.stdout.write(`Skipping empty coverage shard for ${file}\n`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
reports.push(readFileSync(lcovPath, 'utf8'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const lcovPath = join(shardDir, 'lcov.info');
|
writeFileSync(join(coverageDir, 'lcov.info'), mergeLcovReports(reports), 'utf8');
|
||||||
if (!existsSync(lcovPath)) {
|
process.stdout.write(`Merged LCOV written to ${relative(repoRoot, join(coverageDir, 'lcov.info'))}\n`);
|
||||||
process.stdout.write(`Skipping empty coverage shard for ${file}\n`);
|
return 0;
|
||||||
continue;
|
} finally {
|
||||||
}
|
rmSync(shardRoot, { recursive: true, force: true });
|
||||||
|
|
||||||
reports.push(readFileSync(lcovPath, 'utf8'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writeFileSync(join(coverageDir, 'lcov.info'), mergeLcovReports(reports), 'utf8');
|
|
||||||
rmSync(shardRoot, { recursive: true, force: true });
|
|
||||||
process.stdout.write(`Merged LCOV written to ${relative(repoRoot, join(coverageDir, 'lcov.info'))}\n`);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
|
|||||||
Reference in New Issue
Block a user