def test_no_python_file(self): quality = PylintQualityReporter('pylint', []) file_paths = ['file1.coffee', 'subdir/file2.js'] # Expect that we get no results because no Python files for path in file_paths: result = quality.violations(path) self.assertEqual(result, [])
def test_quality(self): # Patch the output of `pylint` _mock_communicate = patch.object(Popen, 'communicate').start() _mock_communicate.return_value = (dedent(""" file1.py:1: [C0111] Missing docstring file1.py:1: [C0111, func_1] Missing docstring file1.py:2: [W0612, cls_name.func] Unused variable 'd' file1.py:2: [W0511] TODO: Not the real way we'll store usages! file1.py:579: [F0401] Unable to import 'rooted_paths' file1.py:113: [W0613, cache_relation.clear_pk] Unused argument 'cls' file1.py:150: [F0010] error while code parsing ([Errno 2] No such file or directory) file1.py:149: [C0324, Foo.__dict__] Comma not followed by a space self.peer_grading._find_corresponding_module_for_location(Location('i4x','a','b','c','d')) file1.py:162: [R0801] Similar lines in 2 files ==external_auth.views:1 ==student.views:4 import json import logging import random path/to/file2.py:100: [W0212, openid_login_complete] Access to a protected member """).strip().encode('ascii'), '') expected_violations = [ Violation(1, 'C0111: Missing docstring'), Violation(1, 'C0111: func_1: Missing docstring'), Violation(2, "W0612: cls_name.func: Unused variable 'd'"), Violation(2, "W0511: TODO: Not the real way we'll store usages!"), Violation(579, "F0401: Unable to import 'rooted_paths'"), Violation( 150, "F0010: error while code parsing ([Errno 2] No such file or directory)" ), Violation(149, "C0324: Foo.__dict__: Comma not followed by a space"), Violation(162, "R0801: Similar lines in 2 files"), Violation(113, "W0613: cache_relation.clear_pk: Unused argument 'cls'") ] # Parse the report quality = PylintQualityReporter('pylint', []) # Expect that the name is set self.assertEqual(quality.name(), 'pylint') # Measured_lines is undefined for a # quality reporter since all lines are measured self.assertEqual(quality.measured_lines('file1.py'), None) # Expect that we get violations for file1.py only # We're not guaranteed that the violations are returned # in any particular order. actual_violations = quality.violations('file1.py') self.assertEqual(len(actual_violations), len(expected_violations)) for expected in expected_violations: self.assertIn(expected, actual_violations)
def test_no_quality_issues_emptystring(self): # Patch the output of `pylint` _mock_communicate = patch.object(Popen, 'communicate').start() _mock_communicate.return_value = ('', '') # Parse the report quality = PylintQualityReporter('pylint', []) self.assertEqual([], quality.violations('file1.py'))
def test_no_quality_issues_emptystring(self): # Patch the output of `pylint` _mock_communicate = patch.object(Popen, 'communicate').start() _mock_communicate.return_value = (b'', b'') # Parse the report quality = PylintQualityReporter('pylint', []) self.assertEqual([], quality.violations('file1.py'))
def test_quality_pregenerated_report_continuation_char(self): # The report contains a non-ASCII continuation char pylint_reports = [BytesIO(b"file.py:2: [W1401] Invalid char '\xc3'")] # Generate the violation report quality = PylintQualityReporter('pylint', pylint_reports) violations = quality.violations('file.py') # Expect that the char is replaced self.assertEqual(violations, [Violation(2, u"W1401: Invalid char '\ufffd'")])
def test_unicode_continuation_char(self): _mock_communicate = patch.object(Popen, 'communicate').start() # Test a unicode continuation char, which pylint can produce (probably an encoding bug in pylint) _mock_communicate.return_value = ("file.py:2: [W1401] Invalid char '\xc3'", '') # Since we are replacing characters we can't interpet, this should # return a valid string with the char replaced with '?' quality = PylintQualityReporter('pylint', []) violations = quality.violations(u'file.py') self.assertEqual(violations, [Violation(2, u"W1401: Invalid char '\ufffd'")])
def test_no_quality_issues_newline(self): # Patch the output of `pylint` _mock_communicate = patch.object(Popen, 'communicate').start() _mock_communicate.return_value = ('\n', '') violations = [] name = "pylint" # Parse the report quality = PylintQualityReporter(name) self.assertEqual(violations, quality.violations('file1.py'))
def test_quality(self): # Patch the output of `pylint` _mock_communicate = patch.object(Popen, 'communicate').start() _mock_communicate.return_value = ( dedent(""" file1.py:1: [C0111] Missing docstring file1.py:1: [C0111, func_1] Missing docstring file1.py:2: [W0612, cls_name.func] Unused variable 'd' file1.py:2: [W0511] TODO: Not the real way we'll store usages! file1.py:579: [F0401] Unable to import 'rooted_paths' file1.py:113: [W0613, cache_relation.clear_pk] Unused argument 'cls' file1.py:150: [F0010] error while code parsing ([Errno 2] No such file or directory) file1.py:149: [C0324, Foo.__dict__] Comma not followed by a space self.peer_grading._find_corresponding_module_for_location(Location('i4x','a','b','c','d')) file1.py:162: [R0801] Similar lines in 2 files ==external_auth.views:1 ==student.views:4 import json import logging import random path/to/file2.py:100: [W0212, openid_login_complete] Access to a protected member """).strip(), '' ) expected_violations = [ Violation(1, 'C0111: Missing docstring'), Violation(1, 'C0111: func_1: Missing docstring'), Violation(2, "W0612: cls_name.func: Unused variable 'd'"), Violation(2, "W0511: TODO: Not the real way we'll store usages!"), Violation(579, "F0401: Unable to import 'rooted_paths'"), Violation(150, "F0010: error while code parsing ([Errno 2] No such file or directory)"), Violation(149, "C0324: Foo.__dict__: Comma not followed by a space"), Violation(162, "R0801: Similar lines in 2 files"), Violation(113, "W0613: cache_relation.clear_pk: Unused argument 'cls'") ] # Parse the report quality = PylintQualityReporter('pylint', []) # Expect that the name is set self.assertEqual(quality.name(), 'pylint') # Measured_lines is undefined for a # quality reporter since all lines are measured self.assertEqual(quality.measured_lines('file1.py'), None) # Expect that we get violations for file1.py only # We're not guaranteed that the violations are returned # in any particular order. actual_violations = quality.violations('file1.py') self.assertEqual(len(actual_violations), len(expected_violations)) for expected in expected_violations: self.assertIn(expected, actual_violations)
def test_unicode_continuation_char(self): _mock_communicate = patch.object(Popen, 'communicate').start() # Test a unicode continuation char, which pylint can produce (probably an encoding bug in pylint) _mock_communicate.return_value = (b"file.py:2: [W1401]" b" Invalid char '\xc3'", '') # Since we are replacing characters we can't interpet, this should # return a valid string with the char replaced with '?' quality = PylintQualityReporter('pylint', []) violations = quality.violations(u'file.py') self.assertEqual(violations, [Violation(2, u"W1401: Invalid char '\ufffd'")])
def test_quality_pregenerated_report(self): # Patch the output of `pylint` _mock_communicate = patch.object(Popen, 'communicate').start() _mock_communicate.return_value = ('\n', '') # When the user provides us with a pre-generated pylint report # then use that instead of calling pylint directly. pylint_reports = [ StringIO( dedent(u""" path/to/file.py:1: [C0111] Missing docstring path/to/file.py:57: [W0511] TODO the name of this method is a little bit confusing another/file.py:41: [W1201, assign_default_role] Specify string format arguments as logging function parameters another/file.py:175: [C0322, Foo.bar] Operator not preceded by a space x=2+3 ^ Unicode: \u9404 \u1239 another/file.py:259: [C0103, bar] Invalid name "\u4920" for type variable (should match [a-z_][a-z0-9_]{2,30}$) """).strip()), StringIO( dedent(u""" path/to/file.py:183: [C0103, Foo.bar.gettag] Invalid name "\u3240" for type argument (should match [a-z_][a-z0-9_]{2,30}$) another/file.py:183: [C0111, Foo.bar.gettag] Missing docstring """).strip()) ] # Generate the violation report quality = PylintQualityReporter('pylint', pylint_reports) # Expect that we get the right violations expected_violations = [ Violation(1, u'C0111: Missing docstring'), Violation( 57, u'W0511: TODO the name of this method is a little bit confusing' ), Violation( 183, u'C0103: Foo.bar.gettag: Invalid name "\u3240" for type argument (should match [a-z_][a-z0-9_]{2,30}$)' ) ] # We're not guaranteed that the violations are returned # in any particular order. actual_violations = quality.violations('path/to/file.py') self.assertEqual(len(actual_violations), len(expected_violations)) for expected in expected_violations: self.assertIn(expected, actual_violations)
def test_quality_error(self): # Patch the output of `pylint` _mock_communicate = patch.object(Popen, 'communicate').start() _mock_communicate.return_value = ("", 'whoops') name = "pylint" # Parse the report quality = PylintQualityReporter(name) # Expect that the name is set self.assertEqual(quality.name(), name) self.assertRaises(QualityReporterError, quality.violations, 'file1.py')
def test_unicode(self): _mock_communicate = patch.object(Popen, 'communicate').start() # Test non-ascii unicode characters in the filename, function name and message _mock_communicate.return_value = (dedent(u""" file_\u6729.py:616: [W1401] Anomalous backslash in string: '\u5922'. String constant might be missing an r prefix. file.py:2: [W0612, cls_name.func_\u9492] Unused variable '\u2920' """).encode('utf-8'), b'') quality = PylintQualityReporter('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."), ]) violations = quality.violations(u'file.py') self.assertEqual(violations, [Violation(2, u"W0612: cls_name.func_\u9492: Unused variable '\u2920'")])
def test_quality(self): # Patch the output of `pylint` _mock_communicate = patch.object(Popen, 'communicate').start() _mock_communicate.return_value = ("************* Module new_file\nC0111: 1,0: Missing docstring\ndef func_1(apple,my_list):\n ^^\nC0111: 1,0:func_1: Missing docstring\n\nW0612: 2,4:func_1: Unused variable 'd'", '') violations = [Violation(1, 'C0111: Missing docstring'), Violation(1, 'C0111: Missing docstring'), Violation(2, "W0612: Unused variable 'd'")] name = "pylint" # Parse the report quality = PylintQualityReporter(name) # Expect that the name is set self.assertEqual(quality.name(), name) # measured_lines is undefined for a quality reporter since all lines are measured self.assertEqual(quality.measured_lines('file1.py'), None) # By construction, each file has the same set # of covered/uncovered lines self.assertEqual(violations, quality.violations('file1.py'))
def test_non_integer_line_num(self): _mock_communicate = patch.object(Popen, 'communicate').start() _mock_communicate.return_value = (dedent(u""" file.py:not_a_number: C0111: Missing docstring file.py:\u8911: C0111: Missing docstring """).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') self.assertEqual(violations, [])
def test_quality_pregenerated_report(self): # Patch the output of `pylint` _mock_communicate = patch.object(Popen, 'communicate').start() _mock_communicate.return_value = ('\n', '') # When the user provides us with a pre-generated pylint report # then use that instead of calling pylint directly. pylint_reports = [ StringIO(dedent(u""" path/to/file.py:1: [C0111] Missing docstring path/to/file.py:57: [W0511] TODO the name of this method is a little bit confusing another/file.py:41: [W1201, assign_default_role] Specify string format arguments as logging function parameters another/file.py:175: [C0322, Foo.bar] Operator not preceded by a space x=2+3 ^ Unicode: \u9404 \u1239 another/file.py:259: [C0103, bar] Invalid name "\u4920" for type variable (should match [a-z_][a-z0-9_]{2,30}$) """).strip()), StringIO(dedent(u""" path/to/file.py:183: [C0103, Foo.bar.gettag] Invalid name "\u3240" for type argument (should match [a-z_][a-z0-9_]{2,30}$) another/file.py:183: [C0111, Foo.bar.gettag] Missing docstring """).strip()) ] # Generate the violation report quality = PylintQualityReporter('pylint', pylint_reports) # Expect that we get the right violations expected_violations = [ Violation(1, u'C0111: Missing docstring'), Violation(57, u'W0511: TODO the name of this method is a little bit confusing'), Violation(183, u'C0103: Foo.bar.gettag: Invalid name "\u3240" for type argument (should match [a-z_][a-z0-9_]{2,30}$)') ] # We're not guaranteed that the violations are returned # in any particular order. actual_violations = quality.violations('path/to/file.py') self.assertEqual(len(actual_violations), len(expected_violations)) for expected in expected_violations: self.assertIn(expected, actual_violations)
def test_quality_error(self): # Patch the output of `pylint` # to output to stderr _mock_communicate = patch.object(Popen, 'communicate').start() _mock_communicate.return_value = (b"", b'whoops') # Parse the report quality = PylintQualityReporter('pylint', []) # Expect an error self.assertRaises(QualityReporterError, quality.violations, 'file1.py')
def test_quality_deprecation_warning(self): # Patch the output stderr/stdout and returncode of `pylint` _mock_communicate = patch.object(subprocess, 'Popen').start() subproc_mock = MagicMock() # Pylint may raise deprecation warnings on pylint usage itself (such # as pylintrc configuration), but continue evaluating for violations. # Diff-quality, likewise, will continue. subproc_mock.returncode = 0 subproc_mock.communicate.return_value = ( b'file1.py:1: [C0111] Missing docstring\n' b'file1.py:1: [C0111, func_1] Missing docstring', b'Foobar: pylintrc deprecation warning' ) _mock_communicate.return_value = subproc_mock # Parse the report quality = PylintQualityReporter('pylint', []) actual_violations = quality.violations('file1.py') # Assert that pylint successfully runs and finds 2 violations self.assertEqual(len(actual_violations), 2)
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_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(""" 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_quality_error(self): # Patch the output stderr/stdout and returncode of `pylint` _mock_communicate = patch.object(subprocess, 'Popen').start() subproc_mock = MagicMock() subproc_mock.returncode = 1 subproc_mock.communicate.return_value = (b'file1.py:1: [C0111] Missing docstring', b'oops') _mock_communicate.return_value = subproc_mock # Parse the report quality = PylintQualityReporter('pylint', []) # Expect an error self.assertRaises(QualityReporterError, quality.violations, 'file1.py')
def test_no_such_file(self): quality = PylintQualityReporter('pylint', []) # Expect that we get no results result = quality.violations('') self.assertEqual(result, [])