4b6817381b
Benchmark image — build + push to ECR (any adapter) / build + push (push) Waiting to run
CI / quality (ubuntu-latest) (push) Waiting to run
CI / test (tools-runtime) (push) Waiting to run
CI / test (e2e-general) (push) Waiting to run
CI / test (cli-runtime) (push) Waiting to run
CI / test (e2e-provider-and-openclaw) (push) Waiting to run
CI / test (integrations-and-misc) (push) Waiting to run
CI / coverage-report (push) Blocked by required conditions
CI / test-kubernetes (push) Waiting to run
CI / should-run-thorough (push) Waiting to run
CI / test-thorough (cloudwatch-demo) (push) Blocked by required conditions
CI / test-thorough (flink-ecs) (push) Blocked by required conditions
CI / test-thorough (upstream-lambda) (push) Blocked by required conditions
CI / test-thorough (prefect-ecs-fargate) (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Blocked by required conditions
Release / publish-release (push) Blocked by required conditions
Release / publish-main-release (push) Blocked by required conditions
Release / prepare (push) Waiting to run
Release / verify (push) Blocked by required conditions
Release / build-python-dist (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Blocked by required conditions
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Blocked by required conditions
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Waiting to run
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Waiting to run
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
186 lines
6.0 KiB
Python
186 lines
6.0 KiB
Python
from __future__ import annotations
|
|
|
|
import time
|
|
|
|
import pytest
|
|
import questionary
|
|
from prompt_toolkit.input.defaults import create_pipe_input # type: ignore[import-not-found]
|
|
from prompt_toolkit.output import DummyOutput # type: ignore[import-not-found]
|
|
|
|
from platform.terminal.prompt_support import (
|
|
_last_ctrl_c,
|
|
handle_ctrl_c_press,
|
|
install_questionary_ctrl_c_double_exit,
|
|
install_questionary_escape_cancel,
|
|
print_session_resume_hint,
|
|
)
|
|
|
|
|
|
def test_print_session_resume_hint_includes_repl_and_cli_commands(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
from io import StringIO
|
|
|
|
from rich.console import Console
|
|
|
|
monkeypatch.setattr("platform.terminal.prompt_support.sys.argv", ["o"])
|
|
console = Console(file=StringIO(), force_terminal=False, color_system=None)
|
|
print_session_resume_hint(console, "8988e743-87ae-4c4c-a37b-0351e62a4855")
|
|
output = console.file.getvalue()
|
|
assert "Resume this session with:" in output
|
|
assert "/resume 8988e743-87ae-4c4c-a37b-0351e62a4855" in output
|
|
assert "o --resume 8988e743-87ae-4c4c-a37b-0351e62a4855" in output
|
|
|
|
|
|
def test_install_questionary_escape_cancel_is_idempotent() -> None:
|
|
install_questionary_escape_cancel()
|
|
first = questionary.select
|
|
install_questionary_escape_cancel()
|
|
assert questionary.select is first
|
|
|
|
|
|
def test_stock_questionary_select_escape_cancels() -> None:
|
|
install_questionary_escape_cancel()
|
|
with create_pipe_input() as pipe_input:
|
|
q = questionary.select(
|
|
"Pick",
|
|
choices=["a", "b"],
|
|
input=pipe_input,
|
|
output=DummyOutput(),
|
|
)
|
|
pipe_input.send_bytes(b"\x1b")
|
|
app = q.application
|
|
app.input = pipe_input
|
|
app.output = DummyOutput()
|
|
assert app.run() is None
|
|
|
|
|
|
def test_stock_questionary_confirm_escape_cancels() -> None:
|
|
"""Verify that pressing Escape cancels a confirm prompt (Issue #1117).
|
|
|
|
Sends the Escape byte (\\x1b) to a questionary.confirm application
|
|
and asserts that it returns None instead of hanging.
|
|
"""
|
|
install_questionary_escape_cancel()
|
|
with create_pipe_input() as pipe_input:
|
|
q = questionary.confirm(
|
|
"Are you sure?",
|
|
input=pipe_input,
|
|
output=DummyOutput(),
|
|
)
|
|
pipe_input.send_bytes(b"\x1b")
|
|
app = q.application
|
|
app.input = pipe_input
|
|
app.output = DummyOutput()
|
|
assert app.run() is None
|
|
|
|
|
|
def test_stock_questionary_text_escape_cancels() -> None:
|
|
"""Verify that pressing Escape cancels a text input prompt (Issue #1117).
|
|
|
|
Sends the Escape byte (\\x1b) to a questionary.text application
|
|
and asserts that it returns None.
|
|
"""
|
|
install_questionary_escape_cancel()
|
|
with create_pipe_input() as pipe_input:
|
|
q = questionary.text(
|
|
"Name",
|
|
input=pipe_input,
|
|
output=DummyOutput(),
|
|
)
|
|
pipe_input.send_bytes(b"\x1b")
|
|
app = q.application
|
|
app.input = pipe_input
|
|
app.output = DummyOutput()
|
|
assert app.run() is None
|
|
|
|
|
|
def test_stock_questionary_path_escape_cancels() -> None:
|
|
"""Verify that pressing Escape cancels a path selection prompt (Issue #1117).
|
|
|
|
Sends the Escape byte (\\x1b) to a questionary.path application
|
|
and asserts that it returns None.
|
|
"""
|
|
install_questionary_escape_cancel()
|
|
with create_pipe_input() as pipe_input:
|
|
q = questionary.path(
|
|
"Path",
|
|
input=pipe_input,
|
|
output=DummyOutput(),
|
|
)
|
|
pipe_input.send_bytes(b"\x1b")
|
|
app = q.application
|
|
app.input = pipe_input
|
|
app.output = DummyOutput()
|
|
assert app.run() is None
|
|
|
|
|
|
def test_install_questionary_ctrl_c_double_exit_is_idempotent() -> None:
|
|
install_questionary_ctrl_c_double_exit()
|
|
first = questionary.select
|
|
install_questionary_ctrl_c_double_exit()
|
|
assert questionary.select is first
|
|
|
|
|
|
def test_ctrl_c_first_press_shows_hint_and_reprompts(capsys) -> None:
|
|
"""First Ctrl+C prints the hint and re-displays the prompt; Enter then submits."""
|
|
_last_ctrl_c[0] = None
|
|
install_questionary_ctrl_c_double_exit()
|
|
with create_pipe_input() as pipe_input:
|
|
q = questionary.select(
|
|
"Pick",
|
|
choices=["a", "b"],
|
|
input=pipe_input,
|
|
output=DummyOutput(),
|
|
)
|
|
# Ctrl+C cancels the first run; Enter submits the re-displayed prompt.
|
|
pipe_input.send_bytes(b"\x03\r")
|
|
result = q.ask()
|
|
assert "(Press Ctrl+C again to exit)" in capsys.readouterr().out
|
|
# After the hint the prompt was re-run and "a" was selected (first choice).
|
|
assert result == "a"
|
|
|
|
|
|
def test_ctrl_c_second_press_exits(capsys) -> None:
|
|
# Simulate a previous Ctrl+C just now so the second press fires immediately.
|
|
_last_ctrl_c[0] = time.monotonic()
|
|
with pytest.raises(SystemExit) as exc_info:
|
|
handle_ctrl_c_press()
|
|
assert exc_info.value.code == 0
|
|
assert "Goodbye" in capsys.readouterr().out
|
|
|
|
|
|
def test_ctrl_c_hint_resets_after_window(capsys) -> None:
|
|
# A press older than the exit window should show the hint again, not exit.
|
|
_last_ctrl_c[0] = None # effectively "long ago"
|
|
handle_ctrl_c_press()
|
|
out = capsys.readouterr().out
|
|
assert "(Press Ctrl+C again to exit)" in out
|
|
|
|
|
|
def test_questionary_ask_inside_running_event_loop_does_not_raise() -> None:
|
|
"""q.ask() called from within a running asyncio event loop must not raise.
|
|
|
|
Regression test for Sentry issue #1650: asyncio.run() cannot be called
|
|
from a running event loop — triggered when questionary prompts are shown
|
|
inside the async REPL dispatch path.
|
|
"""
|
|
import asyncio
|
|
|
|
_last_ctrl_c[0] = None
|
|
install_questionary_ctrl_c_double_exit()
|
|
|
|
async def _run() -> object:
|
|
with create_pipe_input() as pipe_input:
|
|
q = questionary.select(
|
|
"Pick",
|
|
choices=["a", "b"],
|
|
input=pipe_input,
|
|
output=DummyOutput(),
|
|
)
|
|
pipe_input.send_bytes(b"\r")
|
|
return q.ask()
|
|
|
|
result = asyncio.run(_run())
|
|
assert result == "a"
|