Skip to content

Commit

Permalink
Merge 18e8379 into 6eb8c0a
Browse files Browse the repository at this point in the history
  • Loading branch information
faulkner committed Nov 28, 2015
2 parents 6eb8c0a + 18e8379 commit 0353ccf
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 60 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ Daniel Cardin <danielcardin@outlook.com>
Jeff Fairley <jfairley@gmail.com>
Radosław Ganczarek <radoslaw@ganczarek.in>
Daniel Milde <daniel@milde.cz>
Chris Faulkner <thefaulkner@gmail.com>
12 changes: 6 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ diff-cover |build-status| |coverage-status| |requirements-status| |docs-status|

Automatically find diff lines that need test coverage.
Also finds diff lines that have violations (according to tools such as pep8,
pyflakes, flake8, or pylint).
pyflakes, flake8, prospector, or pylint).
This is used as a code quality metric during code reviews.

Overview
Expand Down Expand Up @@ -120,8 +120,8 @@ You can use diff-cover to see quality reports on the diff as well by running
diff-quality --violations=<tool>
Where ``tool`` is the quality checker to use. Currently ``pep8``, ``pyflakes``,
``flake8``, and ``pylint`` are supported, but more checkers can (and should!)
be integrated.
``flake8``, ``prospector``, and ``pylint`` are supported, but more checkers can
(and should!) be integrated.

Like ``diff-cover``, HTML reports can be generated with

Expand All @@ -130,9 +130,9 @@ Like ``diff-cover``, HTML reports can be generated with
diff-quality --violations=<tool> --html-report report.html
If you have already generated a report using ``pep8``, ``pyflakes``, ``flake8``,
or ``pylint`` you can pass the report to ``diff-quality``. This is more
``prospector``, or ``pylint`` you can pass the report to ``diff-quality``. This is more
efficient than letting ``diff-quality`` re-run ``pep8``, ``pyflakes``,
``flake8``, or ``pylint``.
``flake8``, ``prospector``, or ``pylint``.

.. code:: bash
Expand All @@ -154,7 +154,7 @@ the ``pylint`` report for pylint versions less than 1.0 and the
``--msg-template`` option for versions >= 1.0.

``diff-quality`` will also accept multiple ``pep8``, ``pyflakes``, ``flake8``,
or ``pylint`` reports:
``prospector``, or ``pylint`` reports:

.. code:: bash
Expand Down
119 changes: 67 additions & 52 deletions diff_cover/tests/test_violations_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from diff_cover.violations_reporter import XmlCoverageReporter, Violation, \
Pep8QualityReporter, PyflakesQualityReporter, PylintQualityReporter, \
QualityReporterError, Flake8QualityReporter, JsHintQualityReporter, \
BaseQualityReporter
BaseQualityReporter, ProspectorQualityReporter
from diff_cover.tests.helpers import unittest


Expand Down Expand Up @@ -708,7 +708,10 @@ def test_quality_pregenerated_report(self):
self.assertIn(expected, actual_violations)


class PylintQualityReporterTest(unittest.TestCase):
class PylintTestMixin(object):
"""
Tests shared between pylint and prospector (using pylint output).
"""

def tearDown(self):
"""
Expand All @@ -717,14 +720,14 @@ def tearDown(self):
patch.stopall()

def test_no_such_file(self):
quality = PylintQualityReporter('pylint', [])
quality = self.reporter('pylint', [])

# Expect that we get no results
result = quality.violations('')
self.assertEqual(result, [])

def test_no_python_file(self):
quality = PylintQualityReporter('pylint', [])
quality = self.reporter('pylint', [])
file_paths = ['file1.coffee', 'subdir/file2.js']
# Expect that we get no results because no Python files
for path in file_paths:
Expand Down Expand Up @@ -769,7 +772,7 @@ def test_quality(self):
]

# Parse the report
quality = PylintQualityReporter('pylint', [])
quality = self.reporter('pylint', [])

# Expect that the name is set
self.assertEqual(quality.name(), 'pylint')
Expand All @@ -795,7 +798,7 @@ def test_unicode(self):
file.py:2: [W0612, cls_name.func_\u9492] Unused variable '\u2920'
""").encode('utf-8'), b'')

