Repository: amoffat/sh (python)

Issue:
_ok_code=<none zero value> with zero return values would yield KeyError instead of ErrorReturnCode_0
```
In [1]: import sh

In [2]: sh.ls(_ok_code=2)
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-2-d60c33f809d7> in <module>
----> 1 sh.ls(_ok_code=2)

/usr/lib/python3.8/site-packages/sh.py in __call__(self, *args, **kwargs)
   1518             stderr = open(str(stderr), "wb")
   1519
-> 1520         return RunningCommand(cmd, call_args, stdin, stdout, stderr)
   1521
   1522

/usr/lib/python3.8/site-packages/sh.py in __init__(self, cmd, call_args, stdin, stdout, stderr)
    782
    783             if should_wait:
--> 784                 self.wait()
    785
    786     def wait(self, timeout=None):

/usr/lib/python3.8/site-packages/sh.py in wait(self, timeout)
    839
    840             else:
--> 841                 self.handle_command_exit_code(exit_code)
    842
    843                 # if an iterable command is using an instance of OProc for its stdin,

/usr/lib/python3.8/site-packages/sh.py in handle_command_exit_code(self, code)
    860         """
    861         ca = self.call_args
--> 862         exc_class = get_exc_exit_code_would_raise(code, ca["ok_code"], ca["piped"])
    863         if exc_class:
    864             exc = exc_class(self.ran, self.process.stdout, self.process.stderr, ca["truncate_exc"])

/usr/lib/python3.8/site-packages/sh.py in get_exc_exit_code_would_raise(exit_code, ok_codes, sigpipe_ok)
   1736
   1737     if not success or bad_sig:
-> 1738         exc = get_rc_exc(exit_code)
   1739     return exc
   1740

/usr/lib/python3.8/site-packages/sh.py in get_rc_exc(rc)
    491         base = ErrorReturnCode
    492     else:
--> 493         signame = SIGNAL_MAPPING[abs(rc)]
    494         name = "SignalException_" + signame
    495         base = SignalException

KeyError: 0
```

Probably [here](https://github.com/amoffat/sh/blob/develop/sh.py#L489) needs to be changed to `>= 0`

Pull request description:
Properly raise ErrorReturnCode_0 on exit code 0
This handles the case where exit code 0 is disallowed via '_ok_code' and the process exits with this code.

get_exc_from_name() negates signal numbers and calls get_rc_exc() with either the exit code or the negated signal number. Exit codes are unsigned ints and thus >=0. Signal numbers are always >= 1 (see 'kill -l').

get_rc_exc() erroneously handled '0' as a signal when it should be seen as an exit code.

Fixes #544

Patch:
diff --git a/sh.py b/sh.py
index d7038d8..6a0d944 100644
--- a/sh.py
+++ b/sh.py
@@ -486,7 +486,7 @@ def get_rc_exc(rc):
     except KeyError:
         pass
 
-    if rc > 0:
+    if rc >= 0:
         name = "ErrorReturnCode_%d" % rc
         base = ErrorReturnCode
     else:


Test patch:
diff --git a/test.py b/test.py
index a4b9915..d9cc1da 100644
--- a/test.py
+++ b/test.py
@@ -422,6 +422,11 @@ exit(3)
         py = create_tmp_test("exit(0)")
         python(py.name, _ok_code=None)
 
+    def test_ok_code_exception(self):
+        from sh import ErrorReturnCode_0
+        py = create_tmp_test("exit(0)")
+        self.assertRaises(ErrorReturnCode_0, python, py.name, _ok_code=2)
+
     def test_none_arg(self):
         py = create_tmp_test("""
 import sys