chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
"""Native-backend Ctrl+C handling: a SIGINT handler cancels the in-flight turn
|
||||
and never exits the session (Ctrl+D still owns exit), fixing the old behavior
|
||||
where a single Ctrl+C at the prompt was treated as EOF and quit chat."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from opensquilla.cli.tui.adapters import native_bridge
|
||||
from opensquilla.cli.tui.adapters.native_bridge import (
|
||||
NativeTerminalSurface,
|
||||
open_native_terminal_surface,
|
||||
)
|
||||
from opensquilla.engine.commands import Surface
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ctrl_d_ends_the_session(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
def fake_input(_prompt: str) -> str:
|
||||
raise EOFError
|
||||
|
||||
monkeypatch.setattr(native_bridge.console, "input", fake_input)
|
||||
surface = NativeTerminalSurface(approval_surface=Surface.CLI_GATEWAY)
|
||||
shutdown: list[bool] = []
|
||||
surface.set_shutdown_callback(lambda: shutdown.append(True))
|
||||
|
||||
assert await surface.next_line() is None
|
||||
assert shutdown == [True]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_keyboardinterrupt_fallback_cancels_and_reprompts(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
# The fallback path (used where add_signal_handler is unavailable): a
|
||||
# KeyboardInterrupt must cancel the in-flight turn and re-prompt, never exit.
|
||||
calls = 0
|
||||
|
||||
def fake_input(_prompt: str) -> str:
|
||||
nonlocal calls
|
||||
calls += 1
|
||||
if calls == 1:
|
||||
raise KeyboardInterrupt
|
||||
return "after the interrupt"
|
||||
|
||||
monkeypatch.setattr(native_bridge.console, "input", fake_input)
|
||||
surface = NativeTerminalSurface(approval_surface=Surface.CLI_GATEWAY)
|
||||
cancelled: list[bool] = []
|
||||
shutdown: list[bool] = []
|
||||
surface.set_cancel_callback(lambda: cancelled.append(True))
|
||||
surface.set_shutdown_callback(lambda: shutdown.append(True))
|
||||
|
||||
assert await surface.next_line() == "after the interrupt"
|
||||
assert cancelled == [True]
|
||||
assert shutdown == [] # never shuts down on Ctrl+C
|
||||
|
||||
|
||||
def test_on_sigint_cancels_turn_without_exiting() -> None:
|
||||
surface = NativeTerminalSurface(approval_surface=Surface.CLI_GATEWAY)
|
||||
cancelled: list[bool] = []
|
||||
shutdown: list[bool] = []
|
||||
surface.set_cancel_callback(lambda: cancelled.append(True))
|
||||
surface.set_shutdown_callback(lambda: shutdown.append(True))
|
||||
|
||||
surface._on_sigint()
|
||||
|
||||
assert cancelled == [True]
|
||||
assert shutdown == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
sys.platform.startswith("win"),
|
||||
reason="Windows delivers SIGINT to pytest itself; fallback behavior is covered separately",
|
||||
)
|
||||
async def test_real_sigint_cancels_turn_and_does_not_exit() -> None:
|
||||
cancelled: list[bool] = []
|
||||
shutdown: list[bool] = []
|
||||
async with open_native_terminal_surface(surface=Surface.CLI_GATEWAY) as surface:
|
||||
surface.set_cancel_callback(lambda: cancelled.append(True))
|
||||
surface.set_shutdown_callback(lambda: shutdown.append(True))
|
||||
os.kill(os.getpid(), signal.SIGINT) # the real Ctrl+C signal
|
||||
await asyncio.sleep(0.05) # let the loop run the installed handler
|
||||
|
||||
assert cancelled == [True] # the turn was cancelled
|
||||
assert shutdown == [] # the session did NOT exit
|
||||
Reference in New Issue
Block a user