Repository: xonsh/xonsh

Issue:
Function aliases: stdout redirect causes OSError
```
$ def _f(args, stdin=None):
..     print('hello')
..  
$ aliases['f'] = _f
$ f > /dev/null
Exception in thread Thread-66:
Traceback (most recent call last):
  File "/usr/local/Cellar/python3/3.5.2_3/Frameworks/Python.framework/Versions/3.5/lib/python3.5/threading.py", line 914, in _bootstrap_inner
    self.run()
  File "/usr/local/lib/python3.5/site-packages/xonsh/__amalgam__.py", line 10062, in run
    sp_stdout = io.TextIOWrapper(io.open(self.c2pwrite, 'wb', -1),
OSError: [Errno 9] Bad file descriptor
```

I'm on current master (6ee0c3c9)



Discussion:
Thanks for reporting @blahgeek.  My guess is that this is a /dev/null issue, as I believe that redirecting to a concrete file works.

no, it doesn't....

```
BlahGeeks-MBP /p/tmp> def _f(args, stdin=None):
.....................     print('hello')
.....................     
BlahGeeks-MBP /p/tmp> aliases['f'] = _f
BlahGeeks-MBP /p/tmp> $XONSH_SHOW_TRACEBACK = True
BlahGeeks-MBP /p/tmp> f > tmp.txt
xonsh: To log full traceback to a file set: $XONSH_TRACEBACK_LOGFILE = <filename>
Traceback (most recent call last):
  File "/usr/local/lib/python3.5/site-packages/xonsh/__amalgam__.py", line 10092, in run
    r = self.f(self.args, sp_stdin, sp_stdout, sp_stderr, spec)
  File "/usr/local/lib/python3.5/site-packages/xonsh/__amalgam__.py", line 9915, in proxy_two
    return f(args, stdin)
  File "/usr/local/lib/python3.5/site-packages/xontrib/prompt_ret_code.xsh", line 2, in _f
  File "/usr/local/lib/python3.5/site-packages/xonsh/__amalgam__.py", line 9803, in write
    h.flush()
OSError: [Errno 9] Bad file descriptor
```

Oof, OK.


Resolving patch:
diff --git a/xonsh/proc.py b/xonsh/proc.py
--- a/xonsh/proc.py
+++ b/xonsh/proc.py
@@ -1450,6 +1450,7 @@ def iterraw(self):
             # we get here if the process is not bacgroundable or the
             # class is the real Popen
             wait_for_active_job()
+            proc.wait()
             self._endtime()
             if self.captured == 'object':
                 self.end(tee_output=False)


Test patch:
diff --git a/tests/test_integrations.py b/tests/test_integrations.py
new file mode 100644
--- /dev/null
+++ b/tests/test_integrations.py
@@ -0,0 +1,79 @@
+import os
+import sys
+import shutil
+import subprocess
+
+import pytest
+
+import xonsh
+from xonsh.platform import ON_WINDOWS
+
+XONSH_PREFIX = xonsh.__file__
+if 'site-packages' in XONSH_PREFIX:
+    # must be installed version of xonsh
+    num_up = 5
+else:
+    # must be in source dir
+    num_up = 2
+for i in range(num_up):
+    XONSH_PREFIX = os.path.dirname(XONSH_PREFIX)
+PATH = os.path.join(os.path.dirname(__file__), 'bin') + os.pathsep + \
+       os.path.join(XONSH_PREFIX, 'bin') + os.pathsep + \
+       os.path.join(XONSH_PREFIX, 'Scripts') + os.pathsep + \
+       os.path.join(XONSH_PREFIX, 'scripts') + os.pathsep + \
+       os.path.dirname(sys.executable) + os.pathsep + \
+       os.environ['PATH']
+
+#
+# The following list contains a (stdin, stdout, returncode) tuples
+#
+
+ALL_PLATFORMS = [
+# test calling a function alias
+("""
+def _f():
+    print('hello')
+
+aliases['f'] = _f
+f
+""", "hello\n", 0),
+# test redirecting a function alias
+("""
+def _f():
+    print('Wow Mom!')
+
+aliases['f'] = _f
+f > tttt
+
+with open('tttt') as tttt:
+    s = tttt.read().strip()
+print('REDIRECTED OUTPUT: ' + s)
+""", "REDIRECTED OUTPUT: Wow Mom!\n", 0),
+]
+
+
+@pytest.mark.parametrize('case', ALL_PLATFORMS)
+def test_script(case):
+    script, exp_out, exp_rtn = case
+    env = dict(os.environ)
+    env['PATH'] = PATH
+    env['XONSH_DEBUG'] = '1'
+    env['XONSH_SHOW_TRACEBACK'] = '1'
+    xonsh = 'xonsh.bat' if ON_WINDOWS else 'xon.sh'
+    xonsh = shutil.which(xonsh, path=PATH)
+    p = subprocess.Popen([xonsh, '--no-rc'],
+                         env=env,
+                         stdin=subprocess.PIPE,
+                         stdout=subprocess.PIPE,
+                         stderr=subprocess.STDOUT,
+                         universal_newlines=True,
+                         )
+    try:
+        out, err = p.communicate(input=script, timeout=10)
+    except subprocess.TimeoutExpired:
+        p.kill()
+        raise
+    assert exp_out == out
+    assert exp_rtn == p.returncode
+
+