Repository: xonsh/xonsh

Issue:
failback shell should only work with interactive mode
atm if there a a `fail.xsh` script that raises any exception we run `sh`

```
# fail.xsh 
raise ValueError('just because')
```

```
$ xonsh fail.xsh 
Traceback (most recent call last):
  File "/home/laerus/src/xonsh/xonsh/main.py", line 298, in main
    return main_xonsh(argv)
  File "/home/laerus/src/xonsh/xonsh/main.py", line 336, in main_xonsh
    loc=None, mode='exec')
  File "/home/laerus/src/xonsh/xonsh/codecache.py", line 145, in run_script_with_cache
    run_compiled_code(ccode, glb, loc, mode)
  File "/home/laerus/src/xonsh/xonsh/codecache.py", line 65, in run_compiled_code
    func(code, glb, loc)
  File "fail.xsh", line 1, in <module>
    raise ValueError('just because')
ValueError: just because
Xonsh encountered an issue during launch
Failback to /bin/sh
$ 
```


Discussion:
+1, I agree. This should only be a couple of line fix
the tests may need some tweaking too.

Resolving patch:
diff --git a/xonsh/main.py b/xonsh/main.py
--- a/xonsh/main.py
+++ b/xonsh/main.py
@@ -253,17 +253,11 @@ def premain(argv=None):
     return args
 
 
-def _failback_to_other_shells(argv, err):
-    args = None
-    try:
-        args = premain(argv)
-    except Exception:
-        pass
+def _failback_to_other_shells(args, err):
     # only failback for interactive shell; if we cannot tell, treat it
     # as an interactive one for safe.
     if hasattr(args, 'mode') and args.mode != XonshMode.interactive:
         raise err
-
     foreign_shell = None
     shells_file = '/etc/shells'
     if not os.path.exists(shells_file):
@@ -294,15 +288,16 @@ def _failback_to_other_shells(argv, err):
 
 
 def main(argv=None):
+    args = None
     try:
-        return main_xonsh(argv)
+        args = premain(argv)
+        return main_xonsh(args)
     except Exception as err:
-        _failback_to_other_shells(argv, err)
+        _failback_to_other_shells(args, err)
 
 
-def main_xonsh(argv=None):
+def main_xonsh(args):
     """Main entry point for xonsh cli."""
-    args = premain(argv)
     events.on_post_init.fire()
     env = builtins.__xonsh_env__
     shell = builtins.__xonsh_shell__


Test patch:
diff --git a/tests/scripts/raise.xsh b/tests/scripts/raise.xsh
new file mode 100644
--- /dev/null
+++ b/tests/scripts/raise.xsh
@@ -0,0 +1 @@
+raise Exception('oh no')
diff --git a/tests/test_main.py b/tests/test_main.py
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -9,6 +9,7 @@
 
 import xonsh.main
 import pytest
+from tools import TEST_DIR
 
 
 def Shell(*args, **kwargs):
@@ -91,7 +92,7 @@ def mocked_open(*args):
     assert failback_checker == ['/bin/xshell', '/bin/xshell']
 
 
-def test_xonsh_failback_non_interactive(shell, monkeypatch):
+def test_xonsh_failback_single(shell, monkeypatch):
     class FakeFailureError(Exception):
         pass
 
@@ -103,3 +104,17 @@ def mocked_main(*args):
 
     with pytest.raises(FakeFailureError):
         xonsh.main.main()
+
+
+def test_xonsh_failback_script_from_file(shell, monkeypatch):
+    checker = []
+    def mocked_execlp(f, *args):
+        checker.append(f)
+    monkeypatch.setattr(os, 'execlp', mocked_execlp)
+
+    script = os.path.join(TEST_DIR, 'scripts', 'raise.xsh')
+    monkeypatch.setattr(sys, 'argv', ['xonsh', script])
+    monkeypatch.setattr(sys, 'stderr', open(os.devnull, 'w'))
+    with pytest.raises(Exception):
+        xonsh.main.main()
+    assert len(checker) == 0
diff --git a/tests/tools.py b/tests/tools.py
--- a/tests/tools.py
+++ b/tests/tools.py
@@ -24,6 +24,7 @@
 ON_WINDOWS = (platform.system() == 'Windows')
 ON_CONDA = True in [conda in pytest.__file__ for conda
                     in ['anaconda', 'miniconda']]
+TEST_DIR = os.path.dirname(__file__)
 
 # pytest skip decorators
 skip_if_py34 = pytest.mark.skipif(VER_MAJOR_MINOR < VER_3_5, reason="Py3.5+ only test")