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 packages/lint/src/lazy-deps.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
// test-budget: 20 — whole-package import: the in-process case imports the whole @objectstack/lint barrel cold and loads each lazy dep on its trigger, and the two built-dist probes each spawn node once to cold-load the dist and every lazy dep (typescript among them); slowest CI reading 13.91 s (run 40200279306, refresh PR #25960's window (hourly runs 40200279306 to 40272142430)) x 1.25, rounded up
//
// Boot-path contract: importing @objectstack/lint must NOT load its heavy,
// gate-only dependencies. The package sits on the kernel boot path, while each
Expand Down
84 changes: 55 additions & 29 deletions packages/lint/src/non-record-object-entry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,50 @@ const NON_RECORD_ENTRIES: readonly (readonly [string, unknown])[] = [
];

const stackWith = (junk: unknown): AnyRec => ({ objects: [junk, VALID_OBJECT] });
const validOnly: AnyRec = { objects: [VALID_OBJECT] };
const validOnly = (): AnyRec => ({ objects: [VALID_OBJECT] });

const findingCount = (stack: AnyRec): number =>
AUTHORING_RULES.reduce((n, rule) => n + rule.run(stack, {}).length, 0);
/** What one rule did with one stack: the message it threw, or how many findings it returned. */
type RuleOutcome = { readonly rule: string } & ({ readonly threw: string } | { readonly findings: number });

/**
* One sweep per distinct stack. Every rule is pure, `(stack) => Finding[]`, so
* running the table once over a stack and recording what each rule did answers
* both questions a case asks of that stack — did any rule throw, and how many
* findings did the rest return. The "throws" case and the "count" case of one
* stack read the same sweep, and a control stack shared by every junk shape is
* swept once. Each rule still gets a stack of its own, built fresh.
*/
const sweeps = new Map<string, readonly RuleOutcome[]>();
const sweep = (key: string, build: () => AnyRec): readonly RuleOutcome[] => {
let outcomes = sweeps.get(key);
if (!outcomes) {
outcomes = AUTHORING_RULES.map((rule): RuleOutcome => {
try {
return { rule: rule.name, findings: rule.run(build(), {}).length };
} catch (e) {
return { rule: rule.name, threw: (e as Error).message };
}
});
sweeps.set(key, outcomes);
}
return outcomes;
};

/** `rule: message` for every rule that threw. */
const throwsOf = (outcomes: readonly RuleOutcome[]): string[] =>
outcomes.flatMap((o) => ('threw' in o ? [`${o.rule}: ${o.threw}`] : []));

/**
* The finding count over every rule not in `skip`. A rule outside `skip` that
* threw fails the count with its own message rather than counting as zero, as
* the count did when it ran the rules itself.
*/
const countOf = (outcomes: readonly RuleOutcome[], skip: readonly string[] = []): number =>
outcomes.reduce((n, o) => {
if (skip.includes(o.rule)) return n;
if ('threw' in o) throw new Error(`${o.rule}: ${o.threw}`);
return n + o.findings;
}, 0);

