fix: bound task failures and release stalled workers

This commit is contained in:
chenmingxuan committed 2026-10-08 16:10:10 +08:00
1 parent 606ed94160
commit 1fdf7c0d1d
32 files changed
+1121 -93

No files matched your search

+27 -12
View File
@@ -15,6 +15,9 @@
"""Unit tests for Universal FlashRunner."""
import base64
from io import BytesIO
from PIL import Image
from artemis.mcp.observation import DeviceObservationError
from types import SimpleNamespace
from unittest.mock import AsyncMock, Mock, patch
import pytest
@@ -72,9 +75,18 @@ def test_flash_runner_tools_initialization(mock_context):
# --- Post-action screenshot fallback (failed action / unknown tool) ----------------
def _post_image():
buffer = BytesIO()
Image.new("RGB", (64, 64), "white").save(buffer, format="PNG")
return buffer.getvalue()
_POST_IMG = _post_image()
def _screen(elements):
return SimpleNamespace(
base64=base64.b64encode(b"POST-IMG").decode("ascii"),
base64=base64.b64encode(_POST_IMG).decode("ascii"),
elements=elements,
width=1080,
height=2400,
@@ -120,7 +132,7 @@ async def test_fallback_observation_renders_the_indexed_ui_list(mock_context, tm
exec_result, "click", frozenset({"click"}), state
)
assert post == b"POST-IMG"
assert post == _POST_IMG
assert isinstance(exec_result.ui_elements_text, str)
assert exec_result.ui_elements_text == "[1] Text: 'Settings' | Bounds: [37,50][962,112]"
assert "{'text'" not in exec_result.ui_elements_text
@@ -154,8 +166,8 @@ async def test_fallback_observation_is_skipped_for_non_action_tools(mock_context
@pytest.mark.asyncio
async def test_fallback_observation_keeps_the_executors_ui_list(mock_context, tmp_path):
"""An action that reported a UI list but no screenshot keeps its own list."""
async def test_fallback_observation_replaces_stale_executor_ui_list(mock_context, tmp_path):
"""Fresh screenshots and element lists must describe the same observation."""
with patch("artemis.controllers.unified_controller.get_driver"):
runner = FlashRunner(mock_context, goal="g")
runner.controller = Mock()
@@ -175,12 +187,13 @@ async def test_fallback_observation_keeps_the_executors_ui_list(mock_context, tm
exec_result, "click", frozenset({"click"}), None
)
assert post == b"POST-IMG"
assert exec_result.ui_elements_text == "[1] Text: 'Mine' | Bounds: [0,0][10,10]"
assert post == _POST_IMG
assert "Other" in exec_result.ui_elements_text
assert "Mine" not in exec_result.ui_elements_text
@pytest.mark.asyncio
async def test_fallback_observation_failure_is_contained(mock_context):
async def test_fallback_observation_failure_stops_after_bounded_recovery(mock_context):
with patch("artemis.controllers.unified_controller.get_driver"):
runner = FlashRunner(mock_context, goal="g")
runner.controller = Mock()
@@ -189,11 +202,13 @@ async def test_fallback_observation_failure_is_contained(mock_context):
state.latest_screenshot = "old.jpg"
exec_result = _failed_click()
post = await runner._capture_post_screenshot(exec_result, "click", frozenset({"click"}), state)
assert post is None
assert exec_result.ui_elements_text is None
assert state.latest_screenshot == "old.jpg"
with patch("artemis.mcp.observation.settings.ARTEMIS_OBSERVATION_MAX_ATTEMPTS", 3):
with pytest.raises(DeviceObservationError, match="adb gone"):
await runner._capture_post_screenshot(exec_result, "click", frozenset({"click"}), state)
assert runner.controller.get_screen_data.await_count == 3
assert state.latest_screenshot is None
assert state.indexed_elements == []
assert state.indexed_points == []
def test_flash_runner_executor_follows_the_flash_explorer_tier(mock_context):