feat: 内置并统一使用项目 ADB (#149)
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
"""项目内置 ADB 路径与完整性检查测试。"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from src.adb_runtime import (
|
||||
BundledAdbError,
|
||||
REQUIRED_ADB_FILES,
|
||||
bundled_adb_path,
|
||||
configure_bundled_adb_environment,
|
||||
validate_bundled_adb,
|
||||
)
|
||||
|
||||
|
||||
class BundledAdbRuntimeTest(unittest.TestCase):
|
||||
def test_project_adb_is_complete(self):
|
||||
adb_path = validate_bundled_adb()
|
||||
|
||||
self.assertEqual(adb_path, bundled_adb_path())
|
||||
self.assertTrue(adb_path.is_absolute())
|
||||
for file_name in REQUIRED_ADB_FILES:
|
||||
self.assertTrue((adb_path.parent / file_name).is_file())
|
||||
|
||||
def test_missing_file_reports_name_without_path_fallback(self):
|
||||
with tempfile.TemporaryDirectory() as directory, patch(
|
||||
"src.adb_runtime.app_dir", return_value=Path(directory)
|
||||
):
|
||||
with self.assertRaisesRegex(BundledAdbError, "adb.exe"):
|
||||
validate_bundled_adb()
|
||||
|
||||
def test_configure_forces_adbutils_to_use_project_adb(self):
|
||||
old_value = os.environ.get("ADBUTILS_ADB_PATH")
|
||||
try:
|
||||
configured = configure_bundled_adb_environment()
|
||||
self.assertEqual(
|
||||
os.environ["ADBUTILS_ADB_PATH"],
|
||||
str(configured),
|
||||
)
|
||||
finally:
|
||||
if old_value is None:
|
||||
os.environ.pop("ADBUTILS_ADB_PATH", None)
|
||||
else:
|
||||
os.environ["ADBUTILS_ADB_PATH"] = old_value
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -3,12 +3,16 @@
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
from src.adb_runtime import bundled_adb_path
|
||||
from src.android_device_service import (
|
||||
AndroidDeviceSearchError,
|
||||
AndroidDeviceService,
|
||||
)
|
||||
|
||||
|
||||
ADB = str(bundled_adb_path())
|
||||
|
||||
|
||||
def completed(command, stdout="", stderr="", returncode=0):
|
||||
"""创建一条假的 ADB 命令结果。"""
|
||||
|
||||
@@ -56,7 +60,7 @@ USB-002 unauthorized usb:1-2 transport_id:3
|
||||
|
||||
def runner(command, _timeout):
|
||||
commands.append(list(command))
|
||||
if command == ["adb", "devices", "-l"]:
|
||||
if command == [ADB, "devices", "-l"]:
|
||||
return completed(
|
||||
command,
|
||||
"List of devices attached\n"
|
||||
@@ -111,12 +115,12 @@ USB-002 unauthorized usb:1-2 transport_id:3
|
||||
def runner(command, _timeout):
|
||||
command = list(command)
|
||||
commands.append(command)
|
||||
if command[:2] == ["adb", "connect"]:
|
||||
if command[:2] == [ADB, "connect"]:
|
||||
return completed(
|
||||
command,
|
||||
"already connected to 192.168.0.173:5555\n",
|
||||
)
|
||||
if command == ["adb", "devices", "-l"]:
|
||||
if command == [ADB, "devices", "-l"]:
|
||||
return completed(
|
||||
command,
|
||||
"List of devices attached\n"
|
||||
@@ -131,7 +135,7 @@ USB-002 unauthorized usb:1-2 transport_id:3
|
||||
self.assertEqual(devices[0].serial, "192.168.0.173:5555")
|
||||
self.assertEqual(
|
||||
commands[0],
|
||||
["adb", "connect", "192.168.0.173:5555"],
|
||||
[ADB, "connect", "192.168.0.173:5555"],
|
||||
)
|
||||
|
||||
def test_restore_saved_wifi_waits_for_authorization_handshake(self):
|
||||
@@ -141,9 +145,9 @@ USB-002 unauthorized usb:1-2 transport_id:3
|
||||
def runner(command, _timeout):
|
||||
nonlocal device_list_count
|
||||
command = list(command)
|
||||
if command[:2] == ["adb", "connect"]:
|
||||
if command[:2] == [ADB, "connect"]:
|
||||
return completed(command, "connected to 192.168.0.173:5555\n")
|
||||
if command == ["adb", "devices", "-l"]:
|
||||
if command == [ADB, "devices", "-l"]:
|
||||
device_list_count += 1
|
||||
status = "unauthorized" if device_list_count == 1 else "device"
|
||||
return completed(
|
||||
@@ -167,7 +171,7 @@ USB-002 unauthorized usb:1-2 transport_id:3
|
||||
def runner(command, _timeout):
|
||||
command = list(command)
|
||||
commands.append(command)
|
||||
if command == ["adb", "devices", "-l"]:
|
||||
if command == [ADB, "devices", "-l"]:
|
||||
return completed(
|
||||
command,
|
||||
"List of devices attached\n"
|
||||
@@ -181,12 +185,12 @@ USB-002 unauthorized usb:1-2 transport_id:3
|
||||
|
||||
self.assertEqual(devices[0].serial, "USB-001")
|
||||
self.assertFalse(
|
||||
any(command[:2] == ["adb", "connect"] for command in commands)
|
||||
any(command[:2] == [ADB, "connect"] for command in commands)
|
||||
)
|
||||
|
||||
def test_missing_model_is_read_from_device(self):
|
||||
def runner(command, _timeout):
|
||||
if command == ["adb", "devices", "-l"]:
|
||||
if command == [ADB, "devices", "-l"]:
|
||||
return completed(
|
||||
command,
|
||||
"List of devices attached\nUSB-001 device transport_id:1\n",
|
||||
@@ -202,7 +206,7 @@ USB-002 unauthorized usb:1-2 transport_id:3
|
||||
|
||||
def test_property_failure_keeps_discovered_device(self):
|
||||
def runner(command, _timeout):
|
||||
if command == ["adb", "devices", "-l"]:
|
||||
if command == [ADB, "devices", "-l"]:
|
||||
return completed(
|
||||
command,
|
||||
"List of devices attached\nUSB-001 device model:Phone\n",
|
||||
@@ -218,7 +222,7 @@ USB-002 unauthorized usb:1-2 transport_id:3
|
||||
def runner(_command, _timeout):
|
||||
raise FileNotFoundError("adb")
|
||||
|
||||
with self.assertRaisesRegex(AndroidDeviceSearchError, "未找到 adb"):
|
||||
with self.assertRaisesRegex(AndroidDeviceSearchError, "内置 adb.exe"):
|
||||
AndroidDeviceService(runner).search()
|
||||
|
||||
def test_timeout_has_recovery_message(self):
|
||||
@@ -253,7 +257,7 @@ USB-002 unauthorized usb:1-2 transport_id:3
|
||||
self.assertEqual(
|
||||
commands,
|
||||
[[
|
||||
"adb",
|
||||
ADB,
|
||||
"-s",
|
||||
"USB-001",
|
||||
"shell",
|
||||
@@ -296,9 +300,9 @@ USB-002 unauthorized usb:1-2 transport_id:3
|
||||
"192.168.0.0/24 dev wlan0 scope link "
|
||||
"src 192.168.0.173\n",
|
||||
)
|
||||
if command[:2] == ["adb", "connect"]:
|
||||
if command[:2] == [ADB, "connect"]:
|
||||
return completed(command, "connected to 192.168.0.173:5555\n")
|
||||
if command == ["adb", "devices", "-l"]:
|
||||
if command == [ADB, "devices", "-l"]:
|
||||
return completed(
|
||||
command,
|
||||
"List of devices attached\n"
|
||||
@@ -323,14 +327,14 @@ USB-002 unauthorized usb:1-2 transport_id:3
|
||||
["USB-001", "192.168.0.173:5555"],
|
||||
)
|
||||
self.assertIn(
|
||||
["adb", "-s", "USB-001", "tcpip", "5555"],
|
||||
[ADB, "-s", "USB-001", "tcpip", "5555"],
|
||||
commands,
|
||||
)
|
||||
self.assertEqual(
|
||||
commands.count(["adb", "connect", "192.168.0.173:5555"]),
|
||||
commands.count([ADB, "connect", "192.168.0.173:5555"]),
|
||||
1,
|
||||
)
|
||||
self.assertEqual(commands.count(["adb", "devices", "-l"]), 3)
|
||||
self.assertEqual(commands.count([ADB, "devices", "-l"]), 3)
|
||||
self.assertTrue(
|
||||
any("保存后拔掉 USB" in message for message in progress)
|
||||
)
|
||||
@@ -346,9 +350,9 @@ USB-002 unauthorized usb:1-2 transport_id:3
|
||||
command = list(command)
|
||||
if command[-3:] == ["shell", "ip", "route"]:
|
||||
return completed(command, "local src 192.168.0.173\n")
|
||||
if command[:2] == ["adb", "connect"]:
|
||||
if command[:2] == [ADB, "connect"]:
|
||||
return completed(command, "connected to 192.168.0.173:5555\n")
|
||||
if command == ["adb", "devices", "-l"]:
|
||||
if command == [ADB, "devices", "-l"]:
|
||||
return completed(
|
||||
command,
|
||||
"List of devices attached\n"
|
||||
|
||||
@@ -21,7 +21,7 @@ from build_tools.release_manifest import (
|
||||
versioned_manifest_file_name,
|
||||
write_manifest,
|
||||
)
|
||||
from src.db import data_dir
|
||||
from src.db import app_dir, data_dir
|
||||
|
||||
|
||||
class LauncherPathTest(unittest.TestCase):
|
||||
@@ -139,6 +139,26 @@ class PackagedDataPathTest(unittest.TestCase):
|
||||
):
|
||||
self.assertEqual(data_dir(), root / "data")
|
||||
|
||||
def test_packaged_resource_path_uses_pyinstaller_directory(self):
|
||||
with tempfile.TemporaryDirectory() as directory, patch(
|
||||
"src.db.sys.frozen", True, create=True
|
||||
), patch("src.db.sys._MEIPASS", directory, create=True):
|
||||
self.assertEqual(app_dir(), Path(directory).resolve())
|
||||
|
||||
|
||||
class BundledAdbPackagingTest(unittest.TestCase):
|
||||
def test_build_script_packages_and_checks_all_adb_files(self):
|
||||
client_root = Path(__file__).resolve().parents[1]
|
||||
script = (client_root / "packaging" / "build.ps1").read_text(
|
||||
encoding="utf-8-sig"
|
||||
)
|
||||
|
||||
self.assertIn("--add-binary", script)
|
||||
self.assertIn("vendor/android-platform-tools/windows", script)
|
||||
self.assertIn("adb.exe", script)
|
||||
self.assertIn("AdbWinApi.dll", script)
|
||||
self.assertIn("AdbWinUsbApi.dll", script)
|
||||
|
||||
|
||||
class ReleaseManifestTest(unittest.TestCase):
|
||||
def test_manifest_name_and_hash_match_release_files(self):
|
||||
|
||||
@@ -14,6 +14,7 @@ from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from qfluentwidgets import InfoBarPosition
|
||||
|
||||
from src.adb_runtime import BundledAdbError
|
||||
from src.android_device_service import AndroidDeviceSearchError
|
||||
from src.db import open_database
|
||||
from src.pdd_ui import PDDTaskPage
|
||||
@@ -1174,6 +1175,40 @@ class PDDTaskPageEventTest(unittest.TestCase):
|
||||
self.assertEqual(result, 0)
|
||||
self.assertTrue(window.maximized)
|
||||
|
||||
def test_application_startup_shows_dialog_when_bundled_adb_is_missing(self):
|
||||
class FakeApplication:
|
||||
@staticmethod
|
||||
def setAttribute(*_args):
|
||||
pass
|
||||
|
||||
def __init__(self, _args):
|
||||
pass
|
||||
|
||||
def exec_(self):
|
||||
return 0
|
||||
|
||||
class FakeWindow:
|
||||
def showMaximized(self):
|
||||
pass
|
||||
|
||||
window = FakeWindow()
|
||||
message = "内置 ADB 文件缺失:adb.exe。请重新安装完整的软件包。"
|
||||
with patch("src.ui_main.QApplication", FakeApplication), patch(
|
||||
"src.ui_main.MainWindow", return_value=window
|
||||
), patch("src.ui_main.setTheme"), patch(
|
||||
"src.ui_main.mark_current_version_healthy"
|
||||
), patch(
|
||||
"src.ui_main.configure_bundled_adb_environment",
|
||||
side_effect=BundledAdbError(message),
|
||||
), patch(
|
||||
"src.ui_main.QTimer.singleShot",
|
||||
side_effect=lambda _delay, callback: callback(),
|
||||
), patch("src.ui_main._show_adb_missing_dialog") as show_dialog:
|
||||
result = ui_main()
|
||||
|
||||
self.assertEqual(result, 0)
|
||||
show_dialog.assert_called_once_with(window, message)
|
||||
|
||||
def test_main_window_marks_and_clears_update_navigation(self):
|
||||
window = MainWindow(
|
||||
task_repository=self.repository,
|
||||
|
||||
@@ -5,6 +5,7 @@ import tempfile
|
||||
import time
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
||||
|
||||
@@ -12,6 +13,7 @@ from PyQt5.QtCore import Qt, QTimer
|
||||
from PyQt5.QtTest import QTest
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from src.adb_runtime import BundledAdbError
|
||||
from src.android_device_service import (
|
||||
AndroidDevice,
|
||||
AndroidDeviceConversionCancelled,
|
||||
@@ -218,6 +220,24 @@ class SettingsPageEventTest(unittest.TestCase):
|
||||
page.eventBinder.shutdown()
|
||||
page.deleteLater()
|
||||
|
||||
def test_missing_bundled_adb_disables_device_commands(self):
|
||||
message = "内置 ADB 文件缺失:adb.exe。请重新安装完整的软件包。"
|
||||
with patch(
|
||||
"src.settings_ui_event.validate_bundled_adb",
|
||||
side_effect=BundledAdbError(message),
|
||||
):
|
||||
page = SettingsPage(
|
||||
settings_repository=self.repository,
|
||||
admin_gateway=MockAdminGateway(),
|
||||
)
|
||||
|
||||
self.assertEqual(page.deviceStatusLabel.text(), message)
|
||||
self.assertFalse(page.searchButton.isEnabled())
|
||||
self.assertFalse(page.convertWifiButton.isEnabled())
|
||||
self.assertTrue(page.currentDeviceSaveButton.isEnabled())
|
||||
page.eventBinder.shutdown()
|
||||
page.deleteLater()
|
||||
|
||||
def test_compact_form_layout_keeps_related_fields_on_same_row(self):
|
||||
page = SettingsPage(
|
||||
settings_repository=self.repository,
|
||||
|
||||
Reference in New Issue
Block a user