Add dark theme, richer Markdown and readable plugin results. Improve Android input evidence, response integrity and diagnostics. Unify lifecycle, guidance receipts and evidence sources across clients. Keep direct action dispatch without an added AI quota approval layer. Align version metadata at 2.2.0 and bundle Helper 1.2.2. Validation: 3063 backend tests before the version bump; 44 after. Frontend: 317 tests and production build passed.
119 lines
4.0 KiB
Python
119 lines
4.0 KiB
Python
# 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 asyncio
|
|
import json
|
|
import time
|
|
|
|
import pytest
|
|
|
|
from artemis.runtime import cancel_requests as cr
|
|
from artemis.runtime.cancellation import CancellationReason
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def isolated_dir(tmp_path, monkeypatch):
|
|
directory = tmp_path / "cancel-requests"
|
|
monkeypatch.setattr(cr, "get_temp_dir", lambda _subfolder=None: directory)
|
|
return directory
|
|
|
|
|
|
def test_session_marker_roundtrip(isolated_dir):
|
|
assert not cr.is_cancel_requested(session_id="s-1")
|
|
|
|
written = cr.request_cancel(session_id="s-1", reason="test")
|
|
|
|
assert len(written) == 1
|
|
assert written[0].parent == isolated_dir
|
|
assert cr.is_cancel_requested(session_id="s-1")
|
|
|
|
cr.clear_cancel_request(session_id="s-1")
|
|
|
|
assert not cr.is_cancel_requested(session_id="s-1")
|
|
|
|
|
|
def test_pid_marker_ignores_recycled_pid():
|
|
cr.request_cancel(pid=4242, process_created_at=1000.0)
|
|
|
|
assert cr.is_cancel_requested(pid=4242, process_created_at=1000.4)
|
|
assert not cr.is_cancel_requested(pid=4242, process_created_at=2000.0)
|
|
|
|
# A marker without a creation time applies to whoever holds the pid.
|
|
cr.request_cancel(pid=4343)
|
|
assert cr.is_cancel_requested(pid=4343, process_created_at=5.0)
|
|
|
|
|
|
def test_expired_markers_are_ignored_and_purged():
|
|
path = cr.request_cancel(session_id="old")[0]
|
|
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
payload["requested_at"] = time.time() - cr.MARKER_TTL_SECONDS - 5
|
|
path.write_text(json.dumps(payload), encoding="utf-8")
|
|
|
|
assert not cr.is_cancel_requested(session_id="old")
|
|
assert cr.purge_expired() == 1
|
|
assert not path.exists()
|
|
|
|
|
|
def test_unreadable_pid_marker_is_not_trusted(isolated_dir):
|
|
isolated_dir.mkdir(parents=True, exist_ok=True)
|
|
(isolated_dir / "pid-77.cancel").write_text("{not json", encoding="utf-8")
|
|
|
|
assert not cr.is_cancel_requested(pid=77, process_created_at=1.0)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_watch_delivers_once_and_clears_marker():
|
|
delivered: list[int] = []
|
|
watcher = asyncio.create_task(
|
|
cr.watch_for_cancel_request(
|
|
lambda: delivered.append(1),
|
|
session_id="w-1",
|
|
pid=0,
|
|
poll_seconds=0.05,
|
|
)
|
|
)
|
|
await asyncio.sleep(0.12)
|
|
assert not delivered
|
|
assert not watcher.done()
|
|
|
|
cr.request_cancel(session_id="w-1")
|
|
|
|
assert await asyncio.wait_for(watcher, timeout=2) is True
|
|
assert delivered == [1]
|
|
assert not cr.is_cancel_requested(session_id="w-1")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize("cause", [CancellationReason.USER_STOP, CancellationReason.UNKNOWN])
|
|
async def test_watch_forwards_only_the_structured_cause(cause):
|
|
path = cr.request_cancel(
|
|
session_id="with-reason", reason="private stop details", cancel_reason=cause
|
|
)[0]
|
|
delivered = []
|
|
assert await cr.watch_for_cancel_request(
|
|
delivered.append, session_id="with-reason", pid=0, pass_reason=True
|
|
)
|
|
assert delivered == [cause]
|
|
assert not path.exists()
|
|
|
|
|
|
def test_legacy_or_unrecognized_marker_reasons_remain_unknown():
|
|
path = cr.request_cancel(session_id="legacy", reason="stop from UI")[0]
|
|
payload = json.loads(path.read_text(encoding="utf-8"))
|
|
for value in [None, "private user instruction", ["user_stop"]]:
|
|
payload["cancel_reason"] = value
|
|
path.write_text(json.dumps(payload), encoding="utf-8")
|
|
assert cr.get_cancel_reason(session_id="legacy") is CancellationReason.UNKNOWN
|
|
assert cr.is_cancel_requested(session_id="legacy")
|