ci: pin the HA harness and stop tests from editing sys.path

Два независимых хвоста из разбора #389, оба про то, что «зелёный» значит не
то, что читается.

#392. Оба места, где поднимается HA-харнесс, ставили зависимости без единой
версии. Python при этом закреплён на 3.13, а pytest-homeassistant-custom-
component с 0.13.348 требует 3.14 — резолвер молча уезжал на 0.13.316, а та
тянет homeassistant 2026.2.3. Интеграция полгода проверялась против
февральского HA, и состав окружения мог измениться без нашего коммита.
Версии закреплены в tests_backend/requirements.txt ровно те, что резолвились
30.08; шаг печатает установленное в лог. Это остановка дрейфа, а не
обновление: переход на 3.14 и свежий phcc — отдельная задача с отдельным
измерением.

#393. test_trails.py клал каталог пакета в sys.path при коллекции — на всю
сессию, включая HA-харнесс. Модули интеграции становились импортируемыми ещё
и как модули верхнего уровня, то есть один файл мог оказаться в sys.modules
дважды. Вставка при этом не работала ни на что: TrailBook берётся чтением
текста и exec среза, а не импортом. Убрана вместе с мёртвым spec.

Гейт статический, по исходникам: он ловит намерение, а рантайм поймал бы
последствие и только при сегодняшнем порядке тестов.

Issue: #392
User-Visible: no
This commit is contained in:
Matysh
2026-08-30 19:49:37 +03:00
parent 162c8a3bef
commit eb5aa2a0f8
6 changed files with 89 additions and 10 deletions
+28
View File
@@ -0,0 +1,28 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync, readdirSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
// Гигиена backend-тестов (#393): тест не имеет права оставлять после себя
// изменённую импорт-машину. В #389 это
// стоило пяти часов красного dev: пустышка в sys.modules пережила свой тест,
// Home Assistant перестал видеть у интеграции async_setup, и 85 тестов
// харнесса падали с голым `assert False`, не намекая на причину.
//
// Проверка статическая — по исходникам, без запуска. Это намеренно: рантайм
// ловит последствие и только на том порядке тестов, который сегодня сложился,
// а исходник ловит намерение.
const BACKEND = fileURLToPath(new URL('../tests_backend/', import.meta.url));
const files = () => readdirSync(BACKEND).filter((name) => name.endsWith('.py'));
const read = (name) => readFileSync(new URL(name, `file://${BACKEND}`), 'utf8');
test('backend-тесты не правят sys.path (#393)', () => {
const offenders = files().filter((name) => /^\s*sys\.path\b/m.test(read(name)));
assert.deepEqual(offenders, [],
'sys.path, изменённый при коллекции, действует на всю сессию: модули пакета'
+ ' становятся импортируемыми ещё и как модули верхнего уровня, и один файл'
+ ' может оказаться в sys.modules дважды с разными объектами классов.'
+ ' Читать модуль без Home Assistant можно и без этого — см. test_trails.py:'
+ ' текст файла и exec нужного среза.');
});