diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 05a5723..ff814f0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -27,7 +27,7 @@ jobs: run: | apk update apk add bash curl - sed -e 's,filename="mpcurses/,filename="src/main/python/mpcurses/,g' target/reports/coverage.xml > coverage.xml + sed -e 's,filename="pybuilder-radon/,filename="src/main/python/pybuilder_radon/,g' target/reports/coverage.xml > coverage.xml - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 diff --git a/README.md b/README.md index cb6f84a..6b044a8 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ # pybuilder-radon # -A pybuilder plugin that checks the cyclomatic complexity of your project using `radon`. For more information about radon refer to the [radon home page](https://pypi.org/project/radon/). +A pybuilder plugin that checks the cyclomatic complexity of your project using `radon`. For more information about radon refer to the [radon pypi page](https://pypi.org/project/radon/). -To add this plugin into your pybuilder project, add the following line at the top of your build.py: +To add this plugin into your pybuilder project, add the following line near the top of your build.py: ```python use_plugin('pypi:pybuilder_radon', '~=0.1.0') ``` @@ -29,7 +29,7 @@ Refer to [cyclomatic complexity](https://www.c-sharpcorner.com/article/code-metr ### Pybuilder radon properties ### -The pybuilder task `pyb complexity` will use radon to to analyze your project and display the overall average cyclomatic complexity, verbose mode will display complexity of all classes, functions and methods analyzed. The following plugin properties can be set to fail the build if a complexity threshold has been exceeded. +The pybuilder task `pyb radon` will use radon to to analyze your project and display the average cyclomatic complexity, verbose mode will display complexity of all classes, functions and methods analyzed. The following plugin properties are available to further configure the plugin's execution. Name | Type | Default Value | Description -- | -- | -- | -- diff --git a/build.py b/build.py index c4c25db..357fc5c 100644 --- a/build.py +++ b/build.py @@ -4,7 +4,7 @@ from pybuilder.core import Author # only for functional testing plugin -# from pybuilder_radon import complexity +# from pybuilder_radon import radon use_plugin("python.core") use_plugin("python.unittest") @@ -16,9 +16,9 @@ name = 'pybuilder-radon' authors = [Author('Emilio Reyes', 'soda480@gmail.com')] -summary = 'Pybuilder plugin for radon' +summary = 'Pybuilder plugin for radon cyclomatic complexity' url = 'https://github.com/soda480/pybuilder-radon' -version = '0.1.1' +version = '0.1.2' default_task = ['clean', 'analyze'] license = 'Apache License, Version 2.0' description = summary @@ -51,5 +51,5 @@ def set_properties(project): 'Programming Language :: Python :: 3.6', 'Topic :: Software Development :: Build Tools']) # only for functional testing plugin - # project.set_property('radon_break_build_average_complexity_threshold', 3.2) - # project.set_property('radon_break_build_complexity_threshold', 6) + # project.set_property('radon_break_build_average_complexity_threshold', 2.8) + # project.set_property('radon_break_build_complexity_threshold', 5) diff --git a/src/main/python/pybuilder_radon/__init__.py b/src/main/python/pybuilder_radon/__init__.py index 7662859..81f9661 100644 --- a/src/main/python/pybuilder_radon/__init__.py +++ b/src/main/python/pybuilder_radon/__init__.py @@ -1,2 +1,2 @@ # -*- coding: utf-8 -*- -from .task import complexity +from .task import radon diff --git a/src/main/python/pybuilder_radon/task.py b/src/main/python/pybuilder_radon/task.py index 8926203..ed17038 100644 --- a/src/main/python/pybuilder_radon/task.py +++ b/src/main/python/pybuilder_radon/task.py @@ -9,20 +9,22 @@ @init -def init_complexity(project): - """ initialize complexity task properties +def init_radon(project): + """ initialize radon task properties """ project.set_property_if_unset('radon_break_build_average_complexity_threshold', None) project.set_property_if_unset('radon_break_build_complexity_threshold', None) project.plugin_depends_on('radon') -@task('complexity', description='checks cyclomatic complexity') +@task('radon', description='execute radon cyclomatic complexity') @depends('prepare') -def complexity(project, logger): - """ checks cyclomatic complexity +def radon(project, logger): + """ execute radon cyclomatic complexity """ + set_verbose_property(project) command = get_command(project) + logger.info(f'Executing radon cyclomatic complexity: \"{command.as_string}\"') # assert_can_execute(command.parts, prerequisite='radon', caller='complexity') result = command.run_on_production_source_files(logger) if not verify_result(result, logger, command): @@ -43,14 +45,21 @@ def get_command(project): return command +def set_verbose_property(project): + """ set verbose property + """ + verbose = project.get_property('verbose') + project.set_property('radon_verbose_output', verbose) + + def verify_result(result, logger, command): """ return True if result contains lines, False otherwise """ if not result.report_lines: - logger.warn(f"Command {command.as_string()} produced no output") + logger.warn(f"Command {command.as_string} produced no output") return False if len(result.error_report_lines) > 0: - logger.error(f"Command {command.as_string()} produced errors, see {result.error_report_file}") + logger.error(f"Command {command.as_string} produced errors, see {result.error_report_file}") return False return True @@ -68,10 +77,6 @@ def get_complexity(project, result, logger): regex_line = r'[A-Z] \d+:\d+ (?P.*) - [A-Z] \((?P\d+)\)' for line in result.report_lines[:-1]: line = line.strip() - # using this place to conform to pybuilder verbosity - verbose = project.get_property('verbose') - if verbose: - logger.debug(line) match = re.match(regex_line, line) if match: score = float(match.group('score')) diff --git a/src/unittest/python/test_task.py b/src/unittest/python/test_task.py index d30394c..f3f8f1b 100644 --- a/src/unittest/python/test_task.py +++ b/src/unittest/python/test_task.py @@ -4,9 +4,10 @@ from mock import call from mock import Mock -from pybuilder_radon.task import init_complexity -from pybuilder_radon.task import complexity +from pybuilder_radon.task import init_radon +from pybuilder_radon.task import radon from pybuilder_radon.task import get_command +from pybuilder_radon.task import set_verbose_property from pybuilder_radon.task import verify_result from pybuilder_radon.task import get_complexity from pybuilder_radon.task import verify_complexity @@ -27,29 +28,29 @@ def tearDown(self): """ pass - def test__init_complexity_Should_CallExpected_When_Called(self, *patches): + def test__init_radon_Should_CallExpected_When_Called(self, *patches): project_mock = Mock() - init_complexity(project_mock) + init_radon(project_mock) self.assertTrue(call('radon_break_build_average_complexity_threshold', None) in project_mock.set_property_if_unset.mock_calls) self.assertTrue(call('radon_break_build_complexity_threshold', None) in project_mock.set_property_if_unset.mock_calls) @patch('pybuilder_radon.task.get_command') @patch('pybuilder_radon.task.process_complexity') @patch('pybuilder_radon.task.verify_result') - def test__complexity_Should_CallExpected_When_VerifyResultFalse(self, verify_result_patch, process_complexity_patch, *patches): + def test__radon_Should_CallExpected_When_VerifyResultFalse(self, verify_result_patch, process_complexity_patch, *patches): verify_result_patch.return_value = False project_mock = Mock() - complexity(project_mock, Mock()) + radon(project_mock, Mock()) process_complexity_patch.assert_not_called() @patch('pybuilder_radon.task.get_command') @patch('pybuilder_radon.task.process_complexity') @patch('pybuilder_radon.task.get_complexity') @patch('pybuilder_radon.task.verify_result') - def test__complexity_Should_CallExpected_When_VerifyResultTrue(self, verify_result_patch, get_complexity_patch, process_complexity_patch, *patches): + def test__radon_Should_CallExpected_When_VerifyResultTrue(self, verify_result_patch, get_complexity_patch, process_complexity_patch, *patches): verify_result_patch.return_value = True project_mock = Mock() - complexity(project_mock, Mock()) + radon(project_mock, Mock()) process_complexity_patch.assert_called_once_with(project_mock, get_complexity_patch.return_value) @patch('pybuilder_radon.task.get_command') @@ -57,11 +58,11 @@ def test__complexity_Should_CallExpected_When_VerifyResultTrue(self, verify_resu @patch('pybuilder_radon.task.process_complexity') @patch('pybuilder_radon.task.verify_complexity') @patch('pybuilder_radon.task.verify_result') - def test__complexity_Should_CallExpected_When_VerifyComplexityFalse(self, verify_result_patch, verify_complexity_patch, process_complexity_patch, *patches): + def test__radon_Should_CallExpected_When_VerifyComplexityFalse(self, verify_result_patch, verify_complexity_patch, process_complexity_patch, *patches): verify_result_patch.return_value = True verify_complexity_patch.return_value = False project_mock = Mock() - complexity(project_mock, Mock()) + radon(project_mock, Mock()) process_complexity_patch.assert_not_called() @patch('pybuilder_radon.task.ExternalCommandBuilder') @@ -71,6 +72,11 @@ def test__get_command_Should_CallAndReturnExpected_When_Called(self, external_co external_command_builder_patch.assert_called_once_with('radon', project_mock) self.assertEqual(result, external_command_builder_patch.return_value) + def test__set_verbose_property_Should_CallExpected_When_Called(self, *patches): + project_mock = Mock() + set_verbose_property(project_mock) + project_mock.set_property.assert_called_once_with('radon_verbose_output', project_mock.get_property.return_value) + def test__verify_result_Should_ReturnFalse_When_NoReportLines(self, *patches): result_mock = Mock() result_mock.report_lines = [] @@ -91,30 +97,6 @@ def test__verify_result_Should_ReturnTrue_When_ReportLinesAndNoErrorReportLines( result = verify_result(result_mock, Mock(), Mock()) self.assertTrue(result) - def test__get_complexity_Should_ReturnExpected_When_CalledVerbose(self, *patches): - result_mock = Mock() - result_mock.report_lines = [ - '\n', - ' M 81:4 class.ma - C (14)\n', - ' M 231:4 class.mb - A (4)\n', - 'src/main/python/package/module.py\n', - ' C 40:0 class.mc - B (9)\n', - '\n', - 'Average complexity: A (3.557377049180328)'] - project_mock = Mock() - project_mock.get_property.return_value = True - logger_mock = Mock() - result = get_complexity(project_mock, result_mock, logger_mock) - expected_result = { - 'average': 3.557377049180328, - 'highest': { - 'name': 'class.ma', - 'score': 14 - } - } - self.assertEqual(result, expected_result) - self.assertEqual(len(logger_mock.debug.mock_calls), 6) - def test__get_complexity_Should_ReturnExpected_When_CalledNoVerbose(self, *patches): result_mock = Mock() result_mock.report_lines = [ @@ -160,7 +142,6 @@ def test__get_complexity_Should_ReturnExpected_When_NoMatch(self, *patches): } } self.assertEqual(result, expected_result) - self.assertEqual(len(logger_mock.debug.mock_calls), 6) def test__verify_complexity_Should_ReturnFalse_When_AverageIsNone(self, *patches): complexity = {