Repository: xonsh/xonsh

Issue:
xonsh should return a non-zero exit code when a script cannot be found
Currently xonsh returns a zero exit code when the script cannot be found:
```
$ xonsh xx.xsh && echo "OK"
xonsh: xx.xsh: No such file or directory.
OK
```
That seems like a bug. Compared to bash, which returns a non-zero exit code:
```
$ bash xx.xsh && echo "OK"
bash: xx.xsh: No such file or directory
```

Discussion:

Patch:
diff --git a/.appveyor.yml b/.appveyor.yml
index 563d3440..2676e243 100644
--- a/.appveyor.yml
+++ b/.appveyor.yml
@@ -1,4 +1,4 @@
-version: 0.9.11.{build}
+version: 0.9.10.{build}
 os: Windows Server 2012 R2
 environment:
 
diff --git a/.authors.yml b/.authors.yml
index ba4f8036..0706a41e 100644
--- a/.authors.yml
+++ b/.authors.yml
@@ -61,7 +61,7 @@
   - Gilbert.Forsyth@capitalone.com
   - gforsyth@gwu.edu
   - gil@forsyth.dev
-  num_commits: 511
+  num_commits: 510
   first_commit: 2015-10-19 16:04:32
   github: gforsyth
 - name: Morten Enemark Lund
@@ -432,7 +432,7 @@
   github: funkyfuture
 - name: Anthony Scopatz
   email: scopatz@gmail.com
-  num_commits: 2501
+  num_commits: 2498
   first_commit: 2015-01-21 17:04:13
   github: scopatz
 - name: anatoly techtonik
@@ -994,7 +994,7 @@
   first_commit: 2019-05-06 14:17:57
 - name: con-f-use
   email: con-f-use@users.noreply.github.com
-  num_commits: 12
+  num_commits: 8
   first_commit: 2019-05-15 04:53:37
 - name: cclauss
   email: cclauss@me.com
@@ -1054,8 +1054,3 @@
   num_commits: 1
   first_commit: 2019-07-28 22:19:49
   github: chengxuncc
-- name: Caleb Hattingh
-  email: caleb.hattingh@gmail.com
-  num_commits: 10
-  first_commit: 2019-08-02 22:11:22
-  github: cjrh
diff --git a/.coveragerc b/.coveragerc
index 8076ebd3..ad3d8fd0 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -2,7 +2,6 @@
 branch = true
 source =
   xonsh/
-  xontrib/
 omit =
   */__amalgam__.py
   xonsh/lazyasd.py
diff --git a/.mailmap b/.mailmap
index 0a576714..92287d35 100644
--- a/.mailmap
+++ b/.mailmap
@@ -57,9 +57,7 @@ Carmen Bianca Bakker <carmen@carmenbianca.eu>
 Andrew Hundt <ATHundt@gmail.com>
 Jonathan Slenders <jonathan@slenders.be>
 Justin Moen <jamoen7@gmail.com>
-con-f-use <con-f-use@users.noreply.github.com>
 Stephan Fitzpatrick <knowsuchagency@gmail.com>
-Caleb Hattingh <caleb.hattingh@gmail.com>
 Raphael Das Gupta <raphael.das.gupta@hsr.ch>
 K.-Michael Aye <kmichael.aye@gmail.com> K.-Michael Aye <michaelaye@users.noreply.github.com>
 Jason R. Coombs <jaraco@jaraco.com>
@@ -68,6 +66,7 @@ Will Wykeham <will@wykeham.net>
 Oleh Prypin <oleh@pryp.in>
 Brian Skinn <brian.skinn@gmail.com>
 Will S <wsha.code@gmail.com>
+con-f-use <con-f-use@users.noreply.github.com>
 Alessio Bogon <youtux@gmail.com> Alessio Bogon <youtux@users.noreply.github.com>
 Yohei Tamura <tamuhey@gmail.com>
 Maximilian Köhl <mail@koehlma.de>
diff --git a/AUTHORS.rst b/AUTHORS.rst
index 04a41d3b..fdf928e0 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -41,9 +41,7 @@ Authors are sorted by number of commits.
 * Andrew Hundt
 * Jonathan Slenders
 * Justin Moen
-* con-f-use
 * Stephan Fitzpatrick
-* Caleb Hattingh
 * Raphael Das Gupta
 * K.-Michael Aye
 * Jason R. Coombs
@@ -52,6 +50,7 @@ Authors are sorted by number of commits.
 * Oleh Prypin
 * Brian Skinn
 * Will S
+* con-f-use
 * Alessio Bogon
 * Yohei Tamura
 * Maximilian Köhl
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 7b3757d6..20f5fd60 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -4,31 +4,6 @@ Xonsh Change Log
 
 .. current developments
 
