xonsh/tests/test_pipelines.py
Andy Kipp f81d55d09b
Do not load ~/.xonshrc in not interactive mode (#5491)
### Motivation

In #5099 was introduced the logic where all RC files are loaded in
interactive and non-interactive modes. This logic is not working good
for home based `~/.xonshrc` file.

First of all `~/.xonshrc` is the buffer/accumulator of settings focused
on interactive mode. Most tools with integration with xonsh (e.g.
`conda`, `zoxide`, etc) offer to just do `echo "init_tool()" >>
~/.xonshrc` (`conda` has around 20 lines of init code) to start using
the tool and users are doing this without any doubts.

But because of after 5099 `~/.xonshrc` is executed in non-interactive
mode the adding code to it leads to unexpected and unintended side
effects:

* If you run a script with shebang (e.g. `#!/usr/bin/env xonsh` or
[xonsh-awesome-cli-app](https://github.com/anki-code/xonsh-awesome-cli-app))
or just from `xonsh script.xsh` the code will be unexpected and
unintended slower.

* If you're using xonsh-based tools (e.g. you install them using pip)
and run them in environment that has no packages that initiated in
`~/.xonshrc` you will see epic errors.

* Additional context: 
* Bash and Zsh do not load `~/.bashrc` and `~/.zshrc` in non-interactive
mode by the same reasons.
* We have welcome message `Create ~/.xonshrc file manually or use xonfig
to suppress the welcome message` and we don't want to make the process
of creating this file complex.

All of this leads to bad unexpected and unintended experience. This PR
is to fix this.

### Expectation

By doing this fix we assume that experienced user who wants to build
good repeatable run control files will use another ways to create config
files and this has [reflection in
docs](8860f2bd52/docs/xonshrc.rst).
In the nutshell if you want to create the RC files that affect every run
of code you should use one or many of these ways:

* Cross-desktop group (XDG) compliant `~/.config/xonsh/rc.xsh` control
file.
* The system-wide control file `/etc/xonsh/xonshrc` for Linux and OSX
and in `%ALLUSERSPROFILE%\xonsh\xonshrc` on Windows. It controls options
that are applied to all users of Xonsh on a given system.
* The home-based directory `~/.config/xonsh/rc.d/` and system
`/etc/xonsh/rc.d/` can contain .xsh files. They will be executed at
startup in order. This allows for drop-in configuration where your
configuration can be split across scripts and common and local
configurations more easily separated.

In your configs you need to check `$XONSH_INTERACTIVE` and
`$XONSH_LOGIN` explicitly.

### Before

`~/.xonshrc` is used in non-interactive mode.

```xsh
echo "echo RC" >> ~/.xonshrc
cd /tmp
echo "echo Script" > script.xsh
xonsh script.xsh
# RC
# Script
```
```xsh
cd /tmp
echo 'echo RC' >> ~/.xonshrc
echo '#!/usr/bin/env xonsh' > myscript
chmod +x myscript

./myscript
# RC
```

### After

`~/.xonshrc` is not used in non-interactive mode. Use `-i` if you need
it.

```xsh
echo "echo RC" >> ~/.xonshrc
cd /tmp
echo "echo Script" > script.xsh
xonsh script.xsh
# Script

xonsh -i script.xsh
# RC
# Script
```
```xsh
cd /tmp
echo 'echo RC' >> ~/.xonshrc
echo '#!/usr/bin/env xonsh' > myscript
chmod +x myscript

./myscript
```
Closes #5488 #4096 #5496

### Fun

I want to leave here some nice representation of how it works in
bash/sh/zsh from [twitter
post](https://twitter.com/paxx39/status/1742768007154479109):



![image](https://github.com/xonsh/xonsh/assets/1708680/cd7b3803-483f-4d5d-bf9d-baa61c794f68)


## 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-06-13 10:08:23 -04:00

128 lines
3.7 KiB
Python

"""
Tests for command pipelines.
"""
import os
import pytest
from xonsh.platform import ON_WINDOWS
from xonsh.procs.pipelines import CommandPipeline
from xonsh.pytest.tools import skip_if_on_unix, skip_if_on_windows
@pytest.fixture(autouse=True)
def patched_events(monkeypatch, xonsh_events, xonsh_session):
from xonsh.jobs import get_tasks
get_tasks().clear()
# needed for ci tests
monkeypatch.setitem(
xonsh_session.env, "RAISE_SUBPROC_ERROR", False
) # for the failing `grep` commands
monkeypatch.setitem(
xonsh_session.env, "XONSH_CAPTURE_ALWAYS", True
) # capture output of ![]
if ON_WINDOWS:
monkeypatch.setattr(
xonsh_session.commands_cache,
"aliases",
{
"echo": "cmd /c echo".split(),
"grep": "cmd /c findstr".split(),
},
raising=False,
)
@pytest.mark.parametrize(
"cmdline, stdout, stderr, raw_stdout",
(
("!(echo hi)", "hi", "", "hi\n"),
("!(echo hi o>e)", "", "hi\n", ""),
pytest.param(
"![echo hi]",
"hi",
"",
"hi\n",
marks=pytest.mark.xfail(
ON_WINDOWS,
reason="ConsoleParallelReader doesn't work without a real console",
),
),
pytest.param(
"![echo hi o>e]",
"",
"hi\n",
"",
marks=pytest.mark.xfail(
ON_WINDOWS, reason="stderr isn't captured in ![] on windows"
),
),
pytest.param(
r"!(echo 'hi\nho')", "hi\nho\n", "", "hi\nho\n", marks=skip_if_on_windows
), # won't work with cmd
# for some reason cmd's echo adds an extra space:
pytest.param(
r"!(cmd /c 'echo hi && echo ho')",
"hi \nho\n",
"",
"hi \nho\n",
marks=skip_if_on_unix,
),
("!(echo hi | grep h)", "hi", "", "hi\n"),
("!(echo hi | grep x)", "", "", ""),
),
)
@pytest.mark.flaky(reruns=3, reruns_delay=2)
def test_command_pipeline_capture(cmdline, stdout, stderr, raw_stdout, xonsh_execer):
pipeline: CommandPipeline = xonsh_execer.eval(cmdline)
assert pipeline.out == stdout
assert pipeline.err == (stderr or None)
assert pipeline.raw_out == raw_stdout.replace("\n", os.linesep).encode()
assert pipeline.raw_err == stderr.replace("\n", os.linesep).encode()
@pytest.mark.parametrize(
"cmdline, output",
(
("echo hi", "hi"),
("echo hi | grep h", "hi"),
("echo hi | grep x", ""),
pytest.param("echo -n hi", "hi", marks=skip_if_on_windows),
),
)
def test_simple_capture(cmdline, output, xonsh_execer):
assert xonsh_execer.eval(f"$({cmdline})") == output
def test_raw_substitution(xonsh_execer):
assert xonsh_execer.eval("$(echo @(b'bytes!'))") == "bytes!"
@pytest.mark.parametrize(
"cmdline, result",
(
("bool(!(echo 1))", True),
("bool(!(nocommand))", False),
("int(!(echo 1))", 0),
("int(!(nocommand))", 1),
("hash(!(echo 1))", 0),
("hash(!(nocommand))", 1),
("str(!(echo 1))", "1"),
("str(!(nocommand))", ""),
("!(echo 1) == 0", True),
("!(nocommand) == 1", True),
pytest.param("!(echo -n str) == 'str'", True, marks=skip_if_on_windows),
("!(nocommand) == ''", True),
),
)
def test_casting(cmdline, result, xonsh_execer):
assert xonsh_execer.eval(f"{cmdline}") == result
@skip_if_on_windows
def test_background_pgid(xonsh_session, monkeypatch):
monkeypatch.setitem(xonsh_session.env, "XONSH_INTERACTIVE", True)
pipeline = xonsh_session.execer.eval("![echo hi &]")
assert pipeline.term_pgid is not None