168 lines
10 KiB
JavaScript
168 lines
10 KiB
JavaScript
import { describe, it, expect } from 'vitest';
|
||
import { readFileSync } from 'node:fs';
|
||
import { join } from 'node:path';
|
||
import { freeGb, diskState, porogMesta, GB_NA_ODIN_UGOL } from './disk.mjs';
|
||
import { REPO_ROOT } from './paths.mjs';
|
||
import { tolkoKod } from './schyot-zovushchih.mjs';
|
||
|
||
describe('freeGb — замер живьём', () => {
|
||
it('на живой машине отдаёт положительное число', async () => {
|
||
const gb = await freeGb(process.cwd());
|
||
expect(typeof gb).toBe('number');
|
||
expect(gb).toBeGreaterThan(0);
|
||
});
|
||
|
||
it('каталога нет вовсе — не выдумываем число, отдаём null', async () => {
|
||
// 🪤 Ноль почти всегда сбой, а не правда о мире. Ноль здесь означал бы «места нет»,
|
||
// а правда другая: мы не смогли посмотреть.
|
||
expect(await freeGb('Z:\\такого-каталога-нет-никогда')).toBeNull();
|
||
});
|
||
});
|
||
|
||
describe('diskState — можно ли работать', () => {
|
||
it('места больше порога — работать можно', () => {
|
||
expect(diskState(50, 5)).toEqual({ ok: true, free_gb: 50, min_gb: 5, reason: null });
|
||
});
|
||
|
||
it('места ровно порог — уже нельзя: порог это черта, а не «можно до неё»', () => {
|
||
const s = diskState(5, 5);
|
||
expect(s.ok).toBe(false);
|
||
expect(s.reason).toMatch(/место/i);
|
||
});
|
||
|
||
it('места меньше порога — нельзя, и причина названа человеку понятно', () => {
|
||
const s = diskState(1.5, 5);
|
||
expect(s.ok).toBe(false);
|
||
expect(s.reason).toContain('1.5');
|
||
expect(s.reason).toContain('5');
|
||
});
|
||
|
||
it('замерить не удалось — считаем, что нельзя, а не «наверное хватит»', () => {
|
||
const s = diskState(null, 5);
|
||
expect(s.ok).toBe(false);
|
||
expect(s.free_gb).toBeNull();
|
||
expect(s.reason).toMatch(/замерить не удалось/i);
|
||
});
|
||
});
|
||
|
||
describe('diskState — ПОРОГА НЕ ДАЛИ (дыра 3)', () => {
|
||
// 🔴 Раньше правило читало «настройку потеряли» как «можно»: `50 <= undefined` — ложь,
|
||
// и место пропускало что угодно. Ни одна проверка этого не ловила: `diskState` во всех
|
||
// проверках звался с порогом-числом, случая «порога нет» не было ни одного.
|
||
// Вырежи ветку про порог в disk.mjs — покраснеет весь этот раздел.
|
||
it('порога нет вовсе — НЕЛЬЗЯ, а не «места полно»', () => {
|
||
const s = diskState(50, undefined);
|
||
expect(s.ok).toBe(false);
|
||
expect(s.min_gb).toBeNull();
|
||
expect(s.reason).toMatch(/порог/i);
|
||
});
|
||
|
||
it('порог пустой (null) — тоже нельзя', () => {
|
||
expect(diskState(50, null).ok).toBe(false);
|
||
});
|
||
|
||
it('порог не число (NaN, строка) — тоже нельзя', () => {
|
||
expect(diskState(50, NaN).ok).toBe(false);
|
||
expect(diskState(50, '5').ok).toBe(false);
|
||
expect(diskState(50, Infinity).ok).toBe(false);
|
||
});
|
||
|
||
it('в отказе названа сама потерянная настройка — владельцу есть куда смотреть', () => {
|
||
expect(diskState(50, undefined).reason).toContain('disk_free_min_gb');
|
||
});
|
||
|
||
it('и замерить не удалось, и порога нет — отказ, и число не выдумано', () => {
|
||
const s = diskState(null, undefined);
|
||
expect(s.ok).toBe(false);
|
||
expect(s.free_gb).toBeNull();
|
||
expect(s.min_gb).toBeNull();
|
||
});
|
||
|
||
it('порог ноль — это настоящий порог, а не «не задан»', () => {
|
||
// 🪤 Ноль легко спутать с «не дали». Здесь он законен: владелец вправе поставить 0,
|
||
// и тогда работает правило «ровно порог — уже нельзя».
|
||
expect(diskState(0, 0).ok).toBe(false);
|
||
expect(diskState(1, 0)).toEqual({ ok: true, free_gb: 1, min_gb: 0, reason: null });
|
||
});
|
||
});
|
||
|
||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
// 🔴🔴 ПОРОГ СЛЕП К ЧИСЛУ РАБОТНИКОВ (найдено 05.08.2026)
|
||
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||
describe('🔴 порог считается от числа работников, а не вписывается числом', () => {
|
||
it('нынешняя настройка затеи: семеро при запасе 5 — это 22.5 ГБ, а не 5', () => {
|
||
expect(porogMesta({ rabotnikov: 7, zapasGb: 5 }).gb).toBe(22.5);
|
||
});
|
||
|
||
it('🔴 ГЛАВНОЕ: шесть свободных гигабайтов при семерых — ночь НЕ трогается', () => {
|
||
// Ровно та беда, ради которой всё писалось. При плоском пороге 5 здесь было `ok: true`:
|
||
// ночь законно стартовала и упиралась в диск часа в три, когда на машине никого нет.
|
||
const s = diskState(6, porogMesta({ rabotnikov: 7, zapasGb: 5 }));
|
||
expect(s.ok).toBe(false);
|
||
expect(s.min_gb).toBe(22.5);
|
||
});
|
||
|
||
it('одному работнику того же места ХВАТАЕТ — порог не «просто больше», он считается', () => {
|
||
// 🪤 Сторож, который просто задрал число, покраснел бы и здесь. Порог обязан ехать
|
||
// за числом работников в ОБЕ стороны, иначе это не счёт, а другая плоская цифра.
|
||
expect(diskState(9, porogMesta({ rabotnikov: 1, zapasGb: 5 })).ok).toBe(true);
|
||
expect(diskState(6, porogMesta({ rabotnikov: 1, zapasGb: 5 })).ok).toBe(false);
|
||
});
|
||
|
||
it('в отказе сказано, ОТКУДА взялось число — иначе владелец снизит порог рукой', () => {
|
||
const s = diskState(6, porogMesta({ rabotnikov: 7, zapasGb: 5 }));
|
||
expect(s.reason).toContain('7 работников');
|
||
expect(s.reason).toContain(String(GB_NA_ODIN_UGOL));
|
||
expect(s.reason).toMatch(/разбудить владельца/);
|
||
});
|
||
|
||
it('цена угла замерена, а не выдумана: не меньше самого тяжёлого замеренного угла (2,42 ГБ)', () => {
|
||
// 🔴 Занизить эту цену — значит вернуть ту же дыру тихо. Замер 05.08.2026 своей рукой,
|
||
// и все числа замера — нижняя граница (прибор Windows молча пропускает длинные пути).
|
||
expect(GB_NA_ODIN_UGOL).toBeGreaterThanOrEqual(2.42);
|
||
});
|
||
|
||
it('числа работников не назвали — порога НЕТ, а не «наверное один»', () => {
|
||
expect(porogMesta({ rabotnikov: undefined, zapasGb: 5 })).toBeNull();
|
||
expect(porogMesta({ rabotnikov: 0, zapasGb: 5 })).toBeNull();
|
||
expect(porogMesta({ rabotnikov: 2.5, zapasGb: 5 })).toBeNull();
|
||
expect(porogMesta({ rabotnikov: '7', zapasGb: 5 })).toBeNull();
|
||
});
|
||
|
||
it('запаса не назвали — порога тоже нет: это потерянная настройка, а не ноль', () => {
|
||
expect(porogMesta({ rabotnikov: 7, zapasGb: undefined })).toBeNull();
|
||
expect(porogMesta({ rabotnikov: 7, zapasGb: null })).toBeNull();
|
||
});
|
||
|
||
it('порога нет — и место через diskState отказывает, называя обе настройки', () => {
|
||
const s = diskState(50, porogMesta({ rabotnikov: undefined, zapasGb: 5 }));
|
||
expect(s.ok).toBe(false);
|
||
expect(s.reason).toContain('disk_free_min_gb');
|
||
expect(s.reason).toContain('max_workers_per_task');
|
||
});
|
||
});
|
||
|
||
// ━━━ 🛡️ СТОРОЖ ЗОВУЩИХ: правило мертво, если зовущий его обходит ━━━
|
||
// 🔴 Именно это и было: ветка «порога не дали» стояла в disk.mjs, была покрыта проверками
|
||
// и НЕ РАБОТАЛА НИ РАЗУ — оба живых зовущих подставляли `?? 5`, и потерянная настройка
|
||
// молча становилась пятёркой. Проверка правила такого не ловит: она зовёт правило сама.
|
||
// 🪤 Примечания ВЫРЕЗАЮТСЯ ДО ПОИСКА, и не своим вторым способом, а тем же `tolkoKod`,
|
||
// каким это делает датчик зовущих. Поймано на себе: моё же примечание «прежнее было
|
||
// `cfg.disk_free_min_gb ?? 5`» покраснело как живой код. Это ровно та мина, о которой
|
||
// датчик зовущих предупреждает вслух, только вывернутая наизнанку.
|
||
describe('🛡️ живые зовущие места подают счёт, а не плоское число', () => {
|
||
const istochnik = imya => tolkoKod(readFileSync(join(REPO_ROOT, 'tools', 'night', imya), 'utf8'));
|
||
|
||
for (const imya of ['cli.mjs', 'supervisor.mjs']) {
|
||
it(`${imya} зовёт diskState с разбором porogMesta`, () => {
|
||
const text = istochnik(imya);
|
||
expect(text).toContain('porogMesta(');
|
||
expect(text).toMatch(/rabotnikov:\s*cfg\??\.?\??\.max_workers_per_task/);
|
||
});
|
||
|
||
it(`🔴 в ${imya} не осталось плоского умолчания «disk_free_min_gb ?? число»`, () => {
|
||
expect(istochnik(imya)).not.toMatch(/disk_free_min_gb\s*\?\?\s*\d/);
|
||
});
|
||
}
|
||
});
|