mirror of
https://github.com/xonsh/xonsh.git
synced 2025-03-04 00:14:41 +01:00
Loading rc in non-interactive login shell (#3422)
* Setting -l flag loads environment on non-interactive shell * Added checking dash existance in sys.argv[0] * Fixed -a switch behaviour * Added tests for xexec * Added test for python<3.7 on Windows * Testing non existent command with mocked execvpe
This commit is contained in:
parent
1b1c668049
commit
b37675a6e8
5 changed files with 156 additions and 13 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -36,6 +36,7 @@ pip-selfcheck.json
|
|||
bin/
|
||||
/lib/
|
||||
include/
|
||||
venv/
|
||||
|
||||
# Mac
|
||||
.DS_Store
|
||||
|
|
24
news/extended-exec.rst
Normal file
24
news/extended-exec.rst
Normal file
|
@ -0,0 +1,24 @@
|
|||
**Added:**
|
||||
|
||||
* Added ``-l``, ``-c`` and ``-a`` options to ``xexec``, works now like ``exec``
|
||||
in bash/zsh
|
||||
|
||||
**Changed:**
|
||||
|
||||
* ``-l`` switch works like bash, loads environment in non-interactive shell
|
||||
|
||||
**Deprecated:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Removed:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Fixed:**
|
||||
|
||||
* <news item>
|
||||
|
||||
**Security:**
|
||||
|
||||
* <news item>
|
84
tests/aliases/test_xexec.py
Normal file
84
tests/aliases/test_xexec.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
import os
|
||||
import inspect
|
||||
import pytest
|
||||
import sys
|
||||
|
||||
from xonsh.aliases import xexec
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mockexecvpe(monkeypatch):
|
||||
def mocked_execvpe(_command, _args, _env):
|
||||
pass
|
||||
|
||||
monkeypatch.setattr(os, "execvpe", mocked_execvpe)
|
||||
|
||||
|
||||
def test_noargs(mockexecvpe):
|
||||
assert xexec([]) == (None, "xonsh: exec: no args specified\n", 1)
|
||||
|
||||
|
||||
def test_missing_command(mockexecvpe):
|
||||
assert xexec(["-a", "foo"]) == (None, "xonsh: exec: no command specified\n", 1)
|
||||
assert xexec(["-c"]) == (None, "xonsh: exec: no command specified\n", 1)
|
||||
assert xexec(["-l"]) == (None, "xonsh: exec: no command specified\n", 1)
|
||||
|
||||
|
||||
def test_command_not_found(monkeypatch):
|
||||
|
||||
dummy_error_msg = "This is dummy error message, file not found or something like that"
|
||||
command = "non_existing_command"
|
||||
|
||||
def mocked_execvpe(_command, _args, _env):
|
||||
raise FileNotFoundError(2, dummy_error_msg)
|
||||
monkeypatch.setattr(os, "execvpe", mocked_execvpe)
|
||||
|
||||
assert xexec([command]) == (None,
|
||||
"xonsh: exec: file not found: {}: {}" "\n".format(dummy_error_msg, command),
|
||||
1)
|
||||
|
||||
|
||||
def test_help(mockexecvpe):
|
||||
assert xexec(["-h"]) == inspect.getdoc(xexec)
|
||||
assert xexec(["--help"]) == inspect.getdoc(xexec)
|
||||
|
||||
|
||||
def test_a_switch(monkeypatch):
|
||||
called = {}
|
||||
|
||||
def mocked_execvpe(command, args, env):
|
||||
called.update({"command": command, "args": args, "env": env})
|
||||
|
||||
monkeypatch.setattr(os, "execvpe", mocked_execvpe)
|
||||
proc_name = "foo"
|
||||
command = "bar"
|
||||
command_args = ["1"]
|
||||
xexec(["-a", proc_name, command] + command_args)
|
||||
assert called["command"] == command
|
||||
assert called["args"][0] == proc_name
|
||||
assert len(called["args"]) == len([command] + command_args)
|
||||
|
||||
|
||||
def test_l_switch(monkeypatch):
|
||||
called = {}
|
||||
|
||||
def mocked_execvpe(command, args, env):
|
||||
called.update({"command": command, "args": args, "env": env})
|
||||
|
||||
monkeypatch.setattr(os, "execvpe", mocked_execvpe)
|
||||
command = "bar"
|
||||
xexec(["-l", command, "1"])
|
||||
|
||||
assert called["args"][0].startswith("-")
|
||||
|
||||
|
||||
def test_c_switch(monkeypatch):
|
||||
called = {}
|
||||
|
||||
def mocked_execvpe(command, args, env):
|
||||
called.update({"command": command, "args": args, "env": env})
|
||||
|
||||
monkeypatch.setattr(os, "execvpe", mocked_execvpe)
|
||||
command = "sleep"
|
||||
xexec(["-c", command, "1"])
|
||||
assert called["env"] == {}
|
|
@ -610,7 +610,7 @@ def source_cmd(args, stdin=None):
|
|||
|
||||
|
||||
def xexec(args, stdin=None):
|
||||
"""exec [-h|--help] command [args...]
|
||||
"""exec [-h|--help] [-cl] [-a name] command [args...]
|
||||
|
||||
exec (also aliased as xexec) uses the os.execvpe() function to
|
||||
replace the xonsh process with the specified program. This provides
|
||||
|
@ -620,6 +620,13 @@ def xexec(args, stdin=None):
|
|||
bash $
|
||||
|
||||
The '-h' and '--help' options print this message and exit.
|
||||
If the '-l' option is supplied, the shell places a dash at the
|
||||
beginning of the zeroth argument passed to command to simulate login
|
||||
shell.
|
||||
The '-c' option causes command to be executed with an empty environment.
|
||||
If '-a' is supplied, the shell passes name as the zeroth argument
|
||||
to the executed command.
|
||||
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
@ -633,18 +640,40 @@ def xexec(args, stdin=None):
|
|||
"""
|
||||
if len(args) == 0:
|
||||
return (None, "xonsh: exec: no args specified\n", 1)
|
||||
elif args[0] == "-h" or args[0] == "--help":
|
||||
|
||||
parser = argparse.ArgumentParser(add_help=False)
|
||||
parser.add_argument("-h", "--help", action="store_true")
|
||||
parser.add_argument("-l", dest="login", action="store_true")
|
||||
parser.add_argument("-c", dest="clean", action="store_true")
|
||||
parser.add_argument("-a", dest="name", nargs="?")
|
||||
parser.add_argument("command", nargs=argparse.REMAINDER)
|
||||
args = parser.parse_args(args)
|
||||
|
||||
if args.help:
|
||||
return inspect.getdoc(xexec)
|
||||
else:
|
||||
|
||||
if len(args.command) == 0:
|
||||
return (None, "xonsh: exec: no command specified\n", 1)
|
||||
|
||||
command = args.command[0]
|
||||
if args.name is not None:
|
||||
args.command[0] = args.name
|
||||
if args.login:
|
||||
args.command[0] = "-{}".format(args.command[0])
|
||||
|
||||
denv = {}
|
||||
if not args.clean:
|
||||
denv = builtins.__xonsh__.env.detype()
|
||||
try:
|
||||
os.execvpe(args[0], args, denv)
|
||||
except FileNotFoundError as e:
|
||||
return (
|
||||
None,
|
||||
"xonsh: exec: file not found: {}: {}" "\n".format(e.args[1], args[0]),
|
||||
1,
|
||||
)
|
||||
|
||||
try:
|
||||
os.execvpe(command, args.command, denv)
|
||||
except FileNotFoundError as e:
|
||||
return (
|
||||
None,
|
||||
"xonsh: exec: file not found: {}: {}"
|
||||
"\n".format(e.args[1], args.command[0]),
|
||||
1,
|
||||
)
|
||||
|
||||
|
||||
class AWitchAWitch(argparse.Action):
|
||||
|
|
|
@ -292,7 +292,11 @@ def start_services(shell_kwargs, args):
|
|||
env = builtins.__xonsh__.env
|
||||
rc = shell_kwargs.get("rc", None)
|
||||
rc = env.get("XONSHRC") if rc is None else rc
|
||||
if args.mode != XonshMode.interactive and not args.force_interactive:
|
||||
if (
|
||||
args.mode != XonshMode.interactive
|
||||
and not args.force_interactive
|
||||
and not args.login
|
||||
):
|
||||
# Don't load xonshrc if not interactive shell
|
||||
rc = None
|
||||
events.on_pre_rc.fire()
|
||||
|
@ -329,7 +333,8 @@ def premain(argv=None):
|
|||
"cacheall": args.cacheall,
|
||||
"ctx": builtins.__xonsh__.ctx,
|
||||
}
|
||||
if args.login:
|
||||
if args.login or sys.argv[0].startswith("-"):
|
||||
args.login = True
|
||||
shell_kwargs["login"] = True
|
||||
if args.norc:
|
||||
shell_kwargs["rc"] = ()
|
||||
|
|
Loading…
Add table
Reference in a new issue