"""binary_locator.py 探测逻辑测试。 用 monkeypatch 模拟环境变量、PATH、可执行性检查,覆盖 env/path/common/wsl/全失败分支。 """ import os import platform from pathlib import Path from unittest.mock import patch import pytest from plugins.browser.browser import binary_locator def _make_executable(tmp_path: Path, name: str = "lightpanda") -> Path: """在 tmp_path 下造一个可执行文件。""" p = tmp_path / name p.write_text("#!/bin/sh\n") p.chmod(0o755) return p def test_env_path_detected(tmp_path, monkeypatch): """LIGHTPANDA_BINARY 指向可执行文件时 source=env。""" binp = _make_executable(tmp_path) monkeypatch.setenv("LIGHTPANDA_BINARY", str(binp)) monkeypatch.setattr(binary_locator.shutil, "which", lambda _: None) monkeypatch.setattr(binary_locator, "_read_version", lambda b: "0.2.0") r = binary_locator.locate_lightpanda() assert r.available is True assert r.path == str(binp) assert r.source == "env" assert r.version == "0.2.0" def test_env_path_not_executable_falls_through(tmp_path, monkeypatch): """LIGHTPANDA_BINARY 指向不可执行文件时不命中。 注:Windows 不用 Unix 权限位判定可执行性,故直接 mock _check_path_executable 返回 False 来模拟"存在但不可执行"。 """ p = tmp_path / "lightpanda" p.write_text("not executable") monkeypatch.setenv("LIGHTPANDA_BINARY", str(p)) monkeypatch.setattr(binary_locator.shutil, "which", lambda _: None) monkeypatch.setattr(binary_locator, "_probe_wsl_interop", lambda: None) monkeypatch.setattr(binary_locator, "_check_path_executable", lambda path: False) r = binary_locator.locate_lightpanda() assert r.available is False def test_path_detected(tmp_path, monkeypatch): """shutil.which 找到可执行文件时 source=path。""" binp = _make_executable(tmp_path) monkeypatch.delenv("LIGHTPANDA_BINARY", raising=False) monkeypatch.setattr(binary_locator.shutil, "which", lambda _: str(binp)) monkeypatch.setattr(binary_locator, "_read_version", lambda b: "0.2.0") monkeypatch.setattr(binary_locator, "_probe_wsl_interop", lambda: None) r = binary_locator.locate_lightpanda() assert r.available is True assert r.source == "path" def test_all_fail_returns_unavailable(monkeypatch): """全部探测失败时 available=False 且 source=none。""" monkeypatch.delenv("LIGHTPANDA_BINARY", raising=False) monkeypatch.setattr(binary_locator.shutil, "which", lambda _: None) monkeypatch.setattr(binary_locator, "_probe_wsl_interop", lambda: None) monkeypatch.setattr(binary_locator, "_check_path_executable", lambda p: False) r = binary_locator.locate_lightpanda() assert r.available is False assert r.source == "none" assert r.error assert r.install_hints # 必须带安装提示 def test_install_hints_contain_docker_and_wsl(monkeypatch): """install_hints 总是包含 docker 和 wsl 备选。""" monkeypatch.delenv("LIGHTPANDA_BINARY", raising=False) monkeypatch.setattr(binary_locator.shutil, "which", lambda _: None) monkeypatch.setattr(binary_locator, "_probe_wsl_interop", lambda: None) monkeypatch.setattr(binary_locator, "_check_path_executable", lambda p: False) r = binary_locator.locate_lightpanda() assert "docker" in r.install_hints assert "wsl" in r.install_hints def test_to_dict_serializable(monkeypatch): """to_dict 返回可 JSON 序列化的 dict。""" import json monkeypatch.delenv("LIGHTPANDA_BINARY", raising=False) monkeypatch.setattr(binary_locator.shutil, "which", lambda _: None) monkeypatch.setattr(binary_locator, "_probe_wsl_interop", lambda: None) monkeypatch.setattr(binary_locator, "_check_path_executable", lambda p: False) r = binary_locator.locate_lightpanda() d = r.to_dict() json.dumps(d) # 不抛即 OK assert d["available"] is False def test_is_binary_available_consistent(monkeypatch): """is_binary_available 与 locate_lightpanda().available 一致。""" monkeypatch.delenv("LIGHTPANDA_BINARY", raising=False) monkeypatch.setattr(binary_locator.shutil, "which", lambda _: None) monkeypatch.setattr(binary_locator, "_probe_wsl_interop", lambda: None) monkeypatch.setattr(binary_locator, "_check_path_executable", lambda p: False) assert binary_locator.is_binary_available() is False