Files
msd-core/tests/eslint-glob-coverage.test.cjs
Jakub Zych a9a7a328e6 refactor: hard-fork GSD -> MSD (Make Software Done)
Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD
across contents and paths, upstream package/repo coordinates -> @golem15/msd-core
and golem15com/msd-core. Deep links into upstream history, sibling upstream
packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is.

Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line,
package/plugin identity, regenerated lockfile, install-tree fixtures, derived
registries and benchmark baseline; migration checksum baseline re-locked
(MSD keeps its own install state, so no install had applied the old sums);
sort-order and regex-escaped expectations in tests adjusted.
2026-10-06 01:47:40 +02:00

451 lines
18 KiB
JavaScript

'use strict';
/**
* eslint-glob-coverage.test.cjs
*
* Coverage for scripts/lint-eslint-glob-coverage.cjs (#3059): every unit row
* uses injected deps (no temp repos, no real subprocess, no chmod). The
* ANCHOR rows and the real-tree row drive the actual ESLint config against
* eslint.config.mjs to prove the discrimination logic holds outside the
* mocked seams.
*/
const { describe, test } = require('node:test');
const assert = require('node:assert/strict');
const path = require('path');
const fc = require('./helpers/fast-check-setup.cjs');
const {
checkGlobCoverage,
listTrackedSourceFiles,
loadAllowlist,
SOURCE_EXT_RE,
MIN_TRACKED_SOURCE_FILES,
} = require('../scripts/lint-eslint-glob-coverage.cjs');
const ROOT = path.join(__dirname, '..');
/** A resolveConfig stub that reports every file as covered by exactly one rule. */
function coveredResolver() {
return () => ({ ignored: false, ruleCount: 1 });
}
/** A resolveConfig stub driven by a Map<path, {ignored, ruleCount}>. */
function mapResolver(entries) {
return (relPath) => entries.get(relPath) || { ignored: false, ruleCount: 0 };
}
describe('eslint-glob-coverage: real tree (expected end state)', () => {
test('the current tree resolves clean against the guard (drives the guard to green)', async () => {
const result = await checkGlobCoverage();
assert.equal(
result.ok,
true,
`expected 0 violations, got ${result.violations.length}: ${JSON.stringify(result.violations, null, 2)}`
);
});
});
describe('eslint-glob-coverage: uncovered detection', () => {
test('an unmatched file is reported uncovered; a matched file is not', async () => {
const resolver = mapResolver(
new Map([
['scripts/covered.cjs', { ignored: false, ruleCount: 3 }],
['scripts/escaped.cjs', { ignored: false, ruleCount: 0 }],
])
);
const result = await checkGlobCoverage({
trackedFiles: ['scripts/covered.cjs', 'scripts/escaped.cjs'],
resolveConfig: resolver,
allowlist: [],
minTrackedFiles: 0,
});
assert.equal(result.ok, false);
assert.deepEqual(result.escapes, [{ path: 'scripts/escaped.cjs' }]);
const kinds = result.violations.map((v) => v.path);
assert.ok(!kinds.includes('scripts/covered.cjs'));
});
});
describe('eslint-glob-coverage: allowlist reason validation', () => {
test('non-empty reason passes; empty/whitespace reason and missing reason each fail', async () => {
const resolver = mapResolver(
new Map([
['a.cjs', { ignored: false, ruleCount: 0 }],
['b.cjs', { ignored: false, ruleCount: 0 }],
['c.cjs', { ignored: false, ruleCount: 0 }],
['d.cjs', { ignored: false, ruleCount: 0 }],
])
);
const result = await checkGlobCoverage({
trackedFiles: ['a.cjs', 'b.cjs', 'c.cjs', 'd.cjs'],
resolveConfig: resolver,
minTrackedFiles: 0,
allowlist: [
{ path: 'a.cjs', reason: 'a real reason' },
{ path: 'b.cjs', reason: '' },
{ path: 'c.cjs', reason: ' ' },
{ path: 'd.cjs' },
],
});
const byPath = (p) => result.violations.filter((v) => v.path === p).map((v) => v.kind);
assert.deepEqual(byPath('a.cjs'), []);
assert.deepEqual(byPath('b.cjs'), ['allowlist_empty_reason']);
assert.deepEqual(byPath('c.cjs'), ['allowlist_empty_reason']);
assert.deepEqual(byPath('d.cjs'), ['allowlist_missing_reason']);
});
});
describe('eslint-glob-coverage: allowlist ratchet', () => {
test('a stale entry (path now resolves to >=1 rule) reports allowlist_stale', async () => {
const resolver = mapResolver(new Map([['now-covered.cjs', { ignored: false, ruleCount: 2 }]]));
const result = await checkGlobCoverage({
trackedFiles: ['now-covered.cjs'],
resolveConfig: resolver,
minTrackedFiles: 0,
allowlist: [{ path: 'now-covered.cjs', reason: 'stale — should be pruned' }],
});
assert.equal(result.ok, false);
const kinds = result.violations.filter((v) => v.path === 'now-covered.cjs').map((v) => v.kind);
assert.deepEqual(kinds, ['allowlist_stale']);
});
test('an allowlist path not in the tracked list reports allowlist_missing_path', async () => {
const result = await checkGlobCoverage({
trackedFiles: ['tracked.cjs'],
resolveConfig: coveredResolver(),
minTrackedFiles: 0,
allowlist: [{ path: 'ghost.cjs', reason: 'not actually tracked' }],
});
assert.equal(result.ok, false);
const kinds = result.violations.filter((v) => v.path === 'ghost.cjs').map((v) => v.kind);
assert.deepEqual(kinds, ['allowlist_missing_path']);
});
test('a duplicate entry reports allowlist_duplicate', async () => {
const result = await checkGlobCoverage({
trackedFiles: ['dup.cjs'],
resolveConfig: mapResolver(new Map([['dup.cjs', { ignored: false, ruleCount: 0 }]])),
minTrackedFiles: 0,
allowlist: [
{ path: 'dup.cjs', reason: 'first' },
{ path: 'dup.cjs', reason: 'second' },
],
});
assert.equal(result.ok, false);
const kinds = result.violations.filter((v) => v.path === 'dup.cjs').map((v) => v.kind);
assert.ok(kinds.includes('allowlist_duplicate'));
});
});
describe('eslint-glob-coverage: tracked-count floor boundary', () => {
function fakeTrackedFiles(n) {
const files = [];
for (let i = 0; i < n; i += 1) files.push(`scripts/fake-${i}.cjs`);
return files;
}
test('499 tracked files (limit-1) fails tracked_count_below_floor', async () => {
const files = fakeTrackedFiles(MIN_TRACKED_SOURCE_FILES - 1);
const result = await checkGlobCoverage({
trackedFiles: files,
resolveConfig: coveredResolver(),
allowlist: [],
});
assert.equal(result.ok, false);
assert.ok(result.violations.some((v) => v.kind === 'tracked_count_below_floor'));
});
test('500 tracked files (limit) passes', async () => {
const files = fakeTrackedFiles(MIN_TRACKED_SOURCE_FILES);
const result = await checkGlobCoverage({
trackedFiles: files,
resolveConfig: coveredResolver(),
allowlist: [],
});
assert.equal(result.ok, true);
assert.ok(!result.violations.some((v) => v.kind === 'tracked_count_below_floor'));
});
test('501 tracked files (limit+1) passes', async () => {
const files = fakeTrackedFiles(MIN_TRACKED_SOURCE_FILES + 1);
const result = await checkGlobCoverage({
trackedFiles: files,
resolveConfig: coveredResolver(),
allowlist: [],
});
assert.equal(result.ok, true);
assert.ok(!result.violations.some((v) => v.kind === 'tracked_count_below_floor'));
});
});
describe('eslint-glob-coverage: never a vacuous clean', () => {
test('an empty git ls-files result never reports clean (fails the floor)', async () => {
const result = await checkGlobCoverage({
trackedFiles: [],
resolveConfig: coveredResolver(),
allowlist: [],
});
assert.equal(result.ok, false);
assert.ok(result.violations.some((v) => v.kind === 'tracked_count_below_floor'));
});
test('listTrackedSourceFiles itself returns ok:true with zero files on empty stdout (not a throw)', () => {
const result = listTrackedSourceFiles({ execFile: () => '' });
assert.deepEqual(result, { ok: true, files: [] });
});
});
describe('eslint-glob-coverage: container safe.directory (#3059)', () => {
test('git invocation passes -c safe.directory=* before ls-files, so a container-owned repo ("dubious ownership") never degrades the guard to git_failed', () => {
let capturedArgs;
const result = listTrackedSourceFiles({
execFile: (cmd, args) => {
capturedArgs = args;
return 'scripts/a.cjs\n';
},
});
assert.equal(result.ok, true);
assert.deepEqual(
capturedArgs.slice(0, 3),
['-c', 'safe.directory=*', 'ls-files'],
'the safe.directory override must precede the ls-files subcommand'
);
});
});
describe('eslint-glob-coverage: git failure handling', () => {
test('an injected git failure reports git_failed without throwing', async () => {
const result = await checkGlobCoverage({
trackedFiles: { ok: false, files: [], error: 'git ls-files: exit 128' },
resolveConfig: coveredResolver(),
allowlist: [],
});
assert.equal(result.ok, false);
assert.deepEqual(
result.violations.map((v) => v.kind),
['git_failed']
);
});
test('listTrackedSourceFiles degrades (does not throw) when execFile throws', () => {
const boom = () => {
throw new Error('git: command not found');
};
const result = listTrackedSourceFiles({ execFile: boom });
assert.equal(result.ok, false);
assert.equal(result.files.length, 0);
assert.match(result.error, /git: command not found/);
});
});
describe('eslint-glob-coverage: path normalization', () => {
test('a backslash path from git ls-files normalizes before allowlist matching', () => {
const result = listTrackedSourceFiles({
execFile: () => 'scripts\\weird-windows-path.cjs\nsrc\\normal.cts\n',
});
assert.equal(result.ok, true);
assert.deepEqual(result.files.sort(), ['scripts/weird-windows-path.cjs', 'src/normal.cts'].sort());
});
test('CRLF git ls-files output parses with no phantom paths', () => {
const result = listTrackedSourceFiles({
execFile: () => 'scripts/a.cjs\r\nscripts/b.cjs\r\n',
});
assert.equal(result.ok, true);
assert.deepEqual(result.files, ['scripts/a.cjs', 'scripts/b.cjs']);
});
});
describe('eslint-glob-coverage: listTrackedSourceFiles parser properties (fast-check)', () => {
// Generators for `git ls-files`-style path-like lines. Segments avoid '.',
// '\n', '\r' so extension/line boundaries stay unambiguous; the generators
// below deliberately introduce the shapes the parser must handle: source
// extensions, non-source extensions, no extension, backslashes, spaces,
// and empty lines.
const pathSegment = fc.stringMatching(/^[A-Za-z0-9_\- ]{1,12}$/);
const sourceExt = fc.constantFrom('cjs', 'cts', 'js', 'mjs');
const nonSourceExt = fc.constantFrom('md', 'json', 'txt');
const sourcePath = fc
.tuple(fc.array(pathSegment, { minLength: 1, maxLength: 3 }), sourceExt)
.map(([segs, ext]) => `${segs.join('/')}.${ext}`);
const nonSourcePath = fc
.tuple(fc.array(pathSegment, { minLength: 1, maxLength: 3 }), nonSourceExt)
.map(([segs, ext]) => `${segs.join('/')}.${ext}`);
const noExtPath = fc.array(pathSegment, { minLength: 1, maxLength: 3 }).map((segs) => segs.join('/'));
const backslashPath = fc
.tuple(fc.array(pathSegment, { minLength: 1, maxLength: 3 }), sourceExt)
.map(([segs, ext]) => `${segs.join('\\')}.${ext}`);
const spacedPath = fc
.tuple(fc.array(pathSegment, { minLength: 1, maxLength: 2 }), sourceExt)
.map(([segs, ext]) => `${segs.join(' / ')} file.${ext}`);
const emptyPath = fc.constant('');
const anyPathLine = fc.oneof(sourcePath, nonSourcePath, noExtPath, backslashPath, spacedPath, emptyPath);
const terminator = fc.constantFrom('\n', '\r\n');
// (1) Extension totality / soundness: every returned path ends in a
// source extension, is traceable back to a generated input line (modulo
// backslash->slash normalization), and no returned entry is empty. Each
// generated line carries its OWN terminator (mixing \n and \r\n within a
// single stdout blob), including the last line — this also exercises "a
// trailing terminator produces no phantom empty entry" on every run,
// since every line (including the last) is terminator-suffixed.
test('property: extension totality — every returned path is source-extensioned, traceable, non-empty', () => {
fc.assert(
fc.property(
fc.array(fc.tuple(anyPathLine, terminator), { maxLength: 20 }),
(lines) => {
const stdout = lines.map(([p, t]) => p + t).join('');
const result = listTrackedSourceFiles({ execFile: () => stdout });
assert.equal(result.ok, true);
const normalizedInputs = new Set(
lines.map(([p]) => p.replace(/\\/g, '/')).filter((p) => p.length > 0)
);
for (const file of result.files) {
assert.notEqual(file, '', 'no returned entry is the empty string');
assert.match(file, SOURCE_EXT_RE, `${file} must end in a source extension`);
assert.ok(normalizedInputs.has(file), `${file} must be traceable to a generated input line`);
}
// Exact-equality corollary: the parser's own filter/normalize
// pipeline applied to the same inputs must reproduce the result.
const expected = lines
.map(([p]) => p)
.filter((p) => p.length > 0)
.map((p) => p.replace(/\\/g, '/'))
.filter((p) => SOURCE_EXT_RE.test(p));
assert.deepEqual(result.files, expected);
}
)
);
});
// (2) Separator normalization is total: any generated path containing a
// backslash never survives into the output with a backslash intact.
test('property: separator normalization is total — no returned path contains a backslash', () => {
fc.assert(
fc.property(fc.array(backslashPath, { minLength: 0, maxLength: 15 }), (paths) => {
const stdout = paths.map((p) => `${p}\n`).join('');
const result = listTrackedSourceFiles({ execFile: () => stdout });
assert.equal(result.ok, true);
for (const file of result.files) {
assert.ok(!file.includes('\\'), `${file} must not contain a backslash`);
}
})
);
});
// (3) CRLF/LF equivalence: the same set of path lines, terminated
// uniformly with LF vs. uniformly with CRLF, must parse to the same
// result — the invariant the recurring CRLF defect class breaks.
test('property: CRLF and LF inputs describing the same path set yield the same result', () => {
fc.assert(
fc.property(fc.array(anyPathLine, { maxLength: 20 }), (lines) => {
const lfStdout = lines.map((p) => `${p}\n`).join('');
const crlfStdout = lines.map((p) => `${p}\r\n`).join('');
const lfResult = listTrackedSourceFiles({ execFile: () => lfStdout });
const crlfResult = listTrackedSourceFiles({ execFile: () => crlfStdout });
assert.equal(lfResult.ok, true);
assert.equal(crlfResult.ok, true);
assert.deepEqual(lfResult.files, crlfResult.files);
})
);
});
});
describe('eslint-glob-coverage: SOURCE_EXT_RE filtering', () => {
test('listTrackedSourceFiles filters to cjs/cts/js/mjs only', () => {
const result = listTrackedSourceFiles({
execFile: () =>
['scripts/a.cjs', 'src/b.cts', 'hooks/c.js', 'msd-core/d.mjs', 'README.md', 'docs/e.txt'].join('\n'),
});
assert.equal(result.ok, true);
assert.deepEqual(
result.files.sort(),
['msd-core/d.mjs', 'hooks/c.js', 'scripts/a.cjs', 'src/b.cts'].sort()
);
});
test('SOURCE_EXT_RE matches the four source extensions and rejects others', () => {
assert.equal(SOURCE_EXT_RE.test('foo.cjs'), true);
assert.equal(SOURCE_EXT_RE.test('foo.cts'), true);
assert.equal(SOURCE_EXT_RE.test('foo.js'), true);
assert.equal(SOURCE_EXT_RE.test('foo.mjs'), true);
assert.equal(SOURCE_EXT_RE.test('foo.ts'), false);
assert.equal(SOURCE_EXT_RE.test('foo.md'), false);
});
});
describe('eslint-glob-coverage: ANCHOR rows (real ESLint against real config)', () => {
// One shared resolver for the anchor rows: the anchors deliberately re-resolve
// the config themselves rather than importing the guard's resolveFileCoverage,
// so an anchor still fails if the guard's own resolution regresses.
const { ESLint } = require('eslint');
const anchorEslint = new ESLint({ cwd: ROOT });
async function rulesFor(relPath) {
const config = await anchorEslint.calculateConfigForFile(path.join(ROOT, relPath));
return config && config.rules;
}
test('tests/*.test.cjs has local/no-unbounded-spawn reachable', async () => {
const rules = await rulesFor('tests/eslint-glob-coverage.test.cjs');
assert.ok(rules, 'expected a resolved rule set');
assert.ok(
Object.prototype.hasOwnProperty.call(rules, 'local/no-unbounded-spawn'),
'expected local/no-unbounded-spawn to be reachable on a tests/*.test.cjs file'
);
});
test('scripts/*.cjs has n/no-path-concat reachable', async () => {
const rules = await rulesFor('scripts/lint-eslint-glob-coverage.cjs');
assert.ok(rules, 'expected a resolved rule set');
assert.ok(
Object.prototype.hasOwnProperty.call(rules, 'n/no-path-concat'),
'expected n/no-path-concat to be reachable on a scripts/*.cjs file'
);
});
test('src/*.cts has at least one @typescript-eslint/* rule reachable', async () => {
const rules = await rulesFor('src/milestone.cts');
assert.ok(rules, 'expected a resolved rule set');
const tsRules = Object.keys(rules).filter((r) => r.startsWith('@typescript-eslint/'));
assert.ok(tsRules.length > 0, 'expected at least one @typescript-eslint/* rule reachable on src/*.cts');
});
});
describe('eslint-glob-coverage: ignored vs. unmatched discrimination (real ESLint)', () => {
test('an ADR-457 tsc artifact under msd-core/bin/lib/*.cjs is not reported as uncovered', async () => {
const result = await checkGlobCoverage({
trackedFiles: ['msd-core/bin/lib/milestone.cjs'],
allowlist: [],
minTrackedFiles: 0,
});
assert.equal(result.ok, true);
assert.deepEqual(result.escapes, []);
});
test('bin/install.js is not reported as uncovered and is not in the real allowlist', async () => {
const result = await checkGlobCoverage({
trackedFiles: ['bin/install.js'],
allowlist: [],
minTrackedFiles: 0,
});
assert.equal(result.ok, true);
assert.deepEqual(result.escapes, []);
const realAllowlist = loadAllowlist();
assert.ok(!realAllowlist.some((entry) => entry.path === 'bin/install.js'));
});
});