mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-10-30 03:48:03 +08:00
224 lines
6.4 KiB
Python
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",
|
|
)
|