From 1b65afdd0467314100bf071b9c5377ae796ad37b Mon Sep 17 00:00:00 2001 From: Sarina Canelake Date: Sat, 14 Mar 2015 00:48:28 -0400 Subject: [PATCH 1/4] Give run_pep8 more readable output Jenkins "Quality Report" pep8 output now lists all pep8 violations, not just the number of violations. --- pavelib/quality.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pavelib/quality.py b/pavelib/quality.py index ae74a107f9..b08fe067b2 100644 --- a/pavelib/quality.py +++ b/pavelib/quality.py @@ -179,16 +179,22 @@ def run_pep8(options): # Print number of violations to log violations_count_str = "Number of pep8 violations: {count}".format(count=count) print(violations_count_str) + violations = '' + with open("{report_dir}/pep8.report".format(report_dir=report_dir), 'r') as f: + violations_list = f.readlines() + violations_str = '\n'.join(violations_list) + print(violations_str) # Also write the number of violations to a file with open(Env.METRICS_DIR / "pep8", "w") as f: - f.write(violations_count_str) + f.write(violations_count_str + '\n\n') + f.write(violations_str) # Fail if any violations are found if count: raise Exception( - "Too many pep8 violations. Number of violations found: {count}.".format( - count=count + "Too many pep8 violations. Number of violations found: {count}.\n\n{violations}".format( + count=count, violations=violations_str ) ) From a51fbc8123235a7229ef078b312adf6e4ca46e50 Mon Sep 17 00:00:00 2001 From: Sarina Canelake Date: Mon, 16 Mar 2015 21:09:52 -0400 Subject: [PATCH 2/4] Refactor pep8 to run instead of diff-quality pep8 --- pavelib/quality.py | 122 +++++++++++++++++++++++++++++---------------- 1 file changed, 79 insertions(+), 43 deletions(-) diff --git a/pavelib/quality.py b/pavelib/quality.py index b08fe067b2..5b9b6efb96 100644 --- a/pavelib/quality.py +++ b/pavelib/quality.py @@ -150,17 +150,13 @@ def _count_pylint_violations(report_file): return num_violations_report -@task -@needs('pavelib.prereqs.install_python_prereqs') -@cmdopts([ - ("system=", "s", "System to act on"), -]) -def run_pep8(options): +def _get_pep8_violations(): """ - Run pep8 on system code. - Fail the task if any violations are found. + Runs pep8. Returns a tuple of (number_of_violations, violations_string) + where violations_string is a string of all pep8 violations found, separated + by new lines. """ - systems = getattr(options, 'system', ALL_SYSTEMS).split(',') + systems = ALL_SYSTEMS.split(',') report_dir = (Env.REPORT_DIR / 'pep8') report_dir.rmtree(ignore_errors=True) @@ -172,17 +168,40 @@ def run_pep8(options): for system in systems: sh('pep8 {system} | tee {report_dir}/pep8.report -a'.format(system=system, report_dir=report_dir)) - count = _count_pep8_violations( + count, violations_list = _pep8_violations( "{report_dir}/pep8.report".format(report_dir=report_dir) ) + return (count, violations_list) + + +def _pep8_violations(report_file): + """ + Returns a tuple of (num_violations, violations_list) for all + pep8 violations in the given report_file. + """ + with open(report_file) as f: + violations_list = f.readlines() + num_lines = len(violations_list) + return num_lines, violations_list + + +@task +@needs('pavelib.prereqs.install_python_prereqs') +@cmdopts([ + ("system=", "s", "System to act on"), +]) +def run_pep8(options): + """ + Run pep8 on system code. + Fail the task if any violations are found. + """ + (count, violations_list) = _get_pep8_violations() + violations_str = ''.join(violations_list) + # Print number of violations to log violations_count_str = "Number of pep8 violations: {count}".format(count=count) print(violations_count_str) - violations = '' - with open("{report_dir}/pep8.report".format(report_dir=report_dir), 'r') as f: - violations_list = f.readlines() - violations_str = '\n'.join(violations_list) print(violations_str) # Also write the number of violations to a file @@ -199,11 +218,6 @@ def run_pep8(options): ) -def _count_pep8_violations(report_file): - num_lines = sum(1 for line in open(report_file)) - return num_lines - - @task @needs('pavelib.prereqs.install_python_prereqs') def run_complexity(): @@ -238,12 +252,57 @@ def run_quality(options): quality of the branch vs the compare branch is less than 80%, then this task will fail. This threshold would be applied to both pep8 and pylint. """ - # Directory to put the diff reports in. # This makes the folder if it doesn't already exist. dquality_dir = (Env.REPORT_DIR / "diff_quality").makedirs_p() diff_quality_percentage_failure = False + # Run pep8 directly since we have 0 violations on master + def _pep8_output(count, violations_list, is_html=False): + + if is_html: + lines = ['\n'] + sep = '-------------
\n' + title = "