-v0.9.11
-====================
-
-**Changed:**
-
-* ``vox activate`` will now prepend the absolute path of the virtualenv ``bin/`` directory (or ``Scripts/`` on Windows) to ``$PATH``; before this was a relative path.
-
-**Fixed:**
-
-* "lou carcolh" example and description of ``endidx`` in completer tutorial
-* Logical operators in aliases are now executed as expected, e.g.
-  ``aliases['echocat'] = 'echo "hi" and echo "there"'`` will, when run, return
-
-  .. code-block::
-     hi
-     there
-
-**Authors:**
-
-* Gil Forsyth
-* con-f-use
-* Caleb Hattingh
-
-
-
 v0.9.10
 ====================
 
diff --git a/docs/tutorial_completers.rst b/docs/tutorial_completers.rst
index dd37025b..6a8da0f3 100644
--- a/docs/tutorial_completers.rst
+++ b/docs/tutorial_completers.rst
@@ -55,7 +55,7 @@ Completers are implemented as Python functions that take five arguments:
 * ``prefix``: the string to be matched (the last whitespace-separated token in the current line)
 * ``line``: a string representing the entire current line
 * ``begidx``: the index at which ``prefix`` starts in ``line``
-* ``endidx``: the length of the ``prefix`` in ``line``
+* ``endidx``: the index at which ``prefix`` ends in ``line``
 * ``ctx``: the current Python environment, as a dictionary mapping names to values
 
 This function should return a Python set of possible completions for ``prefix``
@@ -92,10 +92,10 @@ xonsh actually uses, in the ``xonsh.completers`` module.
 
     def unbeliever_completer(prefix, line, begidx, endidx, ctx):
         '''
-        Replaces "lou carcolh" with "snail" if tab is pressed after at least
-        typing the "lou " part.
+        Replaces "lou carcolh" with "snail" if tab is pressed after typing
+        "lou" and when typing "carcolh"
         '''
-        if  'carcolh'.startswith(prefix) and 'lou' in line[:begidx].split()[-1:]:
+        if 'carcolh'.startswith(prefix) and line[:begidx].split()[-1] == 'lou':
             return ({'snail'}, len('lou ') + len(prefix))
 
 
diff --git a/news/co.rst b/news/co.rst
deleted file mode 100644
index 2cf43847..00000000
--- a/news/co.rst
+++ /dev/null
@@ -1,23 +0,0 @@
-**Added:**
-
-* <news item>
-
-**Changed:**
-
-* <news item>
-
-**Deprecated:**
-
-* <news item>
-
-**Removed:**
-
-* <news item>
-
-**Fixed:**
-
-* ``xonsh.lib.subprocess.check_output()`` now properly captures output.
-
-**Security:**
-
-* <news item>
diff --git a/news/fnf_exit_1.rst b/news/fnf_exit_1.rst
new file mode 100644
index 00000000..b23582d1
--- /dev/null
+++ b/news/fnf_exit_1.rst
@@ -0,0 +1,31 @@
+**Added:**
+
+* <news item>
+
+**Changed:**
+
+* <news item>
+
+**Deprecated:**
+
+* <news item>
+
+**Removed:**
+
+* <news item>
+
+**Fixed:**
+
+* ``xonsh`` will return a non-zero exit code if it is run in file mode and
+  cannot find the file specified, e.g.
+
+  .. code-block::
+
+     $ xonsh thisfiledoesntexist.xsh
+     xonsh: thisfiledoesntexist.xsh: No such file or directory.
+     $ _.returncode
+     1
+
+**Security:**
+
+* <news item>
diff --git a/news/space-logical.rst b/news/space-logical.rst
deleted file mode 100644
index 1636a4d3..00000000
--- a/news/space-logical.rst
+++ /dev/null
@@ -1,24 +0,0 @@
-**Added:**
-
-* <news item>
-
-**Changed:**
-
-* Changed the ``ExecAlias`` to only be applied when the logical operators
-  (``and``, ``or``) are surrounded by whitespace.
-
-**Deprecated:**
-
-* <news item>
-
-**Removed:**
-
-* <news item>
-
-**Fixed:**
-
-* <news item>
-
-**Security:**
-
-* <news item>
diff --git a/xonsh/__init__.py b/xonsh/__init__.py
index 69f99927..6519694b 100644
--- a/xonsh/__init__.py
+++ b/xonsh/__init__.py
@@ -1,4 +1,4 @@
-__version__ = "0.9.11"
+__version__ = "0.9.10"
 
 
 # amalgamate exclude jupyter_kernel parser_table parser_test_table pyghooks
