Skip to content

Commit af7db35

Browse files
committed
Wait for alert processing before quitting in async alert tests
1 parent 75e5334 commit af7db35

1 file changed

Lines changed: 35 additions & 5 deletions

File tree

‎tests/test_cmd2.py‎

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import signal
66
import sys
77
import tempfile
8+
import threading
89
from code import InteractiveConsole
10+
from concurrent.futures import ThreadPoolExecutor
911
from typing import (
1012
NoReturn,
1113
cast,
@@ -1272,6 +1274,37 @@ def test_ctrl_d_at_prompt(say_app, monkeypatch) -> None:
12721274
assert out == "hello\n\n"
12731275

12741276

1277+
def _run_until_alerts_processed(app: cmd2.Cmd, pipe_input) -> None:
1278+
"""Quit only once the alert worker has finished the queued batch.
1279+
1280+
Sending quit before starting the prompt races command input against the alert
1281+
worker. Observe the worker returning to its condition wait after draining the
1282+
queue, so even a stale prompt-only alert (which emits nothing) is accounted for.
1283+
"""
1284+
assert app._alert_queue
1285+
processed = threading.Event()
1286+
wait_for = app._alert_condition.wait_for
1287+
1288+
def observe_wait(predicate, timeout=None):
1289+
# The worker still holds the condition here; all effects of the previous
1290+
# batch, including printing or invalidation, have completed.
1291+
if not app._alert_queue:
1292+
processed.set()
1293+
return wait_for(predicate, timeout)
1294+
1295+
def quit_after_processing() -> None:
1296+
try:
1297+
assert processed.wait(timeout=5), "the alert worker never finished the queued batch"
1298+
finally:
1299+
# Also release the prompt on failure, so the assertion cannot hang it.
1300+
pipe_input.send_text("quit\n")
1301+
1302+
with mock.patch.object(app._alert_condition, "wait_for", side_effect=observe_wait), ThreadPoolExecutor() as executor:
1303+
interaction = executor.submit(quit_after_processing)
1304+
app._cmdloop()
1305+
interaction.result(timeout=5)
1306+
1307+
12751308
@pytest.mark.skipif(
12761309
sys.platform.startswith("win"),
12771310
reason="Don't have a real Windows console with how we are currently running tests in GitHub Actions",
@@ -1318,9 +1351,7 @@ def test_async_alert(base_app: cmd2.Cmd, msg: str, prompt: str, is_stale: bool)
13181351
history=base_app.main_session.history,
13191352
completer=base_app.main_session.completer,
13201353
)
1321-
pipe_input.send_text("quit\n")
1322-
1323-
base_app._cmdloop()
1354+
_run_until_alerts_processed(base_app, pipe_input)
13241355

13251356
# If there was a message, patch_stdout handles the redraw (no invalidate)
13261357
if msg:
@@ -1370,8 +1401,7 @@ def test_async_alert_rich(base_app: cmd2.Cmd) -> None:
13701401
history=base_app.main_session.history,
13711402
completer=base_app.main_session.completer,
13721403
)
1373-
pipe_input.send_text("quit\n")
1374-
base_app._cmdloop()
1404+
_run_until_alerts_processed(base_app, pipe_input)
13751405

13761406
# Verify that print_formatted_text was called with a formatted ANSI object
13771407
assert mock_print.called

0 commit comments

Comments
 (0)