feat: add multi-device concurrency, server lifecycle CLI, and unified execution stream

- Implement DevicePool and DeviceExecutionLock concurrency modes (per-device FIFO vs global serialization)
- Extend ArtemisClient SDK and CLI with explicit device targeting and concurrency strategy configuration
- Add server lifecycle management (artemis restart, stop, status) with cross-platform PID discovery
- Update MCP server tools to support multi-device dispatch, trace tagging, and per-device execution
- Enhance Showcase UI with step-by-step visual replay, playback speed controls, and device serial badges
- Add comprehensive unit test coverage and edge-case verification scripts
This commit is contained in:
somew1nd committed 2026-08-26 15:53:14 -07:00
1 parent a36a66ce6d
commit c2c90a6378
84 files changed
+5612 -645

No files matched your search

+85
View File
@@ -0,0 +1,85 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pytest
from artemis.runtime.device_lock import DeviceExecutionLock
from artemis.runtime.device_pool import DevicePool, DeviceStatus
@pytest.fixture(autouse=True)
def isolated_lock_directory(tmp_path, monkeypatch):
monkeypatch.setattr(
"artemis.runtime.device_lock.get_temp_dir",
lambda _name: tmp_path,
)
def test_device_pool_parse_lines():
lines = [
"List of devices attached",
"emulator-5554 device product:sdk_gphone64_arm64 model:sdk_gphone64_arm64 device:emu64a",
"9A123XYZ device product:husky model:Pixel_8_Pro device:husky",
"offline-dev offline",
"",
]
parsed = DevicePool._parse_device_lines(lines)
assert len(parsed) == 3
assert parsed[0] == ("emulator-5554", "device", "sdk gphone64 arm64", "sdk_gphone64_arm64")
assert parsed[1] == ("9A123XYZ", "device", "Pixel 8 Pro", "husky")
assert parsed[2] == ("offline-dev", "offline", None, None)
def test_device_pool_list_devices_and_busy_status(monkeypatch):
pool = DevicePool()
monkeypatch.setattr(
pool,
"_query_adb_devices_sync",
lambda: [
("emulator-5554", "device", "Emulator", "sdk"),
("pixel-8-pro", "device", "Pixel 8 Pro", "husky"),
],
)
# Initially neither is busy
devs = pool.list_devices()
assert len(devs) == 2
assert not devs[0].is_busy
assert not devs[1].is_busy
# Acquire lock for emulator-5554
lock = DeviceExecutionLock("emulator-5554", "test automation")
lock.acquire()
try:
devs = pool.list_devices()
emu = next(d for d in devs if d.serial == "emulator-5554")
pixel = next(d for d in devs if d.serial == "pixel-8-pro")
assert emu.is_busy is True
assert emu.active_task_desc == "test automation"
assert pixel.is_busy is False
# Test idle device filtering
idle = pool.get_idle_devices()
assert len(idle) == 1
assert idle[0].serial == "pixel-8-pro"
# Test select_device picks the idle device automatically
selected = pool.select_device()
assert selected == "pixel-8-pro"
# Test explicit selection returns requested device
assert pool.select_device("emulator-5554") == "emulator-5554"
finally:
lock.release()