Files
MoviePilot/tests/test_native_dependency_update_probe.py
DDSRem 2de976f6b3 Merge pull request #7050 from DDSRem-Dev/codex-comment-migration
docs: 统一全项目中文注释和结构化契约说明
2026-10-17 19:26:29 +08:00

224 lines
6.4 KiB
Python

"""
已加载原生依赖更新探针的纯逻辑测试。
"""
import pytest
from scripts.probe_native_dependency_update import (
TARGET_BUILD,
_write_manifest,
classify_online_attempt,
online_attempt_succeeded,
validate_runtime_target,
)
def _attempt(
*,
success: bool,
fresh: int | None,
loaded: int | None,
fresh_version: str | None = "2.0.0",
fresh_hash: str | None = "v2-hash",
) -> dict:
"""
构造分类器所需的最小探针结果。
"""
return {
"before": {"binary_sha256": "v1-hash"},
"install_success": success,
"fresh_process": {
"distribution_version": fresh_version,
"compiled_version": fresh,
"binary_sha256": fresh_hash,
},
"loaded_after": {"compiled_version": loaded},
}
def test_classify_online_attempt_distinguishes_failed_update_states():
"""
安装失败后必须区分原载荷未变和环境已发生变化。
"""
assert (
classify_online_attempt(
_attempt(
success=False,
fresh=100,
loaded=100,
fresh_version="1.0.0",
fresh_hash="v1-hash",
)
)
== "install_blocked_unchanged"
)
assert (
classify_online_attempt(_attempt(success=False, fresh=200, loaded=100))
== "install_failed_with_environment_change"
)
assert (
classify_online_attempt(
{
**_attempt(success=False, fresh=None, loaded=100),
"fresh_process": {"error": "ImportError: broken payload"},
}
)
== "install_failed_with_environment_change"
)
def test_classify_online_attempt_distinguishes_restart_boundaries():
"""
成功回执还要结合磁盘载荷和当前进程状态判定。
"""
assert (
classify_online_attempt(
_attempt(
success=True,
fresh=100,
loaded=100,
fresh_version="1.0.0",
fresh_hash="v1-hash",
)
)
== "reported_success_without_new_payload"
)
assert (
classify_online_attempt(_attempt(success=True, fresh=TARGET_BUILD, loaded=100))
== "restart_required_for_activation"
)
assert (
classify_online_attempt(_attempt(success=True, fresh=TARGET_BUILD, loaded=TARGET_BUILD))
== "online_activation_succeeded"
)
@pytest.mark.parametrize(
("classification", "expected"),
[
("restart_required_for_activation", True),
("online_activation_succeeded", True),
("install_blocked_unchanged", False),
("install_failed_with_environment_change", False),
("reported_success_without_new_payload", False),
("probe_error", False),
],
)
def test_online_attempt_succeeded_requires_target_payload(
classification: str,
expected: bool,
):
"""
恢复成功不能掩盖在线阶段未写入目标载荷。
"""
assert online_attempt_succeeded({"classification": classification}) is expected
def test_validate_runtime_target_checks_host_and_interpreter_architecture(monkeypatch):
"""
宿主架构与解释器 ABI 必须分别进入证据门禁。
"""
monkeypatch.setattr(
"scripts.probe_native_dependency_update.platform.system",
lambda: "Windows",
)
monkeypatch.setattr(
"scripts.probe_native_dependency_update.platform.machine",
lambda: "ARM64",
)
monkeypatch.setattr(
"scripts.probe_native_dependency_update.sysconfig.get_platform",
lambda: "win-amd64",
)
assert validate_runtime_target(
expected_system="Windows",
expected_machine="ARM64",
expected_python_platform_suffix="win-amd64",
) == {
"system": "Windows",
"machine": "ARM64",
"python_platform": "win-amd64",
}
@pytest.mark.parametrize(
("expected_system", "expected_machine", "expected_suffix", "message"),
[
("Linux", "ARM64", "win-amd64", "宿主系统不匹配"),
("Windows", "AMD64", "win-amd64", "宿主架构不匹配"),
("Windows", "ARM64", "win-arm64", "解释器目标平台不匹配"),
],
)
def test_validate_runtime_target_rejects_mislabeled_evidence(
monkeypatch,
expected_system: str,
expected_machine: str,
expected_suffix: str,
message: str,
):
"""
平台标签与真实运行目标不一致时不得继续生成成功证据。
"""
monkeypatch.setattr(
"scripts.probe_native_dependency_update.platform.system",
lambda: "Windows",
)
monkeypatch.setattr(
"scripts.probe_native_dependency_update.platform.machine",
lambda: "ARM64",
)
monkeypatch.setattr(
"scripts.probe_native_dependency_update.sysconfig.get_platform",
lambda: "win-amd64",
)
with pytest.raises(RuntimeError, match=message):
validate_runtime_target(
expected_system=expected_system,
expected_machine=expected_machine,
expected_python_platform_suffix=expected_suffix,
)
def test_classify_online_attempt_preserves_probe_failures():
"""
探针自身异常不能被误报成系统文件锁或安装结果。
"""
attempt = _attempt(success=False, fresh=None, loaded=100)
attempt["probe_error"] = "RuntimeError: fixture failed"
assert classify_online_attempt(attempt) == "probe_error"
def test_write_manifest_covers_legacy_and_modern_plugin_contracts(tmp_path):
"""
探针清单必须分别复现 requirements 与 PEP 621 入口。
"""
legacy = _write_manifest(
tmp_path,
kind="requirements",
distribution_version="2.0.0",
)
modern = _write_manifest(
tmp_path,
kind="pyproject",
distribution_version="2.0.0",
)
assert legacy.name == "requirements.txt"
assert "moviepilot-native-update-probe==2.0.0" in legacy.read_text(encoding="utf-8")
assert modern.name == "pyproject.toml"
assert 'dependencies = ["moviepilot-native-update-probe==2.0.0"]' in (modern.read_text(encoding="utf-8"))
def test_write_manifest_rejects_unknown_contract(tmp_path):
"""
未知清单形态不能静默退化成 legacy 行为。
"""
with pytest.raises(ValueError, match="不支持的清单类型"):
_write_manifest(
tmp_path,
kind="unknown",
distribution_version="2.0.0",
)