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
13 changes: 12 additions & 1 deletion scripts/__tests__/bash32-floor-wiring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { parse as parseYaml } from 'yaml';

import { selfTestCases, stripAnsi } from './helpers/child-verdict';

const ROOT = path.resolve(fileURLToPath(import.meta.url), '../../..');
const GATE = 'scripts/check-bash32-floor.mjs';

Expand Down Expand Up @@ -97,7 +99,16 @@ describe('check-bash32-floor is wired, not merely present', () => {

it('its self-test passes — the half that makes a green scan mean something', () => {
const out = execFileSync('node', [GATE, '--self-test'], { cwd: ROOT, encoding: 'utf8' });
expect(out).toMatch(/check-bash32-floor self-test: \d+ cases pass/);
// objectui#7897 — the COUNT, not the shape. `\d+ cases pass` is satisfied
// by `0 cases pass`, so the old spelling passed for a self-test whose case
// table had gone empty: the outcome it exists to refuse. `selfTestCases`
// also strips ANSI, the second belt for a child that starts colouring —
// that is the CI-only direction, and no repo gate colours today.
expect(stripAnsi(out)).toMatch(/check-bash32-floor self-test: \d+ cases pass/);
expect(
selfTestCases(out, 'check-bash32-floor'),
'a self-test that ran no cases is not a passing self-test',
).toBeGreaterThan(0);
});
});

Expand Down
74 changes: 67 additions & 7 deletions scripts/__tests__/check-control-bytes.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { execFileSync } from 'node:child_process';
import { execFileSync, spawnSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
Expand Down Expand Up @@ -286,6 +286,38 @@ describe('repo state — the gate is green on this tree', () => {
});
});

/**
* A content search, read on the stream grep actually writes its refusal to.
*
* objectui#7897. Measured on GNU grep 3.11 (the build this repo's containers
* carry, and the one this file's own header already cites): when grep declines
* a binary file it writes `grep: <path>: binary file matches` to **stderr**,
* prints nothing at all on stdout, and still exits **0**. `execFileSync`
* returns stdout ONLY — so the previous spelling here,
* `expect(out).not.toMatch(/binary file matches/)`, was matching against a
* stream that message can never reach. It could not fail, for any file, ever:
* a guard that reads as a pin while being satisfied by every outcome including
* the one it names. The whole pin was carried by the positive assertion beside
* it. Both halves are load-bearing now, and
* `the refusal this asserts against is a refusal grep really makes` below is
* the control that proves the negative half can go red.
*
* The two spellings grep has used for the refusal are both recognised: modern
* GNU grep prefixes `grep: <file>: `, older builds print `Binary file <file>
* matches` on stdout. Reading BOTH streams means this does not depend on which.
*/
function contentSearch(needle: string, file: string, cwd: string = repoRoot) {
const run = spawnSync('grep', ['-n', needle, file], { cwd, encoding: 'utf8' });
const both = `${run.stdout ?? ''}${run.stderr ?? ''}`;
return {
status: run.status,
stdout: run.stdout ?? '',
stderr: run.stderr ?? '',
/** grep's own refusal to search the file, on whichever stream it lands. */
refusedAsBinary: /^grep: .*: binary file matches$|^Binary file .* matches$/im.test(both),
};
}

describe('objectstack#5425 — the file that started this is readable again', () => {
const target = 'packages/app-shell/src/views/metadata-admin/inspectors/useDatasetFields.ts';

Expand All @@ -299,9 +331,36 @@ describe('objectstack#5425 — the file that started this is readable again', ()
// The regression this pins is not "the byte is gone", it is "grep can see
// the file". grep exits 0 and prints the line; before the fix it printed
// `binary file matches` and no line at all.
const out = execFileSync('grep', ['-n', 'includeKey', target], { cwd: repoRoot, encoding: 'utf8' });
expect(out).toMatch(/includeKey/);
expect(out).not.toMatch(/binary file matches/);
const found = contentSearch('includeKey', target);
expect(found.status, found.stderr).toBe(0);
expect(
found.refusedAsBinary,
'grep declined to search the file — the objectstack#5425 harm, back again',
).toBe(false);
expect(
found.stdout,
'a declined file yields an EMPTY stdout and exit 0, so the printed line is the real pin',
).toMatch(/^\d+:.*includeKey/m);
});

it('the refusal this asserts against is a refusal grep really makes', () => {
// The control. Without it `refusedAsBinary: false` above proves nothing —
// and the spelling it replaced was exactly that: it matched stdout for a
// message GNU grep writes on stderr, so it was false for every file on
// earth. Here grep is handed a file that IS binary and must decline it.
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'control-bytes-grep-'));
try {
const probe = path.join(dir, 'probe.ts');
// U+0000 written from its CODE POINT: a raw control byte in this source
// is precisely what the gate under test refuses.
fs.writeFileSync(probe, `const includeKey = 1;${String.fromCharCode(0)}\n`);
const declined = contentSearch('includeKey', probe, dir);
expect(declined.refusedAsBinary, 'grep must decline a NUL-bearing file').toBe(true);
expect(declined.stdout, 'and print no line at all — that is the search outage').toBe('');
expect(declined.status, 'while exiting 0, which is what makes the outage silent').toBe(0);
} finally {
fs.rmSync(dir, { recursive: true, force: true });
}
});
});

