test(#2665): reversion guards for all three round-4 fixes
None of the round-4 fixes had a test that fails on reversion: re-shipping the test-instrumentation chain passes #2858 (everything ships, so every require resolves), dropping the strict env from test.yml demotes the guard to report-only with nothing red, and the skillsHome derivation tests are enumeration-relative over declarations that are all empty today. One guard each, every one negative-controlled against its reverted fix (fails pre-fix, passes post-fix): - packaging-shipped-scripts-require-only-shipped.test.cjs asserts the four chain files are absent from the npm pack file list (reuses the tarball set the #2858 gate already resolves — no second npm pack). - live-config-guard.test.cjs asserts all three test jobs wire GSD_STRICT_LIVE_CONFIG_GUARD, matching the WHOLE expression anchored — a prefix match accepted both a Windows-silently-strict tail and a malformed one. - helpers-process-isolation.test.cjs cold-requires helpers.cjs in a child with sentinel skillsHome env vars injected into both enumerations, so the walk itself is under test rather than today's empty declarations.
This commit is contained in:
@@ -235,3 +235,57 @@ describe('#2665: TEST_ENV_BASE config-location coverage', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#2665 round 4: the skillsHome walk is reversion-sensitive', () => {
|
||||
// The coverage tests above are enumeration-relative, and skillsHome.env is
|
||||
// empty everywhere today — so reverting the skillsHome rungs from the
|
||||
// derivation leaves every one of them green (measured by this round's
|
||||
// pre-push adversarial review). This test closes that: it cold-requires
|
||||
// helpers.cjs in a child process after injecting sentinel skillsHome env
|
||||
// vars into BOTH enumerations (registry and non-registry), so the walk
|
||||
// itself is what is under test, not today's empty declarations.
|
||||
test('sentinel skillsHome vars flow into TEST_ENV_BASE on both rungs', () => {
|
||||
const { execFileSync } = require('node:child_process');
|
||||
const regPath = require.resolve('../gsd-core/bin/lib/capability-registry.cjs');
|
||||
const rhPath = require.resolve('../gsd-core/bin/lib/runtime-homes.cjs');
|
||||
const helpersPath = require.resolve('./helpers.cjs');
|
||||
|
||||
const script = `
|
||||
'use strict';
|
||||
const reg = require(${JSON.stringify(regPath)});
|
||||
const rh = require(${JSON.stringify(rhPath)});
|
||||
// Rung 1 (registry): give one runtime a skillsHome env var. kilo already
|
||||
// declares skillsHome (env: []); push a sentinel into whichever runtime
|
||||
// declares it, or graft one onto the first runtime if none does.
|
||||
const declaring = Object.values(reg.runtimes).find(
|
||||
(r) => r?.runtime?.configHome?.skillsHome,
|
||||
) ?? Object.values(reg.runtimes)[0];
|
||||
if (!declaring.runtime.configHome.skillsHome) {
|
||||
declaring.runtime.configHome.skillsHome = { kind: 'dot-home', name: '.x', env: [] };
|
||||
}
|
||||
declaring.runtime.configHome.skillsHome.env = ['GSD_TEST_SENTINEL_REGISTRY_SKILLS'];
|
||||
// Rung 2 (non-registry): graft a skillsHome onto the first descriptor.
|
||||
rh.NON_REGISTRY_CONFIG_HOME_DESCRIPTORS[0].skillsHome = {
|
||||
kind: 'dot-home', name: '.x', env: ['GSD_TEST_SENTINEL_NONREG_SKILLS'],
|
||||
};
|
||||
const { TEST_ENV_BASE } = require(${JSON.stringify(helpersPath)});
|
||||
const missing = [
|
||||
'GSD_TEST_SENTINEL_REGISTRY_SKILLS',
|
||||
'GSD_TEST_SENTINEL_NONREG_SKILLS',
|
||||
].filter((k) => TEST_ENV_BASE[k] !== '');
|
||||
if (missing.length > 0) {
|
||||
console.error('skillsHome walk missed: ' + missing.join(', '));
|
||||
process.exit(1);
|
||||
}
|
||||
process.exit(0);
|
||||
`;
|
||||
|
||||
const out = execFileSync(process.execPath, ['-e', script], {
|
||||
cwd: __dirname,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
timeout: 30_000,
|
||||
});
|
||||
void out; // exit 0 is the assertion; execFileSync throws on nonzero
|
||||
});
|
||||
});
|
||||
|
||||
@@ -410,3 +410,40 @@ describe('#2665: scan-budget truncation', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('#2665 round 4: CI wires the guard to strict mode', () => {
|
||||
// The reversion this guards: dropping GSD_STRICT_LIVE_CONFIG_GUARD from
|
||||
// test.yml silently demotes the guard back to report-only, and a future
|
||||
// leak of exactly the class #2665 closes prints a warning and CI stays
|
||||
// green. Windows lanes are deliberately report-only until the documented
|
||||
// pre-existing USERPROFILE leak class is swept (SEVERITY note in
|
||||
// scripts/live-config-guard.cjs) — so the assertion is per-OS, not global.
|
||||
test('all three test jobs set GSD_STRICT_LIVE_CONFIG_GUARD (Windows carved out)', () => {
|
||||
const yaml = require('js-yaml');
|
||||
const wf = yaml.load(
|
||||
fs.readFileSync(
|
||||
path.join(__dirname, '..', '.github', 'workflows', 'test.yml'),
|
||||
'utf8',
|
||||
),
|
||||
);
|
||||
|
||||
for (const job of ['test', 'test-full']) {
|
||||
const v = String(wf.jobs?.[job]?.env?.GSD_STRICT_LIVE_CONFIG_GUARD ?? '');
|
||||
assert.match(
|
||||
v,
|
||||
// The WHOLE expression, anchored — a prefix match accepted both
|
||||
// `&& '1' || '1'` (Windows silently strict) and a malformed tail
|
||||
// (found by this round's pre-push adversarial review).
|
||||
/^\$\{\{\s*matrix\.os\s*!=\s*'windows-latest'\s*&&\s*'1'\s*\|\|\s*''\s*\}\}$/,
|
||||
`jobs.${job}.env.GSD_STRICT_LIVE_CONFIG_GUARD must be strict on ` +
|
||||
`non-Windows lanes and empty on windows-latest; got: ${JSON.stringify(v)}`,
|
||||
);
|
||||
}
|
||||
|
||||
assert.strictEqual(
|
||||
String(wf.jobs?.['test-inert']?.env?.GSD_STRICT_LIVE_CONFIG_GUARD ?? ''),
|
||||
'1',
|
||||
'jobs.test-inert (ubuntu-only) must set GSD_STRICT_LIVE_CONFIG_GUARD: 1',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -135,6 +135,27 @@ describe('#2858 — shipped scripts require only shipped paths', () => {
|
||||
.sort();
|
||||
});
|
||||
|
||||
test('#2665: the test-instrumentation chain does not ship', () => {
|
||||
// These four are one closed require chain of test instrumentation
|
||||
// (run-tests -> live-config-guard, affected-tests-lib -> run-tests,
|
||||
// run-affected-tests -> affected-tests-lib). Excluding a strict subset
|
||||
// re-trips the shipped-requires-only-shipped gate above on whichever links
|
||||
// still ship, so the exclusion set and this assertion cover the chain.
|
||||
const TEST_INSTRUMENTATION = [
|
||||
'scripts/live-config-guard.cjs',
|
||||
'scripts/run-tests.cjs',
|
||||
'scripts/affected-tests-lib.cjs',
|
||||
'scripts/run-affected-tests.cjs',
|
||||
];
|
||||
for (const f of TEST_INSTRUMENTATION) {
|
||||
assert.ok(
|
||||
!shippedFiles.has(f),
|
||||
`${f} is test instrumentation and must not ship — restore its ` +
|
||||
"package.json files[] '!'-exclusion (and keep the whole chain excluded)",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('every shipped scripts/*.{cjs,js} is require-able from a shipped-only tree', () => {
|
||||
assert.ok(shippedScripts.length > 0, 'expected at least one shipped script');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user