mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
17b6ca36f8
* test(coverage): rename-only hunks owe no changed-line coverage Pass --find-renames=90% to the changed-line diff so rename detection no longer depends on the host diff.renames setting: a 100%-similarity move contributes no changed lines and an edited move contributes only the hunks that differ from its source. Threshold unchanged. * docs(agents): pure moves carry their tests unchanged Drops the stale src/daemon/handlers/session.ts over-budget bullet (242 lines on main) to stay under the AGENTS.md byte budget. * style: format coverage-changed run.ts * docs(agents): restore the session.ts over-budget rule
161 lines
5.9 KiB
TypeScript
161 lines
5.9 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import { execFileSync } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { afterEach, beforeEach, test } from 'node:test';
|
|
import { run } from './run.ts';
|
|
|
|
let repo: string;
|
|
|
|
function git(...args: string[]): void {
|
|
execFileSync('git', args, { cwd: repo, stdio: 'pipe' });
|
|
}
|
|
|
|
function write(rel: string, content: string): void {
|
|
const abs = path.join(repo, rel);
|
|
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
fs.writeFileSync(abs, content);
|
|
}
|
|
|
|
function writeLcov(records: string): void {
|
|
write('coverage/lcov.info', records);
|
|
}
|
|
|
|
// Redirect the module's stdout/stderr writers so the entrypoint's rendering
|
|
// runs without spamming the test reporter.
|
|
function capture(fn: () => number): { code: number; out: string } {
|
|
const chunks: string[] = [];
|
|
const original = process.stdout.write.bind(process.stdout);
|
|
const originalErr = process.stderr.write.bind(process.stderr);
|
|
process.stdout.write = ((chunk: string | Uint8Array): boolean => {
|
|
chunks.push(String(chunk));
|
|
return true;
|
|
}) as typeof process.stdout.write;
|
|
process.stderr.write = ((chunk: string | Uint8Array): boolean => {
|
|
chunks.push(String(chunk));
|
|
return true;
|
|
}) as typeof process.stderr.write;
|
|
try {
|
|
const code = fn();
|
|
return { code, out: chunks.join('') };
|
|
} finally {
|
|
process.stdout.write = original;
|
|
process.stderr.write = originalErr;
|
|
}
|
|
}
|
|
|
|
beforeEach(() => {
|
|
repo = fs.mkdtempSync(path.join(os.tmpdir(), 'cov-changed-'));
|
|
git('init', '-q');
|
|
git('config', 'user.email', 'test@example.com');
|
|
git('config', 'user.name', 'Test');
|
|
git('checkout', '-q', '-b', 'main');
|
|
write('src/base.ts', 'export const base = 1;\n');
|
|
git('add', '-A');
|
|
git('commit', '-q', '-m', 'base');
|
|
git('checkout', '-q', '-b', 'feature');
|
|
});
|
|
|
|
afterEach(() => {
|
|
fs.rmSync(repo, { recursive: true, force: true });
|
|
});
|
|
|
|
test('passes and prints n/a for a docs-only change without touching coverage', () => {
|
|
write('README.md', '# hello\nnew docs line\n');
|
|
git('add', '-A');
|
|
git('commit', '-q', '-m', 'docs');
|
|
writeLcov('SF:src/base.ts\nDA:1,1\nend_of_record\n');
|
|
const { code, out } = capture(() => run(['--base', 'main'], repo));
|
|
assert.equal(code, 0);
|
|
assert.match(out, /Changed-line coverage gate: PASS/);
|
|
assert.match(out, /0\/0 \(n\/a\)/);
|
|
});
|
|
|
|
test('reads a large stacked diff beyond the subprocess default buffer', () => {
|
|
write('README.md', '# large stack\n' + 'documentation line\n'.repeat(80_000));
|
|
git('add', '-A');
|
|
git('commit', '-q', '-m', 'large docs stack');
|
|
writeLcov('SF:src/base.ts\nDA:1,1\nend_of_record\n');
|
|
|
|
const { code, out } = capture(() => run(['--base', 'main'], repo));
|
|
|
|
assert.equal(code, 0);
|
|
assert.match(out, /Changed-line coverage gate: PASS/);
|
|
assert.match(out, /0\/0 \(n\/a\)/);
|
|
});
|
|
|
|
test('fails when a changed source line is uncovered and names that line', () => {
|
|
write('src/feature.ts', 'export const covered = 1;\nexport const uncovered = 2;\n');
|
|
git('add', '-A');
|
|
git('commit', '-q', '-m', 'feature');
|
|
writeLcov('SF:src/feature.ts\nDA:1,3\nDA:2,0\nend_of_record\n');
|
|
const { code, out } = capture(() => run(['--base', 'main'], repo));
|
|
assert.equal(code, 1);
|
|
assert.match(out, /Changed-line coverage gate: FAIL/);
|
|
assert.match(out, /`src\/feature\.ts`: 2/);
|
|
});
|
|
|
|
test('fails when a changed workspace package line is uncovered and names that line', () => {
|
|
write(
|
|
'packages/contracts/src/feature.ts',
|
|
'export const covered = 1;\nexport const uncovered = 2;\n',
|
|
);
|
|
git('add', '-A');
|
|
git('commit', '-q', '-m', 'package feature');
|
|
writeLcov('SF:packages/contracts/src/feature.ts\nDA:1,3\nDA:2,0\nend_of_record\n');
|
|
const { code, out } = capture(() => run(['--base', 'main'], repo));
|
|
assert.equal(code, 1);
|
|
assert.match(out, /Changed-line coverage gate: FAIL/);
|
|
assert.match(out, /`packages\/contracts\/src\/feature\.ts`: 2/);
|
|
});
|
|
|
|
test('waiver env keeps the job green, still reporting numbers to the job summary', () => {
|
|
write('src/feature.ts', 'export const covered = 1;\nexport const uncovered = 2;\n');
|
|
git('add', '-A');
|
|
git('commit', '-q', '-m', 'feature');
|
|
writeLcov('SF:src/feature.ts\nDA:1,3\nDA:2,0\nend_of_record\n');
|
|
const summaryPath = path.join(repo, 'summary.md');
|
|
const previous = {
|
|
waiver: process.env.AGENT_DEVICE_COVERAGE_WAIVER,
|
|
summary: process.env.GITHUB_STEP_SUMMARY,
|
|
};
|
|
process.env.AGENT_DEVICE_COVERAGE_WAIVER = 'true';
|
|
process.env.GITHUB_STEP_SUMMARY = summaryPath;
|
|
try {
|
|
const { code, out } = capture(() => run(['--base', 'main'], repo));
|
|
assert.equal(code, 0);
|
|
assert.match(out, /WAIVED/);
|
|
assert.match(out, /1\/2/);
|
|
const summary = fs.readFileSync(summaryPath, 'utf8');
|
|
assert.match(summary, /## Changed-line coverage gate/);
|
|
assert.match(summary, /Changed-branch coverage \(non-gating\)/);
|
|
assert.match(summary, /Changed executable lines excluded/);
|
|
} finally {
|
|
if (previous.waiver === undefined) delete process.env.AGENT_DEVICE_COVERAGE_WAIVER;
|
|
else process.env.AGENT_DEVICE_COVERAGE_WAIVER = previous.waiver;
|
|
if (previous.summary === undefined) delete process.env.GITHUB_STEP_SUMMARY;
|
|
else process.env.GITHUB_STEP_SUMMARY = previous.summary;
|
|
}
|
|
});
|
|
|
|
test('errors when the lcov report is missing rather than silently passing', () => {
|
|
write('src/feature.ts', 'export const x = 1;\n');
|
|
git('add', '-A');
|
|
git('commit', '-q', '-m', 'feature');
|
|
const { code, out } = capture(() => run(['--base', 'main'], repo));
|
|
assert.equal(code, 1);
|
|
assert.match(out, /no lcov report/);
|
|
});
|
|
|
|
test('a pure move owes nothing regardless of the host diff.renames setting', () => {
|
|
git('config', 'diff.renames', 'false');
|
|
git('mv', 'src/base.ts', 'src/moved.ts');
|
|
git('commit', '-q', '-m', 'move');
|
|
writeLcov('SF:src/moved.ts\nDA:1,0\nend_of_record\n');
|
|
const { code, out } = capture(() => run(['--base', 'main'], repo));
|
|
assert.equal(code, 0);
|
|
assert.match(out, /Changed-line coverage gate: PASS/);
|
|
assert.match(out, /0\/0 \(n\/a\)/);
|
|
});
|