diff --git a/xonsh/aliases.py b/xonsh/aliases.py
index 5577ccf7..3c5d9ed9 100644
--- a/xonsh/aliases.py
+++ b/xonsh/aliases.py
@@ -44,7 +44,7 @@ import xonsh.xoreutils.which as xxw
 
 @lazyobject
 def SUB_EXEC_ALIAS_RE():
-    return re.compile(r"@\(|\$\(|!\(|\$\[|!\[|\&\&|\|\||\s+and\s+|\s+or\s+")
+    return re.compile(r"@\(|\$\(|!\(|\$\[|!\[")
 
 
 class Aliases(cabc.MutableMapping):
diff --git a/xonsh/lib/subprocess.xsh b/xonsh/lib/subprocess.xsh
index 0f8fa810..e3a56854 100644
--- a/xonsh/lib/subprocess.xsh
+++ b/xonsh/lib/subprocess.xsh
@@ -25,10 +25,6 @@ def check_call(cmd, cwd=None):
 
 def check_output(cmd, cwd=None):
     """Drop in replacement for ``subprocess.check_output`` like functionality"""
-    if cwd is None:
-        with ${...}.swap(RAISE_SUBPROC_ERROR=True):
-            output = $(@(cmd))
-    else:
-        with indir(cwd), ${...}.swap(RAISE_SUBPROC_ERROR=True):
-            output = $(@(cmd))
-    return output.encode('utf-8')
+    p = run(cmd, cwd=cwd, check=True)
+    # Note the encoding is to match the expected output of check_output
+    return p.output.encode('utf-8')
diff --git a/xonsh/main.py b/xonsh/main.py
index ee8a4a44..ae7c593d 100644
--- a/xonsh/main.py
+++ b/xonsh/main.py
@@ -447,6 +447,8 @@ def main_xonsh(args):
                 )
             else:
                 print("xonsh: {0}: No such file or directory.".format(args.file))
+                events.on_exit.fire()
+                sys.exit(1)
         elif args.mode == XonshMode.script_from_stdin:
             # run a script given on stdin
             code = sys.stdin.read()
diff --git a/xontrib/voxapi.py b/xontrib/voxapi.py
index 920e7bef..348757a3 100644
--- a/xontrib/voxapi.py
+++ b/xontrib/voxapi.py
@@ -4,8 +4,8 @@ API for Vox, the Python virtual environment manager for xonsh.
 Vox defines several events related to the life cycle of virtual environments:
 
 * ``vox_on_create(env: str) -> None``
-* ``vox_on_activate(env: str, path: pathlib.Path) -> None``
-* ``vox_on_deactivate(env: str, path: pathlib.Path) -> None``
+* ``vox_on_activate(env: str) -> None``
+* ``vox_on_deactivate(env: str) -> None``
 * ``vox_on_delete(env: str) -> None``
 """
 import os
@@ -37,7 +37,7 @@ Fired after an environment is created.
 events.doc(
     "vox_on_activate",
     """
-vox_on_activate(env: str, path: pathlib.Path) -> None
+vox_on_activate(env: str) -> None
 
 Fired after an environment is activated.
 """,
@@ -46,7 +46,7 @@ Fired after an environment is activated.
 events.doc(
     "vox_on_deactivate",
     """
-vox_on_deactivate(env: str, path: pathlib.Path) -> None
+vox_on_deactivate(env: str) -> None
 
 Fired after an environment is deactivated.
 """,
@@ -88,7 +88,7 @@ def _mkvenv(env_dir):
 
     This only cares about the platform. No filesystem calls are made.
     """
-    env_dir = os.path.abspath(env_dir)
+    env_dir = os.path.normpath(env_dir)
     if ON_WINDOWS:
         binname = os.path.join(env_dir, "Scripts")
         incpath = os.path.join(env_dir, "Include")
@@ -363,7 +363,7 @@ class Vox(collections.abc.Mapping):
         if "PYTHONHOME" in env:
             type(self).oldvars["PYTHONHOME"] = env.pop("PYTHONHOME")
 
