xonsh/tests/procs/test_specs.py

597 lines
18 KiB
Python
Raw Normal View History

"""Tests the xonsh.procs.specs"""
import itertools
Fixed populating the return code for interrupted process. (#5380) ### Motivation There is annoying behavior when you run command in the loop and can't interrupt e.g. [this report](https://github.com/xonsh/xonsh/discussions/5371) and a bit #5342. After diving into this I see the issue around return code. ### The essence Basically ``p = subprocess.Popen()`` populates ``p.returncode`` after ``p.wait()``, ``p.poll()`` or ``p.communicate()`` ([doc](https://docs.python.org/3/library/os.html#os.waitpid)). But if you're using `os.waitpid()` BEFORE these functions you're capturing return code from a signal subsystem and ``p.returncode`` will be ``0`` like success but it's not success. So after ``os.waitid`` call you need to set return code manually ``p.returncode = -os.WTERMSIG(status)`` like in Popen. Example: ```xsh python # python interactive import os, signal, subprocess as sp p = sp.Popen(['sleep', '100']) p.pid # 123 p.wait() # Ctrl+C or `kill -SIGINT 123` from another terminal p.returncode # -2 # BUT: p = sp.Popen(['sleep', '100']) p.pid # 123 pid, status = os.waitpid(p.pid, os.WUNTRACED) # status=2 # From another terminal: kill -SIGINT 123 p.wait() # 0 p.returncode # 0 ``` ```xsh from xonsh.tools import describe_waitpid_status describe_waitpid_status(2) # WIFEXITED - False - Return True if the process returning status exited via the exit() system call. # WEXITSTATUS - 0 - Return the process return code from status. # WIFSIGNALED - True - Return True if the process returning status was terminated by a signal. # WTERMSIG - 2 - Return the signal that terminated the process that provided the status value. # WIFSTOPPED - False - Return True if the process returning status was stopped. # WSTOPSIG - 0 - Return the signal that stopped the process that provided the status value. ``` See also: [Helpful things for knight](https://github.com/xonsh/xonsh/pull/5361#issuecomment-2078826181). ### Before ```xsh $RAISE_SUBPROC_ERROR = True sleep 100 # Ctrl+C _.rtn # 0 # It's wrong and RAISE_SUBPROC_ERROR ignored. for i in range(5): print(i) sleep 5 # 0 # Ctrl+C # Can't interrupt # 1 # 2 ``` ### After ```xsh sleep 100 # Ctrl+C _.rtn # -2 # Like in default Popen $RAISE_SUBPROC_ERROR = True for i in range(5): print(i) sleep 5 # 0 # Ctrl+C # subprocess.CalledProcessError ``` ### Notes * We need to refactor `xonsh.jobs`. It's pretty uncomfortable work with module. * The logic is blurry between Specs, Pipelines and Jobs. We need to bring more clear functions. * The `captured` variable looks like "just the way of capturing (stdout, object)" but in fact it affects all logic very much. We need to create table where we can see the logic difference for every type of capturing. E.g. in `captured=stdout` mode we use `xonsh.jobs` to monitor the command but in `captured=object` we avoid this and some logic from `xonsh.jobs` applied in `stdout` mode but missed in `object` mode. We need clear map. ## For community ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment** --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-02 22:10:53 +02:00
import signal
import sys
from subprocess import CalledProcessError, Popen
import pytest
2022-01-31 21:26:34 +05:30
from xonsh.procs.posix import PopenThread
from xonsh.procs.proxies import STDOUT_DISPATCHER, ProcProxy, ProcProxyThread
Fixed populating the return code for interrupted process. (#5380) ### Motivation There is annoying behavior when you run command in the loop and can't interrupt e.g. [this report](https://github.com/xonsh/xonsh/discussions/5371) and a bit #5342. After diving into this I see the issue around return code. ### The essence Basically ``p = subprocess.Popen()`` populates ``p.returncode`` after ``p.wait()``, ``p.poll()`` or ``p.communicate()`` ([doc](https://docs.python.org/3/library/os.html#os.waitpid)). But if you're using `os.waitpid()` BEFORE these functions you're capturing return code from a signal subsystem and ``p.returncode`` will be ``0`` like success but it's not success. So after ``os.waitid`` call you need to set return code manually ``p.returncode = -os.WTERMSIG(status)`` like in Popen. Example: ```xsh python # python interactive import os, signal, subprocess as sp p = sp.Popen(['sleep', '100']) p.pid # 123 p.wait() # Ctrl+C or `kill -SIGINT 123` from another terminal p.returncode # -2 # BUT: p = sp.Popen(['sleep', '100']) p.pid # 123 pid, status = os.waitpid(p.pid, os.WUNTRACED) # status=2 # From another terminal: kill -SIGINT 123 p.wait() # 0 p.returncode # 0 ``` ```xsh from xonsh.tools import describe_waitpid_status describe_waitpid_status(2) # WIFEXITED - False - Return True if the process returning status exited via the exit() system call. # WEXITSTATUS - 0 - Return the process return code from status. # WIFSIGNALED - True - Return True if the process returning status was terminated by a signal. # WTERMSIG - 2 - Return the signal that terminated the process that provided the status value. # WIFSTOPPED - False - Return True if the process returning status was stopped. # WSTOPSIG - 0 - Return the signal that stopped the process that provided the status value. ``` See also: [Helpful things for knight](https://github.com/xonsh/xonsh/pull/5361#issuecomment-2078826181). ### Before ```xsh $RAISE_SUBPROC_ERROR = True sleep 100 # Ctrl+C _.rtn # 0 # It's wrong and RAISE_SUBPROC_ERROR ignored. for i in range(5): print(i) sleep 5 # 0 # Ctrl+C # Can't interrupt # 1 # 2 ``` ### After ```xsh sleep 100 # Ctrl+C _.rtn # -2 # Like in default Popen $RAISE_SUBPROC_ERROR = True for i in range(5): print(i) sleep 5 # 0 # Ctrl+C # subprocess.CalledProcessError ``` ### Notes * We need to refactor `xonsh.jobs`. It's pretty uncomfortable work with module. * The logic is blurry between Specs, Pipelines and Jobs. We need to bring more clear functions. * The `captured` variable looks like "just the way of capturing (stdout, object)" but in fact it affects all logic very much. We need to create table where we can see the logic difference for every type of capturing. E.g. in `captured=stdout` mode we use `xonsh.jobs` to monitor the command but in `captured=object` we avoid this and some logic from `xonsh.jobs` applied in `stdout` mode but missed in `object` mode. We need clear map. ## For community ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment** --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-02 22:10:53 +02:00
from xonsh.procs.specs import (
DecoratorAlias,
SpecAttrDecoratorAlias,
Fixed populating the return code for interrupted process. (#5380) ### Motivation There is annoying behavior when you run command in the loop and can't interrupt e.g. [this report](https://github.com/xonsh/xonsh/discussions/5371) and a bit #5342. After diving into this I see the issue around return code. ### The essence Basically ``p = subprocess.Popen()`` populates ``p.returncode`` after ``p.wait()``, ``p.poll()`` or ``p.communicate()`` ([doc](https://docs.python.org/3/library/os.html#os.waitpid)). But if you're using `os.waitpid()` BEFORE these functions you're capturing return code from a signal subsystem and ``p.returncode`` will be ``0`` like success but it's not success. So after ``os.waitid`` call you need to set return code manually ``p.returncode = -os.WTERMSIG(status)`` like in Popen. Example: ```xsh python # python interactive import os, signal, subprocess as sp p = sp.Popen(['sleep', '100']) p.pid # 123 p.wait() # Ctrl+C or `kill -SIGINT 123` from another terminal p.returncode # -2 # BUT: p = sp.Popen(['sleep', '100']) p.pid # 123 pid, status = os.waitpid(p.pid, os.WUNTRACED) # status=2 # From another terminal: kill -SIGINT 123 p.wait() # 0 p.returncode # 0 ``` ```xsh from xonsh.tools import describe_waitpid_status describe_waitpid_status(2) # WIFEXITED - False - Return True if the process returning status exited via the exit() system call. # WEXITSTATUS - 0 - Return the process return code from status. # WIFSIGNALED - True - Return True if the process returning status was terminated by a signal. # WTERMSIG - 2 - Return the signal that terminated the process that provided the status value. # WIFSTOPPED - False - Return True if the process returning status was stopped. # WSTOPSIG - 0 - Return the signal that stopped the process that provided the status value. ``` See also: [Helpful things for knight](https://github.com/xonsh/xonsh/pull/5361#issuecomment-2078826181). ### Before ```xsh $RAISE_SUBPROC_ERROR = True sleep 100 # Ctrl+C _.rtn # 0 # It's wrong and RAISE_SUBPROC_ERROR ignored. for i in range(5): print(i) sleep 5 # 0 # Ctrl+C # Can't interrupt # 1 # 2 ``` ### After ```xsh sleep 100 # Ctrl+C _.rtn # -2 # Like in default Popen $RAISE_SUBPROC_ERROR = True for i in range(5): print(i) sleep 5 # 0 # Ctrl+C # subprocess.CalledProcessError ``` ### Notes * We need to refactor `xonsh.jobs`. It's pretty uncomfortable work with module. * The logic is blurry between Specs, Pipelines and Jobs. We need to bring more clear functions. * The `captured` variable looks like "just the way of capturing (stdout, object)" but in fact it affects all logic very much. We need to create table where we can see the logic difference for every type of capturing. E.g. in `captured=stdout` mode we use `xonsh.jobs` to monitor the command but in `captured=object` we avoid this and some logic from `xonsh.jobs` applied in `stdout` mode but missed in `object` mode. We need clear map. ## For community ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment** --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-02 22:10:53 +02:00
SubprocSpec,
_run_command_pipeline,
cmds_to_specs,
run_subproc,
)
from xonsh.pytest.tools import ON_WINDOWS, VER_MAJOR_MINOR, skip_if_on_windows
from xonsh.tools import XonshError
# TODO: track down which pipeline + spec test is hanging CI
# Skip entire test file for Linux on Python 3.12
pytestmark = pytest.mark.skipif(
not ON_WINDOWS and VER_MAJOR_MINOR == (3, 12),
reason="Backgrounded test is hanging on CI on 3.12 only",
allow_module_level=True,
)
Read stop signals from the process and update the process state. (#5361) Reading stop signals from the process and update the process state. ### The issue Technically. In a couple of places that critical for processing signals we have `os.waitpid()`. The function behavior is pretty unobvious and one of things is processing return code after catching the signal. We had no good signal processing around this and this PR fixes this. See also `proc_untraced_waitpid` function description. From user perspective. For example we have process that is waiting for user input from terminal e.g. `python -c "input()"` or `fzf`. If this process will be in captured pipeline e.g. `!(echo 1 | fzf | head)` it will be suspended by OS and the pipeline will be in the endless loop with future crashing and corrupting std at the end. This PR fixes this. ### The solution Technically. The key function is `proc_untraced_waitpid` - it catches the stop signals and updates the process state. From user perspective. First of all we expect that users will use captured object `!()` only for capturable processes. Because of it our goal here is to just make the behavior in this case stable. In this PR we detect that process in the pipeline is suspended and we need to finish the command pipeline carefully: * Show the message about suspended process. * Keep suspended process in `jobs`. The same behavior we can see in bash. This is good because we don't know what process suspended and why. May be experienced user will want to continue it manually. * Finish the CommandPipeline with returncode=None and suspended=True. ### Before ```xsh !(fzf) # or !(python -c "input()") # Hanging / Exceptions / OSError / No way to end the command. # After exception: $(echo 1) # OSError / IO error ``` ### After ```xsh !(fzf) # or `!(ls | fzf | head)` or `!(python -c "input()")` # Process ['fzf'] with pid 60000 suspended with signal 22 SIGTTOU and stay in `jobs`. # This happends when process start waiting for input but there is no terminal attached in captured mode. # CommandPipeline(returncode=None, suspended=True, ...) $(echo 1) # Success. ``` Closes #4752 #4577 ### Notes * There is pretty edge case situation when the process was terminated so fast that we can't catch pid alive and check signal ([src](https://github.com/xonsh/xonsh/blob/67d672783db6397bdec7ae44a9d9727b1e20a772/xonsh/jobs.py#L71-L80)). I leave it as is for now. ### Mentions #2159 ## For community ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment** --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>
2024-05-22 17:45:39 +02:00
def cmd_sig(sig):
return [
"python",
"-c",
f"import os, signal; os.kill(os.getpid(), signal.{sig})",
]
@skip_if_on_windows
def test_cmds_to_specs_thread_subproc(xession):
env = xession.env
cmds = [["pwd"]]
# XONSH_CAPTURE_ALWAYS=False should disable interactive threaded subprocs
env["XONSH_CAPTURE_ALWAYS"] = False
env["THREAD_SUBPROCS"] = True
specs = cmds_to_specs(cmds, captured="hiddenobject")
assert specs[0].cls is Popen
# Now for the other situations
env["XONSH_CAPTURE_ALWAYS"] = True
# First check that threadable subprocs become threadable
env["THREAD_SUBPROCS"] = True
specs = cmds_to_specs(cmds, captured="hiddenobject")
assert specs[0].cls is PopenThread
# turn off threading and check we use Popen
env["THREAD_SUBPROCS"] = False
specs = cmds_to_specs(cmds, captured="hiddenobject")
assert specs[0].cls is Popen
# now check the threadbility of callable aliases
cmds = [[lambda: "Keras Selyrian"]]
# check that threadable alias become threadable
env["THREAD_SUBPROCS"] = True
specs = cmds_to_specs(cmds, captured="hiddenobject")
assert specs[0].cls is ProcProxyThread
# turn off threading and check we use ProcProxy
env["THREAD_SUBPROCS"] = False
specs = cmds_to_specs(cmds, captured="hiddenobject")
assert specs[0].cls is ProcProxy
@pytest.mark.parametrize("thread_subprocs", [True, False])
update test xsh usage (#4581) * todo * test: remove usage of DummyEnv and setting .env attribute on xession fixture one step closer to making too much of tweaking to xession during tests * test: fix tests vox and gitstatus-prompt * docs: update test-fixture usage * fix: import flake8 error * test: remove direct access to XSH in tests * test: remove usage of XSH in test files * todo * test: use tmp-dir to create stubs * refactor: use fixture factory to mock out XonshSession * refactor: remove direct access of XSH from functions * refactor: remove direct access of XSH from functions * fix: qa checks * refactor: rename variables to match their values * test: update failing tests because it had no PATH set previously * fix: remove builtins usage from pyghooks.py * style: * refactor: update tests to use fixtures * fix: env varialbe is setup per function some tests accidentally update the env variables and that is leaking into next tests * fix: failing vox tests * test: set commands_cache per test * test: fix failing tests * fix: failing tests on linux ptk-highlight * fix: failing tests on Windows cwd-prompt * test: copy env as to not affect original object * fix: lazily evaluate cmds-cache in pyghooks * test: fix failing tests * fix: qa errors import * test: set commands-cache per test while caching path results * test: speedup test_thread_local_swap * fix: failing tests on windows * refactor: Execer doesn't control session * refactor: XSH.unload will take care of reversing builtins attrs set * test: use env.update over monkeypatch * Revert "test: use env.update over monkeypatch" This reverts commit 010a5022247a098f1741966b8af1bf758663480e.
2022-01-08 04:03:22 +05:30
def test_cmds_to_specs_capture_stdout_not_stderr(thread_subprocs, xonsh_session):
env = xonsh_session.env
cmds = (["ls", "/root"],)
env["THREAD_SUBPROCS"] = thread_subprocs
specs = cmds_to_specs(cmds, captured="stdout")
assert specs[0].stdout is not None
assert specs[0].stderr is None
@skip_if_on_windows
@pytest.mark.parametrize("pipe", (True, False))
@pytest.mark.parametrize("alias_type", (None, "func", "exec", "simple"))
@pytest.mark.parametrize(
"thread_subprocs, capture_always", list(itertools.product((True, False), repeat=2))
)
@pytest.mark.flaky(reruns=3, reruns_delay=2)
def test_capture_always(
update test xsh usage (#4581) * todo * test: remove usage of DummyEnv and setting .env attribute on xession fixture one step closer to making too much of tweaking to xession during tests * test: fix tests vox and gitstatus-prompt * docs: update test-fixture usage * fix: import flake8 error * test: remove direct access to XSH in tests * test: remove usage of XSH in test files * todo * test: use tmp-dir to create stubs * refactor: use fixture factory to mock out XonshSession * refactor: remove direct access of XSH from functions * refactor: remove direct access of XSH from functions * fix: qa checks * refactor: rename variables to match their values * test: update failing tests because it had no PATH set previously * fix: remove builtins usage from pyghooks.py * style: * refactor: update tests to use fixtures * fix: env varialbe is setup per function some tests accidentally update the env variables and that is leaking into next tests * fix: failing vox tests * test: set commands_cache per test * test: fix failing tests * fix: failing tests on linux ptk-highlight * fix: failing tests on Windows cwd-prompt * test: copy env as to not affect original object * fix: lazily evaluate cmds-cache in pyghooks * test: fix failing tests * fix: qa errors import * test: set commands-cache per test while caching path results * test: speedup test_thread_local_swap * fix: failing tests on windows * refactor: Execer doesn't control session * refactor: XSH.unload will take care of reversing builtins attrs set * test: use env.update over monkeypatch * Revert "test: use env.update over monkeypatch" This reverts commit 010a5022247a098f1741966b8af1bf758663480e.
2022-01-08 04:03:22 +05:30
capfd, thread_subprocs, capture_always, alias_type, pipe, monkeypatch, xonsh_session
):
if not thread_subprocs and alias_type in ["func", "exec"]:
if pipe:
return pytest.skip("https://github.com/xonsh/xonsh/issues/4443")
else:
return pytest.skip("https://github.com/xonsh/xonsh/issues/4444")
update test xsh usage (#4581) * todo * test: remove usage of DummyEnv and setting .env attribute on xession fixture one step closer to making too much of tweaking to xession during tests * test: fix tests vox and gitstatus-prompt * docs: update test-fixture usage * fix: import flake8 error * test: remove direct access to XSH in tests * test: remove usage of XSH in test files * todo * test: use tmp-dir to create stubs * refactor: use fixture factory to mock out XonshSession * refactor: remove direct access of XSH from functions * refactor: remove direct access of XSH from functions * fix: qa checks * refactor: rename variables to match their values * test: update failing tests because it had no PATH set previously * fix: remove builtins usage from pyghooks.py * style: * refactor: update tests to use fixtures * fix: env varialbe is setup per function some tests accidentally update the env variables and that is leaking into next tests * fix: failing vox tests * test: set commands_cache per test * test: fix failing tests * fix: failing tests on linux ptk-highlight * fix: failing tests on Windows cwd-prompt * test: copy env as to not affect original object * fix: lazily evaluate cmds-cache in pyghooks * test: fix failing tests * fix: qa errors import * test: set commands-cache per test while caching path results * test: speedup test_thread_local_swap * fix: failing tests on windows * refactor: Execer doesn't control session * refactor: XSH.unload will take care of reversing builtins attrs set * test: use env.update over monkeypatch * Revert "test: use env.update over monkeypatch" This reverts commit 010a5022247a098f1741966b8af1bf758663480e.
2022-01-08 04:03:22 +05:30
env = xonsh_session.env
exp = "HELLO\nBYE\n"
cmds = [["echo", "-n", exp]]
if pipe:
feat: add subproc output format, autostrip singleline output (#5377) ### Motivation * To have an ability to manage the output format added ``$XONSH_SUBPROC_OUTPUT_FORMAT`` to switch the way to return the output lines. Default ``stream_lines`` to return text. Alternative ``list_lines`` to return the list of lines. Also supported custom lambda function. * Additionally the [proposal to change default behavior](https://github.com/xonsh/xonsh/pull/5377#discussion_r1587627131) for a single line case. * Closes #3924 as soft solution. ### Before ```xsh mkdir -p /tmp/tst && cd /tmp/tst && touch 1 2 3 $(ls) # '1\n2\n3\n' id $(whoami) # id: ‘pc\n’: no such user: Invalid argument du $(ls) # du: cannot access '1'$'\n''2'$'\n''3'$'\n': No such file or directory ls $(fzf) # ls: cannot access 'FUNDING.yml'$'\n': No such file or directory ``` ### After ```xsh mkdir -p /tmp/tst && cd /tmp/tst && touch 1 2 3 $XONSH_SUBPROC_OUTPUT_FORMAT = 'list_lines' $(ls) # ['1', '2', '3'] [f for f in $(ls)] # ['1', '2', '3'] id $(whoami) # uid=501(user) gid=20(staff) du $(ls) # 0 1 # 0 2 # 0 3 ls $(fzf) # FUNDING.yml # etc mkdir -p /tmp/@($(whoami))/dir cat /etc/passwd | grep $(whoami) ``` ### Notes * It will be good to improve parser for cases like `mkdir -p /tmp/$(whoami)/dir`. PR is welcome! * I named the default mode as `stream_lines` (instead of just `stream` or `raw`) because in fact we transform raw output into stream of lines and possibly reduce the length of output ([replacing `\r\n` to `\n`](https://github.com/xonsh/xonsh/blob/c3a12b2a9c2958ce2a8e97a59b41f030f24cb45c/xonsh/procs/pipelines.py#L380-L383)). May be some day we need to add raw "stream" output format. * Now anybody can implement bash `IFS` behavior in [bashisms](https://github.com/xonsh/xontrib-bashisms). --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-02 17:48:25 +02:00
exp = exp.splitlines()[1] # second line
cmds += ["|", ["grep", "--color=never", exp.strip()]]
if alias_type:
first_cmd = cmds[0]
# Enable capfd for function aliases:
monkeypatch.setattr(STDOUT_DISPATCHER, "default", sys.stdout)
if alias_type == "func":
update test xsh usage (#4581) * todo * test: remove usage of DummyEnv and setting .env attribute on xession fixture one step closer to making too much of tweaking to xession during tests * test: fix tests vox and gitstatus-prompt * docs: update test-fixture usage * fix: import flake8 error * test: remove direct access to XSH in tests * test: remove usage of XSH in test files * todo * test: use tmp-dir to create stubs * refactor: use fixture factory to mock out XonshSession * refactor: remove direct access of XSH from functions * refactor: remove direct access of XSH from functions * fix: qa checks * refactor: rename variables to match their values * test: update failing tests because it had no PATH set previously * fix: remove builtins usage from pyghooks.py * style: * refactor: update tests to use fixtures * fix: env varialbe is setup per function some tests accidentally update the env variables and that is leaking into next tests * fix: failing vox tests * test: set commands_cache per test * test: fix failing tests * fix: failing tests on linux ptk-highlight * fix: failing tests on Windows cwd-prompt * test: copy env as to not affect original object * fix: lazily evaluate cmds-cache in pyghooks * test: fix failing tests * fix: qa errors import * test: set commands-cache per test while caching path results * test: speedup test_thread_local_swap * fix: failing tests on windows * refactor: Execer doesn't control session * refactor: XSH.unload will take care of reversing builtins attrs set * test: use env.update over monkeypatch * Revert "test: use env.update over monkeypatch" This reverts commit 010a5022247a098f1741966b8af1bf758663480e.
2022-01-08 04:03:22 +05:30
xonsh_session.aliases["tst"] = (
lambda: run_subproc([first_cmd], "hiddenobject") and None
) # Don't return a value
elif alias_type == "exec":
first_cmd = " ".join(repr(arg) for arg in first_cmd)
update test xsh usage (#4581) * todo * test: remove usage of DummyEnv and setting .env attribute on xession fixture one step closer to making too much of tweaking to xession during tests * test: fix tests vox and gitstatus-prompt * docs: update test-fixture usage * fix: import flake8 error * test: remove direct access to XSH in tests * test: remove usage of XSH in test files * todo * test: use tmp-dir to create stubs * refactor: use fixture factory to mock out XonshSession * refactor: remove direct access of XSH from functions * refactor: remove direct access of XSH from functions * fix: qa checks * refactor: rename variables to match their values * test: update failing tests because it had no PATH set previously * fix: remove builtins usage from pyghooks.py * style: * refactor: update tests to use fixtures * fix: env varialbe is setup per function some tests accidentally update the env variables and that is leaking into next tests * fix: failing vox tests * test: set commands_cache per test * test: fix failing tests * fix: failing tests on linux ptk-highlight * fix: failing tests on Windows cwd-prompt * test: copy env as to not affect original object * fix: lazily evaluate cmds-cache in pyghooks * test: fix failing tests * fix: qa errors import * test: set commands-cache per test while caching path results * test: speedup test_thread_local_swap * fix: failing tests on windows * refactor: Execer doesn't control session * refactor: XSH.unload will take care of reversing builtins attrs set * test: use env.update over monkeypatch * Revert "test: use env.update over monkeypatch" This reverts commit 010a5022247a098f1741966b8af1bf758663480e.
2022-01-08 04:03:22 +05:30
xonsh_session.aliases["tst"] = f"![{first_cmd}]"
else:
# alias_type == "simple"
update test xsh usage (#4581) * todo * test: remove usage of DummyEnv and setting .env attribute on xession fixture one step closer to making too much of tweaking to xession during tests * test: fix tests vox and gitstatus-prompt * docs: update test-fixture usage * fix: import flake8 error * test: remove direct access to XSH in tests * test: remove usage of XSH in test files * todo * test: use tmp-dir to create stubs * refactor: use fixture factory to mock out XonshSession * refactor: remove direct access of XSH from functions * refactor: remove direct access of XSH from functions * fix: qa checks * refactor: rename variables to match their values * test: update failing tests because it had no PATH set previously * fix: remove builtins usage from pyghooks.py * style: * refactor: update tests to use fixtures * fix: env varialbe is setup per function some tests accidentally update the env variables and that is leaking into next tests * fix: failing vox tests * test: set commands_cache per test * test: fix failing tests * fix: failing tests on linux ptk-highlight * fix: failing tests on Windows cwd-prompt * test: copy env as to not affect original object * fix: lazily evaluate cmds-cache in pyghooks * test: fix failing tests * fix: qa errors import * test: set commands-cache per test while caching path results * test: speedup test_thread_local_swap * fix: failing tests on windows * refactor: Execer doesn't control session * refactor: XSH.unload will take care of reversing builtins attrs set * test: use env.update over monkeypatch * Revert "test: use env.update over monkeypatch" This reverts commit 010a5022247a098f1741966b8af1bf758663480e.
2022-01-08 04:03:22 +05:30
xonsh_session.aliases["tst"] = first_cmd
cmds[0] = ["tst"]
env["THREAD_SUBPROCS"] = thread_subprocs
env["XONSH_CAPTURE_ALWAYS"] = capture_always
hidden = run_subproc(cmds, "hiddenobject") # ![]
# Check that interactive subprocs are always printed
assert exp in capfd.readouterr().out
if capture_always and thread_subprocs:
# Check that the interactive output was captured
assert hidden.out == exp
else:
# without THREAD_SUBPROCS capturing in ![] isn't possible
assert not hidden.out
# Explicitly captured commands are always captured
hidden = run_subproc(cmds, "object") # !()
hidden.end()
if thread_subprocs:
assert exp not in capfd.readouterr().out
assert hidden.out == exp
else:
# for some reason THREAD_SUBPROCS=False fails to capture in `!()` but still succeeds in `$()`
assert exp in capfd.readouterr().out
assert not hidden.out
output = run_subproc(cmds, "stdout") # $()
assert exp not in capfd.readouterr().out
assert output == exp
# Explicitly non-captured commands are never captured (/always printed)
run_subproc(cmds, captured=False) # $[]
assert exp in capfd.readouterr().out
@skip_if_on_windows
@pytest.mark.flaky(reruns=3, reruns_delay=2)
def test_callias_captured_redirect(xonsh_session, tmpdir):
@xonsh_session.aliases.register("a")
def _a(a, i, o, e):
print("print_stdout")
xonsh_session.subproc_captured_stdout(["echo", "cap_stdout"])
xonsh_session.subproc_captured_object(["echo", "cap_object"])
xonsh_session.subproc_captured_hiddenobject(["echo", "hiddenobject"])
xonsh_session.subproc_uncaptured(["echo", "uncaptured"])
print("print_error", file=e)
f = tmpdir / "capture.txt"
cmd = (["a", (">", str(f))],)
specs = cmds_to_specs(cmd, captured="hiddenobject")
_run_command_pipeline(specs, cmd).end()
assert f.read_text(encoding="utf-8") == "print_stdout\nhiddenobject\n"
Fixed populating the return code for interrupted process. (#5380) ### Motivation There is annoying behavior when you run command in the loop and can't interrupt e.g. [this report](https://github.com/xonsh/xonsh/discussions/5371) and a bit #5342. After diving into this I see the issue around return code. ### The essence Basically ``p = subprocess.Popen()`` populates ``p.returncode`` after ``p.wait()``, ``p.poll()`` or ``p.communicate()`` ([doc](https://docs.python.org/3/library/os.html#os.waitpid)). But if you're using `os.waitpid()` BEFORE these functions you're capturing return code from a signal subsystem and ``p.returncode`` will be ``0`` like success but it's not success. So after ``os.waitid`` call you need to set return code manually ``p.returncode = -os.WTERMSIG(status)`` like in Popen. Example: ```xsh python # python interactive import os, signal, subprocess as sp p = sp.Popen(['sleep', '100']) p.pid # 123 p.wait() # Ctrl+C or `kill -SIGINT 123` from another terminal p.returncode # -2 # BUT: p = sp.Popen(['sleep', '100']) p.pid # 123 pid, status = os.waitpid(p.pid, os.WUNTRACED) # status=2 # From another terminal: kill -SIGINT 123 p.wait() # 0 p.returncode # 0 ``` ```xsh from xonsh.tools import describe_waitpid_status describe_waitpid_status(2) # WIFEXITED - False - Return True if the process returning status exited via the exit() system call. # WEXITSTATUS - 0 - Return the process return code from status. # WIFSIGNALED - True - Return True if the process returning status was terminated by a signal. # WTERMSIG - 2 - Return the signal that terminated the process that provided the status value. # WIFSTOPPED - False - Return True if the process returning status was stopped. # WSTOPSIG - 0 - Return the signal that stopped the process that provided the status value. ``` See also: [Helpful things for knight](https://github.com/xonsh/xonsh/pull/5361#issuecomment-2078826181). ### Before ```xsh $RAISE_SUBPROC_ERROR = True sleep 100 # Ctrl+C _.rtn # 0 # It's wrong and RAISE_SUBPROC_ERROR ignored. for i in range(5): print(i) sleep 5 # 0 # Ctrl+C # Can't interrupt # 1 # 2 ``` ### After ```xsh sleep 100 # Ctrl+C _.rtn # -2 # Like in default Popen $RAISE_SUBPROC_ERROR = True for i in range(5): print(i) sleep 5 # 0 # Ctrl+C # subprocess.CalledProcessError ``` ### Notes * We need to refactor `xonsh.jobs`. It's pretty uncomfortable work with module. * The logic is blurry between Specs, Pipelines and Jobs. We need to bring more clear functions. * The `captured` variable looks like "just the way of capturing (stdout, object)" but in fact it affects all logic very much. We need to create table where we can see the logic difference for every type of capturing. E.g. in `captured=stdout` mode we use `xonsh.jobs` to monitor the command but in `captured=object` we avoid this and some logic from `xonsh.jobs` applied in `stdout` mode but missed in `object` mode. We need clear map. ## For community ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment** --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-02 22:10:53 +02:00
@skip_if_on_windows
Read stop signals from the process and update the process state. (#5361) Reading stop signals from the process and update the process state. ### The issue Technically. In a couple of places that critical for processing signals we have `os.waitpid()`. The function behavior is pretty unobvious and one of things is processing return code after catching the signal. We had no good signal processing around this and this PR fixes this. See also `proc_untraced_waitpid` function description. From user perspective. For example we have process that is waiting for user input from terminal e.g. `python -c "input()"` or `fzf`. If this process will be in captured pipeline e.g. `!(echo 1 | fzf | head)` it will be suspended by OS and the pipeline will be in the endless loop with future crashing and corrupting std at the end. This PR fixes this. ### The solution Technically. The key function is `proc_untraced_waitpid` - it catches the stop signals and updates the process state. From user perspective. First of all we expect that users will use captured object `!()` only for capturable processes. Because of it our goal here is to just make the behavior in this case stable. In this PR we detect that process in the pipeline is suspended and we need to finish the command pipeline carefully: * Show the message about suspended process. * Keep suspended process in `jobs`. The same behavior we can see in bash. This is good because we don't know what process suspended and why. May be experienced user will want to continue it manually. * Finish the CommandPipeline with returncode=None and suspended=True. ### Before ```xsh !(fzf) # or !(python -c "input()") # Hanging / Exceptions / OSError / No way to end the command. # After exception: $(echo 1) # OSError / IO error ``` ### After ```xsh !(fzf) # or `!(ls | fzf | head)` or `!(python -c "input()")` # Process ['fzf'] with pid 60000 suspended with signal 22 SIGTTOU and stay in `jobs`. # This happends when process start waiting for input but there is no terminal attached in captured mode. # CommandPipeline(returncode=None, suspended=True, ...) $(echo 1) # Success. ``` Closes #4752 #4577 ### Notes * There is pretty edge case situation when the process was terminated so fast that we can't catch pid alive and check signal ([src](https://github.com/xonsh/xonsh/blob/67d672783db6397bdec7ae44a9d9727b1e20a772/xonsh/jobs.py#L71-L80)). I leave it as is for now. ### Mentions #2159 ## For community ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment** --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>
2024-05-22 17:45:39 +02:00
@pytest.mark.parametrize("captured", ["stdout", "object"])
@pytest.mark.parametrize("interactive", [True, False])
@pytest.mark.flaky(reruns=3, reruns_delay=2)
Read stop signals from the process and update the process state. (#5361) Reading stop signals from the process and update the process state. ### The issue Technically. In a couple of places that critical for processing signals we have `os.waitpid()`. The function behavior is pretty unobvious and one of things is processing return code after catching the signal. We had no good signal processing around this and this PR fixes this. See also `proc_untraced_waitpid` function description. From user perspective. For example we have process that is waiting for user input from terminal e.g. `python -c "input()"` or `fzf`. If this process will be in captured pipeline e.g. `!(echo 1 | fzf | head)` it will be suspended by OS and the pipeline will be in the endless loop with future crashing and corrupting std at the end. This PR fixes this. ### The solution Technically. The key function is `proc_untraced_waitpid` - it catches the stop signals and updates the process state. From user perspective. First of all we expect that users will use captured object `!()` only for capturable processes. Because of it our goal here is to just make the behavior in this case stable. In this PR we detect that process in the pipeline is suspended and we need to finish the command pipeline carefully: * Show the message about suspended process. * Keep suspended process in `jobs`. The same behavior we can see in bash. This is good because we don't know what process suspended and why. May be experienced user will want to continue it manually. * Finish the CommandPipeline with returncode=None and suspended=True. ### Before ```xsh !(fzf) # or !(python -c "input()") # Hanging / Exceptions / OSError / No way to end the command. # After exception: $(echo 1) # OSError / IO error ``` ### After ```xsh !(fzf) # or `!(ls | fzf | head)` or `!(python -c "input()")` # Process ['fzf'] with pid 60000 suspended with signal 22 SIGTTOU and stay in `jobs`. # This happends when process start waiting for input but there is no terminal attached in captured mode. # CommandPipeline(returncode=None, suspended=True, ...) $(echo 1) # Success. ``` Closes #4752 #4577 ### Notes * There is pretty edge case situation when the process was terminated so fast that we can't catch pid alive and check signal ([src](https://github.com/xonsh/xonsh/blob/67d672783db6397bdec7ae44a9d9727b1e20a772/xonsh/jobs.py#L71-L80)). I leave it as is for now. ### Mentions #2159 ## For community ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment** --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>
2024-05-22 17:45:39 +02:00
def test_interrupted_process_returncode(xonsh_session, captured, interactive):
xonsh_session.env["XONSH_INTERACTIVE"] = interactive
Fixed populating the return code for interrupted process. (#5380) ### Motivation There is annoying behavior when you run command in the loop and can't interrupt e.g. [this report](https://github.com/xonsh/xonsh/discussions/5371) and a bit #5342. After diving into this I see the issue around return code. ### The essence Basically ``p = subprocess.Popen()`` populates ``p.returncode`` after ``p.wait()``, ``p.poll()`` or ``p.communicate()`` ([doc](https://docs.python.org/3/library/os.html#os.waitpid)). But if you're using `os.waitpid()` BEFORE these functions you're capturing return code from a signal subsystem and ``p.returncode`` will be ``0`` like success but it's not success. So after ``os.waitid`` call you need to set return code manually ``p.returncode = -os.WTERMSIG(status)`` like in Popen. Example: ```xsh python # python interactive import os, signal, subprocess as sp p = sp.Popen(['sleep', '100']) p.pid # 123 p.wait() # Ctrl+C or `kill -SIGINT 123` from another terminal p.returncode # -2 # BUT: p = sp.Popen(['sleep', '100']) p.pid # 123 pid, status = os.waitpid(p.pid, os.WUNTRACED) # status=2 # From another terminal: kill -SIGINT 123 p.wait() # 0 p.returncode # 0 ``` ```xsh from xonsh.tools import describe_waitpid_status describe_waitpid_status(2) # WIFEXITED - False - Return True if the process returning status exited via the exit() system call. # WEXITSTATUS - 0 - Return the process return code from status. # WIFSIGNALED - True - Return True if the process returning status was terminated by a signal. # WTERMSIG - 2 - Return the signal that terminated the process that provided the status value. # WIFSTOPPED - False - Return True if the process returning status was stopped. # WSTOPSIG - 0 - Return the signal that stopped the process that provided the status value. ``` See also: [Helpful things for knight](https://github.com/xonsh/xonsh/pull/5361#issuecomment-2078826181). ### Before ```xsh $RAISE_SUBPROC_ERROR = True sleep 100 # Ctrl+C _.rtn # 0 # It's wrong and RAISE_SUBPROC_ERROR ignored. for i in range(5): print(i) sleep 5 # 0 # Ctrl+C # Can't interrupt # 1 # 2 ``` ### After ```xsh sleep 100 # Ctrl+C _.rtn # -2 # Like in default Popen $RAISE_SUBPROC_ERROR = True for i in range(5): print(i) sleep 5 # 0 # Ctrl+C # subprocess.CalledProcessError ``` ### Notes * We need to refactor `xonsh.jobs`. It's pretty uncomfortable work with module. * The logic is blurry between Specs, Pipelines and Jobs. We need to bring more clear functions. * The `captured` variable looks like "just the way of capturing (stdout, object)" but in fact it affects all logic very much. We need to create table where we can see the logic difference for every type of capturing. E.g. in `captured=stdout` mode we use `xonsh.jobs` to monitor the command but in `captured=object` we avoid this and some logic from `xonsh.jobs` applied in `stdout` mode but missed in `object` mode. We need clear map. ## For community ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment** --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-02 22:10:53 +02:00
xonsh_session.env["RAISE_SUBPROC_ERROR"] = False
Read stop signals from the process and update the process state. (#5361) Reading stop signals from the process and update the process state. ### The issue Technically. In a couple of places that critical for processing signals we have `os.waitpid()`. The function behavior is pretty unobvious and one of things is processing return code after catching the signal. We had no good signal processing around this and this PR fixes this. See also `proc_untraced_waitpid` function description. From user perspective. For example we have process that is waiting for user input from terminal e.g. `python -c "input()"` or `fzf`. If this process will be in captured pipeline e.g. `!(echo 1 | fzf | head)` it will be suspended by OS and the pipeline will be in the endless loop with future crashing and corrupting std at the end. This PR fixes this. ### The solution Technically. The key function is `proc_untraced_waitpid` - it catches the stop signals and updates the process state. From user perspective. First of all we expect that users will use captured object `!()` only for capturable processes. Because of it our goal here is to just make the behavior in this case stable. In this PR we detect that process in the pipeline is suspended and we need to finish the command pipeline carefully: * Show the message about suspended process. * Keep suspended process in `jobs`. The same behavior we can see in bash. This is good because we don't know what process suspended and why. May be experienced user will want to continue it manually. * Finish the CommandPipeline with returncode=None and suspended=True. ### Before ```xsh !(fzf) # or !(python -c "input()") # Hanging / Exceptions / OSError / No way to end the command. # After exception: $(echo 1) # OSError / IO error ``` ### After ```xsh !(fzf) # or `!(ls | fzf | head)` or `!(python -c "input()")` # Process ['fzf'] with pid 60000 suspended with signal 22 SIGTTOU and stay in `jobs`. # This happends when process start waiting for input but there is no terminal attached in captured mode. # CommandPipeline(returncode=None, suspended=True, ...) $(echo 1) # Success. ``` Closes #4752 #4577 ### Notes * There is pretty edge case situation when the process was terminated so fast that we can't catch pid alive and check signal ([src](https://github.com/xonsh/xonsh/blob/67d672783db6397bdec7ae44a9d9727b1e20a772/xonsh/jobs.py#L71-L80)). I leave it as is for now. ### Mentions #2159 ## For community ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment** --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>
2024-05-22 17:45:39 +02:00
cmd = [cmd_sig("SIGINT")]
Fixed populating the return code for interrupted process. (#5380) ### Motivation There is annoying behavior when you run command in the loop and can't interrupt e.g. [this report](https://github.com/xonsh/xonsh/discussions/5371) and a bit #5342. After diving into this I see the issue around return code. ### The essence Basically ``p = subprocess.Popen()`` populates ``p.returncode`` after ``p.wait()``, ``p.poll()`` or ``p.communicate()`` ([doc](https://docs.python.org/3/library/os.html#os.waitpid)). But if you're using `os.waitpid()` BEFORE these functions you're capturing return code from a signal subsystem and ``p.returncode`` will be ``0`` like success but it's not success. So after ``os.waitid`` call you need to set return code manually ``p.returncode = -os.WTERMSIG(status)`` like in Popen. Example: ```xsh python # python interactive import os, signal, subprocess as sp p = sp.Popen(['sleep', '100']) p.pid # 123 p.wait() # Ctrl+C or `kill -SIGINT 123` from another terminal p.returncode # -2 # BUT: p = sp.Popen(['sleep', '100']) p.pid # 123 pid, status = os.waitpid(p.pid, os.WUNTRACED) # status=2 # From another terminal: kill -SIGINT 123 p.wait() # 0 p.returncode # 0 ``` ```xsh from xonsh.tools import describe_waitpid_status describe_waitpid_status(2) # WIFEXITED - False - Return True if the process returning status exited via the exit() system call. # WEXITSTATUS - 0 - Return the process return code from status. # WIFSIGNALED - True - Return True if the process returning status was terminated by a signal. # WTERMSIG - 2 - Return the signal that terminated the process that provided the status value. # WIFSTOPPED - False - Return True if the process returning status was stopped. # WSTOPSIG - 0 - Return the signal that stopped the process that provided the status value. ``` See also: [Helpful things for knight](https://github.com/xonsh/xonsh/pull/5361#issuecomment-2078826181). ### Before ```xsh $RAISE_SUBPROC_ERROR = True sleep 100 # Ctrl+C _.rtn # 0 # It's wrong and RAISE_SUBPROC_ERROR ignored. for i in range(5): print(i) sleep 5 # 0 # Ctrl+C # Can't interrupt # 1 # 2 ``` ### After ```xsh sleep 100 # Ctrl+C _.rtn # -2 # Like in default Popen $RAISE_SUBPROC_ERROR = True for i in range(5): print(i) sleep 5 # 0 # Ctrl+C # subprocess.CalledProcessError ``` ### Notes * We need to refactor `xonsh.jobs`. It's pretty uncomfortable work with module. * The logic is blurry between Specs, Pipelines and Jobs. We need to bring more clear functions. * The `captured` variable looks like "just the way of capturing (stdout, object)" but in fact it affects all logic very much. We need to create table where we can see the logic difference for every type of capturing. E.g. in `captured=stdout` mode we use `xonsh.jobs` to monitor the command but in `captured=object` we avoid this and some logic from `xonsh.jobs` applied in `stdout` mode but missed in `object` mode. We need clear map. ## For community ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment** --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-02 22:10:53 +02:00
specs = cmds_to_specs(cmd, captured="stdout")
(p := _run_command_pipeline(specs, cmd)).end()
assert p.proc.returncode == -signal.SIGINT
@skip_if_on_windows
Alias that returns modified command (#5473) * Command Alias * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * news * docs * tests * docs * wip * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * clean * news * news * bumptests * bumptests * new api * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * doooocs * comments * comments * comments * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * comments * bump test * remove CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * test * test * wip * revert * wip * wip * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * wip * docs * news * tests * tests * test * test * test * Update docs/tutorial.rst Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> * Update xonsh/aliases.py Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix, thanks jaraco! * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * more comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * cleaning --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
2024-07-12 01:35:20 +02:00
@pytest.mark.flaky(reruns=3, reruns_delay=1)
def test_proc_raise_subproc_error(xonsh_session):
xonsh_session.env["RAISE_SUBPROC_ERROR"] = False
specs = cmds_to_specs(cmd := [["ls"]], captured="stdout")
specs[-1].raise_subproc_error = True
exception = None
try:
(p := _run_command_pipeline(specs, cmd)).end()
assert p.proc.returncode == 0
except Exception as e:
exception = e
assert exception is None
specs = cmds_to_specs(cmd := [["ls", "nofile"]], captured="stdout")
specs[-1].raise_subproc_error = False
exception = None
try:
(p := _run_command_pipeline(specs, cmd)).end()
assert p.proc.returncode > 0
except Exception as e:
exception = e
assert exception is None
specs = cmds_to_specs(cmd := [["ls", "nofile"]], captured="stdout")
specs[-1].raise_subproc_error = True
exception = None
try:
(p := _run_command_pipeline(specs, cmd)).end()
except Exception as e:
assert p.proc.returncode > 0
exception = e
assert isinstance(exception, CalledProcessError)
xonsh_session.env["RAISE_SUBPROC_ERROR"] = True
specs = cmds_to_specs(cmd := [["ls", "nofile"]], captured="stdout")
exception = None
try:
(p := _run_command_pipeline(specs, cmd)).end()
except Exception as e:
assert p.proc.returncode > 0
exception = e
assert isinstance(exception, CalledProcessError)
Read stop signals from the process and update the process state. (#5361) Reading stop signals from the process and update the process state. ### The issue Technically. In a couple of places that critical for processing signals we have `os.waitpid()`. The function behavior is pretty unobvious and one of things is processing return code after catching the signal. We had no good signal processing around this and this PR fixes this. See also `proc_untraced_waitpid` function description. From user perspective. For example we have process that is waiting for user input from terminal e.g. `python -c "input()"` or `fzf`. If this process will be in captured pipeline e.g. `!(echo 1 | fzf | head)` it will be suspended by OS and the pipeline will be in the endless loop with future crashing and corrupting std at the end. This PR fixes this. ### The solution Technically. The key function is `proc_untraced_waitpid` - it catches the stop signals and updates the process state. From user perspective. First of all we expect that users will use captured object `!()` only for capturable processes. Because of it our goal here is to just make the behavior in this case stable. In this PR we detect that process in the pipeline is suspended and we need to finish the command pipeline carefully: * Show the message about suspended process. * Keep suspended process in `jobs`. The same behavior we can see in bash. This is good because we don't know what process suspended and why. May be experienced user will want to continue it manually. * Finish the CommandPipeline with returncode=None and suspended=True. ### Before ```xsh !(fzf) # or !(python -c "input()") # Hanging / Exceptions / OSError / No way to end the command. # After exception: $(echo 1) # OSError / IO error ``` ### After ```xsh !(fzf) # or `!(ls | fzf | head)` or `!(python -c "input()")` # Process ['fzf'] with pid 60000 suspended with signal 22 SIGTTOU and stay in `jobs`. # This happends when process start waiting for input but there is no terminal attached in captured mode. # CommandPipeline(returncode=None, suspended=True, ...) $(echo 1) # Success. ``` Closes #4752 #4577 ### Notes * There is pretty edge case situation when the process was terminated so fast that we can't catch pid alive and check signal ([src](https://github.com/xonsh/xonsh/blob/67d672783db6397bdec7ae44a9d9727b1e20a772/xonsh/jobs.py#L71-L80)). I leave it as is for now. ### Mentions #2159 ## For community ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment** --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>
2024-05-22 17:45:39 +02:00
@skip_if_on_windows
@pytest.mark.parametrize(
"suspended_pipeline",
[
[cmd_sig("SIGTTIN")],
[["echo", "1"], "|", cmd_sig("SIGTTIN")],
[["echo", "1"], "|", cmd_sig("SIGTTIN"), "|", ["head"]],
],
)
@pytest.mark.flaky(reruns=3, reruns_delay=1)
Read stop signals from the process and update the process state. (#5361) Reading stop signals from the process and update the process state. ### The issue Technically. In a couple of places that critical for processing signals we have `os.waitpid()`. The function behavior is pretty unobvious and one of things is processing return code after catching the signal. We had no good signal processing around this and this PR fixes this. See also `proc_untraced_waitpid` function description. From user perspective. For example we have process that is waiting for user input from terminal e.g. `python -c "input()"` or `fzf`. If this process will be in captured pipeline e.g. `!(echo 1 | fzf | head)` it will be suspended by OS and the pipeline will be in the endless loop with future crashing and corrupting std at the end. This PR fixes this. ### The solution Technically. The key function is `proc_untraced_waitpid` - it catches the stop signals and updates the process state. From user perspective. First of all we expect that users will use captured object `!()` only for capturable processes. Because of it our goal here is to just make the behavior in this case stable. In this PR we detect that process in the pipeline is suspended and we need to finish the command pipeline carefully: * Show the message about suspended process. * Keep suspended process in `jobs`. The same behavior we can see in bash. This is good because we don't know what process suspended and why. May be experienced user will want to continue it manually. * Finish the CommandPipeline with returncode=None and suspended=True. ### Before ```xsh !(fzf) # or !(python -c "input()") # Hanging / Exceptions / OSError / No way to end the command. # After exception: $(echo 1) # OSError / IO error ``` ### After ```xsh !(fzf) # or `!(ls | fzf | head)` or `!(python -c "input()")` # Process ['fzf'] with pid 60000 suspended with signal 22 SIGTTOU and stay in `jobs`. # This happends when process start waiting for input but there is no terminal attached in captured mode. # CommandPipeline(returncode=None, suspended=True, ...) $(echo 1) # Success. ``` Closes #4752 #4577 ### Notes * There is pretty edge case situation when the process was terminated so fast that we can't catch pid alive and check signal ([src](https://github.com/xonsh/xonsh/blob/67d672783db6397bdec7ae44a9d9727b1e20a772/xonsh/jobs.py#L71-L80)). I leave it as is for now. ### Mentions #2159 ## For community ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment** --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>
2024-05-22 17:45:39 +02:00
def test_specs_with_suspended_captured_process_pipeline(
xonsh_session, suspended_pipeline
):
xonsh_session.env["XONSH_INTERACTIVE"] = True
specs = cmds_to_specs(suspended_pipeline, captured="object")
p = _run_command_pipeline(specs, suspended_pipeline)
p.proc.send_signal(signal.SIGCONT)
p.end()
assert p.suspended
feat: add subproc output format, autostrip singleline output (#5377) ### Motivation * To have an ability to manage the output format added ``$XONSH_SUBPROC_OUTPUT_FORMAT`` to switch the way to return the output lines. Default ``stream_lines`` to return text. Alternative ``list_lines`` to return the list of lines. Also supported custom lambda function. * Additionally the [proposal to change default behavior](https://github.com/xonsh/xonsh/pull/5377#discussion_r1587627131) for a single line case. * Closes #3924 as soft solution. ### Before ```xsh mkdir -p /tmp/tst && cd /tmp/tst && touch 1 2 3 $(ls) # '1\n2\n3\n' id $(whoami) # id: ‘pc\n’: no such user: Invalid argument du $(ls) # du: cannot access '1'$'\n''2'$'\n''3'$'\n': No such file or directory ls $(fzf) # ls: cannot access 'FUNDING.yml'$'\n': No such file or directory ``` ### After ```xsh mkdir -p /tmp/tst && cd /tmp/tst && touch 1 2 3 $XONSH_SUBPROC_OUTPUT_FORMAT = 'list_lines' $(ls) # ['1', '2', '3'] [f for f in $(ls)] # ['1', '2', '3'] id $(whoami) # uid=501(user) gid=20(staff) du $(ls) # 0 1 # 0 2 # 0 3 ls $(fzf) # FUNDING.yml # etc mkdir -p /tmp/@($(whoami))/dir cat /etc/passwd | grep $(whoami) ``` ### Notes * It will be good to improve parser for cases like `mkdir -p /tmp/$(whoami)/dir`. PR is welcome! * I named the default mode as `stream_lines` (instead of just `stream` or `raw`) because in fact we transform raw output into stream of lines and possibly reduce the length of output ([replacing `\r\n` to `\n`](https://github.com/xonsh/xonsh/blob/c3a12b2a9c2958ce2a8e97a59b41f030f24cb45c/xonsh/procs/pipelines.py#L380-L383)). May be some day we need to add raw "stream" output format. * Now anybody can implement bash `IFS` behavior in [bashisms](https://github.com/xonsh/xontrib-bashisms). --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-02 17:48:25 +02:00
@skip_if_on_windows
@pytest.mark.parametrize(
"cmds, exp_stream_lines, exp_list_lines",
[
([["echo", "-n", "1"]], "1", ["1"]),
([["echo", "-n", "1\n"]], "1", ["1"]),
feat: add subproc output format, autostrip singleline output (#5377) ### Motivation * To have an ability to manage the output format added ``$XONSH_SUBPROC_OUTPUT_FORMAT`` to switch the way to return the output lines. Default ``stream_lines`` to return text. Alternative ``list_lines`` to return the list of lines. Also supported custom lambda function. * Additionally the [proposal to change default behavior](https://github.com/xonsh/xonsh/pull/5377#discussion_r1587627131) for a single line case. * Closes #3924 as soft solution. ### Before ```xsh mkdir -p /tmp/tst && cd /tmp/tst && touch 1 2 3 $(ls) # '1\n2\n3\n' id $(whoami) # id: ‘pc\n’: no such user: Invalid argument du $(ls) # du: cannot access '1'$'\n''2'$'\n''3'$'\n': No such file or directory ls $(fzf) # ls: cannot access 'FUNDING.yml'$'\n': No such file or directory ``` ### After ```xsh mkdir -p /tmp/tst && cd /tmp/tst && touch 1 2 3 $XONSH_SUBPROC_OUTPUT_FORMAT = 'list_lines' $(ls) # ['1', '2', '3'] [f for f in $(ls)] # ['1', '2', '3'] id $(whoami) # uid=501(user) gid=20(staff) du $(ls) # 0 1 # 0 2 # 0 3 ls $(fzf) # FUNDING.yml # etc mkdir -p /tmp/@($(whoami))/dir cat /etc/passwd | grep $(whoami) ``` ### Notes * It will be good to improve parser for cases like `mkdir -p /tmp/$(whoami)/dir`. PR is welcome! * I named the default mode as `stream_lines` (instead of just `stream` or `raw`) because in fact we transform raw output into stream of lines and possibly reduce the length of output ([replacing `\r\n` to `\n`](https://github.com/xonsh/xonsh/blob/c3a12b2a9c2958ce2a8e97a59b41f030f24cb45c/xonsh/procs/pipelines.py#L380-L383)). May be some day we need to add raw "stream" output format. * Now anybody can implement bash `IFS` behavior in [bashisms](https://github.com/xonsh/xontrib-bashisms). --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-02 17:48:25 +02:00
([["echo", "-n", "1\n2\n3\n"]], "1\n2\n3\n", ["1", "2", "3"]),
([["echo", "-n", "1\r\n2\r3\r\n"]], "1\n2\n3\n", ["1", "2", "3"]),
([["echo", "-n", "1\n2\n3"]], "1\n2\n3", ["1", "2", "3"]),
([["echo", "-n", "1\n2 3"]], "1\n2 3", ["1", "2 3"]),
],
)
Read stop signals from the process and update the process state. (#5361) Reading stop signals from the process and update the process state. ### The issue Technically. In a couple of places that critical for processing signals we have `os.waitpid()`. The function behavior is pretty unobvious and one of things is processing return code after catching the signal. We had no good signal processing around this and this PR fixes this. See also `proc_untraced_waitpid` function description. From user perspective. For example we have process that is waiting for user input from terminal e.g. `python -c "input()"` or `fzf`. If this process will be in captured pipeline e.g. `!(echo 1 | fzf | head)` it will be suspended by OS and the pipeline will be in the endless loop with future crashing and corrupting std at the end. This PR fixes this. ### The solution Technically. The key function is `proc_untraced_waitpid` - it catches the stop signals and updates the process state. From user perspective. First of all we expect that users will use captured object `!()` only for capturable processes. Because of it our goal here is to just make the behavior in this case stable. In this PR we detect that process in the pipeline is suspended and we need to finish the command pipeline carefully: * Show the message about suspended process. * Keep suspended process in `jobs`. The same behavior we can see in bash. This is good because we don't know what process suspended and why. May be experienced user will want to continue it manually. * Finish the CommandPipeline with returncode=None and suspended=True. ### Before ```xsh !(fzf) # or !(python -c "input()") # Hanging / Exceptions / OSError / No way to end the command. # After exception: $(echo 1) # OSError / IO error ``` ### After ```xsh !(fzf) # or `!(ls | fzf | head)` or `!(python -c "input()")` # Process ['fzf'] with pid 60000 suspended with signal 22 SIGTTOU and stay in `jobs`. # This happends when process start waiting for input but there is no terminal attached in captured mode. # CommandPipeline(returncode=None, suspended=True, ...) $(echo 1) # Success. ``` Closes #4752 #4577 ### Notes * There is pretty edge case situation when the process was terminated so fast that we can't catch pid alive and check signal ([src](https://github.com/xonsh/xonsh/blob/67d672783db6397bdec7ae44a9d9727b1e20a772/xonsh/jobs.py#L71-L80)). I leave it as is for now. ### Mentions #2159 ## For community ⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍 comment** --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Gil Forsyth <gforsyth@users.noreply.github.com>
2024-05-22 17:45:39 +02:00
@pytest.mark.flaky(reruns=3, reruns_delay=1)
feat: add subproc output format, autostrip singleline output (#5377) ### Motivation * To have an ability to manage the output format added ``$XONSH_SUBPROC_OUTPUT_FORMAT`` to switch the way to return the output lines. Default ``stream_lines`` to return text. Alternative ``list_lines`` to return the list of lines. Also supported custom lambda function. * Additionally the [proposal to change default behavior](https://github.com/xonsh/xonsh/pull/5377#discussion_r1587627131) for a single line case. * Closes #3924 as soft solution. ### Before ```xsh mkdir -p /tmp/tst && cd /tmp/tst && touch 1 2 3 $(ls) # '1\n2\n3\n' id $(whoami) # id: ‘pc\n’: no such user: Invalid argument du $(ls) # du: cannot access '1'$'\n''2'$'\n''3'$'\n': No such file or directory ls $(fzf) # ls: cannot access 'FUNDING.yml'$'\n': No such file or directory ``` ### After ```xsh mkdir -p /tmp/tst && cd /tmp/tst && touch 1 2 3 $XONSH_SUBPROC_OUTPUT_FORMAT = 'list_lines' $(ls) # ['1', '2', '3'] [f for f in $(ls)] # ['1', '2', '3'] id $(whoami) # uid=501(user) gid=20(staff) du $(ls) # 0 1 # 0 2 # 0 3 ls $(fzf) # FUNDING.yml # etc mkdir -p /tmp/@($(whoami))/dir cat /etc/passwd | grep $(whoami) ``` ### Notes * It will be good to improve parser for cases like `mkdir -p /tmp/$(whoami)/dir`. PR is welcome! * I named the default mode as `stream_lines` (instead of just `stream` or `raw`) because in fact we transform raw output into stream of lines and possibly reduce the length of output ([replacing `\r\n` to `\n`](https://github.com/xonsh/xonsh/blob/c3a12b2a9c2958ce2a8e97a59b41f030f24cb45c/xonsh/procs/pipelines.py#L380-L383)). May be some day we need to add raw "stream" output format. * Now anybody can implement bash `IFS` behavior in [bashisms](https://github.com/xonsh/xontrib-bashisms). --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-02 17:48:25 +02:00
def test_subproc_output_format(cmds, exp_stream_lines, exp_list_lines, xonsh_session):
xonsh_session.env["XONSH_SUBPROC_OUTPUT_FORMAT"] = "stream_lines"
output = run_subproc(cmds, "stdout")
assert output == exp_stream_lines
xonsh_session.env["XONSH_SUBPROC_OUTPUT_FORMAT"] = "list_lines"
output = run_subproc(cmds, "stdout")
assert output == exp_list_lines
@skip_if_on_windows
@pytest.mark.parametrize(
"captured, exp_is_none",
[
("object", False),
("stdout", True),
("hiddenobject", False),
(False, True),
],
)
def test_run_subproc_background(captured, exp_is_none):
cmds = (["echo", "hello"], "&")
return_val = run_subproc(cmds, captured)
assert (return_val is None) == exp_is_none
def test_spec_decorator_alias_alone(xession):
xession.aliases["xunthread"] = SpecAttrDecoratorAlias(
{"threadable": False, "force_threadable": False}
)
cmds = [["xunthread"]]
spec = cmds_to_specs(cmds, captured="object")[-1]
assert spec.cmd == []
assert spec.alias_name == "xunthread"
def test_spec_decorator_alias(xession):
xession.aliases["xunthread"] = SpecAttrDecoratorAlias(
{"threadable": False, "force_threadable": False}
)
cmds = [["xunthread", "echo", "arg0", "arg1"]]
spec = cmds_to_specs(cmds, captured="object")[-1]
assert spec.cmd == ["echo", "arg0", "arg1"]
assert spec.threadable is False
assert spec.force_threadable is False
def test_spec_decorator_alias_tree(xession):
xession.aliases["xthread"] = SpecAttrDecoratorAlias(
{"threadable": True, "force_threadable": True}
)
xession.aliases["xunthread"] = SpecAttrDecoratorAlias(
{"threadable": False, "force_threadable": False}
)
xession.aliases["foreground"] = "xthread midground f0 f1"
xession.aliases["midground"] = "ground m0 m1"
xession.aliases["ground"] = "xthread underground g0 g1"
xession.aliases["underground"] = "xunthread echo u0 u1"
cmds = [
["foreground"],
]
spec = cmds_to_specs(cmds, captured="object")[-1]
assert spec.cmd == ["echo", "u0", "u1", "g0", "g1", "m0", "m1", "f0", "f1"]
assert spec.alias_name == "foreground"
assert spec.threadable is False
assert spec.force_threadable is False
def test_spec_decorator_alias_multiple(xession):
xession.aliases["@unthread"] = SpecAttrDecoratorAlias(
{"threadable": False, "force_threadable": False}
)
xession.aliases["@dict"] = SpecAttrDecoratorAlias({"output_format": "list_lines"})
cmds = [
["@unthread", "@dict", "echo", "1"],
]
spec = cmds_to_specs(cmds, captured="object")[-1]
assert spec.cmd == ["echo", "1"]
assert spec.alias_name is None
assert spec.threadable is False
assert spec.force_threadable is False
assert spec.output_format == "list_lines"
@skip_if_on_windows
def test_spec_decorator_alias_output_format(xession):
class OutputLinesDecoratorAlias(DecoratorAlias):
def decorate_spec(self, spec):
spec.output_format = "list_lines"
xession.aliases["xlines"] = OutputLinesDecoratorAlias()
cmds = [["xlines", "echo", "1\n2\n3"]]
specs = cmds_to_specs(cmds, captured="stdout")
(p := _run_command_pipeline(specs, cmds)).end()
assert p.output == ["1", "2", "3"]
@pytest.mark.parametrize("thread_subprocs", [False, True])
def test_callable_alias_cls(thread_subprocs, xession):
class Cls:
def __call__(self, *args, **kwargs):
print(args, kwargs)
obj = Cls()
xession.aliases["tst"] = obj
env = xession.env
cmds = (["tst", "/root"],)
env["THREAD_SUBPROCS"] = thread_subprocs
spec = cmds_to_specs(cmds, captured="stdout")[0]
proc = spec.run()
assert proc.f == obj
feat: add subproc output format, autostrip singleline output (#5377) ### Motivation * To have an ability to manage the output format added ``$XONSH_SUBPROC_OUTPUT_FORMAT`` to switch the way to return the output lines. Default ``stream_lines`` to return text. Alternative ``list_lines`` to return the list of lines. Also supported custom lambda function. * Additionally the [proposal to change default behavior](https://github.com/xonsh/xonsh/pull/5377#discussion_r1587627131) for a single line case. * Closes #3924 as soft solution. ### Before ```xsh mkdir -p /tmp/tst && cd /tmp/tst && touch 1 2 3 $(ls) # '1\n2\n3\n' id $(whoami) # id: ‘pc\n’: no such user: Invalid argument du $(ls) # du: cannot access '1'$'\n''2'$'\n''3'$'\n': No such file or directory ls $(fzf) # ls: cannot access 'FUNDING.yml'$'\n': No such file or directory ``` ### After ```xsh mkdir -p /tmp/tst && cd /tmp/tst && touch 1 2 3 $XONSH_SUBPROC_OUTPUT_FORMAT = 'list_lines' $(ls) # ['1', '2', '3'] [f for f in $(ls)] # ['1', '2', '3'] id $(whoami) # uid=501(user) gid=20(staff) du $(ls) # 0 1 # 0 2 # 0 3 ls $(fzf) # FUNDING.yml # etc mkdir -p /tmp/@($(whoami))/dir cat /etc/passwd | grep $(whoami) ``` ### Notes * It will be good to improve parser for cases like `mkdir -p /tmp/$(whoami)/dir`. PR is welcome! * I named the default mode as `stream_lines` (instead of just `stream` or `raw`) because in fact we transform raw output into stream of lines and possibly reduce the length of output ([replacing `\r\n` to `\n`](https://github.com/xonsh/xonsh/blob/c3a12b2a9c2958ce2a8e97a59b41f030f24cb45c/xonsh/procs/pipelines.py#L380-L383)). May be some day we need to add raw "stream" output format. * Now anybody can implement bash `IFS` behavior in [bashisms](https://github.com/xonsh/xontrib-bashisms). --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-02 17:48:25 +02:00
def test_specs_resolve_args_list():
spec = cmds_to_specs([["echo", ["1", "2", "3"]]], captured="stdout")[0]
assert spec.cmd[-3:] == ["1", "2", "3"]
@pytest.mark.parametrize("captured", ["hiddenobject", False])
def test_procproxy_not_captured(xession, captured):
xession.aliases["tst"] = lambda: 0
cmds = (["tst", "/root"],)
xession.env["THREAD_SUBPROCS"] = False
specs = cmds_to_specs(cmds, captured)
assert specs[0].cls is ProcProxy
# neither stdout nor stderr should be captured
assert specs[0].stdout is None
assert specs[0].stderr is None
def test_on_command_not_found_fires(xession):
xession.env.update(
dict(
XONSH_INTERACTIVE=True,
)
)
fired = False
def my_handler(cmd, **kwargs):
nonlocal fired
assert cmd[0] == "xonshcommandnotfound"
fired = True
xession.builtins.events.on_command_not_found(my_handler)
subproc = SubprocSpec.build(["xonshcommandnotfound"])
with pytest.raises(XonshError) as expected:
subproc.run()
assert "command not found: 'xonshcommandnotfound'" in str(expected.value)
assert fired
def test_on_command_not_found_doesnt_fire_in_non_interactive_mode(xession):
xession.env.update(
dict(
XONSH_INTERACTIVE=False,
)
)
fired = False
def my_handler(cmd, **kwargs):
nonlocal fired
assert cmd[0] == "xonshcommandnotfound"
fired = True
xession.builtins.events.on_command_not_found(my_handler)
subproc = SubprocSpec.build(["xonshcommandnotfound"])
with pytest.raises(XonshError) as expected:
subproc.run()
assert "command not found: 'xonshcommandnotfound'" in str(expected.value)
assert not fired
def test_redirect_to_substitution(xession):
s = SubprocSpec.build(
# `echo hello > @('file')`
["echo", "hello", (">", ["file"])]
)
assert s.stdout.name == "file"
def test_partial_args_from_classmethod(xession):
class Class:
@classmethod
def alias(cls, args, stdin, stdout):
print("ok", file=stdout)
return 0
xession.aliases["alias_with_partial_args"] = Class.alias
out = run_subproc([["alias_with_partial_args"]], captured="stdout")
assert out == "ok"
Alias that returns modified command (#5473) * Command Alias * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * news * docs * tests * docs * wip * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * clean * news * news * bumptests * bumptests * new api * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * doooocs * comments * comments * comments * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * comments * bump test * remove CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * test * test * wip * revert * wip * wip * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * wip * docs * news * tests * tests * test * test * test * Update docs/tutorial.rst Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> * Update xonsh/aliases.py Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix, thanks jaraco! * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * more comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * cleaning --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
2024-07-12 01:35:20 +02:00
def test_alias_return_command_alone(xession):
@xession.aliases.register("wakka")
@xession.aliases.return_command
def _wakka(args):
return ["echo"] + args
cmds = [
["wakka"],
]
spec = cmds_to_specs(cmds, captured="object")[-1]
assert spec.cmd == ["echo"]
assert spec.alias_name == "wakka"
def test_alias_return_command_alone_args(xession):
@xession.aliases.register("wakka")
@xession.aliases.return_command
def _wakka(args):
return ["echo", "e0", "e1"] + args
cmds = [
["wakka", "0", "1"],
]
spec = cmds_to_specs(cmds, captured="object")[-1]
assert spec.cmd == ["echo", "e0", "e1", "0", "1"]
assert spec.alias_name == "wakka"
def test_alias_return_command_chain(xession):
xession.aliases["foreground"] = "midground f0 f1"
@xession.aliases.register("midground")
@xession.aliases.return_command
def _midground(args):
return ["ground", "m0", "m1"] + args
xession.aliases["ground"] = "background g0 g1"
xession.aliases["background"] = "echo b0 b1"
cmds = [
["foreground", "0", "1"],
]
spec = cmds_to_specs(cmds, captured="object")[-1]
assert spec.cmd == [
"echo",
"b0",
"b1",
"g0",
"g1",
"m0",
"m1",
"f0",
"f1",
"0",
"1",
]
assert spec.alias_name == "foreground"
def test_alias_return_command_chain_decorators(xession):
Alias that returns modified command (#5473) * Command Alias * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * news * docs * tests * docs * wip * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * clean * news * news * bumptests * bumptests * new api * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * doooocs * comments * comments * comments * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * comments * bump test * remove CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * test * test * wip * revert * wip * wip * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * wip * docs * news * tests * tests * test * test * test * Update docs/tutorial.rst Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> * Update xonsh/aliases.py Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix, thanks jaraco! * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * more comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * cleaning --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
2024-07-12 01:35:20 +02:00
xession.aliases["foreground"] = "midground f0 f1"
xession.aliases["xunthread"] = SpecAttrDecoratorAlias(
Alias that returns modified command (#5473) * Command Alias * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * news * docs * tests * docs * wip * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * clean * news * news * bumptests * bumptests * new api * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * doooocs * comments * comments * comments * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * comments * bump test * remove CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * test * test * wip * revert * wip * wip * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * wip * docs * news * tests * tests * test * test * test * Update docs/tutorial.rst Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> * Update xonsh/aliases.py Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix, thanks jaraco! * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * more comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * cleaning --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
2024-07-12 01:35:20 +02:00
{"threadable": False, "force_threadable": False}
)
@xession.aliases.register("midground")
@xession.aliases.return_command
def _midground(args):
return ["ground", "m0", "m1"]
xession.aliases["ground"] = "background g0 g1"
xession.aliases["background"] = "xunthread echo b0 b1"
cmds = [
["foreground", "0", "1"],
]
spec = cmds_to_specs(cmds, captured="object")[-1]
assert spec.cmd == ["echo", "b0", "b1", "g0", "g1", "m0", "m1"]
assert spec.alias_name == "foreground"
assert spec.threadable is False
def test_alias_return_command_eval_inside(xession):
xession.aliases["xthread"] = SpecAttrDecoratorAlias(
Alias that returns modified command (#5473) * Command Alias * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * news * docs * tests * docs * wip * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * clean * news * news * bumptests * bumptests * new api * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * doooocs * comments * comments * comments * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * comments * bump test * remove CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * test * test * wip * revert * wip * wip * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * wip * docs * news * tests * tests * test * test * test * Update docs/tutorial.rst Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> * Update xonsh/aliases.py Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix, thanks jaraco! * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * more comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * cleaning --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
2024-07-12 01:35:20 +02:00
{"threadable": True, "force_threadable": True}
)
@xession.aliases.register("xsudo")
@xession.aliases.return_command
def _midground(args, decorators=None):
Alias that returns modified command (#5473) * Command Alias * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * news * docs * tests * docs * wip * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * clean * news * news * bumptests * bumptests * new api * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * doooocs * comments * comments * comments * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * comments * bump test * remove CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * test * test * wip * revert * wip * wip * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * wip * docs * news * tests * tests * test * test * test * Update docs/tutorial.rst Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> * Update xonsh/aliases.py Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix, thanks jaraco! * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * more comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * cleaning --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
2024-07-12 01:35:20 +02:00
return [
"sudo",
*xession.aliases.eval_alias(args, decorators=decorators),
Alias that returns modified command (#5473) * Command Alias * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * news * docs * tests * docs * wip * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * tests * tests * clean * news * news * bumptests * bumptests * new api * tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * doooocs * comments * comments * comments * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * aliases.CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * aliases.CUT_ARGS * comments * bump test * remove CUT_ARGS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * test * test * wip * revert * wip * wip * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * wip * docs * news * tests * tests * test * test * test * Update docs/tutorial.rst Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> * Update xonsh/aliases.py Co-authored-by: Jason R. Coombs <jaraco@jaraco.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix, thanks jaraco! * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * more comments * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * cleaning --------- Co-authored-by: a <1@1.1> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Jason R. Coombs <jaraco@jaraco.com>
2024-07-12 01:35:20 +02:00
]
xession.aliases["cmd"] = "xthread echo 1"
cmds = [
["xsudo", "cmd"],
]
spec = cmds_to_specs(cmds, captured="object")[-1]
assert spec.cmd == ["sudo", "echo", "1"]
assert spec.alias_name == "xsudo"
assert spec.threadable is True
def test_auto_cd(xession, tmpdir):
xession.aliases["cd"] = lambda: "some_cd_alias"
dir = str(tmpdir)
with xession.env.swap(AUTO_CD=True):
spec = cmds_to_specs([[dir]], captured="object")[-1]
assert spec.alias.__name__ == "cd"
assert spec.cmd[0] == dir