Skip to content

Commit 1345818

Browse files
committed
Cover the last untested job-control lines in cmd2.utils
- A Ctrl-Z that reaches cmd2 directly while the pipeline's group is already gone: cmd2 neither counts the stop nor continues the group later, and still suspends itself. - A descriptor relay whose producers have all closed its pipe finishes and closes the consumer's pipe, and then has nothing pending: flush() returns at once and idle() is true.
1 parent 3fe7a97 commit 1345818

1 file changed

Lines changed: 48 additions & 0 deletions

File tree

‎tests/test_utils.py‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,25 @@ def test_pipeline_writer_relay_leaves_the_terminal_after_a_producer_finishes() -
451451
os.close(read_fd)
452452

453453

454+
@pytest.mark.skipif(sys.platform == "win32", reason="POSIX pipes")
455+
def test_descriptor_relay_that_finished_has_nothing_pending() -> None:
456+
"""Once every producer has closed the relay's pipe, the relay finishes and closes it, and nothing is left to wait for."""
457+
read_fd, write_fd = os.pipe()
458+
relay = cu._DescriptorRelay(write_fd, mock.Mock(_lend_terminal=contextlib.nullcontext))
459+
try:
460+
relay.close_write_fd()
461+
# The relay closes the consumer's pipe as it finishes.
462+
assert os.read(read_fd, 1) == b""
463+
deadline = time.monotonic() + 5
464+
while not relay._done and time.monotonic() < deadline:
465+
time.sleep(0.01)
466+
assert relay._done
467+
relay.flush()
468+
assert relay.idle()
469+
finally:
470+
os.close(read_fd)
471+
472+
454473
@pytest.mark.skipif(sys.platform == "win32", reason="POSIX pipes")
455474
def test_pipeline_writer_relay_passes_consumer_exit_to_the_producer() -> None:
456475
import subprocess
@@ -703,6 +722,35 @@ def test_proc_reader_suspend_restores_signal_handler(handler_kind, relayed) -> N
703722
previous.assert_called_once_with(signal.SIGTSTP, None)
704723

705724

725+
@pytest.mark.skipif(sys.platform == "win32", reason="POSIX terminal job control")
726+
def test_proc_reader_direct_suspend_when_the_pipeline_is_gone() -> None:
727+
"""If the pipeline cannot be stopped along with cmd2, cmd2 does not count the stop or continue it."""
728+
proc = mock.Mock(pid=123, stdout=None, stderr=None, returncode=None)
729+
reader = cu.ProcReader(proc, sys.stdout, sys.stderr)
730+
reader._terminal_fd = 10
731+
reader._original_group = 456
732+
733+
def killpg(group, signum):
734+
if group == proc.pid:
735+
raise ProcessLookupError
736+
737+
with (
738+
mock.patch("signal.getsignal", return_value=signal.SIG_DFL),
739+
mock.patch("signal.signal") as set_handler,
740+
mock.patch("signal.raise_signal") as stop,
741+
mock.patch("os.killpg", side_effect=killpg) as sent,
742+
mock.patch("os.tcgetpgrp", return_value=reader._original_group),
743+
mock.patch("threading.Thread"),
744+
reader._manage_terminal(),
745+
):
746+
handler = set_handler.call_args.args[1]
747+
handler(signal.SIGTSTP, None)
748+
assert sent.call_args_list == [mock.call(proc.pid, signal.SIGSTOP), mock.call(reader._original_group, signal.SIGTSTP)]
749+
assert reader._own_stops == 0
750+
stop.assert_called_once_with(signal.SIGTSTP)
751+
assert reader._job_resumed.is_set()
752+
753+
706754
def test_proc_reader_captured_pipeline_needs_no_terminal() -> None:
707755
reader = cu.ProcReader(mock.Mock(stdout=None, stderr=None), sys.stdout, sys.stderr)
708756
with reader._manage_terminal(), reader._lend_terminal():

0 commit comments

Comments
 (0)