# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json from pathlib import Path import time import pytest from artemis.runtime.awake_lease import ScreenAwakeLease @pytest.fixture(autouse=True) def isolated_awake_directory(tmp_path, monkeypatch): monkeypatch.setattr( "artemis.runtime.awake_lease.get_temp_dir", lambda _name: tmp_path, ) def test_first_lease_drains_legacy_references_and_acquires_exactly_one(monkeypatch): lease = ScreenAwakeLease("device-123") count = 3 def state(): return (count > 0, count) def set_lock(action): nonlocal count count += 1 if action == "acquire" else -1 return True monkeypatch.setattr(lease, "_wake_lock_state", state) monkeypatch.setattr(lease, "_set_wake_lock", set_lock) assert lease.acquire() assert count == 1 assert lease.lease_path.exists() lease.release() assert count == 0 def test_overlapping_clients_share_one_android_reference(monkeypatch): first = ScreenAwakeLease("device-123") second = ScreenAwakeLease("device-123") count = 0 def state(): return (count > 0, count) def set_lock(action): nonlocal count count += 1 if action == "acquire" else -1 return True for lease in (first, second): monkeypatch.setattr(lease, "_wake_lock_state", state) monkeypatch.setattr(lease, "_set_wake_lock", set_lock) assert first.acquire() assert second.acquire() assert count == 1 first.release() assert count == 1 second.release() assert count == 0 def test_crashed_client_is_pruned_and_legacy_reference_is_recovered(monkeypatch): crashed = ScreenAwakeLease("device-123") crashed.lease_dir.mkdir(parents=True, exist_ok=True) crashed.lease_path.write_text( json.dumps( { "pid": 999999, "process_created_at": 1.0, "token": crashed.token, "device_id": "device-123", } ), encoding="utf-8", ) replacement = ScreenAwakeLease("device-123") count = 1 def state(): return (count > 0, count) def set_lock(action): nonlocal count count += 1 if action == "acquire" else -1 return True monkeypatch.setattr(replacement, "_wake_lock_state", state) monkeypatch.setattr(replacement, "_set_wake_lock", set_lock) monkeypatch.setattr( ScreenAwakeLease, "_owner_is_alive", staticmethod(lambda payload: int(payload["pid"]) != 999999), ) assert replacement.acquire() assert not crashed.lease_path.exists() assert count == 1 replacement.release() assert count == 0 def test_migration_cleanup_never_drains_a_live_legacy_owner(monkeypatch): owner = ScreenAwakeLease("device-123") owner.lease_dir.mkdir(parents=True, exist_ok=True) owner.lease_path.write_text(json.dumps(owner._payload()), encoding="utf-8") cleanup = ScreenAwakeLease("device-123") cleanup._drain_references = lambda: pytest.fail("must not drain a live owner") assert cleanup.cleanup_unowned_references() is False def test_optional_cleanup_skips_a_busy_mutex_without_delaying_task_start(monkeypatch): owner = ScreenAwakeLease("device-123") owner.mutex_path.write_text(json.dumps(owner._payload()), encoding="utf-8") cleanup = ScreenAwakeLease("device-123") monkeypatch.setattr(cleanup, "_drain_references", lambda: pytest.fail("must not drain")) started_at = time.monotonic() assert cleanup.cleanup_unowned_references() is False assert time.monotonic() - started_at < 2.0 def test_unremovable_stale_mutex_still_obeys_deadline(monkeypatch): lease = ScreenAwakeLease("device-123") lease.mutex_path.write_text(json.dumps({"pid": 999999}), encoding="utf-8") original_unlink = Path.unlink def refuse_mutex_unlink(path, *args, **kwargs): if path == lease.mutex_path: raise PermissionError("file is in use") return original_unlink(path, *args, **kwargs) monkeypatch.setattr(Path, "unlink", refuse_mutex_unlink) with pytest.raises(TimeoutError, match="awake-lease mutex"): with lease._mutex(wait_timeout_seconds=0.1): pytest.fail("stale mutex must not be acquired")