quality = PylintQualityReporter('pylint', [])
quality = self.reporter('pylint', [])
violations = quality.violations(u'file_\u6729.py')
self.assertEqual(violations, [
Violation(616, u"W1401: Anomalous backslash in string: '\u5922'. String constant might be missing an r prefix."),
Expand All @@ -813,7 +816,7 @@ def test_unicode_continuation_char(self):

# Since we are replacing characters we can't interpet, this should
# return a valid string with the char replaced with '?'
quality = PylintQualityReporter('pylint', [])
quality = self.reporter('pylint', [])
violations = quality.violations(u'file.py')
self.assertEqual(violations, [Violation(2, u"W1401: Invalid char '\ufffd'")])

Expand All @@ -825,7 +828,7 @@ def test_non_integer_line_num(self):
""").encode('utf-8'), '')

# None of the violations have a valid line number, so they should all be skipped
violations = PylintQualityReporter('pylint', []).violations(u'file.py')
violations = self.reporter('pylint', []).violations(u'file.py')
self.assertEqual(violations, [])

def test_quality_deprecation_warning(self):
Expand All @@ -845,7 +848,7 @@ def test_quality_deprecation_warning(self):
_mock_communicate.return_value = subproc_mock

# Parse the report
quality = PylintQualityReporter('pylint', [])
quality = self.reporter('pylint', [])
actual_violations = quality.violations('file1.py')

# Assert that pylint successfully runs and finds 2 violations
Expand All @@ -862,57 +865,19 @@ def test_quality_error(self):
_mock_communicate.return_value = subproc_mock

# Parse the report
quality = PylintQualityReporter('pylint', [])
quality = self.reporter('pylint', [])

# Expect an error
self.assertRaises(QualityReporterError, quality.violations, 'file1.py')

def test_legacy_pylint_compatibility(self):
quality = PylintQualityReporter('pylint', [])
_mock_communicate = patch.object(Popen, 'communicate').start()
expected_options = [quality.MODERN_OPTIONS, quality.LEGACY_OPTIONS]

def side_effect():
"""
Assure that the first time we use the modern options, return a failure
Then assert the legacy options were set, return ok
"""
index = _mock_communicate.call_count - 1
self.assertEqual(quality.OPTIONS, expected_options[index])

return [(b"", dedent("""
Adding some unicode to ensure we parse this correctly: ȼȼȼȼȼȼȼȼȼȼȼ
No config file found, using default configuration
Usage: pylint [options] module_or_package
Check that a module satisfies a coding standard (and more !).
pylint --help
Display this help message and exit.
pylint --help-msg <msg-id>[,<msg-id>]
Display help messages about given message identifiers and exit.
pylint: error: no such option: --msg-template
""").encode('utf-8')), (b'\n', b'')][index]

_mock_communicate.side_effect = side_effect
quality.violations('file1.py')
self.assertEqual([], quality.violations('file1.py'))
self.assertEqual(quality.OPTIONS, quality.LEGACY_OPTIONS)
self.assertEqual(_mock_communicate.call_count, 2)

def test_no_quality_issues_newline(self):

# Patch the output of `pylint`
_mock_communicate = patch.object(Popen, 'communicate').start()
_mock_communicate.return_value = (b'\n', b'')

# Parse the report
quality = PylintQualityReporter('pylint', [])
quality = self.reporter('pylint', [])
self.assertEqual([], quality.violations('file1.py'))

def test_no_quality_issues_emptystring(self):
Expand All @@ -922,7 +887,7 @@ def test_no_quality_issues_emptystring(self):
_mock_communicate.return_value = (b'', b'')

# Parse the report
quality = PylintQualityReporter('pylint', [])
quality = self.reporter('pylint', [])
self.assertEqual([], quality.violations('file1.py'))

def test_quality_pregenerated_report(self):
Expand All @@ -948,7 +913,7 @@ def test_quality_pregenerated_report(self):
]

# Generate the violation report
quality = PylintQualityReporter('pylint', pylint_reports)
quality = self.reporter('pylint', pylint_reports)

# Expect that we get the right violations
expected_violations = [
Expand All @@ -970,13 +935,63 @@ def test_quality_pregenerated_report_continuation_char(self):
pylint_reports = [BytesIO(b"file.py:2: [W1401] Invalid char '\xc3'")]

# Generate the violation report
quality = PylintQualityReporter('pylint', pylint_reports)
quality = self.reporter('pylint', pylint_reports)
violations = quality.violations('file.py')

# Expect that the char is replaced
self.assertEqual(violations, [Violation(2, u"W1401: Invalid char '\ufffd'")])


class PylintQualityReporterTest(PylintTestMixin, unittest.TestCase):
reporter = PylintQualityReporter

def test_legacy_pylint_compatibility(self):
quality = self.reporter('pylint', [])
_mock_communicate = patch.object(Popen, 'communicate').start()
expected_options = [quality.MODERN_OPTIONS, quality.LEGACY_OPTIONS]

def side_effect():
"""
Assure that the first time we use the modern options, return a failure
Then assert the legacy options were set, return ok
"""
index = _mock_communicate.call_count - 1
self.assertEqual(quality.OPTIONS, expected_options[index])

return [(b"", dedent("""
Adding some unicode to ensure we parse this correctly: ȼȼȼȼȼȼȼȼȼȼȼ
No config file found, using default configuration
Usage: pylint [options] module_or_package
Check that a module satisfies a coding standard (and more !).
pylint --help
Display this help message and exit.
pylint --help-msg <msg-id>[,<msg-id>]
Display help messages about given message identifiers and exit.
pylint: error: no such option: --msg-template
""").encode('utf-8')), (b'\n', b'')][index]

_mock_communicate.side_effect = side_effect
quality.violations('file1.py')
self.assertEqual([], quality.violations('file1.py'))
self.assertEqual(quality.OPTIONS, quality.LEGACY_OPTIONS)
self.assertEqual(_mock_communicate.call_count, 2)


class ProspectorQualityReporterTest(PylintTestMixin, unittest.TestCase):
"""
Reuses the pylint tests since we're using pylint as the output format for
prospector.
"""
reporter = ProspectorQualityReporter


class JsHintQualityReporterTest(unittest.TestCase):
"""
JsHintQualityReporter tests. Assumes JsHint is not available as a python library,
Expand Down
6 changes: 4 additions & 2 deletions diff_cover/tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from diff_cover.violations_reporter import (
XmlCoverageReporter, Pep8QualityReporter,
PyflakesQualityReporter, PylintQualityReporter,
Flake8QualityReporter, JsHintQualityReporter
Flake8QualityReporter, JsHintQualityReporter,
ProspectorQualityReporter
)
from diff_cover.report_generator import (
HtmlReportGenerator, StringReportGenerator,
Expand All @@ -36,6 +37,7 @@
'pylint': PylintQualityReporter,
'flake8': Flake8QualityReporter,
'jshint': JsHintQualityReporter,
'prospector': ProspectorQualityReporter,
}


Expand Down Expand Up @@ -104,7 +106,7 @@ def parse_quality_args(argv):
valid options:
{
'violations': pep8 | pyflakes | flake8 | pylint
'violations': pep8 | pyflakes | flake8 | pylint | prospector
'html_report': None | HTML_REPORT
}
Expand Down
8 changes: 8 additions & 0 deletions diff_cover/violations_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,14 @@ def _parse_output(self, output, src_path=None):
return violations_dict


class ProspectorQualityReporter(PylintQualityReporter):
"""
Report Prospector violations.
"""
COMMAND = 'prospector'
OPTIONS = ['-o', 'pylint']


class JsHintQualityReporter(BaseQualityReporter):
"""
Report JSHint violations.
Expand Down
1 change: 1 addition & 0 deletions requirements/test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ coverage
pep8
flake8
pyflakes
prospector
-r requirements.txt

0 comments on commit 0353ccf

Please sign in to comment.