Expand Down Expand Up @@ -334,9 +393,10 @@ describe('objectstack#5450 — the four baselined files are clean', () => {
it.each(cleaned.filter((c) => c.grepFor).map((c) => [c.file, c.grepFor as string]))(
'%s is visible to a content search again',
(file, needle) => {
const out = execFileSync('grep', ['-n', needle, file], { cwd: repoRoot, encoding: 'utf8' });
expect(out).toMatch(new RegExp(needle));
expect(out).not.toMatch(/binary file matches/);
const found = contentSearch(needle, file);
expect(found.status, found.stderr).toBe(0);
expect(found.refusedAsBinary, `grep declined to search ${file}`).toBe(false);
expect(found.stdout).toMatch(new RegExp(`^\\d+:.*${needle}`, 'm'));
},
);

Expand Down
13 changes: 12 additions & 1 deletion scripts/__tests__/check-doc-fence-languages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
ROOT_PAGES as COMPONENT_ROOT_PAGES,
} from '../check-doc-component-types.mjs';

import { selfTestCases, stripAnsi } from './helpers/child-verdict';

const ROOT = path.resolve(fileURLToPath(import.meta.url), '../../..');
const GUARD = 'scripts/check-doc-fence-languages.mjs';
const WORKFLOW = 'doc-fence-languages.yml';
Expand Down Expand Up @@ -269,7 +271,16 @@ describe('check-doc-fence-languages is wired, not merely present', () => {

it('its self-test passes — the half that makes a green scan mean something', () => {
const out = execFileSync('node', [GUARD, '--self-test'], { cwd: ROOT, encoding: 'utf8' });
expect(out).toMatch(/check-doc-fence-languages self-test: \d+ cases pass/);
// objectui#7897 — the COUNT, not the shape. `\d+ cases pass` is satisfied
// by `0 cases pass`, so the old spelling passed for a self-test whose case
// table had gone empty: the outcome it exists to refuse. `selfTestCases`
// also strips ANSI, the second belt for a child that starts colouring —
// that is the CI-only direction, and no repo gate colours today.
expect(stripAnsi(out)).toMatch(/check-doc-fence-languages self-test: \d+ cases pass/);
expect(
selfTestCases(out, 'check-doc-fence-languages'),
'a self-test that ran no cases is not a passing self-test',
).toBeGreaterThan(0);
});

/**
Expand Down
13 changes: 12 additions & 1 deletion scripts/__tests__/check-governed-queue-guard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
governedPathsIn,
} from '../check-governed-queue-guard.mjs';

import { selfTestCases, stripAnsi } from './helpers/child-verdict';

const ROOT = path.resolve(fileURLToPath(import.meta.url), '../../..');
const GATE = 'scripts/check-governed-queue-guard.mjs';
const WORKFLOW = `.github/workflows/${CHECK_WORKFLOW}`;
Expand Down Expand Up @@ -123,7 +125,16 @@ describe('check-governed-queue-guard is wired, not merely present', () => {

it('its self-test passes — the half that makes a green run mean something', () => {
const out = execFileSync('node', [GATE, '--self-test'], { cwd: ROOT, encoding: 'utf8' });
expect(out).toMatch(/check-governed-queue-guard self-test: \d+ cases pass/);
// objectui#7897 — the COUNT, not the shape. `\d+ cases pass` is satisfied
// by `0 cases pass`, so the old spelling passed for a self-test whose case
// table had gone empty: the outcome it exists to refuse. `selfTestCases`
// also strips ANSI, the second belt for a child that starts colouring —
// that is the CI-only direction, and no repo gate colours today.
expect(stripAnsi(out)).toMatch(/check-governed-queue-guard self-test: \d+ cases pass/);
expect(
selfTestCases(out, 'check-governed-queue-guard'),
'a self-test that ran no cases is not a passing self-test',
).toBeGreaterThan(0);
});
});

Expand Down
13 changes: 12 additions & 1 deletion scripts/__tests__/check-half-states.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
summaryLine,
} from '../pm/check-half-states.mjs';

import { selfTestCases, stripAnsi } from './helpers/child-verdict';

/**
* objectui#5791 — the half-state patrol, PORTED from objectstack (PR #11294).
*
Expand Down Expand Up @@ -65,7 +67,16 @@ describe('check-half-states — the ported sweeper', () => {
encoding: 'utf8',
cwd: repoRoot,
});
expect(out).toMatch(/✓ check-half-states self-test: \d+ cases pass\./);
// objectui#7897 — the COUNT, not the shape. `\d+ cases pass` is satisfied
// by `0 cases pass`, so the old spelling passed for a self-test whose case
// table had gone empty: the outcome it exists to refuse. `selfTestCases`
// also strips ANSI, the second belt for a child that starts colouring —
// that is the CI-only direction, and no repo gate colours today.
expect(stripAnsi(out)).toMatch(/✓ check-half-states self-test: \d+ cases pass\./);
expect(
selfTestCases(out, 'check-half-states'),
'a self-test that ran no cases is not a passing self-test',
).toBeGreaterThan(0);
});

it('lives at the path the workflow invokes', () => {
Expand Down
15 changes: 13 additions & 2 deletions scripts/__tests__/check-pre-install-import-graph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
} from '../check-pre-install-import-graph.mjs';
import { REQUIRED_CONTEXTS } from '../dependabot-merge-gate.mjs';

import { selfTestCases, stripAnsi } from './helpers/child-verdict';

/**
* objectui#6148 — the gate for the property that lets a gate run pre-install.
*
Expand Down Expand Up @@ -343,7 +345,16 @@ describe('the gate is wired, not merely present', () => {
it('passes its own self-test', () => {
// A scan whose recogniser is broken reports a clean tree.
const out = execFileSync('node', [SCRIPT, '--self-test'], { cwd: repoRoot, encoding: 'utf8' });
expect(out).toContain('self-test:');
expect(out).toMatch(/^✓/);
// objectui#7897 — the COUNT, not the shape. `\d+ cases pass` is satisfied
// by `0 cases pass`, so the old spelling passed for a self-test whose case
// table had gone empty: the outcome it exists to refuse. `selfTestCases`
// also strips ANSI, the second belt for a child that starts colouring —
// that is the CI-only direction, and no repo gate colours today.
expect(stripAnsi(out)).toContain('self-test:');
expect(stripAnsi(out)).toMatch(/^✓/);
expect(
selfTestCases(out, 'check-pre-install-import-graph'),
'a self-test that ran no cases is not a passing self-test',
).toBeGreaterThan(0);
});
});
103 changes: 103 additions & 0 deletions scripts/__tests__/child-verdict.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { describe, expect, it } from 'vitest';

import { selfTestCases, stripAnsi, verdictCount } from './helpers/child-verdict';

/**
* objectui#7897 — the reader that the child-spawning pin tests in this directory
* share, pinned in the two directions it exists to close.
*
* Both directions are pinned against the OLD spelling as well as the new one.
* A reader that only demonstrated the new spelling working would leave the next
* person free to conclude the two are interchangeable — and they are not: that
* is the entire content of this module.
*/

/** ANSI escapes built from the code point; a raw control byte here is refused by `pnpm check:control-bytes`. */
const E = String.fromCharCode(27);

describe('stripAnsi — the colour CI adds', () => {
/**
* The exact bytes from objectui PR #7889's failing CI job (run 34003883330,
* job 101407488095), rebuilt from the escape's code point: under GitHub
* Actions a child vitest colours its summary, so `Tests ` and `1 failed` are
* separated by SGR sequences rather than by whitespace.
*/
const AS_CI_PRINTED = `${E}[2m Tests ${E}[22m ${E}[1m${E}[31m1 failed${E}[39m${E}[22m${E}[90m (1)${E}[39m`;

it('the historical defect reproduces: the raw bytes do NOT match the prose regex', () => {
expect(AS_CI_PRINTED, 'green locally, red only in CI -- the shape objectui#7897 sweeps').not.toMatch(
/Tests\s+1 failed/,
);
});

it('and the same bytes match once the SGR sequences are gone', () => {
expect(stripAnsi(AS_CI_PRINTED)).toMatch(/Tests\s+1 failed/);
expect(stripAnsi(AS_CI_PRINTED)).toBe(' Tests 1 failed (1)');
});

it('leaves output that carries no escape at all byte-identical', () => {
const plain = '✓ check-doc-fence-languages self-test: 26 cases pass.\n';
expect(stripAnsi(plain)).toBe(plain);
});
});

describe('selfTestCases — a count, not a shape', () => {
const REAL = '✓ check-bash32-floor self-test: 155 cases pass.\n';
/** `check-governed-queue-guard` prefixes `OK` rather than `✓`; the prefix is presentation. */
const OK_PREFIXED = 'OK check-governed-queue-guard self-test: 132 cases pass (the five ruled surfaces...).\n';

it('reads the number out of a real gate verdict, whatever the prefix', () => {
expect(selfTestCases(REAL, 'check-bash32-floor')).toBe(155);
expect(selfTestCases(OK_PREFIXED, 'check-governed-queue-guard')).toBe(132);
});

it('reads it through colour, so a gate that starts colouring does not turn every caller red in CI only', () => {
const coloured = `${E}[32m✓ check-entry-guard self-test: ${E}[1m63${E}[22m cases pass${E}[39m`;
expect(coloured, 'the raw bytes do not match -- the SGR sits inside the count').not.toMatch(
/check-entry-guard self-test: \d+ cases pass/,
);
expect(selfTestCases(coloured, 'check-entry-guard')).toBe(63);
});

/**
* ⭐ The non-equivalence pin. `\d+ cases pass` is satisfied by a self-test
* whose case table is EMPTY — it passes for the outcome it exists to refuse,
* and no CI run can catch that, because the assertion is green.
*/
it('the OLD spelling accepts an empty case table; the count refuses it', () => {
const vacuous = '✓ check-bash32-floor self-test: 0 cases pass.\n';
expect(vacuous, 'the old spelling: a pin satisfied by the absence of what it pins').toMatch(
/check-bash32-floor self-test: \d+ cases pass/,
);
expect(selfTestCases(vacuous, 'check-bash32-floor')).toBe(0);
// ...which is what every call site now asserts against:
expect(() => expect(selfTestCases(vacuous, 'check-bash32-floor')).toBeGreaterThan(0)).toThrow();
});

it('throws, naming the output, when the verdict is absent rather than reporting zero', () => {
expect(() => selfTestCases('the gate crashed before printing anything\n', 'check-bash32-floor')).toThrow(
/check-bash32-floor self-test case count/,
);
});

it('does not answer about one gate from another gate line', () => {
expect(() => selfTestCases(REAL, 'check-entry-guard')).toThrow();
});
});

describe('verdictCount — the generic reader', () => {
it('captures the digits the pattern names', () => {
const out = '✓ check-upstream-port-parity: 3 ported file(s) match objectstack-ai/objectstack@bf10debd5 modulo...';
expect(verdictCount(out, /(\d+) ported file\(s\) match/, 'ported file count')).toBe(3);
});

it('is not satisfied by a zero the un-captured spelling would accept', () => {
const empty = '✓ check-upstream-port-parity: 0 ported file(s) match objectstack-ai/objectstack@bf10debd5 modulo...';
expect(empty, 'the old spelling passes on an EMPTY pin').toMatch(/ported file\(s\) match/);
expect(verdictCount(empty, /(\d+) ported file\(s\) match/, 'ported file count')).toBe(0);
});

it('throws with the whole output when nothing matches', () => {
expect(() => verdictCount('nothing here\n', /(\d+) widgets/, 'widget count')).toThrow(/nothing here/);
});
});
13 changes: 12 additions & 1 deletion scripts/__tests__/entry-guard-wiring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { parse as parseYaml } from 'yaml';

import { selfTestCases, stripAnsi } from './helpers/child-verdict';

const ROOT = path.resolve(fileURLToPath(import.meta.url), '../../..');
const GATE = 'scripts/check-entry-guard.mjs';

Expand Down Expand Up @@ -97,6 +99,15 @@ describe('check-entry-guard is wired, not merely present', () => {

it('its self-test passes — the half that makes a green scan mean something', () => {
const out = execFileSync('node', [GATE, '--self-test'], { cwd: ROOT, encoding: 'utf8' });
expect(out).toMatch(/check-entry-guard self-test: \d+ cases pass/);
// objectui#7897 — the COUNT, not the shape. `\d+ cases pass` is satisfied
// by `0 cases pass`, so the old spelling passed for a self-test whose case
// table had gone empty: the outcome it exists to refuse. `selfTestCases`
// also strips ANSI, the second belt for a child that starts colouring —
// that is the CI-only direction, and no repo gate colours today.
expect(stripAnsi(out)).toMatch(/check-entry-guard self-test: \d+ cases pass/);
expect(
selfTestCases(out, 'check-entry-guard'),
'a self-test that ran no cases is not a passing self-test',
).toBeGreaterThan(0);
});
});
Loading
Loading