mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-28 19:01:34 +00:00
Validate ran three smoke shards, golden and performance_smoke on every push, check-docs went red on any src/** change until screenshots were re-captured, and a parallel bundle build made every second task branch fail to rebase. None of these gates ever failed at review time; they fail before betas. - `heavy` output in job `changes` (scripts/classify-changes.mjs): smoke, smoke_done, golden, performance_smoke run only for a head commit with a `Release:` trailer, `workflow_dispatch full=true` and pull requests. - nightly.yml dispatches Validate on dev with full=true every night. - check-docs `--screenshots=warn|strict`: freshness of the screenshot index warns on a plain push, errors on the candidate; everything else still errors. - publish-prerelease.yml and release.yml refuse a candidate without the `Release:` trailer and (prerelease) require fresh screenshots — a green Validate without the heavy jobs cannot pass for a release. - scripts/rebase-on-dev.mjs: rebase on origin/dev taking dev's copy of the committed bundle, rebuild with bundle:sync, amend; any other conflict aborts. - npm run gate:small: mandatory PROCESS §8 part in one parallel run. Issue: #479 User-Visible: no
111 lines
6.4 KiB
JavaScript
111 lines
6.4 KiB
JavaScript
import test from 'node:test';
|
||
import assert from 'node:assert/strict';
|
||
import { execFileSync } from 'node:child_process';
|
||
import { classifyChanges, classifyAll, formatOutputs, OUTPUTS, CLASSIFIERS } from '../scripts/classify-changes.mjs';
|
||
|
||
// #473 AC8: диффозависимость перф-смока доказана на самой функции
|
||
// классификации, которую исполняет job `changes`.
|
||
|
||
test('дифф по изометрии включает perf_iso и только его из перф-выходов (#473 AC8)', () => {
|
||
const out = classifyChanges(['src/iso-x.ts']);
|
||
assert.equal(out.perf_iso, 'true');
|
||
assert.equal(out.perf_interaction, 'false');
|
||
assert.equal(out.frontend, 'true');
|
||
});
|
||
|
||
test('дифф по живому пути и оркестраторам кадра включает perf_interaction (#473 AC8)', () => {
|
||
for (const file of ['src/live-x.ts', 'src/render-invalidation.ts',
|
||
'src/houseplan-render-lifecycle.ts', 'src/houseplan-card.ts']) {
|
||
const out = classifyChanges([file]);
|
||
assert.equal(out.perf_interaction, 'true', file);
|
||
assert.equal(out.perf_iso, 'false', file);
|
||
}
|
||
});
|
||
|
||
test('дифф по документации не включает ни одного перф-профиля (#473 AC8)', () => {
|
||
const out = classifyChanges(['docs/x.md']);
|
||
assert.equal(out.perf_iso, 'false');
|
||
assert.equal(out.perf_interaction, 'false');
|
||
assert.equal(out.frontend, 'false');
|
||
});
|
||
|
||
test('тесты и демо перф-профили не включают: кадр они не замедляют', () => {
|
||
const out = classifyChanges(['test/iso-scene-render.test.mjs', 'demo/benchmark_large_house.mjs', 'src/iso-x.test.ts.md']);
|
||
assert.equal(out.perf_iso, 'false');
|
||
assert.equal(out.perf_interaction, 'false');
|
||
});
|
||
|
||
test('прежние три выхода классифицируются как в inline-shell до выноса', () => {
|
||
assert.deepEqual(classifyChanges(['custom_components/houseplan/frontend_registration.py']),
|
||
{ frontend: 'false', backend: 'true', integration: 'true', perf_iso: 'false', perf_interaction: 'false' });
|
||
assert.deepEqual(classifyChanges(['custom_components/houseplan/frontend/houseplan-card.js']),
|
||
{ frontend: 'true', backend: 'false', integration: 'false', perf_iso: 'false', perf_interaction: 'false' });
|
||
assert.deepEqual(classifyChanges(['hacs.json', 'tsconfig.json']),
|
||
{ frontend: 'true', backend: 'false', integration: 'true', perf_iso: 'false', perf_interaction: 'false' });
|
||
assert.equal(classifyChanges(['scripts/support-relay/x.py']).backend, 'true');
|
||
assert.equal(classifyChanges(['']).frontend, 'false');
|
||
});
|
||
|
||
test('fallback --all выставляет каждый известный выход, включая перф-профили', () => {
|
||
const all = classifyAll();
|
||
assert.deepEqual(Object.keys(all), OUTPUTS);
|
||
assert.ok(OUTPUTS.every((name) => all[name] === 'true'));
|
||
assert.deepEqual(Object.keys(CLASSIFIERS), OUTPUTS);
|
||
});
|
||
|
||
test('CLI пишет формат $GITHUB_OUTPUT: stdin — список файлов, --all — всё true', () => {
|
||
const script = new URL('../scripts/classify-changes.mjs', import.meta.url).pathname;
|
||
const fromStdin = execFileSync('node', [script], { input: 'src/iso-x.ts\ndocs/x.md\n', encoding: 'utf8' });
|
||
assert.equal(fromStdin, formatOutputs(classifyChanges(['src/iso-x.ts', 'docs/x.md'])));
|
||
assert.match(fromStdin, /^perf_iso=true$/m);
|
||
assert.match(fromStdin, /^perf_interaction=false$/m);
|
||
const all = execFileSync('node', [script, '--all'], { input: '', encoding: 'utf8' });
|
||
assert.equal(all, OUTPUTS.map((name) => `${name}=true`).join('\n') + '\n');
|
||
});
|
||
|
||
// #479: тяжёлые job идут на кандидате беты, по кнопке, на PR и по расписанию —
|
||
// и НЕ идут на обычном пуше. Обе стороны доказаны на самой функции, которую
|
||
// исполняет шаг `heavy` job `changes`.
|
||
import { heavyGatesRequested, hasReleaseTrailer } from '../scripts/classify-changes.mjs';
|
||
|
||
test('обычный push в dev не запрашивает тяжёлые job (#479)', () => {
|
||
assert.equal(heavyGatesRequested({
|
||
eventName: 'push',
|
||
headMessage: 'fix: speed up wall-chain commits\n\nIssue: #461\nUser-Visible: yes\n',
|
||
}), false);
|
||
assert.equal(heavyGatesRequested({ eventName: 'push', headMessage: '' }), false);
|
||
assert.equal(heavyGatesRequested({}), false);
|
||
});
|
||
|
||
test('кандидат беты и релиза — трейлер Release: — запрашивает тяжёлые job (#479)', () => {
|
||
assert.equal(heavyGatesRequested({
|
||
eventName: 'push',
|
||
headMessage: 'build: prepare v1.73.0-beta.1 candidate\n\nIssue: #160\nUser-Visible: yes\nRelease: v1.73.0-beta.1\n',
|
||
}), true);
|
||
assert.equal(hasReleaseTrailer('Release v1.72.0\n\nRelease: v1.72.0'), true);
|
||
// Слово в теле — не трейлер: строка должна начинаться с `Release:`.
|
||
assert.equal(hasReleaseTrailer('docs: mention the Release: process in AGENTS'), false);
|
||
assert.equal(hasReleaseTrailer('Release: soon'), false);
|
||
});
|
||
|
||
test('workflow_dispatch запрашивает тяжёлые job только с full=true (#479)', () => {
|
||
assert.equal(heavyGatesRequested({ eventName: 'workflow_dispatch', fullInput: 'true' }), true);
|
||
assert.equal(heavyGatesRequested({ eventName: 'workflow_dispatch', fullInput: true }), true);
|
||
assert.equal(heavyGatesRequested({ eventName: 'workflow_dispatch', fullInput: 'false' }), false);
|
||
assert.equal(heavyGatesRequested({ eventName: 'workflow_dispatch', fullInput: '' }), false);
|
||
});
|
||
|
||
test('pull_request и schedule всегда запрашивают тяжёлые job (#479)', () => {
|
||
assert.equal(heavyGatesRequested({ eventName: 'pull_request', headMessage: 'x' }), true);
|
||
assert.equal(heavyGatesRequested({ eventName: 'schedule' }), true);
|
||
});
|
||
|
||
test('CLI --heavy читает событие и сообщение из окружения (#479)', () => {
|
||
const run = (env) => execFileSync(process.execPath, ['scripts/classify-changes.mjs', '--heavy'], {
|
||
encoding: 'utf8', env: { ...process.env, ...env },
|
||
}).trim();
|
||
assert.equal(run({ EVENT_NAME: 'push', HEAD_MESSAGE: 'fix: x\n\nIssue: #1\nUser-Visible: no' }), 'heavy=false');
|
||
assert.equal(run({ EVENT_NAME: 'push', HEAD_MESSAGE: 'x\n\nRelease: v1.2.3' }), 'heavy=true');
|
||
assert.equal(run({ EVENT_NAME: 'workflow_dispatch', FULL_INPUT: 'true', HEAD_MESSAGE: '' }), 'heavy=true');
|
||
});
|