Skip to content

Commit 3ccdbe9

Browse files
committed
fix: scope Android cleanup to test executables
1 parent 3a490bf commit 3ccdbe9

2 files changed

Lines changed: 111 additions & 56 deletions

File tree

scripts/android_smoke.py

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -655,47 +655,83 @@ def _exercise_gadget(
655655
}
656656

657657

658+
def _parse_remote_processes(output: str, remote_executables: Sequence[str]) -> dict[str, list[int]]:
659+
matches: dict[str, list[int]] = {path: [] for path in remote_executables}
660+
for line in output.splitlines():
661+
match = re.search(r"/proc/(\d+)/exe -> (.+)$", line)
662+
if match is None:
663+
continue
664+
target = match.group(2).removesuffix(" (deleted)")
665+
if target in matches:
666+
matches[target].append(int(match.group(1)))
667+
return matches
668+
669+
670+
def _remote_signal_command(remote_executable: str, pid: int, signal: str) -> str:
671+
body = (
672+
f"target=$(readlink /proc/{pid}/exe 2>/dev/null) || exit 0; "
673+
f'case "$target" in "{remote_executable}"|"{remote_executable} (deleted)") '
674+
f"kill -{signal} {pid} || exit 1 ;; esac; exit 0"
675+
)
676+
return f"'{body}'"
677+
678+
658679
def _cleanup(config: AndroidSmokeConfig, serial: str) -> None:
659680
processes = (
660-
(f"{config.name}-server", f"{config.name}-server"[:15]),
661-
("gadget-loader", "gadget-loader"),
681+
(f"{config.name}-server", f"{REMOTE_DIR}/{config.name}-server"),
682+
("gadget-loader", f"{REMOTE_DIR}/gadget-loader"),
662683
)
663684
failures: list[str] = []
664-
signaled = False
685+
remote_executables = tuple(path for _label, path in processes)
665686

666-
for label, process_comm in processes:
687+
def snapshot(stage: str) -> dict[str, list[int]] | None:
667688
result = root_shell(
668689
serial,
669-
f"pkill -TERM -x {process_comm}",
690+
"'ls -l /proc/[0-9]*/exe 2>/dev/null; exit 0'",
670691
check=False,
671692
)
672-
signaled = signaled or result.returncode == 0
673-
if result.returncode not in (0, 1):
674-
failures.append(f"stop {label} (exit {result.returncode})")
675-
676-
if signaled:
677-
time.sleep(0.5)
678-
679-
forced: list[tuple[str, str]] = []
680-
for label, process_comm in processes:
681-
result = root_shell(serial, f"pgrep -x {process_comm}", check=False)
682-
if result.returncode == 0:
683-
forced.append((label, process_comm))
684-
kill_result = root_shell(serial, f"pkill -KILL -x {process_comm}", check=False)
685-
if kill_result.returncode not in (0, 1):
686-
failures.append(f"force-stop {label} (exit {kill_result.returncode})")
687-
elif result.returncode != 1:
688-
failures.append(f"verify {label} (exit {result.returncode})")
689-
690-
if forced:
691-
failures.append("processes did not stop gracefully: " + ", ".join(x[0] for x in forced))
693+
if result.returncode != 0:
694+
failures.append(f"{stage} (exit {result.returncode})")
695+
return None
696+
return _parse_remote_processes(result.stdout, remote_executables)
697+
698+
initial = snapshot("list Android smoke processes")
699+
if initial is not None:
700+
for label, remote_executable in processes:
701+
for pid in initial[remote_executable]:
702+
result = root_shell(
703+
serial,
704+
_remote_signal_command(remote_executable, pid, "TERM"),
705+
check=False,
706+
)
707+
if result.returncode != 0:
708+
failures.append(f"stop {label} (exit {result.returncode})")
709+
if any(initial.values()):
710+
time.sleep(0.5)
711+
712+
remaining = snapshot("verify Android smoke processes")
713+
forced_labels: list[str] = []
714+
if remaining is not None:
715+
for label, remote_executable in processes:
716+
for pid in remaining[remote_executable]:
717+
if label not in forced_labels:
718+
forced_labels.append(label)
719+
result = root_shell(
720+
serial,
721+
_remote_signal_command(remote_executable, pid, "KILL"),
722+
check=False,
723+
)
724+
if result.returncode != 0:
725+
failures.append(f"force-stop {label} (exit {result.returncode})")
726+
727+
if forced_labels:
728+
failures.append("processes did not stop gracefully: " + ", ".join(forced_labels))
692729
time.sleep(0.2)
693-
for label, process_comm in forced:
694-
result = root_shell(serial, f"pgrep -x {process_comm}", check=False)
695-
if result.returncode == 0:
696-
failures.append(f"process remains: {label}")
697-
elif result.returncode != 1:
698-
failures.append(f"verify {label} after SIGKILL (exit {result.returncode})")
730+
final = snapshot("verify Android smoke processes after SIGKILL")
731+
if final is not None:
732+
for label, remote_executable in processes:
733+
if final[remote_executable]:
734+
failures.append(f"process remains: {label}")
699735

700736
remove_result = root_shell(serial, f"rm -rf -- {REMOTE_DIR}", check=False)
701737
if remove_result.returncode != 0:

tests/test_android_smoke.py

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -542,12 +542,24 @@ def fail_run(command: list[str], **_kwargs: object) -> subprocess.CompletedProce
542542
def test_cleanup_removes_remote_test_directory(monkeypatch: pytest.MonkeyPatch) -> None:
543543
root_commands: list[str] = []
544544
adb_commands: list[tuple[str, ...]] = []
545+
snapshot_calls = 0
545546

