testsAndMisc/python_pkg/phone_lease/tests/test_main.py
Krzysztof kuhy Rudnicki c0c51036fc phone_lease: per-app leases, a self-cleaning whole-phone lease, one serial
Several sessions can now share one phone: a lease covers one package (the
real screen stays the default scope, so existing callers keep their
meaning), and only phone-wide actions take `--whole-phone`, which is capped
at 300 s and runs its `--cleanup` commands exactly once on release or, via a
detached reaper, on expiry. The serial is always resolved, never the
"default" placeholder that let android_ui and phone_deploy lease different
keys for the same phone (2026-09-25). Adds status --json, holds and whoami
for phone_vd.sh and the raw-adb hook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Ajzz98xksbfak8CaunZWC
2026-09-25 15:05:57 +02:00

118 lines
4.5 KiB
Python

"""The CLI wrapper: exit codes and the one-line outputs."""
from __future__ import annotations
import json
from pathlib import Path
import runpy
import sys
from typing import TYPE_CHECKING
from unittest.mock import patch
import pytest
from python_pkg.phone_lease import __main__ as cli
from python_pkg.phone_lease import _cleanup
from python_pkg.phone_lease import lease as lease_mod
if TYPE_CHECKING:
from collections.abc import Iterator
@pytest.fixture(autouse=True)
def lease_dir(tmp_path: Path) -> Iterator[None]:
with patch.object(lease_mod, "LEASE_DIR", tmp_path / "leases"):
yield
def test_acquire_then_status_then_release(capsys: pytest.CaptureFixture[str]) -> None:
assert cli.main(["acquire", "PIXEL", "--note", "deploy"]) == 0
assert "held by" in capsys.readouterr().out
assert cli.main(["status", "PIXEL"]) == 0
assert "(deploy)" in capsys.readouterr().out
assert cli.main(["release", "PIXEL"]) == 0
assert capsys.readouterr().out == "released\n"
assert cli.main(["status", "PIXEL"]) == 0
assert capsys.readouterr().out == "free\n"
def test_release_without_holding(capsys: pytest.CaptureFixture[str]) -> None:
assert cli.main(["release", "PIXEL"]) == 1
assert "not held by" in capsys.readouterr().out
def test_foreign_holder_exits_3(capsys: pytest.CaptureFixture[str]) -> None:
lease_mod.acquire(
"PIXEL", "manga", owner="someone-else", terms=lease_mod.Terms(ttl=1000)
)
assert cli.main(["acquire", "PIXEL", "--wait", "0"]) == 3
assert "held by someone-else (manga)" in capsys.readouterr().err
def test_module_entry_point() -> None:
# runpy warns if __main__ is already imported (this file imports it), so
# drop it first; the test then runs the real module top to bottom.
sys.modules.pop("python_pkg.phone_lease.__main__", None)
with (
patch("sys.argv", ["phone-lease", "status", "PIXEL"]),
pytest.raises(SystemExit) as excinfo,
):
runpy.run_module("python_pkg.phone_lease", run_name="__main__")
assert excinfo.value.code == 0
def test_package_scopes_coexist_and_holds(capsys: pytest.CaptureFixture[str]) -> None:
with patch.dict("os.environ", {"PHONE_LEASE_OWNER": "me"}):
assert cli.main(["acquire", "PIXEL", "--package", "a.b"]) == 0
assert "[pkg:a.b]" in capsys.readouterr().out
assert cli.main(["holds", "PIXEL", "--package", "a.b"]) == 0
assert cli.main(["holds", "PIXEL", "--package", "c.d"]) == 1
assert cli.main(["holds", "PIXEL", "--package", "a.b", "--owner", "x"]) == 1
assert cli.main(["release", "PIXEL", "--package", "a.b"]) == 0
def test_whole_phone_with_cleanup(capsys: pytest.CaptureFixture[str]) -> None:
with (
patch.object(_cleanup, "spawn_reaper") as spawn,
patch.object(_cleanup, "run") as run,
):
argv = ["acquire", "PIXEL", "--whole-phone", "--cleanup", "adb x", "--ttl", "9"]
assert cli.main(argv) == 0
spawn.assert_called_once()
assert cli.main(["release", "PIXEL", "--whole-phone"]) == 0
run.assert_called_once_with(("adb x",))
assert "[whole-phone]" in capsys.readouterr().out
def test_status_json_and_text(capsys: pytest.CaptureFixture[str]) -> None:
assert cli.main(["status", "PIXEL", "--json"]) == 0
assert json.loads(capsys.readouterr().out) == []
lease_mod.acquire("PIXEL", "n", owner="o")
assert cli.main(["status", "PIXEL", "--json"]) == 0
(only,) = json.loads(capsys.readouterr().out)
assert (only["owner"], only["scope"]) == ("o", "pkg:__screen__")
def test_whoami(capsys: pytest.CaptureFixture[str]) -> None:
with patch.dict("os.environ", {"PHONE_LEASE_OWNER": "claude:42"}):
assert cli.main(["whoami"]) == 0
assert capsys.readouterr().out == "claude:42\n"
def test_reap_subcommand_delegates() -> None:
with patch.object(cli, "reap") as reap:
assert cli.main(["reap", "S", "--owner", "o", "--scope", "whole-phone"]) == 0
reap.assert_called_once_with("S", "o", "whole-phone")
def test_serial_resolved_or_exit_4(capsys: pytest.CaptureFixture[str]) -> None:
with patch.dict("os.environ", {"ANDROID_SERIAL": "ENVPHONE"}):
assert cli.main(["status"]) == 0
assert capsys.readouterr().out == "free\n"
with (
patch.dict("os.environ", {"ANDROID_SERIAL": "", "ADB_SERIAL": ""}),
patch("python_pkg.phone_lease.serial._adb_serial", return_value=""),
):
assert cli.main(["status"]) == 4
assert "no phone serial" in capsys.readouterr().err