mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
fix(gates): потолок initial-графа с полосой вместо разового храповика
#429 снял `SUPPORT_LAZY_INITIAL_BASELINE_BYTES = 291046` — порог, привязанный к критерию приёмки #423, с пятнадцатью байтами запаса и сообщением про копирайт формы поддержки. Снять было правильно. Но снят он оказался ровно на том релизе, где сработал бы: beta.1 291 031 · снятый порог 291 046 · beta.2 291 069 Рост случился внутри той же беты, уже после снятия, и заметить его стало нечем: единственным сигналом остался `lowHeadroomWarning`, который горит постоянно — третий аудит подряд, и третий раз без реакции. Механизм по образцу ядер (#425): потолок 292 000 Б с полосой 2 000 Б, двусторонний. Рост выше потолка — отказ с числом и указанием, что делать (поднять потолок в том же коммите с объяснением либо вынести код в ленивый граф, #367). Падение ниже полосы — тоже отказ: незафиксированный выигрыш отыгрывается молча, именно так запас бюджета ушёл с 26 КБ до 8.3 КБ за сутки. Полоса, а не точное число, — по измерению, а не из осторожности. На beta.2 initial-чанк стал МЕНЬШЕ на 344 сырых байта и при этом на 40 байт больше в сжатом виде: gzip не монотонен по исходнику. Точный храповик по gzip краснел бы на коммитах, которые код сокращают, — та же лотерея, о которой предупреждает комментарий к бюджету 300 000. Потолок поставлен так, чтобы факт лежал ближе к середине полосы: 931 Б до отказа сверху, 1 069 Б снизу. Предупреждение о запасе стало погашаемым: `LOW_HEADROOM_ACKNOWLEDGED_CEILING` привязан к ЗНАЧЕНИЮ потолка, поэтому признание долга перестаёт покрывать ровно тогда, когда потолок поднимут. Сейчас `null` — не погашено, и текст говорит, чем гасится. Превышение самого бюджета признанием не гасится: там отказ, а не тревога. Свидетели. Девять мутаций, каждая краснит свой тест: снятие верхней стороны, снятие нижней, потолок выше бюджета, полоса 50 Б, признание поверх превышения бюджета, молчащее устаревшее признание, и две — про подключение: вызов потолка убран из main и отказ понижен до console.log. Последние две прошли молча на первой редакции тестов — статическая проверка «в main есть вызов» была бы циклическим доказательством, за которое #430 снял циклический тест гарда benchmark, поэтому добавлен прогон настоящего CLI в подставном дереве. Захардкоженный `lowHeadroomWarning(9058)` из #429 заменён на число из поставляемого манифеста: константа в тесте выглядела измерением, не будучи им. Мутант `initial-view-ceiling-unplugged` в реестре. Гейты: npm test 1819 tests, 1818 pass, 0 fail; node scripts/bundle-budget.mjs зелёный — 291 069 внутри полосы, запас до бюджета 8 931 Б. Issue: #438 User-Visible: no
This commit is contained in:
+101
-18
@@ -44,24 +44,57 @@ export const INITIAL_VIEW_GZIP_BUDGET = 300_000;
|
||||
export const LOW_HEADROOM_WARNING_BYTES = 15_000;
|
||||
|
||||
/**
|
||||
* Числового храповика здесь больше нет, и это решение, а не упущение (#429).
|
||||
* Потолок графа с полосой — работающий храповик (#438), не разовый (#429).
|
||||
*
|
||||
* До #429 функция бросала при `initialViewGzipBytes >= 291 046` — «граф не стал
|
||||
* больше, чем был на момент закрытия #423». На бете 1.71.0 запас до этого
|
||||
* порога составлял пятнадцать байт, на момент правки — сто четыре. Пятнадцать
|
||||
* байт gzip меньше одной строки локали: первый же посторонний коммит получил бы
|
||||
* красный CI с сообщением про копирайт формы поддержки, к которому не имеет
|
||||
* отношения.
|
||||
* История в двух шагах. #429 снял `SUPPORT_LAZY_INITIAL_BASELINE_BYTES = 291046`
|
||||
* — порог, привязанный к критерию приёмки #423, с пятнадцатью байтами запаса и
|
||||
* сообщением про копирайт формы поддержки. Снять было правильно: он обвинял бы
|
||||
* не ту задачу. Но снят он оказался ровно на том релизе, где сработал бы:
|
||||
*
|
||||
* Гейт, обвиняющий не ту задачу, — худший вид гейта: его выключают, не
|
||||
* разбираясь, и вместе с ним выключают проверку владения графом, которая как
|
||||
* раз долговечна. Поэтому число снято, а проверка владения осталась.
|
||||
* v1.71.0-beta.1 291 031 снятый порог 291 046 beta.2 291 069
|
||||
*
|
||||
* Что именно было снято по существу: «граф не вырос» — это критерий приёмки на
|
||||
* момент задачи, а не свойство продукта. Свойство продукта охраняет общий
|
||||
* бюджет (`INITIAL_VIEW_GZIP_BUDGET`) и предупреждение о запасе; они судят
|
||||
* размер целиком и не привязаны к чужому issue.
|
||||
* То есть рост произошёл внутри той же беты, уже после снятия, и заметить его
|
||||
* стало нечем: единственным оставшимся сигналом был `lowHeadroomWarning`, а он
|
||||
* горит постоянно — третий аудит подряд, и третий раз без реакции.
|
||||
*
|
||||
* Почему полоса, а не точное число. Метрика — gzip, и она не монотонна по
|
||||
* исходнику: на beta.2 initial-чанк стал МЕНЬШЕ на 344 сырых байта и при этом
|
||||
* на 40 байт больше в сжатом виде (переименования минификатора и контекст
|
||||
* сжатия). Точный храповик по gzip краснел бы на коммитах, которые код
|
||||
* сокращают, — та же лотерея, о которой предупреждает комментарий к бюджету
|
||||
* выше: красным станет последний пушнувший, а не тот, кто вырастил граф.
|
||||
*
|
||||
* Поэтому правило двустороннее и с полосой, как у ядер (#425): значение обязано
|
||||
* лежать в `[ceiling - band, ceiling]`. Рост выше потолка — отказ с числом:
|
||||
* поднимайте потолок в том же коммите с объяснением либо выносите код в
|
||||
* ленивый граф (#367). Падение ниже полосы — тоже отказ: незафиксированный
|
||||
* выигрыш отыгрывается обратно молча, и это ровно то, что случилось с запасом
|
||||
* бюджета (26 КБ → 8.3 КБ за сутки).
|
||||
*
|
||||
* Полоса 2 000 Б — примерно одна средняя фича в initial-графе и заметно больше
|
||||
* колебаний метрики от переименований (наблюдаемые единицы-десятки байт). До
|
||||
* стены 300 000 остаётся четыре явных шага вместо двухсот незаметных.
|
||||
*
|
||||
* Потолок поставлен так, чтобы измеренный факт лежал ближе к середине полосы:
|
||||
* 291 069 при потолке 292 000 — это 931 Б до отказа сверху и 1 069 Б снизу.
|
||||
* Иначе одна из сторон срабатывает на десятках байт, то есть на шуме: правило
|
||||
* должно требовать решения от РЕАЛЬНОГО изменения, а не от переименования.
|
||||
*/
|
||||
export const INITIAL_VIEW_GZIP_CEILING = 292_000;
|
||||
export const INITIAL_VIEW_CEILING_BAND = 2_000;
|
||||
|
||||
/**
|
||||
* До какого потолка предупреждение о запасе погашено владельцем.
|
||||
*
|
||||
* `null` — не погашено, и это текущее состояние: запас 8 931 Б при пороге
|
||||
* 15 000, долг живёт в #367. Предупреждение, которое нельзя погасить, читается
|
||||
* как выключенное, поэтому его можно погасить — одной строкой здесь, со
|
||||
* ссылкой на решение. Признание привязано к ЗНАЧЕНИЮ потолка: как только
|
||||
* потолок поднимут, оно перестаёт покрывать, и вопрос возвращается ровно
|
||||
* тогда, когда граф снова вырос.
|
||||
*/
|
||||
export const LOW_HEADROOM_ACKNOWLEDGED_CEILING = null;
|
||||
|
||||
export const SUPPORT_LAZY_MARKERS = [
|
||||
'Contact details (email/tg/WhatsApp), optional.',
|
||||
'Контакт для связи (email/tg/WhatsApp), необязательно.',
|
||||
@@ -95,15 +128,60 @@ export function assertSupportBundleOwnership(
|
||||
}
|
||||
}
|
||||
|
||||
/** Тревога о запасе: `null`, пока его хватает. */
|
||||
export function lowHeadroomWarning(headroom, threshold = LOW_HEADROOM_WARNING_BYTES) {
|
||||
/**
|
||||
* Потолок графа: `null`, пока значение внутри полосы.
|
||||
*
|
||||
* Возвращает находку, а не бросает: вызывающий решает, что с ней делать, и
|
||||
* тест может проверить обе стороны, не ловя исключение.
|
||||
*/
|
||||
export function initialViewCeilingViolation(bytes, {
|
||||
ceiling = INITIAL_VIEW_GZIP_CEILING,
|
||||
band = INITIAL_VIEW_CEILING_BAND,
|
||||
} = {}) {
|
||||
if (!Number.isFinite(bytes)) {
|
||||
return { kind: 'missing', text: 'initial View graph не измерен — потолок проверить нечем' };
|
||||
}
|
||||
if (bytes > ceiling) {
|
||||
return {
|
||||
kind: 'grew',
|
||||
over: bytes - ceiling,
|
||||
text: `initial View graph ${bytes} B gzip выше потолка ${ceiling} B на ${bytes - ceiling} B.`
|
||||
+ ' Поднимите потолок в этом же коммите, объяснив рост, либо вынесите код в ленивый'
|
||||
+ ' граф (#367). Молча расти этому графу больше нечем.',
|
||||
};
|
||||
}
|
||||
if (bytes < ceiling - band) {
|
||||
return {
|
||||
kind: 'shrank',
|
||||
under: ceiling - bytes,
|
||||
text: `initial View graph ${bytes} B gzip ниже потолка ${ceiling} B на ${ceiling - bytes} B`
|
||||
+ ` — больше полосы ${band} B. Опустите потолок: незафиксированный выигрыш граф`
|
||||
+ ' отыграет обратно, и это уже происходило (#367).',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Тревога о запасе: `null`, пока его хватает или пока долг признан. */
|
||||
export function lowHeadroomWarning(headroom, {
|
||||
threshold = LOW_HEADROOM_WARNING_BYTES,
|
||||
ceiling = INITIAL_VIEW_GZIP_CEILING,
|
||||
acknowledgedCeiling = LOW_HEADROOM_ACKNOWLEDGED_CEILING,
|
||||
} = {}) {
|
||||
if (!Number.isFinite(headroom) || headroom >= threshold) return null;
|
||||
if (headroom < 0) {
|
||||
return `бюджет превышен на ${-headroom} Б — гейт уже красный`;
|
||||
}
|
||||
// Превышение бюджета не гасится признанием: там уже отказ, а не тревога.
|
||||
if (Number.isFinite(acknowledgedCeiling) && ceiling <= acknowledgedCeiling) return null;
|
||||
const stale = Number.isFinite(acknowledgedCeiling)
|
||||
? ` Признание долга покрывает потолок до ${acknowledgedCeiling} Б, а он уже ${ceiling} Б —`
|
||||
+ ' граф вырос с тех пор, вопрос вернулся.'
|
||||
: ' Погасить можно решением владельца: LOW_HEADROOM_ACKNOWLEDGED_CEILING в'
|
||||
+ ' scripts/bundle-budget.mjs. Пока не погашено — это долг (#367).';
|
||||
return `запас бюджета ${headroom} Б, меньше порога ${threshold} Б:`
|
||||
+ ' следующая средняя фича упрётся в стену. Рекалибровка это не лечит —'
|
||||
+ ' смотрите ленивые графы (#367)';
|
||||
+ ' смотрите ленивые графы (#367).' + stale;
|
||||
}
|
||||
|
||||
export function assertBundleBudget(manifest, budget = INITIAL_VIEW_GZIP_BUDGET) {
|
||||
@@ -143,10 +221,13 @@ if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
|
||||
const manifest = JSON.parse(readFileSync(resolve('dist/houseplan-assets.json'), 'utf8'));
|
||||
const result = assertBundleBudget(manifest);
|
||||
assertSupportBundleOwnership(manifest);
|
||||
const ceiling = initialViewCeilingViolation(result.initialViewGzipBytes);
|
||||
if (ceiling) throw new Error(ceiling.text);
|
||||
const headroom = INITIAL_VIEW_GZIP_BUDGET - result.initialViewGzipBytes;
|
||||
const lines = [
|
||||
`initial View: ${result.initialViewGzipBytes} B gzip`
|
||||
+ ` (budget ${INITIAL_VIEW_GZIP_BUDGET} B, headroom ${headroom} B)`,
|
||||
+ ` (потолок ${INITIAL_VIEW_GZIP_CEILING} B ±${INITIAL_VIEW_CEILING_BAND},`
|
||||
+ ` budget ${INITIAL_VIEW_GZIP_BUDGET} B, headroom ${headroom} B)`,
|
||||
`lazy editor: ${result.lazyEditorGzipBytes} B gzip`,
|
||||
`lazy locale: ${result.lazyLocaleGzipBytes} B gzip`,
|
||||
];
|
||||
@@ -161,6 +242,8 @@ if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
|
||||
`| граф | gzip | бюджет | запас |`,
|
||||
`|---|---|---|---|`,
|
||||
`| initial View | ${result.initialViewGzipBytes} B | ${INITIAL_VIEW_GZIP_BUDGET} B | ${headroom} B |`,
|
||||
`| потолок (#438) | ${INITIAL_VIEW_GZIP_CEILING} B | полоса ${INITIAL_VIEW_CEILING_BAND} B |`
|
||||
+ ` ${INITIAL_VIEW_GZIP_CEILING - result.initialViewGzipBytes} B до потолка |`,
|
||||
...(warning ? ['', `> ${warning}`] : []),
|
||||
'',
|
||||
].join('\n'));
|
||||
|
||||
@@ -4997,6 +4997,19 @@ const MUTANT_DEFINITIONS = [
|
||||
replace: ' stable = True\n',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'initial-view-ceiling-unplugged',
|
||||
guard: 'node --test test/bundle-assets.test.mjs',
|
||||
because: 'the initial-View ceiling must be ENFORCED by the CLI, not merely declared: this '
|
||||
+ 'gate has been silently removable twice (#429 took the old ratchet out on the very '
|
||||
+ 'release where it would have fired, and the growth of #438 went unnoticed for a beta)',
|
||||
patches: [{
|
||||
file: 'scripts/bundle-budget.mjs',
|
||||
find: ' const ceiling = initialViewCeilingViolation(result.initialViewGzipBytes);\n'
|
||||
+ ' if (ceiling) throw new Error(ceiling.text);\n',
|
||||
replace: '',
|
||||
}],
|
||||
},
|
||||
{
|
||||
id: 'decor-oversize-hides-the-downscale-action',
|
||||
guard: 'node --test test/decor-image-upload.test.mjs',
|
||||
|
||||
+151
-6
@@ -1,5 +1,7 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import test from 'node:test';
|
||||
@@ -8,8 +10,11 @@ import {
|
||||
buildBundleManifest, buildFingerprintPlugin, editorRuntimeRetryUrlPlugin,
|
||||
} from '../scripts/bundle-manifest.mjs';
|
||||
import {
|
||||
INITIAL_VIEW_GZIP_BUDGET, LOW_HEADROOM_WARNING_BYTES,
|
||||
assertBundleBudget, assertSupportBundleOwnership, lowHeadroomWarning,
|
||||
INITIAL_VIEW_CEILING_BAND, INITIAL_VIEW_GZIP_BUDGET, INITIAL_VIEW_GZIP_CEILING,
|
||||
LOW_HEADROOM_ACKNOWLEDGED_CEILING, LOW_HEADROOM_WARNING_BYTES,
|
||||
SUPPORT_LAZY_MARKERS,
|
||||
assertBundleBudget, assertSupportBundleOwnership, initialViewCeilingViolation,
|
||||
lowHeadroomWarning,
|
||||
} from '../scripts/bundle-budget.mjs';
|
||||
import { compareBundleTrees, sha256Bytes, verifyBundleTree } from '../scripts/bundle-tree.mjs';
|
||||
import {
|
||||
@@ -290,13 +295,153 @@ test('#429 проверка владения не судит размер гра
|
||||
`размер ${initialViewGzipBytes} не должен влиять на проверку владения`,
|
||||
);
|
||||
}
|
||||
// Размер по-прежнему охраняется — но общим бюджетом, а не чужим номером.
|
||||
// Размер по-прежнему охраняется — но потолком и бюджетом, а не чужим
|
||||
// номером. Число берётся из поставляемого манифеста: захардкоженный запас
|
||||
// в тесте выглядел бы измерением, не будучи им (#438).
|
||||
const shipped = JSON.parse(
|
||||
readFileSync(new URL('../dist/houseplan-assets.json', import.meta.url), 'utf8'),
|
||||
).initialViewGzipBytes;
|
||||
assert.match(
|
||||
lowHeadroomWarning(9058) || '',
|
||||
/запас бюджета 9058 Б/,
|
||||
'предупреждение о запасе остаётся единственным честным сигналом о размере',
|
||||
lowHeadroomWarning(INITIAL_VIEW_GZIP_BUDGET - shipped) || '',
|
||||
new RegExp(`запас бюджета ${INITIAL_VIEW_GZIP_BUDGET - shipped} Б`),
|
||||
'предупреждение о запасе остаётся честным сигналом о размере',
|
||||
);
|
||||
} finally {
|
||||
rmSync(temp, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// --- #438: потолок графа с полосой ------------------------------------------
|
||||
|
||||
test('#438 поставляемый граф лежит внутри полосы потолка', () => {
|
||||
// Гейт живёт и здесь, а не только в `npm run bundle:budget`: манифест
|
||||
// закоммичен, значит проверка стоит ровно там, где её увидит любой прогон
|
||||
// тестов. Рост, который не заметили в бете.2, краснел бы на этом тесте.
|
||||
const manifest = JSON.parse(
|
||||
readFileSync(new URL('../dist/houseplan-assets.json', import.meta.url), 'utf8'),
|
||||
);
|
||||
const violation = initialViewCeilingViolation(manifest.initialViewGzipBytes);
|
||||
assert.equal(violation, null, violation?.text);
|
||||
});
|
||||
|
||||
test('#438 рост выше потолка — отказ с числом и с указанием, что делать', () => {
|
||||
const grew = initialViewCeilingViolation(292_400, { ceiling: 292_000, band: 2_000 });
|
||||
assert.equal(grew.kind, 'grew');
|
||||
assert.equal(grew.over, 400);
|
||||
assert.match(grew.text, /выше потолка 292000 B на 400 B/);
|
||||
assert.match(grew.text, /Поднимите потолок в этом же коммите/);
|
||||
assert.match(grew.text, /#367/, 'у отказа обязан быть выход, а не только запрет');
|
||||
// Ровно на потолке — ещё не рост: граница включительная, иначе гейт краснеет
|
||||
// на равенстве и разбираться идут не с графом, а с гейтом.
|
||||
assert.equal(initialViewCeilingViolation(292_000, { ceiling: 292_000 }), null);
|
||||
});
|
||||
|
||||
test('#438 падение ниже полосы требует опустить потолок', () => {
|
||||
// Вторая половина храповика, без которой он не храповик: выигрыш, который не
|
||||
// зафиксировали, отыгрывается обратно молча. Так запас бюджета ушёл с 26 КБ
|
||||
// до 8.3 КБ за сутки — каждая отдельная строка выглядела нормально.
|
||||
const shrank = initialViewCeilingViolation(289_500, { ceiling: 292_000, band: 2_000 });
|
||||
assert.equal(shrank.kind, 'shrank');
|
||||
assert.equal(shrank.under, 2_500);
|
||||
assert.match(shrank.text, /Опустите потолок/);
|
||||
assert.equal(initialViewCeilingViolation(290_000, { ceiling: 292_000, band: 2_000 }), null,
|
||||
'нижняя граница полосы тоже включительная');
|
||||
assert.equal(initialViewCeilingViolation(NaN).kind, 'missing');
|
||||
assert.equal(initialViewCeilingViolation(undefined).kind, 'missing');
|
||||
});
|
||||
|
||||
test('#438 полоса шире наблюдаемого шума метрики', () => {
|
||||
// gzip не монотонен по исходнику: на beta.2 initial-чанк стал меньше на 344
|
||||
// сырых байта и на 40 байт больше в сжатом виде. Полоса обязана быть заметно
|
||||
// шире таких колебаний, иначе гейт краснеет на коммитах, сокращающих код, —
|
||||
// и красным станет последний пушнувший, а не тот, кто вырастил граф.
|
||||
assert.ok(INITIAL_VIEW_CEILING_BAND >= 1_000,
|
||||
'полоса меньше килобайта превращает потолок в лотерею');
|
||||
// И потолок обязан оставаться под общим бюджетом: иначе он ничего не значит.
|
||||
assert.ok(INITIAL_VIEW_GZIP_CEILING < INITIAL_VIEW_GZIP_BUDGET);
|
||||
// Факт лежит не у края полосы: до отказа есть место в обе стороны.
|
||||
const shipped = JSON.parse(
|
||||
readFileSync(new URL('../dist/houseplan-assets.json', import.meta.url), 'utf8'),
|
||||
).initialViewGzipBytes;
|
||||
assert.ok(INITIAL_VIEW_GZIP_CEILING - shipped > 500, 'сверху меньше 500 Б — это шум');
|
||||
assert.ok(shipped - (INITIAL_VIEW_GZIP_CEILING - INITIAL_VIEW_CEILING_BAND) > 500,
|
||||
'снизу меньше 500 Б — гейт потребует опустить потолок из-за шума');
|
||||
});
|
||||
|
||||
test('#438 предупреждение о запасе можно погасить, и повышение потолка его возвращает', () => {
|
||||
const headroom = LOW_HEADROOM_WARNING_BYTES - 1;
|
||||
// Не погашено — горит и говорит, чем гасится. Это текущее состояние.
|
||||
assert.equal(LOW_HEADROOM_ACKNOWLEDGED_CEILING, null,
|
||||
'долг #367 пока не признан — состояние честное, а не забытое');
|
||||
assert.match(lowHeadroomWarning(headroom, { acknowledgedCeiling: null }),
|
||||
/Погасить можно решением владельца/);
|
||||
// Признано ровно для этого потолка — молчит.
|
||||
assert.equal(
|
||||
lowHeadroomWarning(headroom, { ceiling: 292_000, acknowledgedCeiling: 292_000 }), null,
|
||||
);
|
||||
// Потолок подняли — признание перестало покрывать, вопрос вернулся.
|
||||
assert.match(
|
||||
lowHeadroomWarning(headroom, { ceiling: 294_000, acknowledgedCeiling: 292_000 }),
|
||||
/покрывает потолок до 292000 Б, а он уже 294000 Б/,
|
||||
);
|
||||
// Превышение бюджета признанием не гасится: там уже отказ, а не тревога.
|
||||
assert.match(
|
||||
lowHeadroomWarning(-10, { ceiling: 292_000, acknowledgedCeiling: 292_000 }),
|
||||
/бюджет превышен на 10 Б/,
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Прогон настоящего CLI бюджета в подставном дереве (#438).
|
||||
*
|
||||
* Статическая проверка «в main вызывается initialViewCeilingViolation» была бы
|
||||
* тем самым циклическим доказательством, за которое #430 снял циклический тест
|
||||
* гарда benchmark. Поэтому здесь запуск: манифест кладётся на диск, скрипт
|
||||
* исполняется процессом, читается код возврата. Снятый из main вызов потолка
|
||||
* при этом краснеет — проверено (без этого теста мутация проходила молча).
|
||||
*/
|
||||
const runBudgetCli = (initialViewGzipBytes) => {
|
||||
const dir = mkdtempSync(join(tmpdir(), 'houseplan-budget-cli-'));
|
||||
try {
|
||||
mkdirSync(join(dir, 'dist'));
|
||||
writeFileSync(join(dir, 'dist/initial.js'), 'view graph without support copy');
|
||||
writeFileSync(join(dir, 'dist/editor.js'), SUPPORT_LAZY_MARKERS.join('\n'));
|
||||
writeFileSync(join(dir, 'dist/locale.js'), 'lazy locale dictionary');
|
||||
writeFileSync(join(dir, 'dist/houseplan-assets.json'), JSON.stringify({
|
||||
schema: 1,
|
||||
files: [],
|
||||
initialViewFiles: ['initial.js'],
|
||||
initialViewGzipBytes,
|
||||
lazyEditorFiles: ['editor.js'],
|
||||
lazyEditorGzipBytes: 1000,
|
||||
lazyLocaleFiles: ['locale.js'],
|
||||
lazyLocaleGzipBytes: 100,
|
||||
lazyOnboardingFiles: [],
|
||||
}));
|
||||
const script = fileURLToPath(new URL('../scripts/bundle-budget.mjs', import.meta.url));
|
||||
const run = spawnSync(process.execPath, [script], { cwd: dir, encoding: 'utf8' });
|
||||
return { status: run.status, output: `${run.stdout || ''}${run.stderr || ''}` };
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
};
|
||||
|
||||
test('#438 CLI действительно применяет потолок, а не только объявляет его', () => {
|
||||
const inside = runBudgetCli(INITIAL_VIEW_GZIP_CEILING - 500);
|
||||
assert.equal(inside.status, 0, inside.output);
|
||||
|
||||
const grew = runBudgetCli(INITIAL_VIEW_GZIP_CEILING + 1);
|
||||
assert.equal(grew.status, 1, grew.output);
|
||||
assert.match(grew.output, /выше потолка/);
|
||||
|
||||
const shrank = runBudgetCli(INITIAL_VIEW_GZIP_CEILING - INITIAL_VIEW_CEILING_BAND - 1);
|
||||
assert.equal(shrank.status, 1, shrank.output);
|
||||
assert.match(shrank.output, /Опустите потолок/);
|
||||
|
||||
// И общий бюджет остаётся внешней стеной: он выше потолка, значит красным
|
||||
// становится потолок, а не бюджет — но и бюджет обязан уметь падать.
|
||||
const overBudget = runBudgetCli(INITIAL_VIEW_GZIP_BUDGET + 1);
|
||||
assert.equal(overBudget.status, 1, overBudget.output);
|
||||
assert.match(overBudget.output, /exceeds 300000 B budget/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user