From e8de066f9411d320e31ca0ecb081114f01083434 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Tue, 19 Feb 2019 08:38:37 -0800 Subject: [PATCH] Ensure exceptions are pickleable --- src/flake8/exceptions.py | 45 ++++++++++++----------------- src/flake8/processor.py | 2 +- tests/unit/test_exceptions.py | 54 +++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 27 deletions(-) create mode 100644 tests/unit/test_exceptions.py diff --git a/src/flake8/exceptions.py b/src/flake8/exceptions.py index 51c25d3..bc44d8f 100644 --- a/src/flake8/exceptions.py +++ b/src/flake8/exceptions.py @@ -4,14 +4,10 @@ class Flake8Exception(Exception): """Plain Flake8 exception.""" - pass - class EarlyQuit(Flake8Exception): """Except raised when encountering a KeyboardInterrupt.""" - pass - class ExecutionError(Flake8Exception): """Exception raised during execution of Flake8.""" @@ -22,15 +18,15 @@ class FailedToLoadPlugin(Flake8Exception): FORMAT = 'Flake8 failed to load plugin "%(name)s" due to %(exc)s.' - def __init__(self, *args, **kwargs): + def __init__(self, plugin, exception): """Initialize our FailedToLoadPlugin exception.""" - self.plugin = kwargs.pop("plugin") + self.plugin = plugin self.ep_name = self.plugin.name - self.original_exception = kwargs.pop("exception") - super(FailedToLoadPlugin, self).__init__(*args, **kwargs) + self.original_exception = exception + super(FailedToLoadPlugin, self).__init__(plugin, exception) def __str__(self): - """Return a nice string for our exception.""" + """Format our exception message.""" return self.FORMAT % { "name": self.ep_name, "exc": self.original_exception, @@ -40,9 +36,8 @@ class FailedToLoadPlugin(Flake8Exception): class InvalidSyntax(Flake8Exception): """Exception raised when tokenizing a file fails.""" - def __init__(self, *args, **kwargs): + def __init__(self, exception): # type: (Exception) -> None """Initialize our InvalidSyntax exception.""" - exception = kwargs.pop("exception", None) self.original_exception = exception self.error_message = "{0}: {1}".format( exception.__class__.__name__, exception.args[0] @@ -50,9 +45,11 @@ class InvalidSyntax(Flake8Exception): self.error_code = "E902" self.line_number = 1 self.column_number = 0 - super(InvalidSyntax, self).__init__( - self.error_message, *args, **kwargs - ) + super(InvalidSyntax, self).__init__(exception) + + def __str__(self): + """Format our exception message.""" + return self.error_message class PluginRequestedUnknownParameters(Flake8Exception): @@ -60,12 +57,12 @@ class PluginRequestedUnknownParameters(Flake8Exception): FORMAT = '"%(name)s" requested unknown parameters causing %(exc)s' - def __init__(self, *args, **kwargs): + def __init__(self, plugin, exception): """Pop certain keyword arguments for initialization.""" - self.original_exception = kwargs.pop("exception") - self.plugin = kwargs.pop("plugin") + self.plugin = plugin + self.original_exception = exception super(PluginRequestedUnknownParameters, self).__init__( - *args, **kwargs + plugin, exception ) def __str__(self): @@ -81,13 +78,11 @@ class PluginExecutionFailed(Flake8Exception): FORMAT = '"%(name)s" failed during execution due to "%(exc)s"' - def __init__(self, *args, **kwargs): + def __init__(self, plugin, exception): """Utilize keyword arguments for message generation.""" - self.original_exception = kwargs.pop("exception") - self.plugin = kwargs.pop("plugin") - super(PluginExecutionFailed, self).__init__( - str(self), *args, **kwargs - ) + self.plugin = plugin + self.original_exception = exception + super(PluginExecutionFailed, self).__init__(plugin, exception) def __str__(self): """Format our exception message.""" @@ -100,8 +95,6 @@ class PluginExecutionFailed(Flake8Exception): class HookInstallationError(Flake8Exception): """Parent exception for all hooks errors.""" - pass - class GitHookAlreadyExists(HookInstallationError): """Exception raised when the git pre-commit hook file already exists.""" diff --git a/src/flake8/processor.py b/src/flake8/processor.py index 61a9362..f786b50 100644 --- a/src/flake8/processor.py +++ b/src/flake8/processor.py @@ -118,7 +118,7 @@ class FileProcessor(object): tokenize.generate_tokens(lambda: next(line_iter)) ) except tokenize.TokenError as exc: - raise exceptions.InvalidSyntax(exc.message, exception=exc) + raise exceptions.InvalidSyntax(exception=exc) return self._file_tokens diff --git a/tests/unit/test_exceptions.py b/tests/unit/test_exceptions.py new file mode 100644 index 0000000..4766b78 --- /dev/null +++ b/tests/unit/test_exceptions.py @@ -0,0 +1,54 @@ +"""Tests for the flake8.exceptions module.""" +import pickle + +import entrypoints + +from flake8 import exceptions +from flake8.plugins import manager as plugins_manager + + +class _ExceptionTest: + def test_pickleable(self): + """Test that the exception is round-trip pickleable.""" + for proto in range(pickle.HIGHEST_PROTOCOL + 1): + new_err = pickle.loads(pickle.dumps(self.err, protocol=proto)) + assert str(self.err) == str(new_err) + orig_e = self.err.original_exception + new_e = new_err.original_exception + assert (type(orig_e), orig_e.args) == (type(new_e), new_e.args) + + +class TestFailedToLoadPlugin(_ExceptionTest): + """Tests for the FailedToLoadPlugin exception.""" + + err = exceptions.FailedToLoadPlugin( + plugin=plugins_manager.Plugin( + 'plugin_name', + entrypoints.EntryPoint('plugin_name', 'os.path', None), + ), + exception=ValueError('boom!'), + ) + + +class TestInvalidSyntax(_ExceptionTest): + """Tests for the InvalidSyntax exception.""" + + err = exceptions.InvalidSyntax(exception=ValueError('Unexpected token: $')) + + +class TestPluginRequestedUnknownParameters(_ExceptionTest): + """Tests for the PluginRequestedUnknownParameters exception.""" + + err = exceptions.PluginRequestedUnknownParameters( + plugin={'plugin_name': 'plugin_name'}, + exception=ValueError('boom!'), + ) + + +class TestPluginExecutionFailed(_ExceptionTest): + """Tests for the PluginExecutionFailed exception.""" + + err = exceptions.PluginExecutionFailed( + plugin={'plugin_name': 'plugin_name'}, + exception=ValueError('boom!'), + )