""" 验证运行依赖清单、锁定版本与环境约束。 """ import tomllib from pathlib import Path from types import SimpleNamespace import pytest import app.runtime.dependencies.profile as dependencies from app.doctor import dependencies as dependency_doctor from app.foundation import environment from scripts import verify_runtime_profile def test_free_threaded_runtime_tracks_interpreter_build(monkeypatch): """ 根据解释器编译标记识别自由线程运行时,避免与当前 GIL 开关混淆。 """ monkeypatch.setattr( environment.sysconfig, "get_config_var", lambda name: 1 if name == "Py_GIL_DISABLED" else None, ) assert environment.is_free_threaded_runtime() is True def test_gil_status_tracks_current_interpreter_state(monkeypatch): """ 查询解释器当前 GIL 状态,支持运行时已禁用 GIL 的情况。 """ monkeypatch.setattr(environment.sys, "_is_gil_enabled", lambda: False) assert environment.is_gil_enabled() is False def test_runtime_dependency_group_tracks_interpreter_abi(monkeypatch): """ 按解释器 ABI 选择标准或自由线程运行依赖组。 """ monkeypatch.setattr(dependencies, "is_free_threaded_runtime", lambda: False) assert dependencies.runtime_dependency_group() == "runtime-standard" monkeypatch.setattr(dependencies, "is_free_threaded_runtime", lambda: True) assert dependencies.runtime_dependency_group() == "runtime-free-threaded" def test_runtime_requirements_include_project_and_active_group(tmp_path: Path, monkeypatch): """ 运行依赖同时包含项目公共依赖与当前 ABI 对应的依赖组。 """ project_file = tmp_path / "pyproject.toml" project_file.write_text( """ [project] dependencies = ["shared==1"] [dependency-groups] runtime-standard = ["standard==2"] runtime-free-threaded = ["free-threaded==3"] """, encoding="utf-8", ) monkeypatch.setattr( dependencies, "runtime_dependency_group", lambda: "runtime-free-threaded", ) assert list(dependencies.iter_runtime_requirement_strings(project_file)) == [ "shared==1", "free-threaded==3", ] assert list(dependencies.iter_runtime_profile_requirement_strings(project_file)) == [ "free-threaded==3", ] def test_runtime_profiles_share_gil_safe_crcmod_distribution(): """ 两种运行配置共享支持 GIL 策略的 CRC 实现,排除旧间接依赖。 """ project_file = Path(__file__).resolve().parents[1] / "pyproject.toml" with project_file.open("rb") as file: document = tomllib.load(file) assert "crcmod-plus==2.3.1" in document["project"]["dependencies"] groups = document["dependency-groups"] assert all( not requirement.lower().startswith("crcmod") for group in ("runtime-standard", "runtime-free-threaded") for requirement in groups[group] ) assert { "package": {"name": "oss2"}, "dependencies": ["crcmod"], } in document["tool"]["uv"]["exclude-dependencies"] def test_windows_runtime_profiles_isolate_pywin32_dependency(): """ 标准 Windows 保留 named-pipe 依赖,free-threaded profile 不解析 pywin32。 """ project_file = Path(__file__).resolve().parents[1] / "pyproject.toml" with project_file.open("rb") as file: document = tomllib.load(file) dependencies = document["project"]["dependencies"] groups = document["dependency-groups"] assert not any(requirement.lower().startswith("pympler") for requirement in dependencies) assert "pywin32==312 ; sys_platform == 'win32'" in groups["runtime-standard"] assert not any( requirement.lower().startswith("pywin32") for requirement in groups["runtime-free-threaded"] ) assert { "package": {"name": "docker"}, "dependencies": ["pywin32"], } in document["tool"]["uv"]["exclude-dependencies"] def test_standard_runtime_uses_shared_rust_text_capabilities(monkeypatch): """ 标准镜像的文本能力不得重新引入 profile 专属实现。 """ imported = [] moviepilot_rust = SimpleNamespace( is_available=lambda: True, jieba_cut=lambda _value: ["中文", "分词"], zhconv_fast=lambda value, _target: value, ) def import_module(name): imported.append(name) return moviepilot_rust if name == "moviepilot_rust" else SimpleNamespace() monkeypatch.setattr(dependency_doctor, "import_module", import_module) monkeypatch.setattr(dependency_doctor.sysconfig, "get_config_var", lambda _name: None) dependency_doctor.main() assert "zhconv_rs" not in imported def test_runtime_excluded_dependency_pairs_reads_uv_policy(tmp_path: Path): """ 运行时诊断应复用 uv 排除配置,不维护第二份包名特判。 """ project_file = tmp_path / "pyproject.toml" project_file.write_text( """ [tool.uv] exclude-dependencies = [ { package = { name = "Demo_Package" }, dependencies = ["Legacy-Dep>=1"] }, ] """, encoding="utf-8", ) assert dependencies.runtime_excluded_dependency_pairs(project_file) == { ("demo-package", "legacy-dep") } def test_runtime_profile_probe_accepts_only_declared_uv_exclusions(tmp_path: Path): """ CI 健康检查只能忽略 uv 配置中明确排除的依赖边。 """ project_file = tmp_path / "pyproject.toml" project_file.write_text( """ [tool.uv] exclude-dependencies = [ { package = { name = "docker" }, dependencies = ["pywin32"] }, ] """, encoding="utf-8", ) ignored = "The package `docker` requires `pywin32>=304`, but it's not installed" actionable = "The package `demo` requires `missing>=1`, but it's not installed" summary = "\n".join(( "Using Python 3.14.7 environment at: .venv", "Checked 189 packages in 3ms", "Found 2 incompatibilities", )) assert verify_runtime_profile._uv_health_errors( f"{summary}\n{ignored}\n{actionable}", project_file, ) == {actionable} def test_full_dependency_probe_rejects_psycopg_python_fallback(monkeypatch): """ V3t 构建不得把 psycopg 纯 Python 实现误认为可发布能力。 """ modules = { "moviepilot_rust": SimpleNamespace( is_available=lambda: True, jieba_cut=lambda _value: ["中文", "分词"], zhconv_fast=lambda value, _target: value, ), "crcmod.crcmod": SimpleNamespace(_usingExtension=True), "psycopg": SimpleNamespace(pq=SimpleNamespace(__impl__="python")), } monkeypatch.setattr( dependency_doctor, "import_module", lambda name: modules.get(name, SimpleNamespace()), ) monkeypatch.setattr( dependency_doctor.sysconfig, "get_config_var", lambda name: 1 if name == "Py_GIL_DISABLED" else None, ) monkeypatch.setattr(dependency_doctor.sys, "_is_gil_enabled", lambda: False) with pytest.raises(RuntimeError, match="psycopg C 实现不可用"): dependency_doctor.main(full=True)