diff --git a/requirements.txt b/requirements.txt index 9c5aa5d..b21729a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,4 +20,5 @@ uvicorn==0.54.0 sse-starlette==3.5.0 python-multipart==0.0.32 py-spy==0.4.2 -watchdog==6.0.0numpy>=2.0 # ядро Коди (harness/kodi_core.py): обучение учеников +watchdog==6.0.0 +numpy>=2.0 # ядро Коди (harness/kodi_core.py): обучение учеников diff --git a/tests/test_portal_mcp.py b/tests/test_portal_mcp.py index 6bb3228..80bab95 100644 --- a/tests/test_portal_mcp.py +++ b/tests/test_portal_mcp.py @@ -166,9 +166,18 @@ os.environ["SIMBIOT_PORTAL_MCP_TOKEN_FILE"] = "/tmp/my-token" check("paths: env TOKEN_FILE бьёт схему", _paths.portal_mcp_token_path() == "/tmp/my-token") del os.environ["SIMBIOT_PORTAL_MCP_TOKEN_FILE"] -check("paths: изолят → secrets/portal-mcp-token", - _paths.portal_mcp_token_path().endswith("secrets/portal-mcp-token"), - _paths.portal_mcp_token_path()) +# герметичность: поведение изолята не зависит от того, на каком инстансе +# запущен тест (legacy-хост без SIMBIOT_HOME иначе даёт ложный FAIL) — +# патчим _HOME/IS_ISOLATED как это делают остальные тесты схемы путей +_saved = (_paths._HOME, _paths.IS_ISOLATED) +_paths._HOME = "/tmp/simbiot-test-home" +_paths.IS_ISOLATED = True +try: + check("paths: изолят → secrets/portal-mcp-token", + _paths.portal_mcp_token_path().endswith("secrets/portal-mcp-token"), + _paths.portal_mcp_token_path()) +finally: + _paths._HOME, _paths.IS_ISOLATED = _saved print() print("FAILURES:", fails if fails else "нет — все проверки пройдены") diff --git a/tests/test_requirements_valid.py b/tests/test_requirements_valid.py new file mode 100644 index 0000000..579293c --- /dev/null +++ b/tests/test_requirements_valid.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +"""requirements.txt валиден для pip (t-261019001038-0eb7d7) — hermetic. + +Запуск: ./venv/bin/python tests/test_requirements_valid.py + +Регрессия: на origin/master строка watchdog==6.0.0numpy>=2.0 была склеена +без перевода строки — pip install -r requirements.txt падал на СВЕЖИХ +установках install.sh (curl install.sh | bash) с InvalidRequirement, +ломая канал дистрибуции харнеса. Проверяем через packaging.Requirement +(тот же парсер, что у pip): каждая строка файла — ровно одно валидное +требование, watchdog и numpy распознаны отдельными пакетами. +""" +import os +import sys + +sys.path.insert(0, "/root/simbiot-harness") + +from packaging.requirements import Requirement + +REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +REQ = os.path.join(REPO, "requirements.txt") + +fails = [] + + +def check(name, cond, extra=""): + print(("PASS" if cond else "FAIL"), name, ("| " + str(extra)) if not cond else "") + if not cond: + fails.append(name) + + +# все строки-требования парсятся packaging (тот же парсер, что у pip): +# склейка watchdog==6.0.0numpy>=2.0 дала бы InvalidRequirement +req_names = [] +for i, line in enumerate(open(REQ, encoding="utf-8"), 1): + s = line.split("#", 1)[0].strip() + if not s: + continue + try: + r = Requirement(s) + except Exception as e: + check("строка %d валидна (InvalidRequirement)" % i, False, repr(e)) + continue + req_names.append(r.name.lower()) +check("все строки requirements.txt валидны для pip", True) + +# ключевые пакеты распознаны как отдельные требования +for name in ("watchdog", "numpy"): + check("зависимость %r отдельной строкой" % name, name in req_names) + +# битая склейка действительно ловится парсером (self-check теста) +try: + Requirement("watchdog==6.0.0numpy>=2.0") + glued_raises = False +except Exception: + glued_raises = True +check("парсер отклоняет склейку watchdog==6.0.0numpy>=2.0", glued_raises) + +print() +if fails: + print("FAILED:", len(fails)) + sys.exit(1) +print("OK: requirements.txt валиден (watchdog и numpy отдельными строками)")