bisect-kit: stop bisector for fatal errors

Some errors, e.g. incorrect command line argument, are fatal. Should
stop bisector instead of retrying them.

This CL also changed some non-fatal issues from assertion to normal
exceptions.

BUG=None
TEST=unittest

Change-Id: I0a6ffd0f697f4e79114d752585019e705e69c33f
Reviewed-on: https://chromium-review.googlesource.com/1795943
Tested-by: Zheng-Jie Chang <zjchang@chromium.org>
Tested-by: Kuang-che Wu <kcwu@chromium.org>
Commit-Ready: Zheng-Jie Chang <zjchang@chromium.org>
Commit-Ready: Kuang-che Wu <kcwu@chromium.org>
Legacy-Commit-Queue: Commit Bot <commit-bot@chromium.org>
Reviewed-by: Zheng-Jie Chang <zjchang@chromium.org>
Reviewed-by: Kuang-che Wu <kcwu@chromium.org>
diff --git a/bisect_kit/cli.py b/bisect_kit/cli.py
index 21c409a..e3a8e66 100644
--- a/bisect_kit/cli.py
+++ b/bisect_kit/cli.py
@@ -10,7 +10,9 @@
 import os
 import re
 import signal
+import sys
 
+from bisect_kit import errors
 from bisect_kit import util
 
 logger = logging.getLogger(__name__)
@@ -235,3 +237,42 @@
                                                            signame)
 
   return 'exited with code %d' % returncode
+
+
+def patching_argparser_exit(parser):
+  """Patching argparse.ArgumentParser.exit to exit with fatal exit code.
+
+  Args:
+    parser: argparse.ArgumentParser object
+  """
+  orig_exit = parser.exit
+
+  def exit_hack(status=0, message=None):
+    if status != 0:
+      status = EXIT_CODE_FATAL
+    orig_exit(status, message)
+
+  parser.exit = exit_hack
+
+
+def fatal_error_handler(func):
+  """Function decorator which exits with fatal code for fatal exceptions.
+
+  This is a helper for switcher and evaluator. It catches fatal exceptions and
+  exits with fatal exit code. The fatal exit code (128) is aligned with 'git
+  bisect'.
+
+  See also argparse_fatal_error().
+
+  Args:
+    func: wrapped function
+  """
+
+  def wrapper(*args, **kwargs):
+    try:
+      return func(*args, **kwargs)
+    except (AssertionError, errors.ExecutionFatalError, errors.ArgumentError):
+      logger.fatal('fatal exception, bisection should stop')
+      sys.exit(EXIT_CODE_FATAL)
+
+  return wrapper