describe('a non-record entry in `stack.objects` (#15552)', () => {
/**
Expand All @@ -103,23 +143,17 @@ describe('a non-record entry in `stack.objects` (#15552)', () => {
expect(AUTHORING_RULES.length).toBeGreaterThanOrEqual(42);
});

describe.each(NON_RECORD_ENTRIES)('with %s beside a valid object', (_label, junk) => {
describe.each(NON_RECORD_ENTRIES)('with %s beside a valid object', (label, junk) => {
const withJunk = (): readonly RuleOutcome[] => sweep(`objects · ${label}`, () => stackWith(junk));

it('no authoring rule throws', () => {
const threw: string[] = [];
for (const rule of AUTHORING_RULES) {
try {
rule.run(stackWith(junk), {});
} catch (e) {
threw.push(`${rule.name}: ${(e as Error).message}`);
}
}
expect(threw).toEqual([]);
expect(throwsOf(withJunk())).toEqual([]);
});

it('still judges the valid object standing beside it', () => {
const baseline = findingCount(validOnly);
const baseline = countOf(sweep('objects · valid only', validOnly));
expect(baseline).toBeGreaterThan(0);
expect(findingCount(stackWith(junk))).toBe(baseline);
expect(countOf(withJunk())).toBe(baseline);
});
});

Expand Down Expand Up @@ -575,19 +609,12 @@ describe('a non-record entry in any other stack collection (#15636)', () => {

describe.each(NON_RECORD_ENTRIES)('with %s', (shape, junk) => {
const key = `${collection.label} · ${shape}`;
const withJunk = (): readonly RuleOutcome[] => sweep(key, () => collection.stack(members(junk)));

it('throws out of no rule but the ones still filed as broken', () => {
const threw: string[] = [];
const detail: string[] = [];
for (const rule of AUTHORING_RULES) {
try {
rule.run(collection.stack(members(junk)), {});
} catch (e) {
threw.push(rule.name);
detail.push(`${rule.name}: ${(e as Error).message}`);
}
}
expect(threw.sort(), detail.join(' | ')).toEqual([...(RESIDUAL_THROWS[key] ?? [])].sort());
const outcomes = withJunk();
const threw = outcomes.flatMap((o) => ('threw' in o ? [o.rule] : []));
expect(threw.sort(), throwsOf(outcomes).join(' | ')).toEqual([...(RESIDUAL_THROWS[key] ?? [])].sort());
});

it('invents no finding about the entry no author wrote', () => {
Expand All @@ -596,9 +623,8 @@ describe('a non-record entry in any other stack collection (#15636)', () => {
// folding that into the population count would let a crash read as
// "reported nothing", which is the confusion this file exists to end.
const skip = RESIDUAL_THROWS[key] ?? [];
const count = (stack: AnyRec): number =>
AUTHORING_RULES.reduce((n, rule) => (skip.includes(rule.name) ? n : n + rule.run(stack, {}).length), 0);
expect(count(collection.stack(members(junk))) - count(control())).toBe(RESIDUAL_INVENTED[key] ?? 0);
const controlOutcomes = sweep(`${collection.label} · control`, control);
expect(countOf(withJunk(), skip) - countOf(controlOutcomes, skip)).toBe(RESIDUAL_INVENTED[key] ?? 0);
});
});
});
Expand Down
1 change: 1 addition & 0 deletions packages/lint/src/runtime-lazy-deps.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
// test-budget: 20 — whole-package import: the in-process cases import the @objectstack/lint/runtime entry cold, which reaches the whole gate graph, and the built-dist probes each spawn node once to cold-load it; slowest CI reading 13.05 s (run 40200279306, refresh PR #25960's window (hourly runs 40200279306 to 40272142430)) x 1.25, rounded up
//
// The kernel boot-path contract for `@objectstack/lint/runtime` (#4463).
//
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
// test-budget: 140 — spawns the real producer once per single-cause fixture: every case runs the real schema generator (build-schemas.ts --check) in its own sandbox git tree, one fixture per cause; measured 2026-10-23 on its first CI reading after #25372 split build-schemas-check-mode.test.ts (run 39963105499, Test Core, head dce4c6406): 104.13 s x 1.25, rounded up; one reading only, its wall tracks the run's load, so re-measure once the window has more
// test-budget: 190 — spawns the real producer once per single-cause fixture: every case runs the real schema generator (build-schemas.ts --check) in its own sandbox git tree, one fixture per cause, and one generator run is about 7 s of JSON Schema emission and spec module load before any assertion; re-measured 2026-10-26 over the 10 executed readings in refresh PR #25960's window (hourly runs 40200279306 to 40272142430), slowest 144.97 s (run 40204927516) x 1.25, rounded up
//
// The in-tree anchor: it carries the deletion gate offline (#5235), and only
// --update-base moves it (#5358).
Expand Down
17 changes: 13 additions & 4 deletions packages/spec/scripts/build-schemas-check-mode.manifest.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
// test-budget: 180 — spawns the real producer once per single-cause fixture: every case runs the real schema generator (build-schemas.ts --check) in its own sandbox git tree, one fixture per cause; measured 2026-10-23 on its first CI reading after #25372 split build-schemas-check-mode.test.ts (run 39963105499, Test Core, head dce4c6406): 140.73 s x 1.25, rounded up; one reading only, its wall tracks the run's load, so re-measure once the window has more
// test-budget: 290 — spawns the real producer once per single-cause fixture: every case runs the real schema generator (build-schemas.ts --check) in its own sandbox git tree, one fixture per cause, and one generator run is about 7 s of JSON Schema emission and spec module load before any assertion; re-measured 2026-10-26 over the 10 executed readings in refresh PR #25960's window (hourly runs 40200279306 to 40272142430), slowest 226.64 s (run 40204927516) x 1.25, rounded up
//
// The manifest ratchet and its neighbours: a check reports, it does not write (#4711);
// an export that never published must be declared (#16431); the output clean spares a
Expand Down Expand Up @@ -557,7 +557,7 @@ describe('build-schemas.ts — the output clean spares a sibling generator (#537
describe('build-schemas.ts — the bundle counts the definitions it carries (#12588)', () => {
const OUT = () => path.join(sandbox, 'json-schema');

beforeEach(() => {
beforeAll(() => {
// A current, self-consistent tree, so the run exits 0 and these assertions
// are about the bundle rather than about some ratchet upstream of it.
seedManifest((s) => s);
Expand All @@ -566,11 +566,20 @@ describe('build-schemas.ts — the bundle counts the definitions it carries (#12
seedSurfaceBase(tip, (k) => k);
});

/**
* One generation for the block. Both cases read the same fixture and only
* READ what the run printed and wrote, so the first case to need it runs the
* generator and the other reads that run, the way
* `openapi-self-consistency.test.ts` shares its shipped run.
*/
let generation: ReturnType<typeof run> | undefined;
const generated = (): ReturnType<typeof run> => (generation ??= run([]));

it(
'writes x-schema-count equal to its own $defs size, and to the files on disk',
{ timeout: SPAWN_TIMEOUT_MS },
() => {
const { status, output } = run([]);
const { status, output } = generated();
expect(status).toBe(0);

const bundle = JSON.parse(
Expand Down Expand Up @@ -605,7 +614,7 @@ describe('build-schemas.ts — the bundle counts the definitions it carries (#12
'accounts for every emit the definition count does not include',
{ timeout: SPAWN_TIMEOUT_MS },
() => {
const { status, output } = run([]);
const { status, output } = generated();
expect(status).toBe(0);

const bundle = JSON.parse(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
// test-budget: 190 — spawns the real producer once per single-cause fixture: every case runs the real schema generator (build-schemas.ts --check) in its own sandbox git tree, one fixture per cause; measured 2026-10-23 on its first CI reading after #25372 split build-schemas-check-mode.test.ts (run 39963105499, Test Core, head dce4c6406): 147.5 s x 1.25, rounded up; one reading only, its wall tracks the run's load, so re-measure once the window has more
// test-budget: 310 — spawns the real producer once per single-cause fixture: every case runs the real schema generator (build-schemas.ts --check) in its own sandbox git tree, one fixture per cause, and one generator run is about 7 s of JSON Schema emission and spec module load before any assertion; re-measured 2026-10-26 over the 10 executed readings in refresh PR #25960's window (hourly runs 40200279306 to 40272142430), slowest 241.05 s (run 40204927516) x 1.25, rounded up
//
// RETIRED_KEYS_BY_MAJOR rows, judged by exact key: check (b) matches the retired key,
// not its leaf (#4659); a nested retirement row is judged, not ignored (#17969).
Expand Down
1 change: 1 addition & 0 deletions packages/spec/scripts/openapi-self-consistency.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
// test-budget: 20 — spawns the real producer once per single-cause fixture: the subprocess group runs the real build-openapi.ts once per distinct fixture, the shipped source and each mutation, and cases that share a fixture read its one run; slowest CI reading 10.48 s (run 40204927516, refresh PR #25960's window (hourly runs 40200279306 to 40272142430)) x 1.25, rounded up
//
// Pins the self-consistency gate on the generated OpenAPI document (#5168).
//
Expand Down
1 change: 1 addition & 0 deletions packages/spec/scripts/pure-schema-construction.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
// test-budget: 20 — spawns the real producer once per single-cause fixture: each build case runs the real tsup build (in-process, with the package's own main options and its esbuild plugin) once over the entry or fixture it pins, and the ./shared case bundles two consumers of that output; slowest CI reading 11.86 s (run 40214410982, refresh PR #25960's window (hourly runs 40200279306 to 40272142430)) x 1.25, rounded up
//
// The `pure-schema-construction` build plugin (`tsup.config.ts`) marks real
// calls only (#20686). Its earlier line-prefix rule also rewrote a marked name
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
// test-budget: 20 — compiles the package type graph once: the consumer program is one tsc run over the BUILT root entry's declarations, and the canary is one more tsc run over a one-line fixture; slowest CI reading 12.92 s (run 40214410982, refresh PR #25960's window (hourly runs 40200279306 to 40272142430)) x 1.25, rounded up

/**
* Root-entry nameability pin (#11350) — the consumer shape, verbatim.
Expand Down
2 changes: 1 addition & 1 deletion packages/spec/scripts/upgrade-guide-docs.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
// test-budget: 30 — spawns the real producer once per single-cause fixture: the last block spawns the real build-upgrade-guide.ts once per mode or usage error it pins (twice for the determinism pin), and the rest are in-process; slowest CI reading 19.7 s (hourly run 38798542648) x 1.25, rounded up
// test-budget: 40 — spawns the real producer once per single-cause fixture: the last block spawns the real build-upgrade-guide.ts once per mode or usage error it pins (twice for the determinism pin), and the rest are in-process; re-measured 2026-10-26 over the 8 executed readings in refresh PR #25960's window (hourly runs 40200279306 to 40272142430), slowest 30.07 s (run 40214410982) x 1.25, rounded up
//
// Pins the protocol upgrade guide's public address (#22449 B′, condition 2):
// one docs-site page per protocol major plus an index, generated at docs build
Expand Down
2 changes: 1 addition & 1 deletion packages/spec/src/eager-entry-import.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
// test-budget: 30 — whole-package import: every published entry of the package is imported first in its own fresh tsx child process, eagerly, plus two unflagged controls; slowest CI reading 19.9 s (hourly run 38657693085) x 1.25, rounded up
// test-budget: 60 — whole-package import: every published entry of the package is imported first in its own fresh tsx child process, eagerly, plus two unflagged controls; re-measured 2026-10-26 over the 8 executed readings in refresh PR #25960's window (hourly runs 40200279306 to 40272142430), slowest 41.61 s (run 40266979145) x 1.25, rounded up

/**
* Every published entry of `@objectstack/spec` imports cleanly as a process's
Expand Down
8 changes: 8 additions & 0 deletions packages/spec/src/ui/app.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// test-budget: 20 — compiles the package type graph once: the compiler-API case builds one ts program over app.zod.ts and the spec type graph it imports, and that one program is most of the file's time (the other cases are in-process parses); slowest CI reading 11.90 s (run 40204927516, refresh PR #25960's window (hourly runs 40200279306 to 40272142430)) x 1.25, rounded up
import { describe, it, expect } from 'vitest';
// Loaded here, at module top, not inside the compiler-API case below: a
// clocked window measures behaviour, never loading, and `typescript` alone is
Expand Down Expand Up @@ -1595,7 +1596,14 @@ describe('unknown keys are rejected, not stripped', () => {
// without a build.
it('the retired gates are gone from the TYPE, not only from the parse (compiler API)', () => {
const entry = resolve(dirname(fileURLToPath(import.meta.url)), 'app.zod.ts');
// The program reads what the type needs and nothing else: the ES2022 lib
// rather than the ES5 default's DOM declarations, and no ambient `@types`
// packages. Neither is an input of `app.zod.ts`, and guard #2 below still
// reads the full member list off the resolved type.
const program = ts.createProgram([entry], {
target: ts.ScriptTarget.ES2022,
lib: ['lib.es2022.d.ts'],
types: [],
module: ts.ModuleKind.ESNext,
moduleResolution: ts.ModuleResolutionKind.Bundler,
strict: true,
Expand Down
Loading