summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan Cordasco <graffatcolmingov@gmail.com>2016-08-06 14:16:08 -0500
committerIan Cordasco <graffatcolmingov@gmail.com>2016-08-07 12:31:14 -0500
commitf67f481beea1f32e503d346e9d4d3f2f2a8b2ebe (patch)
treea147072597dfff8d3df7333a744573f29ef95fee
parentf768ed6fd0101de656e19d3029952e69a437549e (diff)
downloadflake8-f67f481beea1f32e503d346e9d4d3f2f2a8b2ebe.tar.gz
Add --bug-report flag to help bug reporters
When invoked it will print out JSON that has all of the debugging information needed by the maintainers to diagnose or reproduce a bug. Closes #207
-rw-r--r--docs/source/release-notes/3.1.0.rst4
-rw-r--r--docs/source/release-notes/index.rst1
-rw-r--r--docs/source/user/options.rst48
-rw-r--r--src/flake8/main/debug.py62
-rw-r--r--src/flake8/main/options.py11
-rw-r--r--src/flake8/options/manager.py5
-rw-r--r--tests/unit/test_debug.py86
7 files changed, 215 insertions, 2 deletions
diff --git a/docs/source/release-notes/3.1.0.rst b/docs/source/release-notes/3.1.0.rst
new file mode 100644
index 0000000..7df073a
--- /dev/null
+++ b/docs/source/release-notes/3.1.0.rst
@@ -0,0 +1,4 @@
+3.1.0 -- 2016-yy-xx
+-------------------
+
+- Add ``--bug-report`` flag to make issue reporters' lives easier.
diff --git a/docs/source/release-notes/index.rst b/docs/source/release-notes/index.rst
index 07b9cd6..c59a64f 100644
--- a/docs/source/release-notes/index.rst
+++ b/docs/source/release-notes/index.rst
@@ -6,6 +6,7 @@ All of the release notes that have been recorded for Flake8 are organized here
with the newest releases first.
.. toctree::
+ 3.1.0
3.0.4
3.0.3
3.0.2
diff --git a/docs/source/user/options.rst b/docs/source/user/options.rst
index acaa67c..c139f69 100644
--- a/docs/source/user/options.rst
+++ b/docs/source/user/options.rst
@@ -728,3 +728,51 @@
flake8 --benchmark dir/
This **can not** be specified in config files.
+
+
+.. option:: --bug-report
+
+ Generate information necessary to file a complete bug report for Flake8.
+ This will pretty-print a JSON blob that should be copied and pasted into a
+ bug report for Flake8.
+
+ Command-line usage:
+
+ .. prompt:: bash
+
+ flake8 --bug-report
+
+ The output should look vaguely like:
+
+ .. code-block:: js
+
+ {
+ "dependencies": [
+ {
+ "dependency": "setuptools",
+ "version": "25.1.1"
+ }
+ ],
+ "platform": {
+ "python_implementation": "CPython",
+ "python_version": "2.7.12",
+ "system": "Darwin"
+ },
+ "plugins": [
+ {
+ "plugin": "mccabe",
+ "version": "0.5.1"
+ },
+ {
+ "plugin": "pycodestyle",
+ "version": "2.0.0"
+ },
+ {
+ "plugin": "pyflakes",
+ "version": "1.2.3"
+ }
+ ],
+ "version": "3.1.0.dev0"
+ }
+
+ This **can not** be specified in config files.
diff --git a/src/flake8/main/debug.py b/src/flake8/main/debug.py
new file mode 100644
index 0000000..e6ea141
--- /dev/null
+++ b/src/flake8/main/debug.py
@@ -0,0 +1,62 @@
+"""Module containing the logic for our debugging logic."""
+from __future__ import print_function
+
+import json
+import platform
+
+import setuptools
+
+
+def print_information(option, option_string, value, parser,
+ option_manager=None):
+ """Print debugging information used in bug reports.
+
+ :param option:
+ The optparse Option instance.
+ :type option:
+ optparse.Option
+ :param str option_string:
+ The option name
+ :param value:
+ The value passed to the callback parsed from the command-line
+ :param parser:
+ The optparse OptionParser instance
+ :type parser:
+ optparse.OptionParser
+ :param option_manager:
+ The Flake8 OptionManager instance.
+ :type option_manager:
+ flake8.options.manager.OptionManager
+ """
+ if not option_manager.registered_plugins:
+ # NOTE(sigmavirus24): Flake8 parses options twice. The first time, we
+ # will not have any registered plugins. We can skip this one and only
+ # take action on the second time we're called.
+ return
+ print(json.dumps(information(option_manager), indent=2, sort_keys=True))
+ raise SystemExit(False)
+
+
+def information(option_manager):
+ """Generate the information to be printed for the bug report."""
+ return {
+ 'version': option_manager.version,
+ 'plugins': plugins_from(option_manager),
+ 'dependencies': dependencies(),
+ 'platform': {
+ 'python_implementation': platform.python_implementation(),
+ 'python_version': platform.python_version(),
+ 'system': platform.system(),
+ },
+ }
+
+
+def plugins_from(option_manager):
+ """Generate the list of plugins installed."""
+ return [{'plugin': plugin, 'version': version}
+ for (plugin, version) in sorted(option_manager.registered_plugins)]
+
+
+def dependencies():
+ """Generate the list of dependencies we care about."""
+ return [{'dependency': 'setuptools', 'version': setuptools.__version__}]
diff --git a/src/flake8/main/options.py b/src/flake8/main/options.py
index c725c38..47fb30c 100644
--- a/src/flake8/main/options.py
+++ b/src/flake8/main/options.py
@@ -1,5 +1,6 @@
"""Contains the logic for all of the default options for Flake8."""
from flake8 import defaults
+from flake8.main import debug
from flake8.main import vcs
@@ -29,6 +30,8 @@ def register_default_options(option_manager):
- ``--append-config``
- ``--config``
- ``--isolated``
+ - ``--benchmark``
+ - ``--bug-report``
"""
add_option = option_manager.add_option
@@ -199,3 +202,11 @@ def register_default_options(option_manager):
'--benchmark', default=False, action='store_true',
help='Print benchmark information about this run of Flake8',
)
+
+ # Debugging
+
+ add_option(
+ '--bug-report', action='callback', callback=debug.print_information,
+ callback_kwargs={'option_manager': option_manager},
+ help='Print information necessary when preparing a bug report',
+ )
diff --git a/src/flake8/options/manager.py b/src/flake8/options/manager.py
index de9356e..b536907 100644
--- a/src/flake8/options/manager.py
+++ b/src/flake8/options/manager.py
@@ -240,9 +240,10 @@ class OptionManager(object):
LOG.debug('Extending default select list with %r', error_codes)
self.extended_default_select.update(error_codes)
- def generate_versions(self, format_str='%(name)s: %(version)s'):
+ def generate_versions(self, format_str='%(name)s: %(version)s',
+ join_on=', '):
"""Generate a comma-separated list of versions of plugins."""
- return ', '.join(
+ return join_on.join(
format_str % self.format_plugin(plugin)
for plugin in self.registered_plugins
)
diff --git a/tests/unit/test_debug.py b/tests/unit/test_debug.py
new file mode 100644
index 0000000..dbafe2e
--- /dev/null
+++ b/tests/unit/test_debug.py
@@ -0,0 +1,86 @@
+"""Tests for our debugging module."""
+import mock
+import pytest
+import setuptools
+
+from flake8.main import debug
+
+
+def test_dependencies():
+ """Verify that we format our dependencies appropriately."""
+ expected = [{'dependency': 'setuptools',
+ 'version': setuptools.__version__}]
+ assert expected == debug.dependencies()
+
+
+@pytest.mark.parametrize('plugins, expected', [
+ ([], []),
+ ([('pycodestyle', '2.0.0')], [{'plugin': 'pycodestyle',
+ 'version': '2.0.0'}]),
+ ([('pycodestyle', '2.0.0'), ('mccabe', '0.5.9')],
+ [{'plugin': 'mccabe', 'version': '0.5.9'},
+ {'plugin': 'pycodestyle', 'version': '2.0.0'}]),
+])
+def test_plugins_from(plugins, expected):
+ """Test that we format plugins appropriately."""
+ option_manager = mock.Mock(registered_plugins=set(plugins))
+ assert expected == debug.plugins_from(option_manager)
+
+
+@mock.patch('platform.python_implementation', return_value='CPython')
+@mock.patch('platform.python_version', return_value='3.5.3')
+@mock.patch('platform.system', return_value='Linux')
+def test_information(system, pyversion, pyimpl):
+ """Verify that we return all the information we care about."""
+ expected = {
+ 'version': '3.1.0',
+ 'plugins': [{'plugin': 'mccabe', 'version': '0.5.9'},
+ {'plugin': 'pycodestyle', 'version': '2.0.0'}],
+ 'dependencies': [{'dependency': 'setuptools',
+ 'version': setuptools.__version__}],
+ 'platform': {
+ 'python_implementation': 'CPython',
+ 'python_version': '3.5.3',
+ 'system': 'Linux',
+ },
+ }
+ option_manager = mock.Mock(
+ registered_plugins=set([('pycodestyle', '2.0.0'),
+ ('mccabe', '0.5.9')]),
+ version='3.1.0',
+ )
+ assert expected == debug.information(option_manager)
+ pyimpl.assert_called_once_with()
+ pyversion.assert_called_once_with()
+ system.assert_called_once_with()
+
+
+@mock.patch('flake8.main.debug.print')
+@mock.patch('flake8.main.debug.information', return_value={})
+@mock.patch('json.dumps', return_value='{}')
+def test_print_information_no_plugins(dumps, information, print_mock):
+ """Verify we print and exit only when we have plugins."""
+ plugins = []
+ option_manager = mock.Mock(registered_plugins=set(plugins))
+ assert debug.print_information(
+ None, None, None, None, option_manager=option_manager,
+ ) is None
+ assert dumps.called is False
+ assert information.called is False
+ assert print_mock.called is False
+
+
+@mock.patch('flake8.main.debug.print')
+@mock.patch('flake8.main.debug.information', return_value={})
+@mock.patch('json.dumps', return_value='{}')
+def test_print_information(dumps, information, print_mock):
+ """Verify we print and exit only when we have plugins."""
+ plugins = [('pycodestyle', '2.0.0'), ('mccabe', '0.5.9')]
+ option_manager = mock.Mock(registered_plugins=set(plugins))
+ with pytest.raises(SystemExit):
+ debug.print_information(
+ None, None, None, None, option_manager=option_manager,
+ )
+ print_mock.assert_called_once_with('{}')
+ dumps.assert_called_once_with({}, indent=2, sort_keys=True)
+ information.assert_called_once_with(option_manager)