feat: release 2.2.0 with PC-only tests and execution reliability fixes
Unify task scheduling and queue state, fix Android input and observation recovery, preserve screenshot paths, and match final review criteria accurately. Add collapsible copyable results and publish the complete 2.1.0 upgrade notes. Validation: 3189 backend tests, 326 frontend tests, 24 client tests, production build and quality gates.
This commit is contained in:
1 parent
fac4ccb760
commit
be6e8c2abd
79 files changed
+2263
-277
No files matched your search
@@ -80,18 +80,14 @@ def test_same_serial_on_different_adb_endpoints_has_independent_locks():
|
||||
)
|
||||
|
||||
first.acquire()
|
||||
second.acquire()
|
||||
try:
|
||||
assert first.path != second.path
|
||||
owners = DeviceExecutionLock.get_active_owners()
|
||||
assert len(owners) == 2
|
||||
assert {owner.lock_scope for owner in owners.values()} == {
|
||||
"tcp:127.0.0.1:5037",
|
||||
"tcp:127.0.0.1:5038",
|
||||
}
|
||||
with pytest.raises(DeviceBusyError, match="concurrency limit"):
|
||||
second.acquire(blocking=False)
|
||||
finally:
|
||||
second.release()
|
||||
first.release()
|
||||
second.acquire(blocking=False)
|
||||
second.release()
|
||||
|
||||
|
||||
def test_device_lock_release_does_not_remove_replacement_owner():
|
||||
@@ -185,28 +181,19 @@ def test_submission_reservations_preserve_order_before_workers_start():
|
||||
thread.join(timeout=2.0)
|
||||
|
||||
|
||||
def test_pending_reservation_at_queue_head_does_not_block_other_devices():
|
||||
"""An unclaimed pending submission must not gate every device's queue head.
|
||||
|
||||
Regression: a reservation whose device is still "pending" used to be
|
||||
counted into every device's FIFO queue, so one unclaimed head-of-queue
|
||||
ticket blocked all devices at once. It must now park on at most one idle
|
||||
candidate device (the first in sorted order) and leave the others
|
||||
schedulable.
|
||||
"""
|
||||
def test_pending_reservation_at_queue_head_preserves_global_submission_order():
|
||||
"""Unresolved earlier submissions retain their place across target devices."""
|
||||
pending_ticket = DeviceExecutionLock.reserve("unclaimed submission")
|
||||
# device-alpha is a known idle device (a queued submission targets it), so
|
||||
# the pending ticket parks there; device-beta must stay schedulable.
|
||||
alpha_ticket = DeviceExecutionLock.reserve("task for device alpha", device_id="device-alpha")
|
||||
try:
|
||||
beta = DeviceExecutionLock("device-beta", "task for device beta")
|
||||
# Must acquire immediately even though the older pending ticket is at
|
||||
# the head of the global queue directory.
|
||||
beta.acquire(blocking=False)
|
||||
beta.release()
|
||||
with pytest.raises(DeviceBusyError, match="earlier queued task"):
|
||||
beta.acquire(blocking=False)
|
||||
finally:
|
||||
DeviceExecutionLock.cancel_reservation(pending_ticket)
|
||||
DeviceExecutionLock.cancel_reservation(alpha_ticket)
|
||||
beta.acquire(blocking=False)
|
||||
beta.release()
|
||||
|
||||
|
||||
def test_pending_reservation_is_served_with_original_fifo_position_after_claim():
|
||||
@@ -326,27 +313,58 @@ def test_active_owner_is_discoverable_and_can_be_annotated(monkeypatch):
|
||||
owner_lock.release()
|
||||
|
||||
|
||||
def test_multi_device_locks_can_run_concurrently():
|
||||
lock_a = DeviceExecutionLock("emulator-5554", "task on device A")
|
||||
lock_b = DeviceExecutionLock("pixel-9-test", "task on device B")
|
||||
@pytest.mark.parametrize("targets", [("emulator-5554", "computer"), ("computer", "pixel-9-test")])
|
||||
@pytest.mark.parametrize("legacy_mode", ["per_device", "parallel", "global"])
|
||||
def test_all_targets_run_serially_even_with_legacy_parallel_options(
|
||||
monkeypatch, targets, legacy_mode
|
||||
):
|
||||
monkeypatch.setenv("ARTEMIS_CONCURRENCY_MODE", legacy_mode)
|
||||
monkeypatch.setenv("ARTEMIS_MAX_CONCURRENT_TASKS", "8")
|
||||
lock_a = DeviceExecutionLock(
|
||||
targets[0], "task A", concurrency_mode=legacy_mode, max_concurrency=0
|
||||
)
|
||||
lock_b = DeviceExecutionLock(
|
||||
targets[1], "task B", concurrency_mode=legacy_mode, max_concurrency=8
|
||||
)
|
||||
|
||||
lock_a.acquire()
|
||||
try:
|
||||
# Device B should be acquired without blocking since it's a different device
|
||||
lock_b.acquire(blocking=False)
|
||||
try:
|
||||
active_owners = DeviceExecutionLock.get_active_owners()
|
||||
assert "emulator-5554" in active_owners
|
||||
assert "pixel-9-test" in active_owners
|
||||
|
||||
# A second task on device A should be blocked
|
||||
lock_a_second = DeviceExecutionLock("emulator-5554", "second task on device A")
|
||||
with pytest.raises(DeviceBusyError, match="task on device A"):
|
||||
lock_a_second.acquire(blocking=False)
|
||||
finally:
|
||||
lock_b.release()
|
||||
with pytest.raises(DeviceBusyError, match="task A"):
|
||||
lock_b.acquire(blocking=False)
|
||||
finally:
|
||||
lock_a.release()
|
||||
lock_b.acquire(blocking=False)
|
||||
lock_b.release()
|
||||
|
||||
|
||||
def test_shared_execution_lease_prevents_racing_admission():
|
||||
"""Both schedulers can see an idle snapshot; only one may create a lease."""
|
||||
first = DeviceExecutionLock("computer", "PC")
|
||||
second = DeviceExecutionLock("android", "Android")
|
||||
assert first._try_acquire_owner_lock()
|
||||
try:
|
||||
assert not second._try_acquire_owner_lock()
|
||||
assert not second.path.exists()
|
||||
finally:
|
||||
first.release()
|
||||
assert second._try_acquire_owner_lock()
|
||||
second.release()
|
||||
|
||||
|
||||
def test_same_session_nested_lease_does_not_wait_behind_other_tasks():
|
||||
parent = DeviceExecutionLock("android", "parent", session_id="same-session")
|
||||
nested = DeviceExecutionLock("computer", "nested", session_id="same-session")
|
||||
parent.acquire()
|
||||
earlier = DeviceExecutionLock.reserve("another task", device_id="computer")
|
||||
try:
|
||||
nested.acquire(blocking=False)
|
||||
nested.release()
|
||||
# Borrowing the shared resource must not delete the parent's lease.
|
||||
challenger = DeviceExecutionLock("another-device", "challenger")
|
||||
assert not challenger._try_acquire_owner_lock()
|
||||
finally:
|
||||
DeviceExecutionLock.cancel_reservation(earlier)
|
||||
parent.release()
|
||||
|
||||
|
||||
def test_global_concurrency_serial_mode_blocks_other_devices(monkeypatch):
|
||||
|
||||
Reference in new issue
Block a user