Repository: ipython/ipython

Issue:
Test failures on OpenBSD
On ipython-1.2.1 the following tests fail on OpenBSD:

```
======================================================================
ERROR: IPython.core.tests.test_oinspect.test_pinfo_nonascii
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
    self.test(*self.arg)
  File "/usr/local/lib/python2.7/site-packages/IPython/core/tests/test_oinspect.py", line 305, in test_pinfo_nonascii
    ip._inspect('pinfo', 'nonascii2', detail_level=1)
  File "/usr/local/lib/python2.7/site-packages/IPython/core/interactiveshell.py", line 1457, in _inspect
    pmethod(info.obj, oname, formatter, info, **kw)
  File "/usr/local/lib/python2.7/site-packages/IPython/core/oinspect.py", line 607, in pinfo
    page.page(self._format_fields(displayfields))
  File "/usr/local/lib/python2.7/site-packages/IPython/testing/globalipapp.py", line 171, in nopage
    print(strng)
UnicodeEncodeError: 'ascii' codec can't encode characters in position 322-359: ordinal not in range(128)

======================================================================
FAIL: IPython.core.tests.test_pylabtools.TestPylabSwitch.test_qt
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
    self.test(*self.arg)
  File "/usr/local/lib/python2.7/site-packages/IPython/core/tests/test_pylabtools.py", line 98, in test_qt
    nt.assert_equal(gui, 'qt')
AssertionError: 'gtk' != 'qt'
    "'gtk' != 'qt'" = '%s != %s' % (safe_repr('gtk'), safe_repr('qt'))
    "'gtk' != 'qt'" = self._formatMessage("'gtk' != 'qt'", "'gtk' != 'qt'")
>>  raise self.failureException("'gtk' != 'qt'")
```

Looks like the first failure only shows when I am teeing the output to a file. E.g. to collect the above I used:

```
/usr/local/bin/python2.7 /usr/local/lib/python2.7/site-packages/IPython/testing/iptest.py -vvs IPython.core 
```

Thanks



Discussion:
Those look like little bugs in the testing, rather than the actual code, so I'm not sure they matter for a backport release.

So there is a bug in the test suite? I'm not sure what you mean by backport release. 1.2.1 is the latest version of ipython.

I'm working on updating the OpenBSD package of ipython. Test failures make it much harder for me to get the update through review.

We're working on IPython 2.0, so any more 1.x releases are backports. We've intended 1.2.1 to be the last 1.x release unless another serious problem comes up.

Thanks for the clarification. Cloning 2.0-dev now. Will let you know if it it a problem there.

Sorry, I'm going to have to delay this until I have updated OpenBSD's tornado package.

OK, so the above test failures are resolved in IPython-2.1, however, there is still one failing test:

```
FAIL: test_exit_code_signal (IPython.core.tests.test_interactiveshell.TestSystemRaw)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/IPython/testing/decorators.py", line 225, in skipper_func
    return f(*args, **kwargs)
  File "/usr/local/lib/python2.7/site-packages/IPython/core/tests/test_interactiveshell.py", line 459, in test_exit_code_signal
    self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
AssertionError: 142 != -14
    '142 != -14' = '%s != %s' % (safe_repr(142), safe_repr(-14))
    '142 != -14' = self._formatMessage('142 != -14', '142 != -14')
>>  raise self.failureException('142 != -14')
```

Thanks

I've seen that before for system_piped on Linux - it's bizarre that it turns up now with system_raw on BSD. Normally when a process is killed by a signal, the exit code is set to the negative of the signal number, but in these cases the shell exits with 128+signal number.

The fix we have for it in system_piped looks like:

``` python
if exitstatus > 128:
    return -(exitstatus - 128)
return exitstatus
```


Resolving patch:
diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py
--- a/IPython/core/interactiveshell.py
+++ b/IPython/core/interactiveshell.py
@@ -2328,6 +2328,11 @@ def system_raw(self, cmd):
             ec = subprocess.call(cmd, shell=True, executable=os.environ.get('SHELL', None))
             # exit code is positive for program failure, or negative for
             # terminating signal number.
+            
+            # Interpret ec > 128 as signal
+            # Some shells (csh, fish) don't follow sh/bash conventions for exit codes
+            if ec > 128:
+                ec = -(ec - 128)
         
         # We explicitly do NOT return the subprocess status code, because
         # a non-None value would trigger :func:`sys.displayhook` calls.


Test patch:
diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py
--- a/IPython/core/tests/test_interactiveshell.py
+++ b/IPython/core/tests/test_interactiveshell.py
@@ -25,9 +25,12 @@
 import nose.tools as nt
 
 from IPython.core.inputtransformer import InputTransformer
-from IPython.testing.decorators import skipif, skip_win32, onlyif_unicode_paths
+from IPython.testing.decorators import (
+    skipif, skip_win32, onlyif_unicode_paths, onlyif_cmds_exist,
+)
 from IPython.testing import tools as tt
 from IPython.utils import io
+from IPython.utils.process import find_cmd
 from IPython.utils import py3compat
 from IPython.utils.py3compat import unicode_type, PY3
 
@@ -436,7 +439,7 @@ def test_exit_code_ok(self):
     def test_exit_code_error(self):
         self.system('exit 1')
         self.assertEqual(ip.user_ns['_exit_code'], 1)
-
+    
     @skipif(not hasattr(signal, 'SIGALRM'))
     def test_exit_code_signal(self):
         self.mktmp("import signal, time\n"
@@ -444,6 +447,18 @@ def test_exit_code_signal(self):
                    "time.sleep(1)\n")
         self.system("%s %s" % (sys.executable, self.fname))
         self.assertEqual(ip.user_ns['_exit_code'], -signal.SIGALRM)
+    
+    @onlyif_cmds_exist("csh")
+    def test_exit_code_signal_csh(self):
+        SHELL = os.environ.get('SHELL', None)
+        os.environ['SHELL'] = find_cmd("csh")
+        try:
+            self.test_exit_code_signal()
+        finally:
+            if SHELL is not None:
+                os.environ['SHELL'] = SHELL
+            else:
+                del os.environ['SHELL']
 
 class TestSystemRaw(unittest.TestCase, ExitCodeChecks):
     system = ip.system_raw