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:
Jerzy Drozdz 2020-03-01 18:56:23 +01:00 committed by GitHub
parent 1b1c668049
commit b37675a6e8
Failed to generate hash of commit
5 changed files with 156 additions and 13 deletions

1
.gitignore vendored
View file

@ -36,6 +36,7 @@ pip-selfcheck.json
bin/ bin/
/lib/ /lib/
include/ include/
venv/
# Mac # Mac
.DS_Store .DS_Store

24
news/extended-exec.rst Normal file
View 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>

View 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"] == {}

View file

@ -610,7 +610,7 @@ def source_cmd(args, stdin=None):
def xexec(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 exec (also aliased as xexec) uses the os.execvpe() function to
replace the xonsh process with the specified program. This provides replace the xonsh process with the specified program. This provides
@ -620,6 +620,13 @@ def xexec(args, stdin=None):
bash $ bash $
The '-h' and '--help' options print this message and exit. 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 Notes
----- -----
@ -633,18 +640,40 @@ def xexec(args, stdin=None):
""" """
if len(args) == 0: if len(args) == 0:
return (None, "xonsh: exec: no args specified\n", 1) 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) 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() denv = builtins.__xonsh__.env.detype()
try:
os.execvpe(args[0], args, denv) try:
except FileNotFoundError as e: os.execvpe(command, args.command, denv)
return ( except FileNotFoundError as e:
None, return (
"xonsh: exec: file not found: {}: {}" "\n".format(e.args[1], args[0]), None,
1, "xonsh: exec: file not found: {}: {}"
) "\n".format(e.args[1], args.command[0]),
1,
)
class AWitchAWitch(argparse.Action): class AWitchAWitch(argparse.Action):

View file

@ -292,7 +292,11 @@ def start_services(shell_kwargs, args):
env = builtins.__xonsh__.env env = builtins.__xonsh__.env
rc = shell_kwargs.get("rc", None) rc = shell_kwargs.get("rc", None)
rc = env.get("XONSHRC") if rc is None else rc 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 # Don't load xonshrc if not interactive shell
rc = None rc = None
events.on_pre_rc.fire() events.on_pre_rc.fire()
@ -329,7 +333,8 @@ def premain(argv=None):
"cacheall": args.cacheall, "cacheall": args.cacheall,
"ctx": builtins.__xonsh__.ctx, "ctx": builtins.__xonsh__.ctx,
} }
if args.login: if args.login or sys.argv[0].startswith("-"):
args.login = True
shell_kwargs["login"] = True shell_kwargs["login"] = True
if args.norc: if args.norc:
shell_kwargs["rc"] = () shell_kwargs["rc"] = ()