"""Runtime snapshots are bounded, read-only evidence, never a task gate.""" import ctypes import hashlib import json from types import SimpleNamespace from unittest.mock import AsyncMock, Mock import psutil import pytest from artemis.runtime import diagnostics as module from artemis.runtime.adb_endpoint import AdbEndpoint @pytest.fixture(autouse=True) def isolated_identity_cache(): identity = module.runtime_identity identity.cache_clear() yield identity.cache_clear() def test_tool_identity_records_exact_binary_hash_and_version(monkeypatch, tmp_path): adb = tmp_path / "platform tools" / "adb.exe" adb.parent.mkdir() adb.write_bytes(b"selected adb") monkeypatch.setattr(module.toolchain, "resolve", lambda _: str(adb)) command = Mock(return_value="Android Debug Bridge version test") monkeypatch.setattr(module, "_command", command) record = module._tool_identity("adb") assert record["path"] == str(adb) assert record["sha256"] == hashlib.sha256(b"selected adb").hexdigest() assert record["version"] == command.return_value command.assert_called_once_with([str(adb), "version"]) def test_unknown_source_and_archive_identity_are_not_invented(monkeypatch, tmp_path): monkeypatch.setattr(module, "ROOT_DIR", tmp_path) monkeypatch.setattr(module, "get_version", lambda: "test-version") command = Mock() monkeypatch.setattr(module, "_command", command) monkeypatch.setenv("OPENAI_API_KEY", "private-test-key") record = module.runtime_identity(False) assert record["source_commit"] is None assert record["source_modified"] is None assert record["installation_archive_sha256"] is None assert record["artemis_version"] == "test-version" assert record["tools"] == {} assert "private-test-key" not in json.dumps(record) command.assert_not_called() def test_version_commands_are_bounded_and_never_repair_devices(monkeypatch): run = Mock(return_value=SimpleNamespace(stdout="version", returncode=0)) monkeypatch.setattr(module.subprocess, "run", run) assert module._command(["adb", "version"]) == "version" assert run.call_args.kwargs["timeout"] == 3 assert run.call_args.kwargs["check"] is False assert run.call_args.args == (["adb", "version"],) run.side_effect = OSError("Cannot create process") assert module._command(["adb", "version"]) is None @pytest.mark.skipif(module.sys.platform != "win32", reason="Windows PSAPI structure") def test_system_commit_converts_pages_to_bytes(monkeypatch): def query(pointer, size): values = pointer._obj assert size == ctypes.sizeof(module._PerformanceInformation) values.PageSize = 4096 values.CommitTotal = 900 values.CommitLimit = 1000 return True dll = SimpleNamespace(GetPerformanceInfo=Mock(side_effect=query)) monkeypatch.setattr(module.ctypes, "WinDLL", lambda *args, **kwargs: dll) result = module._windows_commit() assert result["committed_bytes"] == 900 * 4096 assert result["limit_bytes"] == 1000 * 4096 assert result["available_bytes"] == 100 * 4096 def test_remote_adb_process_is_explicitly_unknown(monkeypatch): monkeypatch.setattr( module, "current_adb_endpoint", lambda: AdbEndpoint.create("192.0.2.10", 5037) ) connections = Mock() monkeypatch.setattr(module.psutil, "net_connections", connections) result = module._adb_server() assert result["local_listener"] is None assert result["endpoint"]["host"] == "192.0.2.10" connections.assert_not_called() def test_local_adb_listener_identity_excludes_unrelated_bindings(monkeypatch): monkeypatch.setattr(module, "current_adb_endpoint", lambda: AdbEndpoint.local()) connections = [ SimpleNamespace(status=psutil.CONN_LISTEN, laddr=SimpleNamespace(ip=ip, port=5037), pid=pid) for ip, pid in [("192.0.2.10", 5), ("127.0.0.1", 6)] ] monkeypatch.setattr(module.psutil, "net_connections", lambda **_: connections) process = SimpleNamespace(pid=6, name=lambda: "adb.exe", exe=lambda: "selected/adb.exe") lookup = Mock(return_value=process) monkeypatch.setattr(module.psutil, "Process", lookup) assert module._adb_server()["local_listener"] == { "pid": 6, "name": "adb.exe", "path": "selected/adb.exe", } lookup.assert_called_once_with(6) def test_start_snapshot_survives_failure_and_repeated_start(monkeypatch, tmp_path): identity = Mock(return_value={"source_commit": "test-commit"}) memory = Mock(side_effect=[{"available_bytes": 100}, {"available_bytes": 1}]) monkeypatch.setattr(module, "runtime_identity", identity) monkeypatch.setattr(module, "_memory_snapshot", memory) monkeypatch.setattr(module, "_adb_server", lambda: {"local_listener": None}) module.write_runtime_diagnostics(tmp_path, "start") module.write_runtime_diagnostics(tmp_path, "start") module.write_runtime_diagnostics(tmp_path, "failure") payload = json.loads((tmp_path / module.FILENAME).read_text(encoding="utf-8")) assert payload["start"]["memory"]["available_bytes"] == 100 assert payload["failure"]["memory"]["available_bytes"] == 1 assert payload["failure"]["runtime"]["source_commit"] == "test-commit" assert memory.call_count == 2 assert not (tmp_path / "runtime_diagnostics.tmp").exists() def test_snapshot_failure_cannot_fail_a_task(monkeypatch, tmp_path): monkeypatch.setattr(module, "runtime_identity", lambda _: {}) monkeypatch.setattr(module, "_memory_snapshot", Mock(side_effect=OSError("commit exhausted"))) module.write_runtime_diagnostics(tmp_path, "failure") assert not (tmp_path / module.FILENAME).exists() @pytest.mark.asyncio @pytest.mark.parametrize( "error", [OSError("commit exhausted"), RuntimeError("cannot start thread")] ) async def test_snapshot_thread_creation_failure_is_optional(monkeypatch, tmp_path, error): monkeypatch.setattr(module.asyncio, "to_thread", AsyncMock(side_effect=error)) await module.capture_runtime_diagnostics(tmp_path, "start") assert not (tmp_path / module.FILENAME).exists()