diff --git a/src/main/python/pybuilder/plugins/python/pylint_plugin.py b/src/main/python/pybuilder/plugins/python/pylint_plugin.py index ecd1543fd..99f473af1 100644 --- a/src/main/python/pybuilder/plugins/python/pylint_plugin.py +++ b/src/main/python/pybuilder/plugins/python/pylint_plugin.py @@ -17,8 +17,9 @@ # limitations under the License. from pybuilder.core import use_plugin, after, init, task -from pybuilder.utils import assert_can_execute +from pybuilder.errors import BuildFailedException from pybuilder.plugins.python.python_plugin_helper import execute_tool_on_modules +from pybuilder.utils import assert_can_execute use_plugin("python.core") use_plugin("analysis") @@ -44,4 +45,63 @@ def execute_pylint(project, logger): logger.info("Executing pylint on project sources") command_and_arguments = ["pylint"] + project.get_property("pylint_options") - execute_tool_on_modules(project, "pylint", command_and_arguments, True) + pylint_output_file_path = execute_tool_on_modules(project, "pylint", command_and_arguments, True)[1] + + with open(pylint_output_file_path, 'r') as f: + file_content = f.read().splitlines() + + module_name = '' + pylint_score = 0 + pylint_change = 0 + errors = 0 + errors_info = '' + warnings = 0 + warnings_info = '' + show_info_messages = project.get_property('pylint_show_info_messages') + show_warning_messages = project.get_property('pylint_show_warning_messages') + for line in file_content: + if line.startswith('************* Module'): + module_name = line.split(' ')[2] + + if line.startswith('E:') or line.startswith('F:'): + logger.error('Pylint: Module %s: ' % module_name + line) + errors += 1 + + if show_warning_messages and line.startswith('W:'): + logger.warn('Pylint: Module %s: ' % module_name + line) + warnings += 1 + + if show_info_messages and (line.startswith('C:') or line.startswith('R:')): + logger.info('Pylint: Module %s: ' % module_name + line) + + if line.startswith('Your code has been rated at '): + pylint_score = float(line.split(' ')[6].split('/')[0]) + pylint_change = float(line.split(' ')[10][:-1]) + + if errors > 0: + errors_info = ' / Errors: {}'.format(errors) + + if warnings > 0: + warnings_info = ' / Warnings {}'.format(warnings) + + logger.info( + 'Pylint ratio: {} / Pylint change: {}'.format(pylint_score, pylint_change) + errors_info + warnings_info + ) + + if errors > 0 and project.get_property('pylint_break_build_on_errors'): + raise BuildFailedException( + "Pylint: Building failed due to {} errors or fatal errors".format(errors) + ) + + pylint_expected_score = project.get_property('pylint_score_threshold') + pylint_expected_score_change = project.get_property('pylint_score_change_threshold') + if pylint_expected_score and pylint_score < pylint_expected_score: + raise BuildFailedException( + "Pylint: Building failed due to Pylint score({}) less then expected({})". + format(pylint_score, pylint_expected_score) + ) + if pylint_expected_score_change and pylint_change < pylint_expected_score_change: + raise BuildFailedException( + "Pylint: Building failed due to Pylint score decrease({}) higher then allowed({})". + format(pylint_change, pylint_expected_score_change) + ) diff --git a/src/unittest/python/plugins/python/pylint_plugin_tests.py b/src/unittest/python/plugins/python/pylint_plugin_tests.py index 80b1edb66..b105de6d6 100644 --- a/src/unittest/python/plugins/python/pylint_plugin_tests.py +++ b/src/unittest/python/plugins/python/pylint_plugin_tests.py @@ -15,45 +15,81 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -from unittest import TestCase -from test_utils import Mock, patch from logging import Logger +from tempfile import NamedTemporaryFile +from unittest import TestCase from pybuilder.core import Project +from pybuilder.errors import BuildFailedException from pybuilder.plugins.python.pylint_plugin import (check_pylint_availability, init_pylint, execute_pylint, DEFAULT_PYLINT_OPTIONS) +from test_utils import Mock, patch +@patch('pybuilder.plugins.python.pylint_plugin.execute_tool_on_modules') class PylintPluginTests(TestCase): + def setUp(self): + self.project = Project(".") + init_pylint(self.project) + self.mock_logger = Mock(Logger) + @patch('pybuilder.plugins.python.pylint_plugin.assert_can_execute') - def test_should_check_that_pylint_can_be_executed(self, mock_assert_can_execute): + def test_should_check_that_pylint_can_be_executed(self, mock_assert_can_execute, mock_execute_tool): + check_pylint_availability(self.mock_logger) + expected_command_line = ('pylint',) + mock_assert_can_execute.assert_called_with(expected_command_line, 'pylint', 'plugin python.pylint') - mock_logger = Mock(Logger) + def test_should_run_pylint_with_default_options(self, mock_execute_tool): + self._create_pylint_log_file('E:476, 8: Error message') + self._run_execute_tool(mock_execute_tool) + mock_execute_tool.assert_called_with(self.project, "pylint", ["pylint"] + DEFAULT_PYLINT_OPTIONS, True) - check_pylint_availability(mock_logger) + def test_should_run_pylint_with_custom_options(self, mock_execute_tool): + self._create_pylint_log_file('E:476, 8: Error message') + self.project.set_property("pylint_options", ["--test", "-f", "--x=y"]) + self._run_execute_tool(mock_execute_tool) + mock_execute_tool.assert_called_with(self.project, "pylint", ["pylint", "--test", "-f", "--x=y"], True) - expected_command_line = ('pylint',) - mock_assert_can_execute.assert_called_with(expected_command_line, 'pylint', 'plugin python.pylint') + def test_should_show_error_message_in_pyb_logs(self, mock_execute_tool): + self._create_pylint_log_file('E:476, 8: Error message') + self._run_execute_tool(mock_execute_tool) + self.mock_logger.error.assert_called_with('Pylint: Module : E:476, 8: Error message') - @patch('pybuilder.plugins.python.pylint_plugin.execute_tool_on_modules') - def test_should_run_pylint_with_default_options(self, execute_tool): - project = Project(".") - init_pylint(project) + def test_should_show_warning_message_in_pyb_logs(self, mock_execute_tool): + self._create_pylint_log_file('W:2476, 8: Warning message') + self.project.set_property('pylint_show_warning_messages', True) + self._run_execute_tool(mock_execute_tool) + self.mock_logger.warn.assert_called_with('Pylint: Module : W:2476, 8: Warning message') - execute_pylint(project, Mock(Logger)) + def test_should_break_build_on_errors(self, mock_execute_tool): + self._create_pylint_log_file('E:2476, 8: Warning message') + self.project.set_property('pylint_break_build_on_errors', True) + expected_message = 'Pylint: Building failed due to 1 errors or fatal errors' + with self.assertRaisesRegexp(BuildFailedException, expected_message): + self._run_execute_tool(mock_execute_tool) - execute_tool.assert_called_with(project, "pylint", ["pylint"] + DEFAULT_PYLINT_OPTIONS, True) + def test_should_break_build_on_too_low_pylint_score(self, mock_execute_tool): + self._create_pylint_log_file('Your code has been rated at 4.60/10 (previous run: 4.60/10, +0.00)') + self.project.set_property('pylint_score_threshold', 5.0) + expected_message = 'Pylint: Building failed due to Pylint score\(4.6\) less then expected\(5.0\)' + with self.assertRaisesRegexp(BuildFailedException, expected_message): + self._run_execute_tool(mock_execute_tool) - @patch('pybuilder.plugins.python.pylint_plugin.execute_tool_on_modules') - def test_should_run_pylint_with_custom_options(self, execute_tool): - project = Project(".") - init_pylint(project) - project.set_property("pylint_options", ["--test", "-f", "--x=y"]) + def test_should_break_build_on_too_high_pylint_score_decrease(self, mock_execute_tool): + self._create_pylint_log_file('Your code has been rated at 4.60/10 (previous run: 6.60/10, -2.00)') + self.project.set_property('pylint_score_change_threshold', -1.0) + expected_message = 'Pylint: Building failed due to Pylint score decrease\(-2.0\) higher then allowed\(-1.0\)' + with self.assertRaisesRegexp(BuildFailedException, expected_message): + self._run_execute_tool(mock_execute_tool) - execute_pylint(project, Mock(Logger)) + def _create_pylint_log_file(self, test_file_content): + self.temp_file = NamedTemporaryFile() + with open(self.temp_file.name, 'w') as f: + f.write(test_file_content) - execute_tool.assert_called_with(project, "pylint", ["pylint", "--test", "-f", "--x=y"], True) + def _run_execute_tool(self, mock_execute_tool): + mock_execute_tool.return_value = (0, self.temp_file.name) + execute_pylint(self.project, self.mock_logger)