Reimplemented exception handling

This commit is contained in:
Brad Warren
2015-06-29 17:31:48 -07:00
parent a248980952
commit 85b5bc0cb2
2 changed files with 52 additions and 33 deletions
+32 -31
View File
@@ -2,11 +2,13 @@
# TODO: Sanity check all input. Be sure to avoid shell code etc... # TODO: Sanity check all input. Be sure to avoid shell code etc...
import argparse import argparse
import atexit import atexit
import functools
import logging import logging
import logging.handlers import logging.handlers
import os import os
import sys import sys
import time import time
import traceback
import configargparse import configargparse
import zope.component import zope.component
@@ -642,15 +644,29 @@ def _setup_logging(args):
logger.info("Saving debug log to %s", log_file_name) logger.info("Saving debug log to %s", log_file_name)
def main2(cli_args, args, config, plugins): def _handle_exception(exc_type, exc_value, trace, args):
"""Continued main script execution.""" logger.debug(
"Exiting abnormally:\n%s",
"".join(traceback.format_exception(exc_type, exc_value, trace)))
# Displayer if issubclass(exc_type, errors.Error) and (not args or not args.debug):
if args.text_mode: sys.exit(exc_value)
displayer = display_util.FileDisplay(sys.stdout) elif issubclass(exc_type, Exception) and args and not args.debug:
sys.exit(
"An unexpected error occurred. Please see the logfiles in {0} for "
"more details.".format(args.logs_dir))
else: else:
displayer = display_util.NcursesDisplay() traceback.print_exception(exc_type, exc_value, trace, file=sys.stderr)
zope.component.provideUtility(displayer)
def main(cli_args=sys.argv[1:]):
"""Command line argument parsing and main script execution."""
sys.excepthook = functools.partial(_handle_exception, args=None)
# note: arg parser internally handles --help (and exits afterwards)
plugins = plugins_disco.PluginsRegistry.find_all()
args = create_parser(plugins, cli_args).parse_args(cli_args)
config = configuration.NamespaceConfig(args)
# Setup logging ASAP, otherwise "No handlers could be found for # Setup logging ASAP, otherwise "No handlers could be found for
# logger ..." TODO: this should be done before plugins discovery # logger ..." TODO: this should be done before plugins discovery
@@ -666,6 +682,15 @@ def main2(cli_args, args, config, plugins):
logger.debug("Arguments: %r", cli_args) logger.debug("Arguments: %r", cli_args)
logger.debug("Discovered plugins: %r", plugins) logger.debug("Discovered plugins: %r", plugins)
sys.excepthook = functools.partial(_handle_exception, args=args)
# Displayer
if args.text_mode:
displayer = display_util.FileDisplay(sys.stdout)
else:
displayer = display_util.NcursesDisplay()
zope.component.provideUtility(displayer)
# Reporter # Reporter
report = reporter.Reporter() report = reporter.Reporter()
zope.component.provideUtility(report) zope.component.provideUtility(report)
@@ -685,29 +710,5 @@ def main2(cli_args, args, config, plugins):
return args.func(args, config, plugins) return args.func(args, config, plugins)
def main(cli_args=sys.argv[1:]):
"""Command line argument parsing and main script execution."""
# note: arg parser internally handles --help (and exits afterwards)
plugins = plugins_disco.PluginsRegistry.find_all()
args = create_parser(plugins, cli_args).parse_args(cli_args)
config = configuration.NamespaceConfig(args)
def handle_exception_common():
"""Logs the exception and reraises it if in debug mode."""
logger.debug("Exiting abnormally", exc_info=True)
if args.debug:
raise
try:
return main2(cli_args, args, config, plugins)
except errors.Error as error:
handle_exception_common()
return error
except Exception: # pylint: disable=broad-except
handle_exception_common()
return ("An unexpected error occured. Please see the logfiles in {0} "
"for more details.".format(args.logs_dir))
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) # pragma: no cover sys.exit(main()) # pragma: no cover
+20 -2
View File
@@ -66,12 +66,30 @@ class CLITest(unittest.TestCase):
attrs = {'view_config_changes.side_effect' : error} attrs = {'view_config_changes.side_effect' : error}
self.assertRaises( self.assertRaises(
errors.Error, self._call, ['--debug'] + cmd_arg, attrs) errors.Error, self._call, ['--debug'] + cmd_arg, attrs)
self._call(cmd_arg, attrs)
attrs['view_config_changes.side_effect'] = [ValueError] attrs['view_config_changes.side_effect'] = [ValueError]
self.assertRaises( self.assertRaises(
ValueError, self._call, ['--debug'] + cmd_arg, attrs) ValueError, self._call, ['--debug'] + cmd_arg, attrs)
self._call(cmd_arg, attrs)
@mock.patch("letsencrypt.cli.sys")
def test_handle_exception(self, mock_sys):
# pylint: disable=protected-access
import StringIO
from letsencrypt import cli
from letsencrypt import errors
cli._handle_exception(errors.Error, "detail", None, None)
mock_sys.exit.assert_called_once_with("detail")
args = mock.MagicMock(debug=False)
cli._handle_exception(ValueError, "detail", None, args)
self.assertTrue("logfile" in mock_sys.exit.call_args_list[1][0][0])
mock_sys.stderr = StringIO.StringIO()
exc_value = "A very specific string"
cli._handle_exception(KeyboardInterrupt, exc_value, None, None)
self.assertTrue(exc_value in mock_sys.stderr.getvalue())
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() # pragma: no cover unittest.main() # pragma: no cover