feat: add global device queue and readiness tracking

This commit is contained in:
somew1nd committed 2026-08-24 14:34:17 -07:00
1 parent a62ac4aef1
commit 42b8988661
57 files changed
+3606 -716

No files matched your search

+34 -123
View File
@@ -2,140 +2,64 @@ from unittest.mock import MagicMock, patch
import pytest
from artemis.clients.ui_automator_client import (
AWAKE_STRATEGY_HEARTBEAT,
AWAKE_STRATEGY_STAY_ON,
AWAKE_STRATEGY_WAKE_LOCK,
UIAutomatorClient,
_keep_device_awake,
)
from artemis.clients.ui_automator_client import UIAutomatorClient
from artemis.drivers.android.adb_driver import AndroidAdbDriver
def completed(stdout="", returncode=0, stderr=""):
return MagicMock(returncode=returncode, stdout=stdout, stderr=stderr)
@patch("artemis.clients.ui_automator_client.subprocess.run")
def test_keep_device_awake_uses_verified_primary_strategy(mock_run):
mock_run.side_effect = [
completed(),
completed(),
completed(),
completed(" mStayOn=true\n"),
]
strategy = _keep_device_awake("device-123")
assert strategy == AWAKE_STRATEGY_STAY_ON
assert mock_run.call_count == 4
@patch("artemis.clients.ui_automator_client.subprocess.run")
def test_keep_device_awake_falls_back_to_verified_screen_wake_lock(mock_run):
mock_run.side_effect = [
completed(),
completed(),
completed(),
completed(" mStayOn=false\n"),
completed("WakeLock{abc held=true, refCount=1}\n"),
completed(
"Display 0, wakelock type: SCREEN_BRIGHT_WAKE_LOCK: "
"WakeLock{abc held=true, refCount=1}\n"
),
]
strategy = _keep_device_awake("device-123")
assert strategy == AWAKE_STRATEGY_WAKE_LOCK
assert mock_run.call_args_list[-2].args[0] == [
"adb",
"-s",
"device-123",
"shell",
"cmd",
"power",
"set-wakelock",
"acquire",
"-d",
"0",
"SCREEN_BRIGHT_WAKE_LOCK",
]
@patch("artemis.clients.ui_automator_client.subprocess.run")
def test_keep_device_awake_uses_heartbeat_when_wake_lock_is_unavailable(mock_run):
mock_run.side_effect = [
completed(),
completed(),
completed(),
completed(" mStayOn=false\n"),
completed(returncode=1, stderr="unknown command"),
completed("Wakelocks:\n"),
]
strategy = _keep_device_awake("device-123")
assert strategy == AWAKE_STRATEGY_HEARTBEAT
@patch.dict("os.environ", {"ARTEMIS_KEEP_DEVICE_AWAKE": "false"})
@patch("artemis.clients.ui_automator_client.subprocess.run")
def test_keep_device_awake_can_be_disabled(mock_run):
assert _keep_device_awake("device-123") is None
mock_run.assert_not_called()
@patch("artemis.clients.ui_automator_client.u2.connect")
@patch(
"artemis.clients.ui_automator_client._keep_device_awake",
return_value=AWAKE_STRATEGY_HEARTBEAT,
"artemis.clients.ui_automator_client.ensure_device_awake",
return_value="host_heartbeat",
)
@patch("artemis.clients.ui_automator_client._ensure_maestro_not_installed")
def test_new_ui_connection_starts_heartbeat_fallback(
mock_remove_maestro, mock_keep_awake, mock_connect
def test_new_ui_connection_enrolls_device_in_shared_awake_strategy(
mock_remove_maestro, mock_ensure_awake, mock_connect
):
client = UIAutomatorClient("device-123")
client._start_awake_heartbeat = MagicMock()
client._ensure_connected()
client.connect()
client.connect()
mock_remove_maestro.assert_called_once_with("device-123")
mock_keep_awake.assert_called_once_with("device-123")
mock_ensure_awake.assert_called_once_with("device-123")
mock_connect.assert_called_once_with("device-123")
client._start_awake_heartbeat.assert_called_once_with()
assert client._awake_strategy == AWAKE_STRATEGY_HEARTBEAT
@patch("artemis.clients.ui_automator_client._release_screen_wake_lock")
def test_disconnect_releases_screen_wake_lock(mock_release):
@patch("artemis.clients.ui_automator_client.u2.connect", side_effect=RuntimeError("offline"))
@patch(
"artemis.clients.ui_automator_client.ensure_device_awake",
return_value="host_heartbeat",
)
@patch("artemis.clients.ui_automator_client._ensure_maestro_not_installed")
def test_failed_ui_connection_does_not_stop_process_awake_service(
_mock_remove_maestro, mock_ensure_awake, _mock_connect
):
client = UIAutomatorClient("device-123")
client._awake_strategy = AWAKE_STRATEGY_WAKE_LOCK
client._stop_awake_heartbeat = MagicMock()
client.disconnect()
with pytest.raises(RuntimeError, match="offline"):
client.connect()
client._stop_awake_heartbeat.assert_called_once_with()
mock_release.assert_called_once_with("device-123")
mock_ensure_awake.assert_called_once_with("device-123")
assert client._awake_strategy is None
@patch("artemis.clients.ui_automator_client.threading.Thread")
@patch("artemis.clients.ui_automator_client.threading.Event")
def test_heartbeat_thread_is_daemonized_and_stoppable(mock_event, mock_thread):
stop_event = MagicMock()
thread = MagicMock()
mock_event.return_value = stop_event
mock_thread.return_value = thread
@patch("artemis.clients.ui_automator_client.u2.connect")
@patch(
"artemis.clients.ui_automator_client.ensure_device_awake",
return_value="host_heartbeat",
)
@patch("artemis.clients.ui_automator_client._ensure_maestro_not_installed")
def test_client_disconnect_does_not_send_power_cleanup_commands(
_mock_remove_maestro, mock_ensure_awake, _mock_connect
):
client = UIAutomatorClient("device-123")
client.connect()
client._start_awake_heartbeat()
client._stop_awake_heartbeat()
client.disconnect()
assert mock_thread.call_args.kwargs["daemon"] is True
thread.start.assert_called_once_with()
stop_event.set.assert_called_once_with()
thread.join.assert_called_once_with(timeout=2.0)
mock_ensure_awake.assert_called_once_with("device-123")
assert client._device is None
assert client._awake_strategy is None
@pytest.mark.asyncio
@@ -150,16 +74,3 @@ async def test_android_driver_disconnect_cleans_up_ui_client():
await driver.disconnect()
ui_client.disconnect.assert_called_once_with()
@patch("artemis.clients.ui_automator_client.subprocess.run")
def test_keep_device_awake_continues_after_an_adb_failure(mock_run):
mock_run.side_effect = [
completed(returncode=1, stderr="not allowed"),
OSError("adb temporarily unavailable"),
completed(),
completed("mStayOn=true\n"),
]
assert _keep_device_awake("device-123") == AWAKE_STRATEGY_STAY_ON
assert mock_run.call_count == 4