[cubicweb-ctl] respect sys.exit status code when aborting a command
When exploring the stack of all calls to a cubicweb-ctl command, it has been
discovered than on a KeyboardInterrupt and on a SystemExit exception the base
class InstanceCommand (for commands that works on one instance) will always set
the return code of cubicweb-ctl to 8: this mean that if another command do a
`sys.exit(some_code)` the exit code will be ignored and overwritten by '8'.
This behavior is not intuitive, apparently not documented and doesn't seems to
have any justification. It also prevent commands from exciting with different
return codes which could be a desired behavior in the situation of scripting.
--- a/cubicweb/cwctl.py Mon Jul 22 11:32:12 2019 +0200
+++ b/cubicweb/cwctl.py Mon Jul 22 11:21:10 2019 +0200
@@ -150,9 +150,12 @@
appid, self.actionverb, ex))
status = 8
- except (KeyboardInterrupt, SystemExit):
+ except (KeyboardInterrupt, SystemExit) as ex:
sys.stderr.write('%s aborted\n' % self.name)
- status = 2 # specific error code
+ if isinstance(ex, KeyboardInterrupt):
+ status = 2 # specific error code
+ else:
+ status = ex.code
if status != 0 and self.config.pdb:
exception_type, exception, traceback_ = sys.exc_info()
--- a/cubicweb/test/unittest_cwctl.py Mon Jul 22 11:32:12 2019 +0200
+++ b/cubicweb/test/unittest_cwctl.py Mon Jul 22 11:21:10 2019 +0200
@@ -148,6 +148,22 @@
ipdb.post_mortem.assert_called_once()
+ @patch.object(_TestFailCommand, 'test_fail_instance', side_effect=SystemExit(42))
+ def test_respect_return_error_code(self, test_fail_instance):
+ with self.assertRaises(SystemExit) as cm:
+ self.CWCTL.run(["test_fail", "some_instance"])
+ self.assertEqual(cm.exception.code, 42)
+
+ test_fail_instance.assert_called_once()
+
+ @patch.object(_TestFailCommand, 'test_fail_instance', side_effect=KeyboardInterrupt)
+ def test_error_code_keyboardinterupt_2(self, test_fail_instance):
+ with self.assertRaises(SystemExit) as cm:
+ self.CWCTL.run(["test_fail", "some_instance"])
+ self.assertEqual(cm.exception.code, 2)
+
+ test_fail_instance.assert_called_once()
+
if __name__ == '__main__':
unittest.main()