diff --git a/tests/test_connection.py b/tests/test_connection.py index 61f1735..16f0315 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -86,8 +86,10 @@ class FakePoller: async def shell(self, cmd: str) -> str | None: self.shell_calls.append(cmd) + import shlex + for package, dump in self.pm_dumps.items(): - if package in cmd: + if package in cmd or shlex.quote(package) in cmd: return dump return None @@ -178,6 +180,45 @@ class TestAppLabelCache: # still None (next tier), no crash — falls through cleanly assert manager.app_label("com.unknown.app") is not None or True + async def test_label_less_package_negatively_cached_no_repeat_dump(self): + """t26 F1: unresolvable packages cost at most one pm dump ever.""" + manager, _t = self._manager_with_dump() + manager._poller.pm_dumps["com.unknown.app"] = "no label line here" + + assert manager.app_label("com.unknown.app") is None # enqueue + await manager._watchdog_cycle(5.0) + import asyncio + + await asyncio.sleep(0) # drain the fetch task + first_dump_count = len(manager._poller.shell_calls) + + # later cycles: ZERO additional pm dumps for the unresolvable pkg + manager.app_label("com.unknown.app") # negative cache hit — not enqueued + manager.app_label("com.unknown.app") + await manager._watchdog_cycle(5.0) + await manager._watchdog_cycle(5.0) + + assert len(manager._poller.shell_calls) == first_dump_count + assert manager.app_label("com.unknown.app") is None # falls through + + async def test_pm_dump_command_is_shell_quoted(self): + """t26 F2: the package argument is shlex.quote'd (consistent).""" + import shlex + + wicked = "com.evil;name" + manager, _t = self._manager_with_dump() + manager._poller.pm_dumps[wicked] = "application-label: 'E'" + + manager.app_label(wicked) # enqueue + await manager._watchdog_cycle(5.0) + import asyncio + + await asyncio.sleep(0) # drain the fetch task + + assert manager._poller.shell_calls == [ + f"pm dump {shlex.quote(wicked)} 2>/dev/null | head -300" + ] + class TestLogcatStream: """P5-3: the dedicated logcat fast-path behind CONF_LOGCAT_EVENTS."""