546547
def fake_root_shell(_serial: str, command: str, **_kwargs: object) -> SimpleNamespace:
548+
nonlocal snapshot_calls
547549
root_commands.append(command)
550+
stdout = ""
551+
if "/proc/[0-9]*/exe" in command:
552+
snapshot_calls += 1
553+
if snapshot_calls == 1:
554+
stdout = (
555+
"lrwxrwxrwx root /proc/101/exe -> "
556+
"/data/local/tmp/phantom-frida-test/oemcodec-server\n"
557+
"lrwxrwxrwx root /proc/102/exe -> "
558+
"/data/local/tmp/phantom-frida-test/gadget-loader\n"
559+
)
548560
return SimpleNamespace(
549561
returncode=1 if command.startswith(("pkill", "pgrep")) else 0,
550-
stdout="",
562+
stdout=stdout,
551563
stderr="",
552564
)
553565

@@ -557,12 +569,21 @@ def fake_adb(_serial: str, *arguments: str, **_kwargs: object) -> SimpleNamespac
557569

558570
monkeypatch.setattr(android_smoke, "root_shell", fake_root_shell)
559571
monkeypatch.setattr(android_smoke, "adb", fake_adb)
572+
monkeypatch.setattr(android_smoke.time, "sleep", lambda _seconds: None)
560573
config = SimpleNamespace(name="oemcodec", port=27142)
561574

562575
android_smoke._cleanup(config, "SERIAL-1")
563576

564-
assert "pkill -TERM -x oemcodec-server" in root_commands
565-
assert "pkill -TERM -x gadget-loader" in root_commands
577+
term_commands = [command for command in root_commands if "kill -TERM" in command]
578+
assert len(term_commands) == 2
579+
assert all("readlink /proc/" in command for command in term_commands)
580+
assert any(
581+
"/data/local/tmp/phantom-frida-test/oemcodec-server" in command for command in term_commands
582+
)
583+
assert any(
584+
"/data/local/tmp/phantom-frida-test/gadget-loader" in command for command in term_commands
585+
)
586+
assert not any(command.startswith("pkill") for command in root_commands)
566587
assert "rm -rf -- /data/local/tmp/phantom-frida-test" in root_commands
567588
assert ("forward", "--list") in adb_commands
568589

@@ -594,8 +615,8 @@ def fake_adb(_serial: str, *arguments: str, **_kwargs: object) -> SimpleNamespac
594615
with pytest.raises(android_smoke.SmokeFailure, match="remove remote test directory"):
595616
android_smoke._cleanup(config, "SERIAL-1")
596617

597-
assert len([command for command in root_commands if command.startswith("pkill")]) == 2
598-
assert len([command for command in root_commands if command.startswith("pgrep")]) == 2
618+
assert len([command for command in root_commands if "/proc/[0-9]*/exe" in command]) == 2
619+
assert not any(command.startswith(("pkill", "pgrep")) for command in root_commands)
599620
assert ("forward", "--remove", "tcp:27142") in adb_commands
600621
assert ("forward", "--remove", "tcp:27143") in adb_commands
601622
assert ("forward", "--list") in adb_commands
@@ -650,27 +671,25 @@ def fake_root_shell(_serial: str, command: str, **_kwargs: object) -> SimpleName
650671
android_smoke._cleanup(config, "SERIAL-1")
651672

652673

653-
def test_cleanup_uses_kernel_comm_limit_for_long_server_name(
654-
monkeypatch: pytest.MonkeyPatch,
655-
) -> None:
656-
root_commands: list[str] = []
674+
def test_remote_signal_is_scoped_to_exact_long_name_executable() -> None:
675+
remote_executable = "/data/local/tmp/phantom-frida-test/abcdefghijklmnopqrst-server"
657676

658-
def fake_root_shell(_serial: str, command: str, **_kwargs: object) -> SimpleNamespace:
659-
root_commands.append(command)
660-
return SimpleNamespace(
661-
returncode=1 if command.startswith(("pkill", "pgrep")) else 0,
662-
stdout="",
663-
stderr="",
664-
)
677+
command = android_smoke._remote_signal_command(remote_executable, 123, "TERM")
665678

666-
monkeypatch.setattr(android_smoke, "root_shell", fake_root_shell)
667-
monkeypatch.setattr(
668-
android_smoke,
669-
"adb",
670-
lambda *_args, **_kwargs: SimpleNamespace(returncode=0, stdout="", stderr=""),
671-
)
672-
config = SimpleNamespace(name="abcdefghijklmnopqrst", port=27142)
679+
assert "readlink /proc/123/exe" in command
680+
assert remote_executable in command
681+
assert "kill -TERM 123" in command
682+
assert "pkill" not in command
673683

674-
android_smoke._cleanup(config, "SERIAL-1")
675684

676-
assert "pkill -TERM -x abcdefghijklmno" in root_commands
685+
def test_parse_remote_processes_excludes_same_basename_outside_test_directory() -> None:
686+
remote_executable = "/data/local/tmp/phantom-frida-test/oemcodec-server"
687+
output = (
688+
f"lrwxrwxrwx root /proc/101/exe -> {remote_executable}\n"
689+
f"lrwxrwxrwx root /proc/102/exe -> {remote_executable} (deleted)\n"
690+
"lrwxrwxrwx root /proc/103/exe -> /other/path/oemcodec-server\n"
691+
)
692+
693+
assert android_smoke._parse_remote_processes(output, (remote_executable,)) == {
694+
remote_executable: [101, 102]
695+
}

0 commit comments

Comments
 (0)