From 6e7e3111fb8479ef66601aee54331d7c527db32f Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Thu, 9 Jul 2026 23:41:04 -0400 Subject: [PATCH] =?UTF-8?q?test(#2126):=20fix=20#1259=20real-eslint=20CPU?= =?UTF-8?q?=20starvation=20=E2=80=94=20lint=20a=20non-type-aware=20.cjs=20?= =?UTF-8?q?clean=20target?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prohibition-enforcement real-runner tests linted src/clock.cts (a .cts) as their clean target. Under eslint.config.mjs's type-aware block for src/**/*.cts (recommendedTypeChecked + parserOptions.project: tsconfig.build.json), each eslint spawn loaded the WHOLE tsconfig.build.json program (~2s, CPU-heavy). The real-runner tests spawn eslint repeatedly; under --test-concurrency those full-program type-checks oversubscribed the bench CPU and blew the 60s subprocess bound -> fail-closed (intermittent, load-dependent — passed 24241/24241 in an earlier run, failed here). Root fix (not a retry/timeout bandaid; measured projectService = no faster since a single-file .cts lint still loads type info): add tests/_ff_lint_clean.cjs, a KNOWN-CLEAN lint-scoped .cjs companion to _ff_lint_violation.cjs, with a flat-config block enabling local/no-source-grep so the clean pass stays non-vacuous. Repoint the 6 src/clock.cts real-runner usages (5 targets + the FF-02 toothless violationFixture) at it. Each spawn is now ~0.8s non-type-aware (no whole-program load) — starvation removed. All 6 tests' semantics verified in-process (SF-01 greens; toothless/fail-closed stay unverified); full-repo `eslint .` green. Refs #2126, #1259 Co-Authored-By: Claude Opus 4.8 --- eslint.config.mjs | 13 +++++++++++++ tests/_ff_lint_clean.cjs | 19 +++++++++++++++++++ tests/prohibition-enforcement.test.cjs | 24 ++++++++++++------------ 3 files changed, 44 insertions(+), 12 deletions(-) create mode 100644 tests/_ff_lint_clean.cjs diff --git a/eslint.config.mjs b/eslint.config.mjs index a71f80e39..142c3eb67 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -399,4 +399,17 @@ export default tseslint.config( languageOptions: { sourceType: 'commonjs', globals: { ...globals.node } }, rules: { 'local/no-source-grep': 'error' }, }, + // ── #2126 lint-rule CLEAN fixture ─────────────────────────────────────────── + // `tests/_ff_lint_clean.cjs` is the KNOWN-CLEAN companion to the violation fixture: the + // prohibition-enforcement real-runner tests lint it as their non-vacuous "clean target" instead of + // a type-aware `src/**/*.cts` file, so each eslint spawn is ~0.8s (non-type-aware) not ~2s + // (whole-tsconfig-program load) — removing the CPU starvation that blew the 60s bound under + // --test-concurrency. Rule enabled (as error) so the pass is non-vacuous; the file is clean so it + // greens. PLAIN `.cjs`, kept OFF the `*.test.cjs` runner glob. (#2126) + { + files: ['tests/_ff_lint_clean.cjs'], + plugins: { local: localPlugin }, + languageOptions: { sourceType: 'commonjs', globals: { ...globals.node } }, + rules: { 'local/no-source-grep': 'error' }, + }, ); diff --git a/tests/_ff_lint_clean.cjs b/tests/_ff_lint_clean.cjs new file mode 100644 index 000000000..f6d8ab3a1 --- /dev/null +++ b/tests/_ff_lint_clean.cjs @@ -0,0 +1,19 @@ +// PERMANENT LOAD-BEARING FIXTURE for #1259 / #2126 — DO NOT delete or rename to `*.test.cjs`. +// +// A KNOWN-CLEAN, lint-scoped `.cjs` companion to `_ff_lint_violation.cjs`. It has NO +// `local/no-source-grep` violation, so the prohibition-enforcement real-runner tests can use it as +// the "clean target" for a NON-VACUOUS pass — the rule RUNS on it (enabled via the flat-config +// block below) and finds nothing. +// +// Why a `.cjs` and not `src/clock.cts`: linting a `src/**/*.cts` file is type-aware +// (`recommendedTypeChecked` + `parserOptions.project`), which loads the whole `tsconfig.build.json` +// program on every eslint spawn (~2s, CPU-heavy). The real-runner tests spawn eslint repeatedly and, +// under `--test-concurrency`, those full-program type-checks oversubscribe the bench CPU and blow the +// 60s subprocess bound (#2126). A plain `.cjs` is linted non-type-aware (~0.8s) — same coverage of +// the AST-only `no-source-grep` rule, no starvation. +// +// PLAIN `.cjs` (NOT `*.test.cjs`) on purpose — same reason as the violation fixture: keep it OFF the +// `node --test` runner glob so it is only ever linted, never executed. +'use strict'; + +module.exports = {}; diff --git a/tests/prohibition-enforcement.test.cjs b/tests/prohibition-enforcement.test.cjs index 9c2c31993..440351159 100644 --- a/tests/prohibition-enforcement.test.cjs +++ b/tests/prohibition-enforcement.test.cjs @@ -716,13 +716,13 @@ describe('prohibition-enforcement REAL runner end-to-end (#1259)', () => { const enforce = require(ENFORCEMENT_LIB); // Migrated to the SHIPPING prover (#1279): the default real prover lints the committed // `_ff_lint_violation.cjs` violationFixture (the rule fires -> fail-first proven) while the - // clean runCheck lints src/clock.cts (no violation -> non-vacuous pass). Both via real eslint. + // clean runCheck lints tests/_ff_lint_clean.cjs (no violation -> non-vacuous pass). Both via real eslint. const result = enforce.runProhibitionEnforcement( TEST_TIER, { kind: 'lint-rule', rule: 'local/no-source-grep', - target: 'src/clock.cts', + target: 'tests/_ff_lint_clean.cjs', failFirst: true, violationFixture: path.join('tests', '_ff_lint_violation.cjs'), }, @@ -759,13 +759,13 @@ describe('prohibition-enforcement REAL runner end-to-end (#1259)', () => { const enforce = require(ENFORCEMENT_LIB); // No injected runCheck/proveFailFirst: the default prover lints the committed // `_ff_lint_violation.cjs` (the rule fires -> fail-first proven) AND the default runner lints - // the clean `src/clock.cts` (no violation -> non-vacuous pass). BOTH directions via real eslint. + // the clean `tests/_ff_lint_clean.cjs` (no violation -> non-vacuous pass). BOTH directions via real eslint. const result = enforce.runProhibitionEnforcement( TEST_TIER, { kind: 'lint-rule', rule: 'local/no-source-grep', - target: 'src/clock.cts', + target: 'tests/_ff_lint_clean.cjs', violationFixture: path.join('tests', '_ff_lint_violation.cjs'), }, { cwd: process.cwd() }, @@ -780,7 +780,7 @@ describe('prohibition-enforcement REAL runner end-to-end (#1259)', () => { test('FULL producer (real): lint-rule hard-gates on a TOOTHLESS violationFixture (rule does not flag it) (FF-02 wrong-direction)', () => { const enforce = require(ENFORCEMENT_LIB); - // The "violation fixture" is a CLEAN in-tree file (src/clock.cts) the rule does NOT flag, so the + // The "violation fixture" is a CLEAN in-tree file (tests/_ff_lint_clean.cjs) the rule does NOT flag, so the // default prover cannot prove fail-first -> the producer must hard-gate (never green), even though // the clean target itself would pass the runner. A toothless guard is not a guard. const result = enforce.runProhibitionEnforcement( @@ -788,8 +788,8 @@ describe('prohibition-enforcement REAL runner end-to-end (#1259)', () => { { kind: 'lint-rule', rule: 'local/no-source-grep', - target: 'src/clock.cts', - violationFixture: 'src/clock.cts', + target: 'tests/_ff_lint_clean.cjs', + violationFixture: 'tests/_ff_lint_clean.cjs', }, { cwd: process.cwd() }, ); @@ -807,8 +807,8 @@ describe('prohibition-enforcement REAL runner end-to-end (#1259)', () => { { kind: 'lint-rule', rule: 'local/no-source-grep', - target: 'src/clock.cts', - violationFixture: 'src/clock.cts', + target: 'tests/_ff_lint_clean.cjs', + violationFixture: 'tests/_ff_lint_clean.cjs', }, { cwd: process.cwd(), mode }, ); @@ -1009,11 +1009,11 @@ describe('prohibition-enforcement defaultProveFailFirst REAL prover (#1279)', () test('lint-rule: a CLEAN violationFixture (rule does not flag) is NOT proven (FF-02 toothless direction)', () => { const enforce = require(ENFORCEMENT_LIB); - // src/clock.cts is a clean in-tree source with no no-source-grep violation. If a "violation + // tests/_ff_lint_clean.cjs is a clean in-tree source with no no-source-grep violation. If a "violation // fixture" does not actually trigger the rule, the rule is toothless on it → not a guard → not // proven → must hard-gate. const proof = enforce.defaultProveFailFirst( - { kind: 'lint-rule', rule: 'local/no-source-grep', target: 'src/clock.cts', violationFixture: 'src/clock.cts' }, + { kind: 'lint-rule', rule: 'local/no-source-grep', target: 'tests/_ff_lint_clean.cjs', violationFixture: 'tests/_ff_lint_clean.cjs' }, process.cwd(), ); assert.equal(proof.provenFailFirst, false, @@ -1023,7 +1023,7 @@ describe('prohibition-enforcement defaultProveFailFirst REAL prover (#1279)', () test('lint-rule: no violationFixture -> not proven (FF-05 fail-closed)', () => { const enforce = require(ENFORCEMENT_LIB); const proof = enforce.defaultProveFailFirst( - { kind: 'lint-rule', rule: 'local/no-source-grep', target: 'src/clock.cts' }, // no violationFixture + { kind: 'lint-rule', rule: 'local/no-source-grep', target: 'tests/_ff_lint_clean.cjs' }, // no violationFixture process.cwd(), ); assert.equal(proof.provenFailFirst, false, 'no violationFixture -> cannot prove -> hard-gate');