Quality Report: pep8

\n" + violations_bullets = ''.join( + ['
  • {violation}

  • \n'.format(violation=violation) for violation in violations_list] + ) + violations_str = '\n'.format(bullets=violations_bullets) + violations_count_str = "Violations: {count}
    \n" + fail_line = "FAILURE: pep8 count should be 0
    \n" + else: + lines = [] + sep = '-------------\n' + title = "Quality Report: pep8\n" + violations_str = ''.join(violations_list) + violations_count_str = "Violations: {count}\n" + fail_line = "FAILURE: pep8 count should be 0\n" + + violations_count_str = violations_count_str.format(count=count) + + lines.extend([sep, title, sep, violations_str, sep, violations_count_str]) + + if count > 0: + lines.append(fail_line) + lines.append(sep + '\n') + if is_html: + lines.append('') + + return ''.join(lines) + + (count, violations_list) = _get_pep8_violations() + + # Print number of violations to log + print _pep8_output(count, violations_list) + + # Also write the number of violations to a file + with open(dquality_dir / "diff_quality_pep8.html", "w") as f: + f.write(_pep8_output(count, violations_list, is_html=True)) + + if count > 0: + diff_quality_percentage_failure = True + + # ----- Set up for diff-quality pylint call ----- # Set the string, if needed, to be used for the diff-quality --compare-branch switch. compare_branch = getattr(options, 'compare_branch', None) compare_branch_string = '' @@ -256,29 +315,6 @@ def run_quality(options): if diff_threshold > -1: percentage_string = '--fail-under={0}'.format(diff_threshold) - # Generate diff-quality html report for pep8, and print to console - # If pep8 reports exist, use those - # Otherwise, `diff-quality` will call pep8 itself - - pep8_files = get_violations_reports("pep8") - pep8_reports = u' '.join(pep8_files) - - try: - sh( - "diff-quality --violations=pep8 {pep8_reports} {percentage_string} " - "{compare_branch_string} --html-report {dquality_dir}/diff_quality_pep8.html".format( - pep8_reports=pep8_reports, - percentage_string=percentage_string, - compare_branch_string=compare_branch_string, - dquality_dir=dquality_dir - ) - ) - except BuildFailure, error_message: - if is_percentage_failure(error_message): - diff_quality_percentage_failure = True - else: - raise BuildFailure(error_message) - # Generate diff-quality html report for pylint, and print to console # If pylint reports exist, use those # Otherwise, `diff-quality` will call pylint itself From bd090a6e583d3b9d9c7ef016b335c9f0a69ddb15 Mon Sep 17 00:00:00 2001 From: Sarina Canelake Date: Fri, 20 Mar 2015 16:20:44 -0400 Subject: [PATCH 3/4] Fix paver quality tests to reflect removed diff-quality call --- pavelib/paver_tests/test_paver_quality.py | 75 ++++++++++++----------- pavelib/quality.py | 35 ++++++----- 2 files changed, 58 insertions(+), 52 deletions(-) diff --git a/pavelib/paver_tests/test_paver_quality.py b/pavelib/paver_tests/test_paver_quality.py index fb8d3c7bed..86e04b4b4c 100644 --- a/pavelib/paver_tests/test_paver_quality.py +++ b/pavelib/paver_tests/test_paver_quality.py @@ -1,7 +1,10 @@ +""" +Tests for paver quality tasks +""" import os import tempfile import unittest -from mock import patch +from mock import patch, MagicMock from ddt import ddt, file_data import pavelib.quality @@ -11,22 +14,26 @@ from paver.easy import BuildFailure @ddt class TestPaverQualityViolations(unittest.TestCase): - + """ + For testing the paver violations-counting tasks + """ def setUp(self): + super(TestPaverQualityViolations, self).setUp() self.f = tempfile.NamedTemporaryFile(delete=False) self.f.close() + self.addCleanup(os.remove, self.f.name) def test_pylint_parser_other_string(self): with open(self.f.name, 'w') as f: f.write("hello") - num = pavelib.quality._count_pylint_violations(f.name) + num = pavelib.quality._count_pylint_violations(f.name) # pylint: disable=protected-access self.assertEqual(num, 0) def test_pylint_parser_pep8(self): # Pep8 violations should be ignored. with open(self.f.name, 'w') as f: f.write("foo/hello/test.py:304:15: E203 whitespace before ':'") - num = pavelib.quality._count_pylint_violations(f.name) + num = pavelib.quality._count_pylint_violations(f.name) # pylint: disable=protected-access self.assertEqual(num, 0) @file_data('pylint_test_list.json') @@ -38,18 +45,15 @@ class TestPaverQualityViolations(unittest.TestCase): """ with open(self.f.name, 'w') as f: f.write(value) - num = pavelib.quality._count_pylint_violations(f.name) + num = pavelib.quality._count_pylint_violations(f.name) # pylint: disable=protected-access self.assertEqual(num, 1) def test_pep8_parser(self): with open(self.f.name, 'w') as f: f.write("hello\nhithere") - num = pavelib.quality._count_pep8_violations(f.name) + num, _violations = pavelib.quality._pep8_violations(f.name) # pylint: disable=protected-access self.assertEqual(num, 2) - def tearDown(self): - os.remove(self.f.name) - class TestPaverRunQuality(unittest.TestCase): """ @@ -57,27 +61,33 @@ class TestPaverRunQuality(unittest.TestCase): """ def setUp(self): + super(TestPaverRunQuality, self).setUp() # mock the @needs decorator to skip it self._mock_paver_needs = patch.object(pavelib.quality.run_quality, 'needs').start() self._mock_paver_needs.return_value = 0 - self._mock_paver_sh = patch('pavelib.quality.sh').start() - self.addCleanup(self._mock_paver_sh.stop()) - self.addCleanup(self._mock_paver_needs.stop()) + patcher = patch('pavelib.quality.sh') + self._mock_paver_sh = patcher.start() + self.addCleanup(patcher.stop) + self.addCleanup(self._mock_paver_needs.stop) def test_failure_on_diffquality_pep8(self): """ - If pep8 diff-quality fails due to the percentage threshold, pylint - should still be run + If pep8 finds errors, pylint should still be run """ + # Mock _get_pep8_violations to return a violation + _mock_pep8_violations = MagicMock( + return_value=(1, ['lms/envs/common.py:32:2: E225 missing whitespace around operator']) + ) + with patch('pavelib.quality._get_pep8_violations', _mock_pep8_violations): + with self.assertRaises(SystemExit): + pavelib.quality.run_quality("") + self.assertRaises(BuildFailure) - # Underlying sh call must fail when it is running the pep8 diff-quality task - self._mock_paver_sh.side_effect = CustomShMock().fail_on_pep8 - with self.assertRaises(SystemExit): - pavelib.quality.run_quality("") - self.assertRaises(BuildFailure) - # Test that both pep8 and pylint were called by counting the calls - self.assertEqual(self._mock_paver_sh.call_count, 2) + # Test that both pep8 and pylint were called by counting the calls to _get_pep8_violations + # (for pep8) and sh (for diff-quality pylint) + self.assertEqual(_mock_pep8_violations.call_count, 1) + self.assertEqual(self._mock_paver_sh.call_count, 1) def test_failure_on_diffquality_pylint(self): """ @@ -90,7 +100,8 @@ class TestPaverRunQuality(unittest.TestCase): pavelib.quality.run_quality("") self.assertRaises(BuildFailure) # Test that both pep8 and pylint were called by counting the calls - self.assertEqual(self._mock_paver_sh.call_count, 2) + # Pep8 is called twice (for lms and cms), then pylint is called an additional time. + self.assertEqual(self._mock_paver_sh.call_count, 3) def test_other_exception(self): """ @@ -105,8 +116,13 @@ class TestPaverRunQuality(unittest.TestCase): def test_no_diff_quality_failures(self): # Assert nothing is raised - pavelib.quality.run_quality("") - self.assertEqual(self._mock_paver_sh.call_count, 2) + _mock_pep8_violations = MagicMock(return_value=(0, [])) + with patch('pavelib.quality._get_pep8_violations', _mock_pep8_violations): + pavelib.quality.run_quality("") + # Assert that _get_pep8_violations (which calls "pep8") is called once + self.assertEqual(_mock_pep8_violations.call_count, 1) + # And assert that sh was called once (for the call to "pylint") + self.assertEqual(self._mock_paver_sh.call_count, 1) class CustomShMock(object): @@ -115,17 +131,6 @@ class CustomShMock(object): of them need to have certain responses. """ - def fail_on_pep8(self, arg): - """ - For our tests, we need the call for diff-quality running pep8 reports to fail, since that is what - is going to fail when we pass in a percentage ("p") requirement. - """ - if "pep8" in arg: - # Essentially mock diff-quality exiting with 1 - paver.easy.sh("exit 1") - else: - return - def fail_on_pylint(self, arg): """ For our tests, we need the call for diff-quality running pep8 reports to fail, since that is what diff --git a/pavelib/quality.py b/pavelib/quality.py index 5b9b6efb96..c3d883b1e3 100644 --- a/pavelib/quality.py +++ b/pavelib/quality.py @@ -56,7 +56,7 @@ def find_fixme(options): num_fixme += _count_pylint_violations( "{report_dir}/pylint_fixme.report".format(report_dir=report_dir)) - print("Number of pylint fixmes: " + str(num_fixme)) + print "Number of pylint fixmes: " + str(num_fixme) @task @@ -75,7 +75,7 @@ def run_pylint(options): violations_limit = int(getattr(options, 'limit', -1)) errors = getattr(options, 'errors', False) systems = getattr(options, 'system', ALL_SYSTEMS).split(',') - + # Make sure the metrics subdirectory exists Env.METRICS_DIR.makedirs_p() @@ -119,7 +119,7 @@ def run_pylint(options): # Print number of violations to log violations_count_str = "Number of pylint violations: " + str(num_violations) - print(violations_count_str) + print violations_count_str # Also write the number of violations to a file with open(Env.METRICS_DIR / "pylint", "w") as f: @@ -139,7 +139,7 @@ def _count_pylint_violations(report_file): # An example string: # common/lib/xmodule/xmodule/tests/test_conditional.py:21: [C0111(missing-docstring), DummySystem] Missing docstring # More examples can be found in the unit tests for this method - pylint_pattern = re.compile(".(\d+):\ \[(\D\d+.+\]).") + pylint_pattern = re.compile(r".(\d+):\ \[(\D\d+.+\]).") for line in open(report_file): violation_list_for_line = pylint_pattern.split(line) @@ -161,7 +161,7 @@ def _get_pep8_violations(): report_dir = (Env.REPORT_DIR / 'pep8') report_dir.rmtree(ignore_errors=True) report_dir.makedirs_p() - + # Make sure the metrics subdirectory exists Env.METRICS_DIR.makedirs_p() @@ -191,31 +191,29 @@ def _pep8_violations(report_file): @cmdopts([ ("system=", "s", "System to act on"), ]) -def run_pep8(options): +def run_pep8(options): # pylint: disable=unused-argument """ Run pep8 on system code. Fail the task if any violations are found. """ (count, violations_list) = _get_pep8_violations() - violations_str = ''.join(violations_list) + violations_list = ''.join(violations_list) # Print number of violations to log violations_count_str = "Number of pep8 violations: {count}".format(count=count) - print(violations_count_str) - print(violations_str) + print violations_count_str + print violations_list # Also write the number of violations to a file with open(Env.METRICS_DIR / "pep8", "w") as f: f.write(violations_count_str + '\n\n') - f.write(violations_str) + f.write(violations_list) # Fail if any violations are found if count: - raise Exception( - "Too many pep8 violations. Number of violations found: {count}.\n\n{violations}".format( - count=count, violations=violations_str - ) - ) + failure_string = "Too many pep8 violations. " + violations_count_str + failure_string += "\n\nViolations:\n{violations_list}".format(violations_list=violations_list) + raise Exception(failure_string) @task @@ -257,9 +255,11 @@ def run_quality(options): dquality_dir = (Env.REPORT_DIR / "diff_quality").makedirs_p() diff_quality_percentage_failure = False - # Run pep8 directly since we have 0 violations on master def _pep8_output(count, violations_list, is_html=False): - + """ + Given a count & list of pep8 violations, pretty-print the pep8 output. + If `is_html`, will print out with HTML markup. + """ if is_html: lines = ['\n'] sep = '-------------
    \n' @@ -290,6 +290,7 @@ def run_quality(options): return ''.join(lines) + # Run pep8 directly since we have 0 violations on master (count, violations_list) = _get_pep8_violations() # Print number of violations to log From e77211b71390f33789848f3da6982a7de30175bf Mon Sep 17 00:00:00 2001 From: Sarina Canelake Date: Mon, 23 Mar 2015 15:50:29 -0400 Subject: [PATCH 4/4] Run pep8 on all files, and fix uncovered errors --- .../djangoapps/course_groups/tests/test_cohorts.py | 2 +- .../user_api/preferences/tests/test_api.py | 6 +++--- .../core/djangoapps/user_api/preferences/views.py | 2 +- .../core/djangoapps/user_api/tests/test_views.py | 1 - openedx/core/djangoapps/user_api/views.py | 5 +++-- pavelib/paver_tests/test_paver_quality.py | 14 +++++++++----- pavelib/quality.py | 5 +---- 7 files changed, 18 insertions(+), 17 deletions(-) diff --git a/openedx/core/djangoapps/course_groups/tests/test_cohorts.py b/openedx/core/djangoapps/course_groups/tests/test_cohorts.py index a2c98ad802..5bc55f706c 100644 --- a/openedx/core/djangoapps/course_groups/tests/test_cohorts.py +++ b/openedx/core/djangoapps/course_groups/tests/test_cohorts.py @@ -24,6 +24,7 @@ from ..tests.helpers import ( CohortFactory, CourseCohortFactory, CourseCohortSettingsFactory ) + @patch("openedx.core.djangoapps.course_groups.cohorts.tracker") class TestCohortSignals(TestCase): def setUp(self): @@ -405,7 +406,6 @@ class TestCohorts(ModuleStoreTestCase): "No groups->default cohort for user2" ) - def test_auto_cohorting_randomization(self): """ Make sure cohorts.get_cohort() randomizes properly. diff --git a/openedx/core/djangoapps/user_api/preferences/tests/test_api.py b/openedx/core/djangoapps/user_api/preferences/tests/test_api.py index a2949a206e..13d05c6492 100644 --- a/openedx/core/djangoapps/user_api/preferences/tests/test_api.py +++ b/openedx/core/djangoapps/user_api/preferences/tests/test_api.py @@ -204,7 +204,7 @@ class TestPreferenceAPI(TestCase): too_long_key = "x" * 256 with self.assertRaises(PreferenceValidationError) as context_manager: - update_user_preferences(self.user, { too_long_key: "new_value"}) + update_user_preferences(self.user, {too_long_key: "new_value"}) errors = context_manager.exception.preference_errors self.assertEqual(len(errors.keys()), 1) self.assertEqual( @@ -217,7 +217,7 @@ class TestPreferenceAPI(TestCase): for empty_value in ("", " "): with self.assertRaises(PreferenceValidationError) as context_manager: - update_user_preferences(self.user, { self.test_preference_key: empty_value}) + update_user_preferences(self.user, {self.test_preference_key: empty_value}) errors = context_manager.exception.preference_errors self.assertEqual(len(errors.keys()), 1) self.assertEqual( @@ -230,7 +230,7 @@ class TestPreferenceAPI(TestCase): user_preference_save.side_effect = [Exception, None] with self.assertRaises(PreferenceUpdateError) as context_manager: - update_user_preferences(self.user, { self.test_preference_key: "new_value"}) + update_user_preferences(self.user, {self.test_preference_key: "new_value"}) self.assertEqual( context_manager.exception.developer_message, u"Save failed for user preference 'test_key' with value 'new_value': " diff --git a/openedx/core/djangoapps/user_api/preferences/views.py b/openedx/core/djangoapps/user_api/preferences/views.py index b9f7544874..3ea9fb295b 100644 --- a/openedx/core/djangoapps/user_api/preferences/views.py +++ b/openedx/core/djangoapps/user_api/preferences/views.py @@ -204,5 +204,5 @@ class PreferencesDetailView(APIView): if not preference_existed: return Response(status=status.HTTP_404_NOT_FOUND) - + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/openedx/core/djangoapps/user_api/tests/test_views.py b/openedx/core/djangoapps/user_api/tests/test_views.py index 91d189e764..bb6d22a7a3 100644 --- a/openedx/core/djangoapps/user_api/tests/test_views.py +++ b/openedx/core/djangoapps/user_api/tests/test_views.py @@ -1686,7 +1686,6 @@ class TestFacebookRegistrationView( self._verify_user_existence(user_exists=False, social_link_exists=False) - @skipUnless(settings.FEATURES.get("ENABLE_THIRD_PARTY_AUTH"), "third party auth not enabled") class TestGoogleRegistrationView( ThirdPartyRegistrationTestMixin, ThirdPartyOAuthTestMixinGoogle, TransactionTestCase diff --git a/openedx/core/djangoapps/user_api/views.py b/openedx/core/djangoapps/user_api/views.py index 2c03c50944..89e52fe34e 100644 --- a/openedx/core/djangoapps/user_api/views.py +++ b/openedx/core/djangoapps/user_api/views.py @@ -382,8 +382,9 @@ class RegistrationView(APIView): # Translators: These instructions appear on the registration form, immediately # below a field meant to hold the user's public username. username_instructions = _( - u"The name that will identify you in your courses - {bold_start}(cannot be changed later){bold_end}").format(bold_start=u'', bold_end=u'' - ) + u"The name that will identify you in your courses - " + "{bold_start}(cannot be changed later){bold_end}" + ).format(bold_start=u'', bold_end=u'') # Translators: This example username is used as a placeholder in # a field on the registration form meant to hold the user's username. diff --git a/pavelib/paver_tests/test_paver_quality.py b/pavelib/paver_tests/test_paver_quality.py index 86e04b4b4c..804bca7773 100644 --- a/pavelib/paver_tests/test_paver_quality.py +++ b/pavelib/paver_tests/test_paver_quality.py @@ -96,12 +96,16 @@ class TestPaverRunQuality(unittest.TestCase): # Underlying sh call must fail when it is running the pylint diff-quality task self._mock_paver_sh.side_effect = CustomShMock().fail_on_pylint - with self.assertRaises(SystemExit): - pavelib.quality.run_quality("") - self.assertRaises(BuildFailure) + _mock_pep8_violations = MagicMock(return_value=(0, [])) + with patch('pavelib.quality._get_pep8_violations', _mock_pep8_violations): + with self.assertRaises(SystemExit): + pavelib.quality.run_quality("") + self.assertRaises(BuildFailure) # Test that both pep8 and pylint were called by counting the calls - # Pep8 is called twice (for lms and cms), then pylint is called an additional time. - self.assertEqual(self._mock_paver_sh.call_count, 3) + # Assert that _get_pep8_violations (which calls "pep8") is called once + self.assertEqual(_mock_pep8_violations.call_count, 1) + # And assert that sh was called once (for the call to "pylint") + self.assertEqual(self._mock_paver_sh.call_count, 1) def test_other_exception(self): """ diff --git a/pavelib/quality.py b/pavelib/quality.py index c3d883b1e3..d91aae6a8b 100644 --- a/pavelib/quality.py +++ b/pavelib/quality.py @@ -156,8 +156,6 @@ def _get_pep8_violations(): where violations_string is a string of all pep8 violations found, separated by new lines. """ - systems = ALL_SYSTEMS.split(',') - report_dir = (Env.REPORT_DIR / 'pep8') report_dir.rmtree(ignore_errors=True) report_dir.makedirs_p() @@ -165,8 +163,7 @@ def _get_pep8_violations(): # Make sure the metrics subdirectory exists Env.METRICS_DIR.makedirs_p() - for system in systems: - sh('pep8 {system} | tee {report_dir}/pep8.report -a'.format(system=system, report_dir=report_dir)) + sh('pep8 . | tee {report_dir}/pep8.report -a'.format(report_dir=report_dir)) count, violations_list = _pep8_violations( "{report_dir}/pep8.report".format(report_dir=report_dir)