|
30 | 30 | CommandSet, |
31 | 31 | Completions, |
32 | 32 | SubcommandRecord, |
33 | | - clipboard, |
34 | 33 | constants, |
35 | 34 | exceptions, |
36 | 35 | plugin, |
@@ -864,29 +863,38 @@ def test_pipe_to_shell_and_redirect(redirection_app) -> None: |
864 | 863 | os.remove(filename) |
865 | 864 |
|
866 | 865 |
|
867 | | -def test_pipe_to_shell_error(redirection_app) -> None: |
868 | | - # Try to pipe command output to a shell command that doesn't exist in order to produce an error |
| 866 | +def test_pipe_to_shell_error(redirection_app, mocker, capsys) -> None: |
| 867 | + """An already-exited pipe process must be reported before the command runs. |
| 868 | +
|
| 869 | + A real nonexistent command may take longer than the startup probe under load. |
| 870 | + In that case the command runs and writes to a closing pipe instead, exercising |
| 871 | + a different path (including EINVAL on Windows). Model the early exit explicitly. |
| 872 | + """ |
| 873 | + popen = mocker.patch("subprocess.Popen", autospec=True) |
| 874 | + process = popen.return_value |
| 875 | + process.returncode = 127 |
| 876 | + process.wait.return_value = 127 |
| 877 | + |
869 | 878 | out, err = run_cmd(redirection_app, "print_output | foobarbaz.this_does_not_exist") |
870 | 879 | assert not out |
871 | | - assert "Pipe process exited with code" in err[0] |
| 880 | + assert "Pipe process exited with code 127 before command could run" in " ".join(err) |
| 881 | + assert capsys.readouterr().out == "" |
| 882 | + process.wait.assert_called_once() |
| 883 | + assert popen.call_args.kwargs["stdin"].closed |
| 884 | + |
872 | 885 |
|
| 886 | +def test_send_to_paste_buffer(redirection_app: RedirectionApp, capsys: pytest.CaptureFixture[str], mocker) -> None: |
| 887 | + # Exercise cmd2's real clipboard redirection against a private backend, not the |
| 888 | + # shared OS clipboard (which another test run or desktop application can alter). |
| 889 | + contents = "previous clipboard contents\n" |
873 | 890 |
|
874 | | -try: |
875 | | - # try getting the contents of the clipboard |
876 | | - _ = clipboard.get_paste_buffer() |
877 | | - # pyperclip raises at least the following types of exceptions |
878 | | - # FileNotFoundError on Windows Subsystem for Linux (WSL) when Windows paths are removed from $PATH |
879 | | - # ValueError for headless Linux systems without Gtk installed |
880 | | - # AssertionError can be raised by paste_klipper(). |
881 | | - # PyperclipException for pyperclip-specific exceptions |
882 | | -except Exception: # noqa: BLE001 |
883 | | - can_paste = False |
884 | | -else: |
885 | | - can_paste = True |
| 891 | + def copy(text: str) -> None: |
| 892 | + nonlocal contents |
| 893 | + contents = text |
886 | 894 |
|
| 895 | + mocker.patch("pyperclip.copy", autospec=True, side_effect=copy) |
| 896 | + mocker.patch("pyperclip.paste", autospec=True, side_effect=lambda: contents) |
887 | 897 |
|
888 | | -@pytest.mark.skipif(not can_paste, reason="Pyperclip could not find a copy/paste mechanism for your system") |
889 | | -def test_send_to_paste_buffer(redirection_app: RedirectionApp, capsys: pytest.CaptureFixture[str]) -> None: |
890 | 898 | # Test writing to the PasteBuffer/Clipboard |
891 | 899 | run_cmd(redirection_app, "print_output >") |
892 | 900 |
|
@@ -932,10 +940,7 @@ def test_allow_clipboard_initializer(redirection_app) -> None: |
932 | 940 | assert noclipcmd.allow_clipboard is False |
933 | 941 |
|
934 | 942 |
|
935 | | -# if clipboard access is not allowed, cmd2 should check that first |
936 | | -# before it tries to do anything with pyperclip, that's why we can |
937 | | -# safely run this test without skipping it if pyperclip doesn't |
938 | | -# work in the test environment, like we do for test_send_to_paste_buffer() |
| 943 | +# Disallowing clipboard access must be checked before contacting the backend. |
939 | 944 | def test_allow_clipboard(base_app) -> None: |
940 | 945 | base_app.allow_clipboard = False |
941 | 946 | out, err = run_cmd(base_app, "help >") |
|
0 commit comments