Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/fixes/6634-6634-testmasking-selfref.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- fix(ci): exclude check-test-masking.test.ts's own tautology fixtures from the diff-based test-masking gate and recognize `✗` in validate-release-green's failure-line detector (#6634)
27 changes: 24 additions & 3 deletions scripts/check/check-test-masking.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -319,13 +319,34 @@ export function partitionDeletedRenamed(nameStatusOutput) {
* Os campos de skip e extTaut são opcionais (default 0) para compatibilidade
* com chamadas legadas que só passam baseAsserts/headAsserts/baseTaut/headTaut.
*/
/**
* (#6634) `check-test-masking.test.ts` legitimately embeds tautology-pattern string
* literals (`assert.ok(true)`, `expect(true).toBe(true)`, `assert.equal(1,1)`) as
* FIXTURES to exercise `countBareTautologies()`/`scanBareTautologies()` (#6404). The
* diff-based tautology counters (`countTautologies()`/`countExtendedTautologies()`)
* are dumb regex scans of raw source text with no awareness that a literal sits
* inside a fixture string rather than real assertion code, so any new fixture line
* self-trips a HARD "new tautology" flag on the gate's own regression-test file.
* Mirrors the exclusion `scanBareTautologies()` already applies for the same reason.
*
* The exclusion covers the whole `check-test-masking*` gate self-test family — not
* just `check-test-masking.test.ts` but sibling regression files such as
* `check-test-masking-selfref-6634.test.ts`, which likewise embed tautology-pattern
* literals as fixtures/documentation to prove this gate's own behavior.
*/
const SELF_TEST_FIXTURE_RE = /(^|\/)check-test-masking(-[\w-]+)?\.test\.tsx?$/;
function isSelfTestFixtureFile(file) {
return SELF_TEST_FIXTURE_RE.test(file);
}

export function evaluateMasking(perFile, assertReductionAllowlist = new Set()) {
const flags = [];
for (const f of perFile) {
const baseSkips = f.baseSkips ?? 0;
const headSkips = f.headSkips ?? 0;
const baseExtTaut = f.baseExtTaut ?? 0;
const headExtTaut = f.headExtTaut ?? 0;
const isSelfTestFixture = isSelfTestFixtureFile(f.file);

// The net-assert-REDUCTION signal can be allowlisted per file when the reduction is a
// verified-legitimate refactor/field-removal (config/quality/test-masking-allowlist.json).
Expand All @@ -334,13 +355,13 @@ export function evaluateMasking(perFile, assertReductionAllowlist = new Set()) {
flags.push(
`${f.file}: asserts ${f.baseAsserts} → ${f.headAsserts} (REMOÇÃO de ${f.baseAsserts - f.headAsserts} — enfraquecimento?)`
);
if (f.headTaut > f.baseTaut)
if (!isSelfTestFixture && f.headTaut > f.baseTaut)
flags.push(`${f.file}: nova(s) ${f.headTaut - f.baseTaut} tautologia(s) assert.ok(true)`);
if (headSkips > baseSkips)
flags.push(
`${f.file}: ${headSkips - baseSkips} novo(s) .skip/.todo/.only (asserts silenciados sem remoção)`
);
if (headExtTaut > baseExtTaut)
if (!isSelfTestFixture && headExtTaut > baseExtTaut)
flags.push(
`${f.file}: nova(s) ${headExtTaut - baseExtTaut} tautologia(s) estendida(s) (expect(true).toBe(true) / assert.equal(1,1))`
);
Expand Down Expand Up @@ -374,7 +395,7 @@ export function scanBareTautologies(testFiles, readFile) {
const read = readFile || ((f) => fs.readFileSync(f, "utf8"));
const flags = [];
for (const file of testFiles || []) {
if (file.endsWith("check-test-masking.test.ts")) continue;
if (isSelfTestFixtureFile(file)) continue;
let src;
try {
src = read(file);
Expand Down
4 changes: 3 additions & 1 deletion scripts/quality/validate-release-green.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ export function firstFailureLine(out) {
.split("\n")
.map((l) => l.trim())
.filter(Boolean);
const hit = lines.find((l) => /✖|not ok|AssertionError|error TS|FAIL|Error:|REGRESS/i.test(l));
const hit = lines.find((l) =>
/✖|✗|not ok|AssertionError|error TS|FAIL|Error:|REGRESS/i.test(l)
);
return (hit || lines[lines.length - 1] || "failed").slice(0, 200);
}

Expand Down
84 changes: 84 additions & 0 deletions tests/unit/check-test-masking-selfref-6634.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Regression test for issue #6634 ("release/v3.8.47 branch not green — nightly
* release-green found HARD failures"). The nightly's "Test-masking
* (weakened-assert guard)" HARD failure was a SELF-REFERENTIAL false positive:
* tests/unit/check-test-masking.test.ts legitimately embeds tautology-pattern
* string literals (e.g. `expect(true).toBe(true);`, `assert.equal(1, 1);`) as
* FIXTURES to exercise countBareTautologies()/scanBareTautologies() — the same
* literal text that the diff-based subcheck (evaluateMasking(), fed by
* countTautologies()/countExtendedTautologies()) treated as "new tautologies in
* the file itself" because those counters are dumb regex scans of raw source
* text, blind to "this is inside a fixture string, not real assertion code".
*
* scanBareTautologies() already special-cases this exact file
* (`if (file.endsWith("check-test-masking.test.ts")) continue;` in
* scripts/check/check-test-masking.mjs) for precisely this reason — this test
* asserts evaluateMasking() now applies the same exclusion for its diff-based
* tautology counters, using the real base(origin/main)/head(HEAD) diff of
* tests/unit/check-test-masking.test.ts.
*/
Comment on lines +1 to +19

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The top-level comment block in this new test file contains literal tautology patterns (such as expect(true).toBe(true); and assert.equal(1, 1);). Because scanBareTautologies is a simple regex scan over all tracked test files, it will match these patterns in the comment and cause the absolute floor scan to fail on this file. Please modify the comment to avoid using the exact executable-like syntax of these tautologies (e.g., describe them in plain English or break up the strings).

Suggested change
/**
* Regression test for issue #6634 ("release/v3.8.47 branch not green — nightly
* release-green found HARD failures"). The nightly's "Test-masking
* (weakened-assert guard)" HARD failure was a SELF-REFERENTIAL false positive:
* tests/unit/check-test-masking.test.ts legitimately embeds tautology-pattern
* string literals (e.g. `expect(true).toBe(true);`, `assert.equal(1, 1);`) as
* FIXTURES to exercise countBareTautologies()/scanBareTautologies() — the same
* literal text that the diff-based subcheck (evaluateMasking(), fed by
* countTautologies()/countExtendedTautologies()) treated as "new tautologies in
* the file itself" because those counters are dumb regex scans of raw source
* text, blind to "this is inside a fixture string, not real assertion code".
*
* scanBareTautologies() already special-cases this exact file
* (`if (file.endsWith("check-test-masking.test.ts")) continue;` in
* scripts/check/check-test-masking.mjs) for precisely this reason — this test
* asserts evaluateMasking() now applies the same exclusion for its diff-based
* tautology counters, using the real base(origin/main)/head(HEAD) diff of
* tests/unit/check-test-masking.test.ts.
*/
/**
* Regression test for issue #6634 ("release/v3.8.47 branch not green — nightly
* release-green found HARD failures"). The nightly's "Test-masking
* (weakened-assert guard)" HARD failure was a SELF-REFERENTIAL false positive:
* tests/unit/check-test-masking.test.ts legitimately embeds tautology-pattern
* string literals (e.g. expect-true-toBe-true, assert-equal-1-1) as
* FIXTURES to exercise countBareTautologies()/scanBareTautologies() — the same
* literal text that the diff-based subcheck (evaluateMasking(), fed by
* countTautologies()/countExtendedTautologies()) treated as "new tautologies in
* the file itself" because those counters are dumb regex scans of raw source
* text, blind to "this is inside a fixture string, not real assertion code".
*
* scanBareTautologies() already special-cases this exact file
* (`if (file.endsWith("check-test-masking.test.ts")) continue;` in
* scripts/check/check-test-masking.mjs) for precisely this reason — this test
* asserts evaluateMasking() now applies the same exclusion for its diff-based
* tautology counters, using the real base(origin/main)/head(HEAD) diff of
* tests/unit/check-test-masking.test.ts.
*/

import test from "node:test";
import assert from "node:assert/strict";
import { execFileSync } from "node:child_process";

import {
countTautologies,
countExtendedTautologies,
evaluateMasking,
} from "../../scripts/check/check-test-masking.mjs";

const FILE = "tests/unit/check-test-masking.test.ts";

function git(args: string[]): string {
return execFileSync("git", args, { encoding: "utf8" });
}

test("#6634: check-test-masking.test.ts's own tautology fixtures must not self-flag as weakening", () => {
// origin/main predates the #6404 fixtures (countBareTautologies/scanBareTautologies
// tests) that legitimately embed tautology-pattern literals as string fixtures.
const baseSrc = git(["show", "origin/main:" + FILE]);
const headSrc = git(["show", "HEAD:" + FILE]);

const perFile = [
{
file: FILE,
baseAsserts: 0, // irrelevant to this assertion — only tautology counters matter
headAsserts: 0,
baseTaut: countTautologies(baseSrc),
headTaut: countTautologies(headSrc),
baseExtTaut: countExtendedTautologies(baseSrc),
headExtTaut: countExtendedTautologies(headSrc),
},
];

const flags = evaluateMasking(perFile, new Set());

assert.deepEqual(
flags,
[],
"check-test-masking.test.ts's own literal tautology fixtures (added for #6404) must be " +
"excluded from the diff-based weakening check the same way scanBareTautologies() already " +
"excludes this file from the absolute-floor scan — otherwise the gate's own regression " +
"test permanently self-flags as a HARD release-green failure whenever new fixtures are added."
);
});

test("#6634: unrelated files still get flagged for genuinely new tautologies (guard is file-scoped, not global)", () => {
const perFile = [
{
file: "tests/unit/some-other-file.test.ts",
baseAsserts: 5,
headAsserts: 5,
baseTaut: 0,
headTaut: 1,
baseExtTaut: 0,
headExtTaut: 1,
},
];

const flags = evaluateMasking(perFile, new Set());

assert.equal(flags.length, 2, "a non-fixture file must still trip both tautology signals");
assert.match(flags[0], /nova\(s\) 1 tautologia\(s\) assert\.ok\(true\)/);
assert.match(flags[1], /nova\(s\) 1 tautologia\(s\) estendida/);
});
15 changes: 15 additions & 0 deletions tests/unit/check-test-masking.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,21 @@ test("scanBareTautologies: excludes check-test-masking.test.ts itself", () => {
assert.deepEqual(scanBareTautologies(files, read), []);
});

test("scanBareTautologies: excludes sibling gate self-test files (#6634 selfref regression)", () => {
// The gate's own regression files (e.g. check-test-masking-selfref-6634.test.ts)
// embed tautology-pattern literals as fixtures/documentation — the family-wide
// exclusion must cover them too, not only check-test-masking.test.ts itself.
const files = ["tests/unit/check-test-masking-selfref-6634.test.ts"];
const read = () => `assert.equal(1, 1);`;
assert.deepEqual(scanBareTautologies(files, read), []);
});

test("scanBareTautologies: a non-family file with the pattern is still flagged (exclusion is scoped)", () => {
const files = ["tests/unit/some-unrelated.test.ts"];
const read = () => `assert.equal(1, 1);`;
assert.equal(scanBareTautologies(files, read).length, 1);
});

test("scanBareTautologies: skips unreadable files instead of throwing", () => {
const files = ["tests/unit/does-not-exist.test.ts"];
const read = () => {
Expand Down
Loading