From c3f5d144bca0f16c946d64b7909a35ef66c3273d Mon Sep 17 00:00:00 2001 From: Christian Long Date: Mon, 23 Feb 2015 17:08:04 -0600 Subject: [PATCH] Add warnings when --jobs is invalid Some platforms and options are not compatible with the --jobs option. For example, it is not valid on Windows. Nor is it compatible with the --diff option. This adds warnings when the --jobs option is supplied but is not allowed. --- flake8/engine.py | 16 +++ flake8/tests/toast_warnings.py | 184 +++++++++++++++++++++++++++++++++ tox.ini | 3 + 3 files changed, 203 insertions(+) create mode 100644 flake8/tests/toast_warnings.py diff --git a/flake8/engine.py b/flake8/engine.py index 1ed06f2..e29b764 100644 --- a/flake8/engine.py +++ b/flake8/engine.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import re import platform +import warnings import pep8 @@ -123,6 +124,21 @@ def get_style_guide(**kwargs): for options_hook in options_hooks: options_hook(options) + if options.jobs and options.jobs.isdigit() and int(options.jobs) > 1: + if not multiprocessing: + warnings.warn("The multiprocessing module is not available. " + "Ignoring --jobs arguments.") + if is_windows(): + warnings.warn("The --jobs option is not available on Windows. " + "Ignoring --jobs arguments.") + if is_using_stdin(styleguide.paths): + warnings.warn("The --jobs option is not compatible with supplying " + "input using - . Ignoring --jobs arguments.") + if options.diff: + warnings.warn("The --diff option was specified with --jobs but " + "they are not compatible. Ignoring --jobs arguments." + ) + if options.diff: options.jobs = None diff --git a/flake8/tests/toast_warnings.py b/flake8/tests/toast_warnings.py new file mode 100644 index 0000000..2313a81 --- /dev/null +++ b/flake8/tests/toast_warnings.py @@ -0,0 +1,184 @@ +""" + toast_warnings.py + + Tests for the warnings that are emitted by flake8. + + This module is named toast_warnings instead of test_warnings so that a + normal nosetests run does not collect it. The tests in this module pass + when they are run alone, but they fail when they are run along with other + tests (nosetests --with-isolation doesn't help). + + In tox.ini, these tests are run separately. +""" + +from __future__ import with_statement + +import os +import warnings +import unittest +try: + from unittest import mock +except ImportError: + import mock # < PY33 + +from flake8 import engine +from flake8.util import is_windows + + +class IntegrationTestCaseWarnings(unittest.TestCase): + """Integration style tests to check that warnings are issued properly for + different command line options.""" + + def this_file(self): + """Return the real path of this file.""" + this_file = os.path.realpath(__file__) + if this_file.endswith("pyc"): + this_file = this_file[:-1] + return this_file + + @staticmethod + def get_style_guide_with_warnings(engine, *args, **kwargs): + """ + Return a style guide object (obtained by calling + engine.get_style_guide) and a list of the warnings that were raised in + the process. + + Note: not threadsafe + """ + + # Note + # https://docs.python.org/2/library/warnings.html + # + # The catch_warnings manager works by replacing and then later + # restoring the module's showwarning() function and internal list of + # filter specifications. This means the context manager is modifying + # global state and therefore is not thread-safe + + with warnings.catch_warnings(record=True) as collected_warnings: + # Cause all warnings to always be triggered. + warnings.simplefilter("always") + + # Get the style guide + style_guide = engine.get_style_guide(*args, **kwargs) + + # Now that the warnings have been collected, return the style guide and + # the + # warnings. + return (style_guide, collected_warnings) + + def verify_warnings(self, collected_warnings, expected_warnings=None): + """ + Verifies that collected_warnings is a sequence that contains user + warnings that match the sequence of string values passed in as + expected_warnings. + """ + if expected_warnings is None: + expected_warnings = [] + + collected_user_warnings = [w for w in collected_warnings + if issubclass(w.category, UserWarning)] + + self.assertEqual(len(collected_user_warnings), + len(expected_warnings)) + + collected_warnings_set = set(str(warning.message) + for warning + in collected_user_warnings) + expected_warnings_set = set(expected_warnings) + self.assertEqual(collected_warnings_set, expected_warnings_set) + + def check_files_collect_warnings(self, + arglist=[], + explicit_stdin=False, + count=0): + """Call check_files and collect any warnings that are issued.""" + if explicit_stdin: + target_file = "-" + else: + target_file = self.this_file() + argv = ['flake8'] + arglist + [target_file] + with mock.patch("sys.argv", argv): + (style_guide, + collected_warnings, + ) = self.get_style_guide_with_warnings(engine, + parse_argv=True) + report = style_guide.check_files() + self.assertEqual(report.total_errors, count) + return style_guide, report, collected_warnings + + def check_files_no_warnings_allowed(self, + arglist=[], + explicit_stdin=False, + count=0): + """Call check_files, and assert that there were no warnings issued.""" + (style_guide, + report, + collected_warnings, + ) = self.check_files_collect_warnings(arglist=arglist, + explicit_stdin=explicit_stdin, + count=count) + self.verify_warnings(collected_warnings) + return style_guide, report + + def test_no_args_no_warnings(self): + # assert there are no reported errors or warnings + self.check_files_no_warnings_allowed() + + def _job_tester(self, jobs): + # mock stdout.flush so we can count the number of jobs created + with mock.patch('sys.stdout.flush') as mocked: + (guide, + report, + collected_warnings, + ) = self.check_files_collect_warnings(arglist=['--jobs=%s' + % jobs]) + + expected_warings = [] + if is_windows(): + expected_warings.append("The --jobs option is not " + "available on Windows. Ignoring " + "--jobs arguments.") + + # The code path where guide.options.jobs gets converted to an + # int is not run on windows. So, do the int conversion here. + self.assertEqual(int(guide.options.jobs), jobs) + # On windows, call count is always zero. + self.assertEqual(mocked.call_count, 0) + else: + self.assertEqual(guide.options.jobs, jobs) + self.assertEqual(mocked.call_count, jobs) + self.verify_warnings(collected_warnings, expected_warings) + + def test_jobs(self): + self._job_tester(2) + self._job_tester(10) + + def test_stdin_jobs_warning(self): + self.count = 0 + + def fake_stdin(): + self.count += 1 + with open(self.this_file(), "r") as f: + return f.read() + + with mock.patch("pep8.stdin_get_value", fake_stdin): + (style_guide, + report, + collected_warnings, + ) = self.check_files_collect_warnings( + arglist=['--jobs=4'], + explicit_stdin=True) + + expected_warings = ["The --jobs option is not compatible with " + "supplying input using - . Ignoring --jobs " + "arguments."] + if is_windows(): + expected_warings.append("The --jobs option is not " + "available on Windows. Ignoring " + "--jobs arguments.") + self.verify_warnings(collected_warnings, expected_warings) + self.assertEqual(self.count, 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/tox.ini b/tox.ini index 2170c62..4c77305 100644 --- a/tox.ini +++ b/tox.ini @@ -6,9 +6,12 @@ envlist = [testenv] usedevelop = True deps = + nose + mock commands = python setup.py test -q python setup.py flake8 + nosetests flake8.tests.toast_warnings [testenv:py27-flake8] basepython = python2.7