From a72cf57494dffe6d745013a1d0ff51c1ae7115e9 Mon Sep 17 00:00:00 2001 From: James Mackay Date: Thu, 8 Oct 2026 08:43:23 +0100 Subject: [PATCH] test: poll_running_apps parsing against SYNTHETIC activity-recents fixture (A= and I= patterns; real R6 capture still outstanding) --- tests/fixtures/activity_recents_synthetic.txt | 12 ++++ tests/test_adb_client.py | 63 +++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 tests/fixtures/activity_recents_synthetic.txt diff --git a/tests/fixtures/activity_recents_synthetic.txt b/tests/fixtures/activity_recents_synthetic.txt new file mode 100644 index 0000000..badc0e3 --- /dev/null +++ b/tests/fixtures/activity_recents_synthetic.txt @@ -0,0 +1,12 @@ +SYNTHETIC fixture — not a real device capture. The real Shield `dumpsys +activity recents` capture (R6) is still outstanding; this file pins the +parsing contract of AdbClientBase.poll_running_apps in the meantime, covering +both the A=uid:package and the I=package/activity fallback patterns. +Represents the post-grep output of: + dumpsys activity recents 2>/dev/null | grep -E 'Recent #|realActivity|visible=' +Note: line order = recency order (most recently used first). +* Recent #0: Task{2c9f81f #817 type=standard A=10185:ar.tvplayer.tv I=ar.tvplayer.tv/.ui.MainActivity ... visible=true numActivities=1 ...} +* Recent #1: Task{3dae20b #802 type=standard A=10106:com.github.damontecres.wholphin ... visible=false numActivities=1 ...} +* Recent #2: Task{4b7ce01 #788 type=standard I=com.example.launcherapp/.ui.HomeActivity ... visible=true ...} +* Recent #3: Task{5c8dd12 #770 type=standard A=1000:com.android.systemui ... visible=true ...} +* Recent #4: Task{6d9ef23 #750 type=unknown A=10003:.HiddenInternalTask ... visible=true ...} diff --git a/tests/test_adb_client.py b/tests/test_adb_client.py index cd9efb8..f934ed0 100644 --- a/tests/test_adb_client.py +++ b/tests/test_adb_client.py @@ -143,3 +143,66 @@ class TestPollMediaSessionsTruncation: client = _client_response(CMD_MEDIA_DEFAULT, Exception("dead")) assert await client.poll_media_sessions() is None assert client.last_info is None + + +class TestPollRunningApps: + """Recent-task parsing against the SYNTHETIC recents fixture. + + The real Shield `dumpsys activity recents` capture (R6) is still + outstanding; tests/fixtures/activity_recents_synthetic.txt pins the + parsing contract in the meantime. + """ + + async def test_parses_visible_user_apps_in_recency_order(self): + recents = (FIXTURES / "activity_recents_synthetic.txt").read_text() + client = _client_response( + "dumpsys activity recents 2>/dev/null " + "| grep -E 'Recent #|realActivity|visible='", + recents, + ) + apps = await client.poll_running_apps() + assert [a["package"] for a in apps] == [ + "ar.tvplayer.tv", + "com.example.launcherapp", + ] + + async def test_i_pattern_fallback_used_without_a_uid_assignment(self): + """`I=com.example.launcherapp/.ui.HomeActivity` is the only package + fragment on its line and must be picked up by the fallback regex.""" + recents = (FIXTURES / "activity_recents_synthetic.txt").read_text() + client = _client_response( + "dumpsys activity recents 2>/dev/null " + "| grep -E 'Recent #|realActivity|visible='", + recents, + ) + apps = await client.poll_running_apps() + assert "com.example.launcherapp" in {a["package"] for a in apps} + + async def test_skips_invisible_tasks_system_packages_and_dot_names(self): + recents = (FIXTURES / "activity_recents_synthetic.txt").read_text() + client = _client_response( + "dumpsys activity recents 2>/dev/null " + "| grep -E 'Recent #|realActivity|visible='", + recents, + ) + apps = await client.poll_running_apps() + packages = {a["package"] for a in apps} + assert "com.github.damontecres.wholphin" not in packages # visible=false + assert "com.android.systemui" not in packages # excluded system UI + assert ".HiddenInternalTask" not in packages # dot-named internals + + async def test_empty_result_returns_empty_list(self): + client = _client_response( + "dumpsys activity recents 2>/dev/null " + "| grep -E 'Recent #|realActivity|visible='", + None, + ) + assert await client.poll_running_apps() == [] + + async def test_failed_shell_returns_empty_list(self): + client = _client_response( + "dumpsys activity recents 2>/dev/null " + "| grep -E 'Recent #|realActivity|visible='", + Exception("offline"), + ) + assert await client.poll_running_apps() == []