-        events.vox_on_activate.fire(name=name, path=ve.env)
+        events.vox_on_activate.fire(name=name)
 
     def deactivate(self):
         """
@@ -382,7 +382,7 @@ class Vox(collections.abc.Mapping):
 
         env.pop("VIRTUAL_ENV")
 
-        events.vox_on_deactivate.fire(name=env_name, path=self[env_name].env)
+        events.vox_on_deactivate.fire(name=env_name)
         return env_name
 
     def __delitem__(self, name):


Test patch:
diff --git a/tests/test_aliases.py b/tests/test_aliases.py
index 0bc2ab89..b6020453 100644
--- a/tests/test_aliases.py
+++ b/tests/test_aliases.py
@@ -7,7 +7,7 @@ import os
 import pytest
 
 import xonsh.built_ins as built_ins
-from xonsh.aliases import Aliases, ExecAlias
+from xonsh.aliases import Aliases
 from xonsh.environ import Env
 
 from tools import skip_if_on_windows
@@ -113,18 +113,3 @@ def test_recursive_callable_partial_none(xonsh_execer, xonsh_builtins):
     args, obs = alias()
     assert args == "wakka"
     assert len(obs) == 0
-
-@pytest.mark.parametrize(
-    "alias",
-    [
-        "echo 'hi' and echo 'there'",
-        "echo 'hi' or echo 'there'",
-        "echo 'hi' && echo 'there'",
-        "echo 'hi' || echo 'there'",
-        "echo 'hi';  echo 'there'",
-    ],
-)
-def test_subprocess_logical_operators(xonsh_execer, xonsh_builtins, alias):
-    ales = make_aliases()
-    ales["echocat"] = alias
-    assert isinstance(ales["echocat"], ExecAlias)
diff --git a/tests/test_integrations.py b/tests/test_integrations.py
index 00dc338e..d9bd9023 100644
--- a/tests/test_integrations.py
+++ b/tests/test_integrations.py
@@ -347,22 +347,6 @@ del _f
         "",
         0,
     ),
-    #
-    # test single check_output
-    #
-    (
-        """
-def _echo(args):
-    print(' '.join(args))
-aliases['echo'] = _echo
-
-from xonsh.lib.subprocess import check_output
-
-print(check_output(["echo", "hello"]).decode("utf8"))
-""",
-        "hello\n\n",
-        0,
-    ),
 ]
 
 
diff --git a/tests/test_main.py b/tests/test_main.py
index aabdf31c..4cdd9367 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -186,3 +186,9 @@ def test_xonsh_failback_script_from_file(shell, monkeypatch, monkeypatch_stderr)
     with pytest.raises(Exception):
         xonsh.main.main()
     assert len(checker) == 0
+
+
+def test_xonsh_no_file_returncode(shell, monkeypatch, monkeypatch_stderr):
+    monkeypatch.setattr(sys, "argv", ["xonsh", "foobazbarzzznotafileatall.xsh"])
+    with pytest.raises(SystemExit):
+        xonsh.main.main()
diff --git a/tests/test_vox.py b/tests/test_vox.py
index 9d84f390..80ee5980 100644
--- a/tests/test_vox.py
+++ b/tests/test_vox.py
@@ -1,10 +1,9 @@
 """Vox tests"""
 
+import builtins
 import stat
 import os
-import subprocess as sp
 import pytest
-import sys
 from xontrib.voxapi import Vox
 
 from tools import skip_if_on_conda, skip_if_on_msys
@@ -81,55 +80,6 @@ def test_activate(xonsh_builtins, tmpdir):
     assert last_event == ("deactivate", "spam")
 
 
-@skip_if_on_msys
-@skip_if_on_conda
-def test_activate_non_vox_venv(xonsh_builtins, tmpdir):
-    """
-    Create a virtual environment using Python's built-in venv module
-    (not in VIRTUALENV_HOME) and verify that vox can activate it correctly.
-    """
-    xonsh_builtins.__xonsh__.env.setdefault("PATH", [])
-
-    last_event = None
-
-    @xonsh_builtins.events.vox_on_activate
-    def activate(name, path, **_):
-        nonlocal last_event
-        last_event = "activate", name, path
-
-    @xonsh_builtins.events.vox_on_deactivate
-    def deactivate(name, path, **_):
-        nonlocal last_event
-        last_event = "deactivate", name, path
-
-    with tmpdir.as_cwd():
-        venv_dirname = 'venv'
-        sp.run([sys.executable, '-m', 'venv', venv_dirname])
-        vox = Vox()
-        vox.activate(venv_dirname)
-        vxv = vox[venv_dirname]
-
-    env = xonsh_builtins.__xonsh__.env
-    assert os.path.isabs(vxv.bin)
-    assert env["PATH"][0] == vxv.bin
-    assert os.path.isabs(vxv.env)
-    assert env["VIRTUAL_ENV"] == vxv.env
-    assert last_event == (
-        "activate",
-        venv_dirname,
-        str(pathlib.Path(str(tmpdir)) / 'venv')
-    )
-
-    vox.deactivate()
-    assert not env["PATH"]
-    assert "VIRTUAL_ENV" not in env
-    assert last_event == (
-        "deactivate",
-        tmpdir.join(venv_dirname),
-        str(pathlib.Path(str(tmpdir)) / 'venv')
-    )
-
-
 @skip_if_on_msys
 @skip_if_on_conda
 def test_path(xonsh_builtins, tmpdir):