From 61bfc6140c7cc15cab932efaf789196d415912a7 Mon Sep 17 00:00:00 2001 From: jce Date: Fri, 29 Nov 2024 12:00:02 +0100 Subject: [PATCH 01/86] Fatal error when a mandatory key is missing from the config; no more print when a checker is not explicitly disabled in the config --- src/mlx/warnings/warnings.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index 4357b56e..ae13fef2 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -207,14 +207,15 @@ def config_parser(self, config): ''' # activate checker for checker in self.public_checkers: - try: + if checker.name in config: checker_config = config[checker.name] - if bool(checker_config['enabled']): - self.activate_checker(checker) - checker.parse_config(checker_config) - print("Config parsing for {name} completed".format(name=checker.name)) - except KeyError as err: - print("Incomplete config. Missing: {key}".format(key=err)) + try: + if bool(checker_config['enabled']): + self.activate_checker(checker) + checker.parse_config(checker_config) + print("Config parsing for {name} completed".format(name=checker.name)) + except KeyError as err: + raise WarningsConfigError(f"Incomplete config. Missing: {err}") from err def write_counted_warnings(self, out_file): ''' Writes counted warnings to the given file From b923ad35596c43e84ecd75794338022e77ca3a6e Mon Sep 17 00:00:00 2001 From: jce Date: Fri, 29 Nov 2024 13:48:00 +0100 Subject: [PATCH 02/86] address warning about incomplete config --- tests/test_in/config_example.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_in/config_example.json b/tests/test_in/config_example.json index a0a44261..8289872e 100644 --- a/tests/test_in/config_example.json +++ b/tests/test_in/config_example.json @@ -29,6 +29,7 @@ "max": "-1" }, "bug": { + "min": 0, "max": 0 }, "pending": { @@ -36,6 +37,7 @@ "max": 0 }, "false_positive": { + "min": 0, "max": -1 } }, From 755044b4b44ac2ea72e147955b39c8a71d819485 Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 2 Dec 2024 12:16:55 +0100 Subject: [PATCH 03/86] Use logging module instead of print --- src/mlx/warnings/junit_checker.py | 5 +++-- src/mlx/warnings/polyspace_checker.py | 4 ++-- src/mlx/warnings/regex_checker.py | 9 +++++---- src/mlx/warnings/robot_checker.py | 3 ++- src/mlx/warnings/warnings.py | 26 ++++++++++++++++---------- src/mlx/warnings/warnings_checker.py | 13 ++----------- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/mlx/warnings/junit_checker.py b/src/mlx/warnings/junit_checker.py index fc7664f5..04b8b233 100644 --- a/src/mlx/warnings/junit_checker.py +++ b/src/mlx/warnings/junit_checker.py @@ -6,6 +6,7 @@ from xml.etree import ElementTree as etree from junitparser import Error, Failure, JUnitXml +import logging from .warnings_checker import WarningsChecker @@ -30,7 +31,7 @@ def check(self, content): suites.update_statistics() self.count += suites.failures + suites.errors - amount_to_exclude except etree.ParseError as err: - print(err) + logging.error(err) @staticmethod def prepare_tree(root_input): @@ -66,5 +67,5 @@ def _check_testcase(self, testcase): return 1 string = '{classname}.{testname}'.format(classname=testcase.classname, testname=testcase.name) self.counted_warnings.append('{}: {}'.format(string, testcase.result.message)) - self.print_when_verbose(string) + logging.info(string) return 0 diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index afd3ab9d..882f00ec 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -4,6 +4,7 @@ from io import TextIOWrapper import os from string import Template +import logging from .code_quality import Finding from .exceptions import WarningsConfigError @@ -244,8 +245,7 @@ def check(self, content): ''' if content[self.column_name].lower() == self.check_value: if content["status"].lower() in ["not a defect", "justified"]: - self.print_when_verbose("Excluded row {!r} because the status is 'Not a defect' or 'Justified'" - .format(content)) + logging.info("Excluded row {!r} because the status is 'Not a defect' or 'Justified'".format(content)) else: tab_sep_string = "\t".join(content.values()) if not self._is_excluded(tab_sep_string): diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 19fbbe77..87d68d9e 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -1,6 +1,7 @@ import os import re from string import Template +import logging from .code_quality import Finding from .exceptions import WarningsConfigError @@ -46,7 +47,7 @@ def check(self, content): continue self.count += 1 self.counted_warnings.append(match_string) - self.print_when_verbose(match_string) + logging.info(match_string) if self.cq_enabled: self.add_code_quality_finding(match) @@ -153,7 +154,7 @@ def check(self, content): checker.cq_default_path = self.cq_default_path checker.check(match) else: - print(f"WARNING: Unrecognized classification {match.group('classification')!r}") + logging.warning(f"Unrecognized classification {match.group('classification')!r}") def parse_config(self, config): """Process configuration @@ -173,7 +174,7 @@ def parse_config(self, config): if classification_key in self.checkers: self.checkers[classification_key].parse_config(checker_config) else: - print(f"WARNING: Unrecognized classification {classification!r}") + logging.warning(f"Unrecognized classification {classification!r}") class CoverityClassificationChecker(WarningsChecker): @@ -248,7 +249,7 @@ def check(self, content): if not self._is_excluded(match_string) and (content.group('curr') == content.group('max')): self.count += 1 self.counted_warnings.append(match_string) - self.print_when_verbose(match_string) + logging.info(match_string) if self.cq_enabled: self.add_code_quality_finding(content) diff --git a/src/mlx/warnings/robot_checker.py b/src/mlx/warnings/robot_checker.py index 85958820..ed311233 100644 --- a/src/mlx/warnings/robot_checker.py +++ b/src/mlx/warnings/robot_checker.py @@ -3,6 +3,7 @@ import sys from junitparser import Error, Failure +import logging from .junit_checker import JUnitChecker from .warnings_checker import WarningsChecker @@ -155,5 +156,5 @@ def check(self, content): """ super().check(content) if not self.is_valid_suite_name and self.check_suite_name: - print('ERROR: No suite with name {!r} found. Returning error code -1.'.format(self.name)) + logging.error('No suite with name {!r} found. Returning error code -1.'.format(self.name)) sys.exit(-1) diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index ae13fef2..15e19ad3 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -10,6 +10,7 @@ import sys from importlib.metadata import distribution from pathlib import Path +import logging from ruamel.yaml import YAML @@ -78,7 +79,7 @@ def activate_checker_name(self, name): self.activate_checker(checker) return checker else: - print("Checker %s does not exist" % name) + logging.error("Checker %s does not exist" % name) def get_checker(self, name): ''' Get checker by name @@ -100,7 +101,7 @@ def check(self, content): if self.printout: print(content) if not self.activated_checkers: - print("No checkers activated. Please use activate_checker function") + logging.error("No checkers activated. Please use activate_checker function") else: for checker in self.activated_checkers.values(): if checker.name == "polyspace": @@ -116,7 +117,7 @@ def check_logfile(self, file): content (_io.TextIOWrapper): The open file to parse ''' if not self.activated_checkers: - print("No checkers activated. Please use activate_checker function") + logging.error("No checkers activated. Please use activate_checker function") elif "polyspace" in self.activated_checkers: if len(self.activated_checkers) > 1: raise WarningsConfigError("Polyspace checker cannot be combined with other warnings checkers") @@ -213,7 +214,7 @@ def config_parser(self, config): if bool(checker_config['enabled']): self.activate_checker(checker) checker.parse_config(checker_config) - print("Config parsing for {name} completed".format(name=checker.name)) + logging.info("Config parsing for {name} completed".format(name=checker.name)) except KeyError as err: raise WarningsConfigError(f"Incomplete config. Missing: {err}") from err @@ -282,12 +283,17 @@ def warnings_wrapper(args): args = parser.parse_args(args) code_quality_enabled = bool(args.code_quality) + logging.basicConfig(format="%(levelname)s: %(message)s") + if args.verbose: + logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.INFO) + else: + logging.basicConfig(format="%(levelname)s: %(message)s") # Read config file if args.configfile is not None: checker_flags = args.sphinx or args.doxygen or args.junit or args.coverity or args.xmlrunner or args.robot warning_args = args.maxwarnings or args.minwarnings or args.exact_warnings if checker_flags or warning_args: - print("Configfile cannot be provided with other arguments") + logging.error("Configfile cannot be provided with other arguments") sys.exit(2) warnings = WarningsPlugin(verbose=args.verbose, config_file=args.configfile, cq_enabled=code_quality_enabled) else: @@ -310,7 +316,7 @@ def warnings_wrapper(args): }) if args.exact_warnings: if args.maxwarnings | args.minwarnings: - print("expected-warnings cannot be provided with maxwarnings or minwarnings") + logging.error("expected-warnings cannot be provided with maxwarnings or minwarnings") sys.exit(2) warnings.configure_maximum(args.exact_warnings) warnings.configure_minimum(args.exact_warnings) @@ -334,8 +340,8 @@ def warnings_wrapper(args): return retval else: if args.flags: - print(f"WARNING: Some keyword arguments have been ignored because they followed positional arguments: " - f"{' '.join(args.flags)!r}") + logging.warning(f"Some keyword arguments have been ignored because they followed positional arguments: " + f"{' '.join(args.flags)!r}") retval = warnings_logfile(warnings, args.logfile) if retval != 0: return retval @@ -386,7 +392,7 @@ def warnings_command(warnings, cmd): return proc.returncode except OSError as err: if err.errno == errno.ENOENT: - print("It seems like program " + str(cmd) + " is not installed.") + logging.error("It seems like program " + str(cmd) + " is not installed.") raise @@ -413,7 +419,7 @@ def warnings_logfile(warnings, log): with open(logfile, "r") as file: warnings.check_logfile(file) else: - print("FILE: %s does not exist" % file_wildcard) + logging.error("FILE: %s does not exist" % file_wildcard) return 1 return 0 diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index e4be5790..b741a7b7 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -5,6 +5,7 @@ import os import re from string import Template +import logging from .exceptions import WarningsConfigError @@ -176,15 +177,6 @@ def _return_error_code(self): .format(self, error_reason, error_code)) return error_code - def print_when_verbose(self, message): - ''' Prints message only when verbose mode is enabled. - - Args: - message (str): Message to conditionally print - ''' - if self.verbose: - print(message) - def parse_config(self, config): substitute_envvar(config, {'min', 'max'}) self.maximum = int(config['max']) @@ -208,8 +200,7 @@ def _is_excluded(self, content): ''' matching_exclude_pattern = self._search_patterns(content, self.exclude_patterns) if not self._search_patterns(content, self.include_patterns) and matching_exclude_pattern: - self.print_when_verbose("Excluded {!r} because of configured regex {!r}" - .format(content, matching_exclude_pattern)) + logging.info("Excluded {!r} because of configured regex {!r}".format(content, matching_exclude_pattern)) return True return False From fdba6f4ed429154586af33617cf2b802708c9d03 Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 2 Dec 2024 12:17:23 +0100 Subject: [PATCH 04/86] Update tests to catch logs instead of prints --- tests/test_config.py | 35 ++++++++++++---------------- tests/test_coverity.py | 13 +++++------ tests/test_doxygen.py | 27 ++++++++++----------- tests/test_integration.py | 49 +++++++++++---------------------------- tests/test_junit.py | 13 ++++------- tests/test_robot.py | 24 +++++++------------ tests/test_sphinx.py | 40 ++++++++++++++------------------ tests/test_warnings.py | 7 ++---- tests/test_xmlrunner.py | 17 ++++++-------- 9 files changed, 88 insertions(+), 137 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 6fef8791..a94963c0 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,7 +2,6 @@ from io import StringIO from pathlib import Path from unittest import TestCase -from unittest.mock import patch from mlx.warnings import (JUnitChecker, DoxyChecker, SphinxChecker, XMLRunnerChecker, RobotChecker, WarningsPlugin, WarningsConfigError) @@ -42,7 +41,7 @@ def test_configfile_parsing_missing_envvar(self): "Failed to find environment variable 'MAX_SPHINX_WARNINGS' for configuration value 'max'") def _helper_exclude(self, warnings): - with patch('sys.stdout', new=StringIO()) as verbose_output: + with self.assertLogs(level="INFO") as verbose_output: warnings.check('testfile.c:6: warning: group test: ignoring title "Some test functions" that does not match old title "Some freaky test functions"') self.assertEqual(warnings.return_count(), 0) warnings.check('') @@ -60,9 +59,9 @@ def _helper_exclude(self, warnings): warnings.check('ERROR [0.000s]: test_some_error_test (something.anything.somewhere)') self.assertEqual(warnings.return_count(), 1) excluded_toctree_warning = "Excluded {!r} because of configured regex {!r}".format(toctree_warning, "WARNING: toctree") - self.assertIn(excluded_toctree_warning, verbose_output.getvalue()) + self.assertIn(f"INFO:root:{excluded_toctree_warning}", verbose_output.output) warning_echo = "home/bljah/test/index.rst:5: WARNING: this warning should not get excluded" - self.assertIn(warning_echo, verbose_output.getvalue()) + self.assertIn(f"INFO:root:{warning_echo}", verbose_output.output) def test_configfile_parsing_exclude_json(self): warnings = WarningsPlugin(verbose=True, config_file=(TEST_IN_DIR / "config_example_exclude.json")) @@ -194,19 +193,17 @@ def test_partial_robot_config_parsing_exclude_regex(self): } warnings.config_parser(tmpjson) with open('tests/test_in/robot_double_fail.xml', 'r') as xmlfile: - with patch('sys.stdout', new=StringIO()) as verbose_output: + with self.assertLogs(level="INFO") as verbose_output: warnings.check(xmlfile.read()) count = warnings.return_count() self.assertEqual(count, 1) self.assertEqual(warnings.return_check_limits(), 0) self.assertEqual( - '\n'.join([ - r"Excluded 'Directory 'C:\\nonexistent' does not exist.' because of configured regex 'does not exist'", - "Suite One & Suite Two.Suite Two.Another test", - "Suite 'Suite One': 0 warnings found", - "Suite 'Suite Two': 1 warnings found", - ]) + '\n', - verbose_output.getvalue() + [ + r"INFO:root:Excluded 'Directory 'C:\\nonexistent' does not exist.' because of configured regex 'does not exist'", + "INFO:root:Suite One & Suite Two.Suite Two.Another test", + ], + verbose_output.output ) def test_partial_robot_config_empty_name(self): @@ -226,18 +223,16 @@ def test_partial_robot_config_empty_name(self): } warnings.config_parser(tmpjson) with open('tests/test_in/robot_double_fail.xml', 'r') as xmlfile: - with patch('sys.stdout', new=StringIO()) as verbose_output: + with self.assertLogs(level="INFO") as verbose_output: warnings.check(xmlfile.read()) count = warnings.return_count() self.assertEqual(count, 1) self.assertEqual(warnings.return_check_limits(), 0) - self.assertEqual( - '\n'.join([ - r"Excluded 'Directory 'C:\\nonexistent' does not exist.' because of configured regex 'does not exist'", - "Suite One & Suite Two.Suite Two.Another test", - "1 warnings found", - ]) + '\n', - verbose_output.getvalue() + self.assertEqual([ + r"INFO:root:Excluded 'Directory 'C:\\nonexistent' does not exist.' because of configured regex 'does not exist'", + "INFO:root:Suite One & Suite Two.Suite Two.Another test", + ], + verbose_output.output ) def test_partial_xmlrunner_config_parsing(self): diff --git a/tests/test_coverity.py b/tests/test_coverity.py index f43c4af3..0aa731c0 100644 --- a/tests/test_coverity.py +++ b/tests/test_coverity.py @@ -3,7 +3,6 @@ from io import StringIO from pathlib import Path from unittest import TestCase, mock -from unittest.mock import patch from mlx.warnings import WarningsPlugin, warnings_wrapper, Finding @@ -39,30 +38,30 @@ def test_no_warning_but_still_command_output(self): def test_single_warning(self): dut = '/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(dut, fake_out.getvalue()) + self.assertIn(f"INFO:root:{dut}", fake_out.output) def test_single_warning_count_one(self): dut1 = '/src/somefile.c:80: CID 113396 (#1 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' dut2 = '/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut1) self.warnings.check(dut2) self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(dut2, fake_out.getvalue()) + self.assertIn(f"INFO:root:{dut2}", fake_out.output) def test_single_warning_real_output(self): dut1 = '/src/somefile.c:80: CID 113396 (#1 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' dut2 = '/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' dut3 = 'src/something/src/somefile.c:82: 1. misra_violation: Essential type of the left hand operand "0U" (unsigned) is not the same as that of the right operand "1U"(signed).' - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut1) self.warnings.check(dut2) self.warnings.check(dut3) self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(dut2, fake_out.getvalue()) + self.assertIn(f"INFO:root:{dut2}", fake_out.output) def test_code_quality_without_config(self): filename = 'coverity_cq.json' diff --git a/tests/test_doxygen.py b/tests/test_doxygen.py index 95f60d11..e2848c4b 100644 --- a/tests/test_doxygen.py +++ b/tests/test_doxygen.py @@ -1,8 +1,5 @@ -from io import StringIO from unittest import TestCase -from unittest.mock import patch - from mlx.warnings import WarningsPlugin @@ -18,21 +15,21 @@ def test_no_warning(self): def test_single_warning(self): dut = 'testfile.c:6: warning: group test: ignoring title "Some test functions" that does not match old title "Some freaky test functions"' - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 1) - self.assertRegex(fake_out.getvalue(), dut) + self.assertIn(f"INFO:root:{dut}", fake_out.output) def test_single_warning_mixed(self): dut1 = 'This1 should not be treated as warning' dut2 = 'testfile.c:6: warning: group test: ignoring title "Some test functions" that does not match old title "Some freaky test functions"' dut3 = 'This should not be treated as warning2' - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut1) self.warnings.check(dut2) self.warnings.check(dut3) self.assertEqual(self.warnings.return_count(), 1) - self.assertRegex(fake_out.getvalue(), dut2) + self.assertIn(f"INFO:root:{dut2}", fake_out.output) def test_multiline(self): duterr1 = "testfile.c:6: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" @@ -41,11 +38,11 @@ def test_multiline(self): dut += duterr1 dut += "This should not be treated as warning2\n" dut += duterr2 - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 2) - self.assertRegex(fake_out.getvalue(), duterr1) - self.assertRegex(fake_out.getvalue(), duterr2) + self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) + self.assertIn(f"INFO:root:{duterr2.strip()}", fake_out.output) def test_git_warning(self): duterr1 = "testfile.c:6: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" @@ -54,21 +51,21 @@ def test_git_warning(self): dut += duterr1 dut += "This should not be treated as warning2\n" dut += duterr2 - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 2) - self.assertRegex(fake_out.getvalue(), duterr1) - self.assertRegex(fake_out.getvalue(), duterr2) + self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) + self.assertIn(f"INFO:root:{duterr2.strip()}", fake_out.output) def test_sphinx_deprecation_warning(self): duterr1 = "testfile.c:6: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" dut = "/usr/local/lib/python3.5/dist-packages/sphinx/application.py:402: RemovedInSphinx20Warning: app.info() "\ "is now deprecated. Use sphinx.util.logging instead. RemovedInSphinx20Warning)\n" dut += duterr1 - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 1) - self.assertRegex(fake_out.getvalue(), duterr1) + self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) def test_doxygen_warnings_txt(self): dut_file = 'tests/test_in/doxygen_warnings.txt' diff --git a/tests/test_integration.py b/tests/test_integration.py index f840e208..2d6c2163 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -183,20 +183,12 @@ def test_robot_default_name_arg(self): def test_robot_verbose(self): ''' If no suite name is configured, all suites must be taken into account ''' - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: retval = warnings_wrapper(['--verbose', '--robot', '--name', 'Suite Two', 'tests/test_in/robot_double_fail.xml']) - stdout_log = fake_out.getvalue() + stdout_log = fake_out.output self.assertEqual(1, retval) - self.assertEqual( - '\n'.join([ - "Suite One & Suite Two.Suite Two.Another test", - "Suite 'Suite Two': 1 warnings found", - "Counted failures for test suite 'Suite Two'.", - "Number of warnings (1) is higher than the maximum limit (0). Returning error code 1.", - ]) + '\n', - stdout_log - ) + self.assertIn("INFO:root:Suite One & Suite Two.Suite Two.Another test", stdout_log) def test_robot_config(self): os.environ['MIN_ROBOT_WARNINGS'] = '0' @@ -208,10 +200,8 @@ def test_robot_config(self): 'tests/test_in/robot_double_fail.xml', ]) stdout_log = fake_out.getvalue() - self.assertEqual( '\n'.join([ - "Config parsing for robot completed", "Suite 'Suite One': 1 warnings found", "2 warnings found", "Suite 'Suite Two': 1 warnings found", @@ -234,35 +224,24 @@ def test_robot_config(self): def test_robot_config_check_names(self): self.maxDiff = None - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: with self.assertRaises(SystemExit) as cm_err: warnings_wrapper(['--config', 'tests/test_in/config_example_robot_invalid_suite.json', 'tests/test_in/robot_double_fail.xml']) - stdout_log = fake_out.getvalue() - - self.assertEqual( - '\n'.join([ - "Config parsing for robot completed", - "ERROR: No suite with name 'b4d su1te name' found. Returning error code -1.", - ]) + '\n', - stdout_log - ) + stdout_log = fake_out.output + self.assertIn("ERROR:root:No suite with name 'b4d su1te name' found. Returning error code -1.", + stdout_log) self.assertEqual(cm_err.exception.code, -1) def test_robot_cli_check_name(self): self.maxDiff = None - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: with self.assertRaises(SystemExit) as cm_err: warnings_wrapper(['--verbose', '--robot', '--name', 'Inv4lid Name', 'tests/test_in/robot_double_fail.xml']) - stdout_log = fake_out.getvalue() + stdout_log = fake_out.output - self.assertEqual( - '\n'.join([ - "ERROR: No suite with name 'Inv4lid Name' found. Returning error code -1.", - ]) + '\n', - stdout_log - ) + self.assertIn("ERROR:root:No suite with name 'Inv4lid Name' found. Returning error code -1.", stdout_log) self.assertEqual(cm_err.exception.code, -1) def test_output_file_sphinx(self): @@ -369,15 +348,15 @@ def test_cq_description_format(self, path_cwd_mock): filename = 'code_quality_format.json' out_file = str(TEST_OUT_DIR / filename) ref_file = str(TEST_IN_DIR / filename) - with patch('sys.stdout', new=StringIO()) as fake_output: + with self.assertLogs(level="INFO") as fake_out: retval = warnings_wrapper([ '--code-quality', out_file, '--config', 'tests/test_in/config_cq_description_format.json', 'tests/test_in/mixed_warnings.txt', ]) - output = fake_output.getvalue().splitlines(keepends=False) - self.assertIn("WARNING: Unrecognized classification 'max'", output) - self.assertIn("WARNING: Unrecognized classification 'min'", output) + output = fake_out.output + self.assertIn("WARNING:root:Unrecognized classification 'max'", output) + self.assertIn("WARNING:root:Unrecognized classification 'min'", output) self.assertEqual(2, retval) self.assertTrue(filecmp.cmp(out_file, ref_file), '{} differs from {}'.format(out_file, ref_file)) diff --git a/tests/test_junit.py b/tests/test_junit.py index 7931f53c..21b08169 100644 --- a/tests/test_junit.py +++ b/tests/test_junit.py @@ -1,8 +1,5 @@ -from io import StringIO from unittest import TestCase -from unittest.mock import patch - from mlx.warnings import WarningsPlugin @@ -18,18 +15,18 @@ def test_no_warning(self): def test_single_warning(self): with open('tests/test_in/junit_single_fail.xml', 'r') as xmlfile: - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(xmlfile.read()) self.assertEqual(self.warnings.return_count(), 1) - self.assertRegex(fake_out.getvalue(), 'myfirstfai1ure') + self.assertIn("INFO:root:test_warn_plugin_single_fail.myfirstfai1ure", fake_out.output) def test_dual_warning(self): with open('tests/test_in/junit_double_fail.xml', 'r') as xmlfile: - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(xmlfile.read()) self.assertEqual(self.warnings.return_count(), 2) - self.assertRegex(fake_out.getvalue(), 'myfirstfai1ure') - self.assertRegex(fake_out.getvalue(), 'mysecondfai1ure') + self.assertIn("INFO:root:test_warn_plugin_double_fail.myfirstfai1ure", fake_out.output) + self.assertIn("INFO:root:test_warn_plugin_no_double_fail.mysecondfai1ure", fake_out.output) def test_invalid_xml(self): self.warnings.check('this is not xml') diff --git a/tests/test_robot.py b/tests/test_robot.py index dbca88f6..f4be5544 100644 --- a/tests/test_robot.py +++ b/tests/test_robot.py @@ -1,8 +1,5 @@ -from io import StringIO import unittest -from unittest.mock import patch - from mlx.warnings import RobotSuiteChecker, WarningsPlugin @@ -24,30 +21,27 @@ def test_no_warning(self): def test_single_warning(self): with open('tests/test_in/robot_single_fail.xml', 'r') as xmlfile: - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(xmlfile.read()) count = self.warnings.return_count() - stdout_log = fake_out.getvalue() + stdout_log = fake_out.output self.assertEqual(count, 1) - self.assertIn("Suite {!r}: 1 warnings found".format(self.suite1), stdout_log) - self.assertIn("Suite {!r}: 0 warnings found".format(self.suite2), stdout_log) + self.assertIn("INFO:root:Suite One & Suite Two.Suite One.First Test", stdout_log) def test_double_warning_and_verbosity(self): with open('tests/test_in/robot_double_fail.xml', 'r') as xmlfile: - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(xmlfile.read()) count = self.warnings.return_count() - stdout_log = fake_out.getvalue() + stdout_log = fake_out.output self.assertEqual(count, 2) self.assertEqual( - '\n'.join([ - "Suite One & Suite Two.Suite One.First Test", - "Suite One & Suite Two.Suite Two.Another test", - "Suite {!r}: 1 warnings found".format(self.suite1), - "Suite {!r}: 1 warnings found".format(self.suite2), - ]) + '\n', + [ + "INFO:root:Suite One & Suite Two.Suite One.First Test", + "INFO:root:Suite One & Suite Two.Suite Two.Another test", + ], stdout_log ) diff --git a/tests/test_sphinx.py b/tests/test_sphinx.py index 4a2065c2..50c0ad2f 100644 --- a/tests/test_sphinx.py +++ b/tests/test_sphinx.py @@ -1,8 +1,5 @@ -from io import StringIO from unittest import TestCase -from unittest.mock import patch - from mlx.warnings import WarningsPlugin @@ -17,10 +14,10 @@ def test_no_warning(self): def test_single_warning(self): dut = "/home/bljah/test/index.rst:5: WARNING: toctree contains reference to nonexisting document u'installation'" - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 1) - self.assertRegex(fake_out.getvalue(), dut) + self.assertIn(f"INFO:root:{dut}", fake_out.output) def test_warning_no_line_number(self): dut1 = "/home/bljah/test/index.rst:5: WARNING: toctree contains reference to nonexisting document u'installation'" @@ -28,29 +25,29 @@ def test_warning_no_line_number(self): dut3 = "/home/bljah/test/index.rst:: WARNING: toctree contains reference to nonexisting document u'installation'" dut4 = "/home/bljah/test/SRS.rst: WARNING: item non_existing_requirement is not defined" dut5 = "CRITICAL: Problems with \"include\" directive path:" - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut1) self.warnings.check(dut2) self.warnings.check(dut3) self.warnings.check(dut4) self.warnings.check(dut5) self.assertEqual(self.warnings.return_count(), 5) - self.assertRegex(fake_out.getvalue(), dut1) - self.assertRegex(fake_out.getvalue(), dut2) - self.assertRegex(fake_out.getvalue(), dut3) - self.assertRegex(fake_out.getvalue(), dut4) - self.assertRegex(fake_out.getvalue(), dut5) + self.assertIn(f"INFO:root:{dut1}", fake_out.output) + self.assertIn(f"INFO:root:{dut2}", fake_out.output) + self.assertIn(f"INFO:root:{dut3}", fake_out.output) + self.assertIn(f"INFO:root:{dut4}", fake_out.output) + self.assertIn(f"INFO:root:{dut5}", fake_out.output) def test_single_warning_mixed(self): dut1 = 'This1 should not be treated as warning' dut2 = "/home/bljah/test/index.rst:5: WARNING: toctree contains reference to nonexisting document u'installation'" dut3 = 'This should not be treated as warning2' - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut1) self.warnings.check(dut2) self.warnings.check(dut3) self.assertEqual(self.warnings.return_count(), 1) - self.assertRegex(fake_out.getvalue(), dut2) + self.assertIn(f"INFO:root:{dut2}", fake_out.output) def test_multiline(self): duterr1 = "/home/bljah/test/index.rst:5: WARNING: toctree contains reference to nonexisting document u'installation'\n" @@ -59,21 +56,20 @@ def test_multiline(self): dut += duterr1 dut += "This should not be treated as warning2\n" dut += duterr2 - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 2) - self.assertRegex(fake_out.getvalue(), duterr1) - self.assertRegex(fake_out.getvalue(), duterr2) + self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) + self.assertIn(f"INFO:root:{duterr2.strip()}", fake_out.output) def test_deprecation_warning(self): duterr1 = "/usr/local/lib/python3.5/dist-packages/sphinx/application.py:402: RemovedInSphinx20Warning: "\ "app.info() is now deprecated. Use sphinx.util.logging instead. RemovedInSphinx20Warning\n" dut = "This should not be treated as warning2\n" dut += duterr1 - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertNoLogs(level="INFO"): self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 0) - self.assertNotEqual(fake_out.getvalue(), duterr1) def test_deprecation_warning_included(self): self.warnings.get_checker('sphinx').include_sphinx_deprecation() @@ -81,14 +77,14 @@ def test_deprecation_warning_included(self): "app.info() is now deprecated. Use sphinx.util.logging instead. RemovedInSphinx20Warning\n" dut = "This1 should not be treated as warning\n" dut += duterr1 - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 1) - self.assertEqual(fake_out.getvalue(), duterr1) + self.assertEqual([f"INFO:root:{duterr1.strip()}"], fake_out.output) def test_warning_no_docname(self): duterr1 = "WARNING: List item 'CL-UNDEFINED_CL_ITEM' in merge/pull request 138 is not defined as a checklist-item.\n" - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(duterr1) self.assertEqual(self.warnings.return_count(), 1) - self.assertEqual(fake_out.getvalue(), duterr1) + self.assertEqual([f"INFO:root:{duterr1.strip()}"], fake_out.output) diff --git a/tests/test_warnings.py b/tests/test_warnings.py index ac2884b8..530e23c2 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -1,8 +1,5 @@ -from io import StringIO from unittest import TestCase -from unittest.mock import patch - from mlx.warnings import WarningsPlugin @@ -125,6 +122,6 @@ def test_all_warning(self): def test_non_existent_checker_name(self): warnings = WarningsPlugin() invalid_checker_name = 'non-existent' - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: warnings.activate_checker_name(invalid_checker_name) - self.assertIn("Checker {} does not exist".format(invalid_checker_name), fake_out.getvalue()) + self.assertIn("ERROR:root:Checker {} does not exist".format(invalid_checker_name), fake_out.output) diff --git a/tests/test_xmlrunner.py b/tests/test_xmlrunner.py index 619e6ed0..a6476041 100644 --- a/tests/test_xmlrunner.py +++ b/tests/test_xmlrunner.py @@ -1,8 +1,5 @@ -from io import StringIO from unittest import TestCase -from unittest.mock import patch - from mlx.warnings import WarningsPlugin @@ -18,21 +15,21 @@ def test_no_warning(self): def test_single_warning(self): dut = 'ERROR [0.000s]: test_some_error_test (something.anything.somewhere)' - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(dut, fake_out.getvalue()) + self.assertIn(f"INFO:root:{dut}", fake_out.output) def test_single_warning_mixed(self): dut1 = 'This1 should not be treated as warning' dut2 = 'ERROR [0.000s]: test_some_error_test (something.anything.somewhere)' dut3 = 'This should not be treated as warning2' - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut1) self.warnings.check(dut2) self.warnings.check(dut3) self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(dut2, fake_out.getvalue()) + self.assertIn(f"INFO:root:{dut2}", fake_out.output) def test_multiline(self): duterr1 = "ERROR [0.000s]: test_some_error_test (something.anything.somewhere) \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" @@ -41,8 +38,8 @@ def test_multiline(self): dut += duterr1 dut += "This should not be treated as warning2\n" dut += duterr2 - with patch('sys.stdout', new=StringIO()) as fake_out: + with self.assertLogs(level="INFO") as fake_out: self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 2) - self.assertIn(duterr1, fake_out.getvalue()) - self.assertIn(duterr2, fake_out.getvalue()) + self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) + self.assertIn(f"INFO:root:{duterr2.strip()}", fake_out.output) From 18b450217d8a7f18c960176259a27a8e1123c630 Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 2 Dec 2024 13:01:35 +0100 Subject: [PATCH 05/86] Fix line endings to LF --- tests/test_coverity.py | 176 ++++++++++++++++++++-------------------- tests/test_doxygen.py | 148 ++++++++++++++++----------------- tests/test_xmlrunner.py | 90 ++++++++++---------- 3 files changed, 207 insertions(+), 207 deletions(-) diff --git a/tests/test_coverity.py b/tests/test_coverity.py index 0aa731c0..cd7093e5 100644 --- a/tests/test_coverity.py +++ b/tests/test_coverity.py @@ -1,88 +1,88 @@ -import filecmp -import os -from io import StringIO -from pathlib import Path -from unittest import TestCase, mock - -from mlx.warnings import WarningsPlugin, warnings_wrapper, Finding - -TEST_IN_DIR = Path(__file__).parent / 'test_in' -TEST_OUT_DIR = Path(__file__).parent / 'test_out' - - -def ordered(obj): - if isinstance(obj, dict): - return sorted((k, ordered(v)) for k, v in obj.items()) - if isinstance(obj, list): - return sorted(ordered(x) for x in obj) - else: - return obj - - -@mock.patch.dict(os.environ, {"MIN_COV_WARNINGS": "1", "MAX_COV_WARNINGS": "2"}) -class TestCoverityWarnings(TestCase): - def setUp(self): - Finding.fingerprints = {} - self.warnings = WarningsPlugin(verbose=True) - self.warnings.activate_checker_name('coverity') - - def test_no_warning_normal_text(self): - dut = 'This should not be treated as warning' - self.warnings.check(dut) - self.assertEqual(self.warnings.return_count(), 0) - - def test_no_warning_but_still_command_output(self): - dut = 'src/something/src/somefile.c:82: 1. misra_violation: Essential type of the left hand operand "0U" (unsigned) is not the same as that of the right operand "1U"(signed).' - self.warnings.check(dut) - self.assertEqual(self.warnings.return_count(), 0) - - def test_single_warning(self): - dut = '/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' - with self.assertLogs(level="INFO") as fake_out: - self.warnings.check(dut) - self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(f"INFO:root:{dut}", fake_out.output) - - def test_single_warning_count_one(self): - dut1 = '/src/somefile.c:80: CID 113396 (#1 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' - dut2 = '/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' - with self.assertLogs(level="INFO") as fake_out: - self.warnings.check(dut1) - self.warnings.check(dut2) - self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(f"INFO:root:{dut2}", fake_out.output) - - def test_single_warning_real_output(self): - dut1 = '/src/somefile.c:80: CID 113396 (#1 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' - dut2 = '/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' - dut3 = 'src/something/src/somefile.c:82: 1. misra_violation: Essential type of the left hand operand "0U" (unsigned) is not the same as that of the right operand "1U"(signed).' - with self.assertLogs(level="INFO") as fake_out: - self.warnings.check(dut1) - self.warnings.check(dut2) - self.warnings.check(dut3) - self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(f"INFO:root:{dut2}", fake_out.output) - - def test_code_quality_without_config(self): - filename = 'coverity_cq.json' - out_file = str(TEST_OUT_DIR / filename) - ref_file = str(TEST_IN_DIR / filename) - retval = warnings_wrapper([ - '--coverity', - '--code-quality', out_file, - str(TEST_IN_DIR / 'defects.txt'), - ]) - self.assertEqual(8, retval) - self.assertTrue(filecmp.cmp(out_file, ref_file)) - - def test_code_quality_with_config(self): - filename = 'coverity_cq.json' - out_file = str(TEST_OUT_DIR / filename) - ref_file = str(TEST_IN_DIR / filename) - retval = warnings_wrapper([ - '--code-quality', out_file, - '--config', str(TEST_IN_DIR / 'config_example_coverity.yml'), - str(TEST_IN_DIR / 'defects.txt'), - ]) - self.assertEqual(3, retval) - self.assertTrue(filecmp.cmp(out_file, ref_file)) +import filecmp +import os +from io import StringIO +from pathlib import Path +from unittest import TestCase, mock + +from mlx.warnings import WarningsPlugin, warnings_wrapper, Finding + +TEST_IN_DIR = Path(__file__).parent / 'test_in' +TEST_OUT_DIR = Path(__file__).parent / 'test_out' + + +def ordered(obj): + if isinstance(obj, dict): + return sorted((k, ordered(v)) for k, v in obj.items()) + if isinstance(obj, list): + return sorted(ordered(x) for x in obj) + else: + return obj + + +@mock.patch.dict(os.environ, {"MIN_COV_WARNINGS": "1", "MAX_COV_WARNINGS": "2"}) +class TestCoverityWarnings(TestCase): + def setUp(self): + Finding.fingerprints = {} + self.warnings = WarningsPlugin(verbose=True) + self.warnings.activate_checker_name('coverity') + + def test_no_warning_normal_text(self): + dut = 'This should not be treated as warning' + self.warnings.check(dut) + self.assertEqual(self.warnings.return_count(), 0) + + def test_no_warning_but_still_command_output(self): + dut = 'src/something/src/somefile.c:82: 1. misra_violation: Essential type of the left hand operand "0U" (unsigned) is not the same as that of the right operand "1U"(signed).' + self.warnings.check(dut) + self.assertEqual(self.warnings.return_count(), 0) + + def test_single_warning(self): + dut = '/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' + with self.assertLogs(level="INFO") as fake_out: + self.warnings.check(dut) + self.assertEqual(self.warnings.return_count(), 1) + self.assertIn(f"INFO:root:{dut}", fake_out.output) + + def test_single_warning_count_one(self): + dut1 = '/src/somefile.c:80: CID 113396 (#1 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' + dut2 = '/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' + with self.assertLogs(level="INFO") as fake_out: + self.warnings.check(dut1) + self.warnings.check(dut2) + self.assertEqual(self.warnings.return_count(), 1) + self.assertIn(f"INFO:root:{dut2}", fake_out.output) + + def test_single_warning_real_output(self): + dut1 = '/src/somefile.c:80: CID 113396 (#1 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' + dut2 = '/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' + dut3 = 'src/something/src/somefile.c:82: 1. misra_violation: Essential type of the left hand operand "0U" (unsigned) is not the same as that of the right operand "1U"(signed).' + with self.assertLogs(level="INFO") as fake_out: + self.warnings.check(dut1) + self.warnings.check(dut2) + self.warnings.check(dut3) + self.assertEqual(self.warnings.return_count(), 1) + self.assertIn(f"INFO:root:{dut2}", fake_out.output) + + def test_code_quality_without_config(self): + filename = 'coverity_cq.json' + out_file = str(TEST_OUT_DIR / filename) + ref_file = str(TEST_IN_DIR / filename) + retval = warnings_wrapper([ + '--coverity', + '--code-quality', out_file, + str(TEST_IN_DIR / 'defects.txt'), + ]) + self.assertEqual(8, retval) + self.assertTrue(filecmp.cmp(out_file, ref_file)) + + def test_code_quality_with_config(self): + filename = 'coverity_cq.json' + out_file = str(TEST_OUT_DIR / filename) + ref_file = str(TEST_IN_DIR / filename) + retval = warnings_wrapper([ + '--code-quality', out_file, + '--config', str(TEST_IN_DIR / 'config_example_coverity.yml'), + str(TEST_IN_DIR / 'defects.txt'), + ]) + self.assertEqual(3, retval) + self.assertTrue(filecmp.cmp(out_file, ref_file)) diff --git a/tests/test_doxygen.py b/tests/test_doxygen.py index e2848c4b..fb0ef259 100644 --- a/tests/test_doxygen.py +++ b/tests/test_doxygen.py @@ -1,74 +1,74 @@ -from unittest import TestCase - -from mlx.warnings import WarningsPlugin - - -class TestDoxygenWarnings(TestCase): - def setUp(self): - self.warnings = WarningsPlugin(verbose=True) - self.warnings.activate_checker_name('doxygen') - - def test_no_warning(self): - dut = 'This should not be treated as warning' - self.warnings.check(dut) - self.assertEqual(self.warnings.return_count(), 0) - - def test_single_warning(self): - dut = 'testfile.c:6: warning: group test: ignoring title "Some test functions" that does not match old title "Some freaky test functions"' - with self.assertLogs(level="INFO") as fake_out: - self.warnings.check(dut) - self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(f"INFO:root:{dut}", fake_out.output) - - def test_single_warning_mixed(self): - dut1 = 'This1 should not be treated as warning' - dut2 = 'testfile.c:6: warning: group test: ignoring title "Some test functions" that does not match old title "Some freaky test functions"' - dut3 = 'This should not be treated as warning2' - with self.assertLogs(level="INFO") as fake_out: - self.warnings.check(dut1) - self.warnings.check(dut2) - self.warnings.check(dut3) - self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(f"INFO:root:{dut2}", fake_out.output) - - def test_multiline(self): - duterr1 = "testfile.c:6: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" - duterr2 = "testfile.c:8: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" - dut = "This1 should not be treated as warning\n" - dut += duterr1 - dut += "This should not be treated as warning2\n" - dut += duterr2 - with self.assertLogs(level="INFO") as fake_out: - self.warnings.check(dut) - self.assertEqual(self.warnings.return_count(), 2) - self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) - self.assertIn(f"INFO:root:{duterr2.strip()}", fake_out.output) - - def test_git_warning(self): - duterr1 = "testfile.c:6: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" - duterr2 = "testfile.c:8: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" - dut = "warning: notes ref refs/notes/review is invalid should not be treated as warning\n" - dut += duterr1 - dut += "This should not be treated as warning2\n" - dut += duterr2 - with self.assertLogs(level="INFO") as fake_out: - self.warnings.check(dut) - self.assertEqual(self.warnings.return_count(), 2) - self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) - self.assertIn(f"INFO:root:{duterr2.strip()}", fake_out.output) - - def test_sphinx_deprecation_warning(self): - duterr1 = "testfile.c:6: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" - dut = "/usr/local/lib/python3.5/dist-packages/sphinx/application.py:402: RemovedInSphinx20Warning: app.info() "\ - "is now deprecated. Use sphinx.util.logging instead. RemovedInSphinx20Warning)\n" - dut += duterr1 - with self.assertLogs(level="INFO") as fake_out: - self.warnings.check(dut) - self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) - - def test_doxygen_warnings_txt(self): - dut_file = 'tests/test_in/doxygen_warnings.txt' - with open(dut_file, 'r') as open_file: - self.warnings.check(open_file.read()) - self.assertEqual(self.warnings.return_count(), 22) +from unittest import TestCase + +from mlx.warnings import WarningsPlugin + + +class TestDoxygenWarnings(TestCase): + def setUp(self): + self.warnings = WarningsPlugin(verbose=True) + self.warnings.activate_checker_name('doxygen') + + def test_no_warning(self): + dut = 'This should not be treated as warning' + self.warnings.check(dut) + self.assertEqual(self.warnings.return_count(), 0) + + def test_single_warning(self): + dut = 'testfile.c:6: warning: group test: ignoring title "Some test functions" that does not match old title "Some freaky test functions"' + with self.assertLogs(level="INFO") as fake_out: + self.warnings.check(dut) + self.assertEqual(self.warnings.return_count(), 1) + self.assertIn(f"INFO:root:{dut}", fake_out.output) + + def test_single_warning_mixed(self): + dut1 = 'This1 should not be treated as warning' + dut2 = 'testfile.c:6: warning: group test: ignoring title "Some test functions" that does not match old title "Some freaky test functions"' + dut3 = 'This should not be treated as warning2' + with self.assertLogs(level="INFO") as fake_out: + self.warnings.check(dut1) + self.warnings.check(dut2) + self.warnings.check(dut3) + self.assertEqual(self.warnings.return_count(), 1) + self.assertIn(f"INFO:root:{dut2}", fake_out.output) + + def test_multiline(self): + duterr1 = "testfile.c:6: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" + duterr2 = "testfile.c:8: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" + dut = "This1 should not be treated as warning\n" + dut += duterr1 + dut += "This should not be treated as warning2\n" + dut += duterr2 + with self.assertLogs(level="INFO") as fake_out: + self.warnings.check(dut) + self.assertEqual(self.warnings.return_count(), 2) + self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) + self.assertIn(f"INFO:root:{duterr2.strip()}", fake_out.output) + + def test_git_warning(self): + duterr1 = "testfile.c:6: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" + duterr2 = "testfile.c:8: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" + dut = "warning: notes ref refs/notes/review is invalid should not be treated as warning\n" + dut += duterr1 + dut += "This should not be treated as warning2\n" + dut += duterr2 + with self.assertLogs(level="INFO") as fake_out: + self.warnings.check(dut) + self.assertEqual(self.warnings.return_count(), 2) + self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) + self.assertIn(f"INFO:root:{duterr2.strip()}", fake_out.output) + + def test_sphinx_deprecation_warning(self): + duterr1 = "testfile.c:6: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" + dut = "/usr/local/lib/python3.5/dist-packages/sphinx/application.py:402: RemovedInSphinx20Warning: app.info() "\ + "is now deprecated. Use sphinx.util.logging instead. RemovedInSphinx20Warning)\n" + dut += duterr1 + with self.assertLogs(level="INFO") as fake_out: + self.warnings.check(dut) + self.assertEqual(self.warnings.return_count(), 1) + self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) + + def test_doxygen_warnings_txt(self): + dut_file = 'tests/test_in/doxygen_warnings.txt' + with open(dut_file, 'r') as open_file: + self.warnings.check(open_file.read()) + self.assertEqual(self.warnings.return_count(), 22) diff --git a/tests/test_xmlrunner.py b/tests/test_xmlrunner.py index a6476041..892f1f24 100644 --- a/tests/test_xmlrunner.py +++ b/tests/test_xmlrunner.py @@ -1,45 +1,45 @@ -from unittest import TestCase - -from mlx.warnings import WarningsPlugin - - -class TestXMLRunnerWarnings(TestCase): - def setUp(self): - self.warnings = WarningsPlugin(verbose=True) - self.warnings.activate_checker_name('xmlrunner') - - def test_no_warning(self): - dut = 'This should not be treated as warning' - self.warnings.check(dut) - self.assertEqual(self.warnings.return_count(), 0) - - def test_single_warning(self): - dut = 'ERROR [0.000s]: test_some_error_test (something.anything.somewhere)' - with self.assertLogs(level="INFO") as fake_out: - self.warnings.check(dut) - self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(f"INFO:root:{dut}", fake_out.output) - - def test_single_warning_mixed(self): - dut1 = 'This1 should not be treated as warning' - dut2 = 'ERROR [0.000s]: test_some_error_test (something.anything.somewhere)' - dut3 = 'This should not be treated as warning2' - with self.assertLogs(level="INFO") as fake_out: - self.warnings.check(dut1) - self.warnings.check(dut2) - self.warnings.check(dut3) - self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(f"INFO:root:{dut2}", fake_out.output) - - def test_multiline(self): - duterr1 = "ERROR [0.000s]: test_some_error_test (something.anything.somewhere) \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" - duterr2 = "ERROR [0.000s]: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" - dut = "This1 should not be treated as warning\n" - dut += duterr1 - dut += "This should not be treated as warning2\n" - dut += duterr2 - with self.assertLogs(level="INFO") as fake_out: - self.warnings.check(dut) - self.assertEqual(self.warnings.return_count(), 2) - self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) - self.assertIn(f"INFO:root:{duterr2.strip()}", fake_out.output) +from unittest import TestCase + +from mlx.warnings import WarningsPlugin + + +class TestXMLRunnerWarnings(TestCase): + def setUp(self): + self.warnings = WarningsPlugin(verbose=True) + self.warnings.activate_checker_name('xmlrunner') + + def test_no_warning(self): + dut = 'This should not be treated as warning' + self.warnings.check(dut) + self.assertEqual(self.warnings.return_count(), 0) + + def test_single_warning(self): + dut = 'ERROR [0.000s]: test_some_error_test (something.anything.somewhere)' + with self.assertLogs(level="INFO") as fake_out: + self.warnings.check(dut) + self.assertEqual(self.warnings.return_count(), 1) + self.assertIn(f"INFO:root:{dut}", fake_out.output) + + def test_single_warning_mixed(self): + dut1 = 'This1 should not be treated as warning' + dut2 = 'ERROR [0.000s]: test_some_error_test (something.anything.somewhere)' + dut3 = 'This should not be treated as warning2' + with self.assertLogs(level="INFO") as fake_out: + self.warnings.check(dut1) + self.warnings.check(dut2) + self.warnings.check(dut3) + self.assertEqual(self.warnings.return_count(), 1) + self.assertIn(f"INFO:root:{dut2}", fake_out.output) + + def test_multiline(self): + duterr1 = "ERROR [0.000s]: test_some_error_test (something.anything.somewhere) \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" + duterr2 = "ERROR [0.000s]: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" + dut = "This1 should not be treated as warning\n" + dut += duterr1 + dut += "This should not be treated as warning2\n" + dut += duterr2 + with self.assertLogs(level="INFO") as fake_out: + self.warnings.check(dut) + self.assertEqual(self.warnings.return_count(), 2) + self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) + self.assertIn(f"INFO:root:{duterr2.strip()}", fake_out.output) From 63f9fbdf670906361009ef08fe277c23188065d7 Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 2 Dec 2024 13:07:40 +0100 Subject: [PATCH 06/86] Order imports; Delete unused imports --- tests/test_config.py | 12 +++++++++--- tests/test_coverity.py | 3 +-- tests/test_integration.py | 3 +-- tests/test_polyspace.py | 12 ++++++++---- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index a94963c0..3bf6f38b 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,10 +1,16 @@ import os -from io import StringIO from pathlib import Path from unittest import TestCase -from mlx.warnings import (JUnitChecker, DoxyChecker, SphinxChecker, XMLRunnerChecker, RobotChecker, WarningsPlugin, - WarningsConfigError) +from mlx.warnings import ( + DoxyChecker, + JUnitChecker, + RobotChecker, + SphinxChecker, + WarningsConfigError, + WarningsPlugin, + XMLRunnerChecker, +) TEST_IN_DIR = Path(__file__).parent / 'test_in' diff --git a/tests/test_coverity.py b/tests/test_coverity.py index cd7093e5..f8939d47 100644 --- a/tests/test_coverity.py +++ b/tests/test_coverity.py @@ -1,10 +1,9 @@ import filecmp import os -from io import StringIO from pathlib import Path from unittest import TestCase, mock -from mlx.warnings import WarningsPlugin, warnings_wrapper, Finding +from mlx.warnings import Finding, WarningsPlugin, warnings_wrapper TEST_IN_DIR = Path(__file__).parent / 'test_in' TEST_OUT_DIR = Path(__file__).parent / 'test_out' diff --git a/tests/test_integration.py b/tests/test_integration.py index 2d6c2163..f85dafba 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -3,10 +3,9 @@ from io import StringIO from pathlib import Path from unittest import TestCase - from unittest.mock import patch -from mlx.warnings import exceptions, warnings_wrapper, WarningsConfigError, Finding +from mlx.warnings import Finding, WarningsConfigError, exceptions, warnings_wrapper TEST_IN_DIR = Path(__file__).parent / 'test_in' TEST_OUT_DIR = Path(__file__).parent / 'test_out' diff --git a/tests/test_polyspace.py b/tests/test_polyspace.py index 9779ba51..a1170c34 100644 --- a/tests/test_polyspace.py +++ b/tests/test_polyspace.py @@ -1,12 +1,16 @@ +import filecmp import os -from io import StringIO import unittest +from io import StringIO from pathlib import Path -import filecmp - from unittest.mock import patch -from mlx.warnings import PolyspaceFamilyChecker, WarningsPlugin, warnings_wrapper, Finding +from mlx.warnings import ( + Finding, + PolyspaceFamilyChecker, + WarningsPlugin, + warnings_wrapper, +) TEST_IN_DIR = Path(__file__).parent / 'test_in' TEST_OUT_DIR = Path(__file__).parent / 'test_out' From 9baf00d8d4eff6135573e5cfeacfc2a147564503 Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 2 Dec 2024 13:09:35 +0100 Subject: [PATCH 07/86] Delete default mode 'r' when opening a file --- tests/test_config.py | 20 ++++++++++---------- tests/test_doxygen.py | 2 +- tests/test_junit.py | 6 +++--- tests/test_robot.py | 12 ++++++------ 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 3bf6f38b..dcca69e7 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -97,7 +97,7 @@ def test_partial_sphinx_config_parsing(self): warnings.config_parser(tmpjson) warnings.check('testfile.c:6: warning: group test: ignoring title "Some test functions" that does not match old title "Some freaky test functions"') self.assertEqual(warnings.return_count(), 0) - with open('tests/test_in/junit_single_fail.xml', 'r') as xmlfile: + with open('tests/test_in/junit_single_fail.xml') as xmlfile: warnings.check(xmlfile.read()) self.assertEqual(warnings.return_count(), 0) warnings.check('ERROR [0.000s]: test_some_error_test (something.anything.somewhere)') @@ -116,7 +116,7 @@ def test_partial_doxygen_config_parsing(self): } warnings.config_parser(tmpjson) - with open('tests/test_in/junit_single_fail.xml', 'r') as xmlfile: + with open('tests/test_in/junit_single_fail.xml') as xmlfile: warnings.check(xmlfile.read()) self.assertEqual(warnings.return_count(), 0) warnings.check("/home/bljah/test/index.rst:5: WARNING: toctree contains reference to nonexisting document u'installation'") @@ -143,7 +143,7 @@ def test_partial_junit_config_parsing(self): self.assertEqual(warnings.return_count(), 0) warnings.check('ERROR [0.000s]: test_some_error_test (something.anything.somewhere)') self.assertEqual(warnings.return_count(), 0) - with open('tests/test_in/junit_single_fail.xml', 'r') as xmlfile: + with open('tests/test_in/junit_single_fail.xml') as xmlfile: warnings.check(xmlfile.read()) self.assertEqual(warnings.return_count(), 1) @@ -172,7 +172,7 @@ def test_partial_junit_config_parsing_exclude_regex(self): } } warnings.config_parser(tmpjson) - with open('tests/test_in/junit_single_fail.xml', 'r') as xmlfile: + with open('tests/test_in/junit_single_fail.xml') as xmlfile: warnings.check(xmlfile.read()) self.assertEqual(warnings.return_count(), 0) @@ -198,7 +198,7 @@ def test_partial_robot_config_parsing_exclude_regex(self): } } warnings.config_parser(tmpjson) - with open('tests/test_in/robot_double_fail.xml', 'r') as xmlfile: + with open('tests/test_in/robot_double_fail.xml') as xmlfile: with self.assertLogs(level="INFO") as verbose_output: warnings.check(xmlfile.read()) count = warnings.return_count() @@ -228,7 +228,7 @@ def test_partial_robot_config_empty_name(self): } } warnings.config_parser(tmpjson) - with open('tests/test_in/robot_double_fail.xml', 'r') as xmlfile: + with open('tests/test_in/robot_double_fail.xml') as xmlfile: with self.assertLogs(level="INFO") as verbose_output: warnings.check(xmlfile.read()) count = warnings.return_count() @@ -252,7 +252,7 @@ def test_partial_xmlrunner_config_parsing(self): } warnings.config_parser(tmpjson) - with open('tests/test_in/junit_single_fail.xml', 'r') as xmlfile: + with open('tests/test_in/junit_single_fail.xml') as xmlfile: warnings.check(xmlfile.read()) self.assertEqual(warnings.return_count(), 0) warnings.check("/home/bljah/test/index.rst:5: WARNING: toctree contains reference to nonexisting document u'installation'") @@ -282,7 +282,7 @@ def test_doxy_junit_options_config_parsing(self): self.assertEqual(warnings.return_count(), 0) warnings.check('testfile.c:6: warning: group test: ignoring title "Some test functions" that does not match old title "Some freaky test functions"') self.assertEqual(warnings.return_count(), 1) - with open('tests/test_in/junit_single_fail.xml', 'r') as xmlfile: + with open('tests/test_in/junit_single_fail.xml') as xmlfile: warnings.check(xmlfile.read()) self.assertEqual(warnings.return_count(), 2) @@ -302,14 +302,14 @@ def test_sphinx_doxy_config_parsing(self): } warnings.config_parser(tmpjson) - with open('tests/test_in/junit_single_fail.xml', 'r') as xmlfile: + with open('tests/test_in/junit_single_fail.xml') as xmlfile: warnings.check(xmlfile.read()) self.assertEqual(warnings.return_count(), 0) warnings.check('testfile.c:6: warning: group test: ignoring title "Some test functions" that does not match old title "Some freaky test functions"') self.assertEqual(warnings.return_count(), 1) warnings.check("/home/bljah/test/index.rst:5: WARNING: toctree contains reference to nonexisting document u'installation'") self.assertEqual(warnings.return_count(), 2) - with open('tests/test_in/junit_single_fail.xml', 'r') as xmlfile: + with open('tests/test_in/junit_single_fail.xml') as xmlfile: warnings.check(xmlfile.read()) self.assertEqual(warnings.return_count(), 2) warnings.check("/home/bljah/test/index.rst:5: WARNING: toctree contains reference to nonexisting document u'installation'") diff --git a/tests/test_doxygen.py b/tests/test_doxygen.py index fb0ef259..3d73f0f3 100644 --- a/tests/test_doxygen.py +++ b/tests/test_doxygen.py @@ -69,6 +69,6 @@ def test_sphinx_deprecation_warning(self): def test_doxygen_warnings_txt(self): dut_file = 'tests/test_in/doxygen_warnings.txt' - with open(dut_file, 'r') as open_file: + with open(dut_file) as open_file: self.warnings.check(open_file.read()) self.assertEqual(self.warnings.return_count(), 22) diff --git a/tests/test_junit.py b/tests/test_junit.py index 21b08169..e53c154e 100644 --- a/tests/test_junit.py +++ b/tests/test_junit.py @@ -9,19 +9,19 @@ def setUp(self): self.warnings.activate_checker_name('junit') def test_no_warning(self): - with open('tests/test_in/junit_no_fail.xml', 'r') as xmlfile: + with open('tests/test_in/junit_no_fail.xml') as xmlfile: self.warnings.check(xmlfile.read()) self.assertEqual(self.warnings.return_count(), 0) def test_single_warning(self): - with open('tests/test_in/junit_single_fail.xml', 'r') as xmlfile: + with open('tests/test_in/junit_single_fail.xml') as xmlfile: with self.assertLogs(level="INFO") as fake_out: self.warnings.check(xmlfile.read()) self.assertEqual(self.warnings.return_count(), 1) self.assertIn("INFO:root:test_warn_plugin_single_fail.myfirstfai1ure", fake_out.output) def test_dual_warning(self): - with open('tests/test_in/junit_double_fail.xml', 'r') as xmlfile: + with open('tests/test_in/junit_double_fail.xml') as xmlfile: with self.assertLogs(level="INFO") as fake_out: self.warnings.check(xmlfile.read()) self.assertEqual(self.warnings.return_count(), 2) diff --git a/tests/test_robot.py b/tests/test_robot.py index f4be5544..7608d121 100644 --- a/tests/test_robot.py +++ b/tests/test_robot.py @@ -15,12 +15,12 @@ def setUp(self): ] def test_no_warning(self): - with open('tests/test_in/junit_no_fail.xml', 'r') as xmlfile: + with open('tests/test_in/junit_no_fail.xml') as xmlfile: self.warnings.check(xmlfile.read()) self.assertEqual(self.warnings.return_count(), 0) def test_single_warning(self): - with open('tests/test_in/robot_single_fail.xml', 'r') as xmlfile: + with open('tests/test_in/robot_single_fail.xml') as xmlfile: with self.assertLogs(level="INFO") as fake_out: self.warnings.check(xmlfile.read()) count = self.warnings.return_count() @@ -30,7 +30,7 @@ def test_single_warning(self): self.assertIn("INFO:root:Suite One & Suite Two.Suite One.First Test", stdout_log) def test_double_warning_and_verbosity(self): - with open('tests/test_in/robot_double_fail.xml', 'r') as xmlfile: + with open('tests/test_in/robot_double_fail.xml') as xmlfile: with self.assertLogs(level="INFO") as fake_out: self.warnings.check(xmlfile.read()) count = self.warnings.return_count() @@ -54,7 +54,7 @@ def test_testsuites_root(self): RobotSuiteChecker('test_warn_plugin_double_fail'), RobotSuiteChecker('test_warn_plugin_no_double_fail'), ] - with open('tests/test_in/junit_double_fail.xml', 'r') as xmlfile: + with open('tests/test_in/junit_double_fail.xml') as xmlfile: self.warnings.check(xmlfile.read()) count = self.warnings.return_count() self.assertEqual(count, 2) @@ -63,7 +63,7 @@ def test_check_suite_name(self): self.dut.checkers = [ RobotSuiteChecker('nonexistent_suite_name', check_suite_name=True), ] - with open('tests/test_in/robot_double_fail.xml', 'r') as xmlfile: + with open('tests/test_in/robot_double_fail.xml') as xmlfile: with self.assertRaises(SystemExit) as c_m: self.warnings.check(xmlfile.read()) self.assertEqual(c_m.exception.code, -1) @@ -72,7 +72,7 @@ def test_robot_version_5(self): self.dut.checkers = [ RobotSuiteChecker('Empty Flash Product Id', check_suite_name=True), ] - with open('tests/test_in/robot_version_5.xml', 'r') as xmlfile: + with open('tests/test_in/robot_version_5.xml') as xmlfile: self.warnings.check(xmlfile.read()) count = self.warnings.return_count() self.assertEqual(count, 6) From 6598d8b22f8363427534240e40a6440daf8cb2dd Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 2 Dec 2024 13:09:59 +0100 Subject: [PATCH 08/86] Use f-string --- tests/test_integration.py | 10 +++++----- tests/test_warnings.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index f85dafba..36665fef 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -262,7 +262,7 @@ def test_output_file_robot_basic(self): 'tests/test_in/robot_double_fail.xml', ]) self.assertEqual(2, retval) - self.assertTrue(filecmp.cmp(out_file, ref_file), '{} differs from {}'.format(out_file, ref_file)) + self.assertTrue(filecmp.cmp(out_file, ref_file), f'{out_file} differs from {ref_file}') def test_output_file_robot_config(self): os.environ['MIN_ROBOT_WARNINGS'] = '0' @@ -276,7 +276,7 @@ def test_output_file_robot_config(self): 'tests/test_in/robot_double_fail.xml', ]) self.assertEqual(2, retval) - self.assertTrue(filecmp.cmp(out_file, ref_file), '{} differs from {}'.format(out_file, ref_file)) + self.assertTrue(filecmp.cmp(out_file, ref_file), f'{out_file} differs from {ref_file}') for var in ('MIN_ROBOT_WARNINGS', 'MAX_ROBOT_WARNINGS'): if var in os.environ: del os.environ[var] @@ -291,7 +291,7 @@ def test_output_file_junit(self): 'tests/test_in/junit_double_fail.xml', ]) self.assertEqual(2, retval) - self.assertTrue(filecmp.cmp(out_file, ref_file), '{} differs from {}'.format(out_file, ref_file)) + self.assertTrue(filecmp.cmp(out_file, ref_file), f'{out_file} differs from {ref_file}') @patch('pathlib.Path.cwd') def test_code_quality(self, path_cwd_mock): @@ -307,7 +307,7 @@ def test_code_quality(self, path_cwd_mock): 'tests/test_in/mixed_warnings.txt', ]) self.assertEqual(2, retval) - self.assertTrue(filecmp.cmp(out_file, ref_file), '{} differs from {}'.format(out_file, ref_file)) + self.assertTrue(filecmp.cmp(out_file, ref_file), f'{out_file} differs from {ref_file}') def test_code_quality_abspath_failure(self): os.environ['MIN_SPHINX_WARNINGS'] = '0' @@ -357,7 +357,7 @@ def test_cq_description_format(self, path_cwd_mock): self.assertIn("WARNING:root:Unrecognized classification 'max'", output) self.assertIn("WARNING:root:Unrecognized classification 'min'", output) self.assertEqual(2, retval) - self.assertTrue(filecmp.cmp(out_file, ref_file), '{} differs from {}'.format(out_file, ref_file)) + self.assertTrue(filecmp.cmp(out_file, ref_file), f'{out_file} differs from {ref_file}') @patch('pathlib.Path.cwd') def test_polyspace_error(self, path_cwd_mock): diff --git a/tests/test_warnings.py b/tests/test_warnings.py index 530e23c2..13dd0af6 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -124,4 +124,4 @@ def test_non_existent_checker_name(self): invalid_checker_name = 'non-existent' with self.assertLogs(level="INFO") as fake_out: warnings.activate_checker_name(invalid_checker_name) - self.assertIn("ERROR:root:Checker {} does not exist".format(invalid_checker_name), fake_out.output) + self.assertIn(f"ERROR:root:Checker {invalid_checker_name} does not exist", fake_out.output) From 04df6a5234822806ab9a0b49666b36a2cb2ca56d Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 2 Dec 2024 13:10:21 +0100 Subject: [PATCH 09/86] Delete line end of file --- tests/test_integration.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 36665fef..d3a2cabd 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -368,4 +368,3 @@ def test_polyspace_error(self, path_cwd_mock): 'tests/test_in/mixed_warnings.txt', ]) self.assertEqual(str(context.exception), 'Polyspace checker cannot be combined with other warnings checkers') - From d83f085ad34e6b533c66c7450ba8a9916c1f6aab Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 2 Dec 2024 13:13:20 +0100 Subject: [PATCH 10/86] Reorder imports of scripts and init file --- src/mlx/warnings/__init__.py | 2 +- src/mlx/warnings/code_quality.py | 1 - src/mlx/warnings/junit_checker.py | 3 ++- src/mlx/warnings/polyspace_checker.py | 4 ++-- src/mlx/warnings/regex_checker.py | 2 +- src/mlx/warnings/robot_checker.py | 2 +- src/mlx/warnings/warnings.py | 4 ++-- src/mlx/warnings/warnings_checker.py | 4 ++-- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/mlx/warnings/__init__.py b/src/mlx/warnings/__init__.py index 041b104e..2aa126d3 100644 --- a/src/mlx/warnings/__init__.py +++ b/src/mlx/warnings/__init__.py @@ -23,8 +23,8 @@ from .code_quality import Finding from .exceptions import WarningsConfigError from .junit_checker import JUnitChecker +from .polyspace_checker import PolyspaceChecker, PolyspaceFamilyChecker from .regex_checker import CoverityChecker, DoxyChecker, SphinxChecker, XMLRunnerChecker from .robot_checker import RobotChecker, RobotSuiteChecker -from .polyspace_checker import PolyspaceChecker, PolyspaceFamilyChecker from .warnings import WarningsPlugin, warnings_wrapper from .warnings_checker import WarningsChecker diff --git a/src/mlx/warnings/code_quality.py b/src/mlx/warnings/code_quality.py index dd0ef296..cb8ddad7 100644 --- a/src/mlx/warnings/code_quality.py +++ b/src/mlx/warnings/code_quality.py @@ -1,4 +1,3 @@ - import hashlib from pathlib import Path diff --git a/src/mlx/warnings/junit_checker.py b/src/mlx/warnings/junit_checker.py index 04b8b233..3b9d9992 100644 --- a/src/mlx/warnings/junit_checker.py +++ b/src/mlx/warnings/junit_checker.py @@ -5,9 +5,10 @@ except ImportError: from xml.etree import ElementTree as etree -from junitparser import Error, Failure, JUnitXml import logging +from junitparser import Error, Failure, JUnitXml + from .warnings_checker import WarningsChecker diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index 882f00ec..f3fd63b4 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -1,10 +1,10 @@ # SPDX-License-Identifier: Apache-2.0 import csv -from io import TextIOWrapper +import logging import os +from io import TextIOWrapper from string import Template -import logging from .code_quality import Finding from .exceptions import WarningsConfigError diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 87d68d9e..3ef43d0f 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -1,7 +1,7 @@ +import logging import os import re from string import Template -import logging from .code_quality import Finding from .exceptions import WarningsConfigError diff --git a/src/mlx/warnings/robot_checker.py b/src/mlx/warnings/robot_checker.py index ed311233..77cce731 100644 --- a/src/mlx/warnings/robot_checker.py +++ b/src/mlx/warnings/robot_checker.py @@ -1,9 +1,9 @@ # SPDX-License-Identifier: Apache-2.0 +import logging import sys from junitparser import Error, Failure -import logging from .junit_checker import JUnitChecker from .warnings_checker import WarningsChecker diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index 15e19ad3..cc8488a5 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -6,19 +6,19 @@ import errno import glob import json +import logging import subprocess import sys from importlib.metadata import distribution from pathlib import Path -import logging from ruamel.yaml import YAML from .exceptions import WarningsConfigError from .junit_checker import JUnitChecker +from .polyspace_checker import PolyspaceChecker from .regex_checker import CoverityChecker, DoxyChecker, SphinxChecker, XMLRunnerChecker from .robot_checker import RobotChecker -from .polyspace_checker import PolyspaceChecker __version__ = distribution('mlx.warnings').version diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index b741a7b7..880929cf 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -1,11 +1,11 @@ # SPDX-License-Identifier: Apache-2.0 import abc -from math import inf +import logging import os import re +from math import inf from string import Template -import logging from .exceptions import WarningsConfigError From cb47ff769f16976f1a8474be3c588bb7cea1e71b Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 2 Dec 2024 13:14:15 +0100 Subject: [PATCH 11/86] Delete newlines --- src/mlx/warnings/__main__.py | 1 - src/mlx/warnings/code_quality.py | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/mlx/warnings/__main__.py b/src/mlx/warnings/__main__.py index 96650370..07e4af95 100644 --- a/src/mlx/warnings/__main__.py +++ b/src/mlx/warnings/__main__.py @@ -7,6 +7,5 @@ """ from mlx.warnings.warnings import main - if __name__ == '__main__': main() diff --git a/src/mlx/warnings/code_quality.py b/src/mlx/warnings/code_quality.py index cb8ddad7..80bb3af2 100644 --- a/src/mlx/warnings/code_quality.py +++ b/src/mlx/warnings/code_quality.py @@ -113,6 +113,3 @@ def to_dict(self): }, "fingerprint": self.fingerprint } - - - From ffcb69e12963ed9fc5ed399fd3136c1ed9077f68 Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 2 Dec 2024 13:15:18 +0100 Subject: [PATCH 12/86] Delete default usage of utf-8 --- src/mlx/warnings/__main__.py | 1 - src/mlx/warnings/code_quality.py | 2 +- src/mlx/warnings/warnings.py | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/mlx/warnings/__main__.py b/src/mlx/warnings/__main__.py index 07e4af95..f865668e 100644 --- a/src/mlx/warnings/__main__.py +++ b/src/mlx/warnings/__main__.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# -*- coding: utf-8 -*- # SPDX-License-Identifier: Apache-2.0 """ diff --git a/src/mlx/warnings/code_quality.py b/src/mlx/warnings/code_quality.py index 80bb3af2..b9bd7b85 100644 --- a/src/mlx/warnings/code_quality.py +++ b/src/mlx/warnings/code_quality.py @@ -33,7 +33,7 @@ def fingerprint(self): hashable_string = f"{self.severity}{self.path}{self.description}" new_hash = hashlib.md5(str(hashable_string).encode('utf-8')).hexdigest() while new_hash in self.fingerprints and self.fingerprints[new_hash] != self: - new_hash = hashlib.md5(f"{hashable_string}{new_hash}".encode('utf-8')).hexdigest() + new_hash = hashlib.md5(f"{hashable_string}{new_hash}".encode()).hexdigest() type(self).fingerprints[new_hash] = self return new_hash diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index cc8488a5..f2833b3a 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # SPDX-License-Identifier: Apache-2.0 import argparse @@ -42,7 +41,7 @@ def __init__(self, verbose=False, config_file=None, cq_enabled=False): RobotChecker(self.verbose), PolyspaceChecker(self.verbose)] if config_file: - with open(config_file, 'r', encoding='utf-8') as open_file: + with open(config_file, encoding='utf-8') as open_file: if config_file.suffix.lower().startswith('.y'): config = YAML().load(open_file) else: From a41079a33c3ddec6034d8cf9f9c81d9f10e6c841 Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 2 Dec 2024 13:18:07 +0100 Subject: [PATCH 13/86] Use f-string --- src/mlx/warnings/junit_checker.py | 4 ++-- src/mlx/warnings/polyspace_checker.py | 4 ++-- src/mlx/warnings/robot_checker.py | 8 ++++---- src/mlx/warnings/warnings.py | 2 +- src/mlx/warnings/warnings_checker.py | 6 +++--- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/mlx/warnings/junit_checker.py b/src/mlx/warnings/junit_checker.py index 3b9d9992..194c6234 100644 --- a/src/mlx/warnings/junit_checker.py +++ b/src/mlx/warnings/junit_checker.py @@ -66,7 +66,7 @@ def _check_testcase(self, testcase): if isinstance(testcase.result, (Failure, Error)): if self._is_excluded(testcase.result.message): return 1 - string = '{classname}.{testname}'.format(classname=testcase.classname, testname=testcase.name) - self.counted_warnings.append('{}: {}'.format(string, testcase.result.message)) + string = f'{testcase.classname}.{testcase.name}' + self.counted_warnings.append(f'{string}: {testcase.result.message}') logging.info(string) return 0 diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index f3fd63b4..42d19bd3 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -209,7 +209,7 @@ def return_count(self): Returns: int: Number of warnings found ''' - print("{} warnings found for {!r}: {!r}".format(self.count, self.column_name, self.check_value)) + print(f"{self.count} warnings found for {self.column_name!r}: {self.check_value!r}") return self.count def add_code_quality_finding(self, row): @@ -245,7 +245,7 @@ def check(self, content): ''' if content[self.column_name].lower() == self.check_value: if content["status"].lower() in ["not a defect", "justified"]: - logging.info("Excluded row {!r} because the status is 'Not a defect' or 'Justified'".format(content)) + logging.info(f"Excluded row {content!r} because the status is 'Not a defect' or 'Justified'") else: tab_sep_string = "\t".join(content.values()) if not self._is_excluded(tab_sep_string): diff --git a/src/mlx/warnings/robot_checker.py b/src/mlx/warnings/robot_checker.py index 77cce731..8284d606 100644 --- a/src/mlx/warnings/robot_checker.py +++ b/src/mlx/warnings/robot_checker.py @@ -85,7 +85,7 @@ def return_check_limits(self): count = 0 for checker in self.checkers: if checker.name: - print('Counted failures for test suite {!r}.'.format(checker.name)) + print(f'Counted failures for test suite {checker.name!r}.') else: print('Counted failures for all test suites.') count += checker.return_check_limits() @@ -120,9 +120,9 @@ def return_count(self): Returns: int: Number of warnings found ''' - msg = "{} warnings found".format(self.count) + msg = f"{self.count} warnings found" if self.name: - msg = "Suite {!r}: {}".format(self.name, msg) + msg = f"Suite {self.name!r}: {msg}" print(msg) return self.count @@ -156,5 +156,5 @@ def check(self, content): """ super().check(content) if not self.is_valid_suite_name and self.check_suite_name: - logging.error('No suite with name {!r} found. Returning error code -1.'.format(self.name)) + logging.error(f'No suite with name {self.name!r} found. Returning error code -1.') sys.exit(-1) diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index f2833b3a..8f208c07 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -213,7 +213,7 @@ def config_parser(self, config): if bool(checker_config['enabled']): self.activate_checker(checker) checker.parse_config(checker_config) - logging.info("Config parsing for {name} completed".format(name=checker.name)) + logging.info(f"Config parsing for {checker.name} completed") except KeyError as err: raise WarningsConfigError(f"Incomplete config. Missing: {err}") from err diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index 880929cf..42fd3215 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -166,9 +166,9 @@ def _return_error_code(self): int: The count of warnings (or 1 in case of a count of 0 warnings) ''' if self.count > self._maximum: - error_reason = "higher than the maximum limit ({0._maximum})".format(self) + error_reason = f"higher than the maximum limit ({self._maximum})" else: - error_reason = "lower than the minimum limit ({0._minimum})".format(self) + error_reason = f"lower than the minimum limit ({self._minimum})" error_code = self.count if error_code == 0: @@ -200,7 +200,7 @@ def _is_excluded(self, content): ''' matching_exclude_pattern = self._search_patterns(content, self.exclude_patterns) if not self._search_patterns(content, self.include_patterns) and matching_exclude_pattern: - logging.info("Excluded {!r} because of configured regex {!r}".format(content, matching_exclude_pattern)) + logging.info(f"Excluded {content!r} because of configured regex {matching_exclude_pattern!r}") return True return False From fa12bf5b61ec9a35433bec3a2729571b54d39561 Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 2 Dec 2024 13:18:53 +0100 Subject: [PATCH 14/86] Delete default mode 'r' when opening a file --- src/mlx/warnings/warnings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index 8f208c07..e04fb4f8 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -415,7 +415,7 @@ def warnings_logfile(warnings, log): for file_wildcard in log: if glob.glob(file_wildcard): for logfile in glob.glob(file_wildcard): - with open(logfile, "r") as file: + with open(logfile) as file: warnings.check_logfile(file) else: logging.error("FILE: %s does not exist" % file_wildcard) From e14b35fde344dfdac20cbc85ba25139e4582a8e1 Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 2 Dec 2024 13:20:43 +0100 Subject: [PATCH 15/86] Use f-string for --version --- src/mlx/warnings/warnings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index e04fb4f8..80e86971 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -275,7 +275,7 @@ def warnings_wrapper(args): help='Treat program arguments as command to execute to obtain data') parser.add_argument('--ignore-retval', dest='ignore', action='store_true', help='Ignore return value of the executed command') - parser.add_argument('--version', action='version', version='%(prog)s {version}'.format(version=__version__)) + parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}') parser.add_argument('logfile', nargs='+', help='Logfile (or command) that might contain warnings') parser.add_argument('flags', nargs=argparse.REMAINDER, help='Possible not-used flags from above are considered as command flags') From b5bdf27ae51a1944d54616af509ab3bc492a4451 Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 2 Dec 2024 17:58:30 +0100 Subject: [PATCH 16/86] Reduce the amount of prints by combining them --- src/mlx/warnings/polyspace_checker.py | 19 ++++---------- src/mlx/warnings/regex_checker.py | 15 +++-------- src/mlx/warnings/robot_checker.py | 37 +++++++++++---------------- src/mlx/warnings/warnings_checker.py | 26 +++++++++++-------- 4 files changed, 40 insertions(+), 57 deletions(-) diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index 42d19bd3..6a1720f1 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -116,11 +116,10 @@ def return_check_limits(self): ''' count = 0 for checker in self.checkers: - print( - 'Counted failures for family {!r} \'{}\': \'{}\'' - .format(checker.family_value, checker.column_name, checker.check_value) - ) - count += checker.return_check_limits() + padded_string = [f"{string:<30}" for string in [f"family {checker.family_value!r} ", + f"{checker.column_name}: {checker.check_value} "]] + count += checker.return_check_limits("".join(padded_string)) + print(f"Returning error code {count}.") return count def parse_config(self, config): @@ -173,6 +172,7 @@ def parse_config(self, config): class PolyspaceFamilyChecker(WarningsChecker): + name = 'polyspace' code_quality_severity = { "impact: high": "critical", "impact: medium": "major", @@ -203,15 +203,6 @@ def cq_description_template(self): def cq_description_template(self, template_obj): self._cq_description_template = template_obj - def return_count(self): - ''' Getter function for the amount of warnings found - - Returns: - int: Number of warnings found - ''' - print(f"{self.count} warnings found for {self.column_name!r}: {self.check_value!r}") - return self.count - def add_code_quality_finding(self, row): '''Add code quality finding diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 3ef43d0f..abbaf0d8 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -131,9 +131,9 @@ def return_check_limits(self): ''' count = 0 for checker in self.checkers.values(): - print(f"Counted failures for classification {checker.classification!r}") - count += checker.return_check_limits() - print(f"total warnings = {count}") + padded_string = [f"{string:<20}" for string in [f"{checker.classification}: "]] + count += checker.return_check_limits("".join(padded_string)) + print(f"Returning error code {count}.") return count def check(self, content): @@ -178,6 +178,7 @@ def parse_config(self, config): class CoverityClassificationChecker(WarningsChecker): + name = 'coverity' SEVERITY_MAP = { 'false positive': 'info', 'intentional': 'info', @@ -204,14 +205,6 @@ def cq_description_template(self): def cq_description_template(self, template_obj): self._cq_description_template = template_obj - def return_count(self): - ''' Getter function for the amount of warnings found - - Returns: - int: Number of warnings found - ''' - return self.count - def add_code_quality_finding(self, match): '''Add code quality finding diff --git a/src/mlx/warnings/robot_checker.py b/src/mlx/warnings/robot_checker.py index 8284d606..ef846c5c 100644 --- a/src/mlx/warnings/robot_checker.py +++ b/src/mlx/warnings/robot_checker.py @@ -84,11 +84,14 @@ def return_check_limits(self): ''' count = 0 for checker in self.checkers: - if checker.name: - print(f'Counted failures for test suite {checker.name!r}.') + if checker.suite_name: + string = f"test suite {checker.suite_name!r}" + padded_string = f"{string:<30}" + count += checker.return_check_limits(padded_string) else: - print('Counted failures for all test suites.') - count += checker.return_check_limits() + string = "all test suites" + count += checker.return_check_limits(f"{string:<30}") + print(f"Returning error code {self.count}.") return count def parse_config(self, config): @@ -102,7 +105,9 @@ def parse_config(self, config): class RobotSuiteChecker(JUnitChecker): - def __init__(self, name, check_suite_name=False, **kwargs): + name = 'robot' + + def __init__(self, suite_name, check_suite_name=False, **kwargs): ''' Constructor Args: @@ -110,22 +115,10 @@ def __init__(self, name, check_suite_name=False, **kwargs): check_suite_name (bool): Whether to raise an error when no test in suite with given name is found ''' super().__init__(**kwargs) - self.name = name + self.suite_name = suite_name self.check_suite_name = check_suite_name self.is_valid_suite_name = False - def return_count(self): - ''' Getter function for the amount of warnings found - - Returns: - int: Number of warnings found - ''' - msg = f"{self.count} warnings found" - if self.name: - msg = f"Suite {self.name!r}: {msg}" - print(msg) - return self.count - def _check_testcase(self, testcase): """ Handles the check of a test case element by checking if the result is a failure/error. @@ -138,10 +131,10 @@ def _check_testcase(self, testcase): Returns: int: 1 if a failure/error is to be subtracted from the final count, 0 otherwise """ - if testcase.classname.endswith(self.name): + if testcase.classname.endswith(self.suite_name): self.is_valid_suite_name = True return super()._check_testcase(testcase) - return int(self.name and isinstance(testcase.result, (Failure, Error))) + return int(self.suite_name and isinstance(testcase.result, (Failure, Error))) def check(self, content): """ Function for counting the number of JUnit failures in a specific text @@ -152,9 +145,9 @@ def check(self, content): content (str): The content to parse Raises: - SystemExit: No suite with name ``self.name`` found. Returning error code -1. + SystemExit: No suite with name ``self.suite_name`` found. Returning error code -1. """ super().check(content) if not self.is_valid_suite_name and self.check_suite_name: - logging.error(f'No suite with name {self.name!r} found. Returning error code -1.') + logging.error(f'No suite with name {self.suite_name!r} found. Returning error code -1.') sys.exit(-1) diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index 42fd3215..d05b488d 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -139,29 +139,33 @@ def return_count(self): Returns: int: Number of warnings found ''' - print("{0.count} {0.name} warnings found".format(self)) return self.count - def return_check_limits(self): + def return_check_limits(self, extra=""): ''' Function for checking whether the warning count is within the configured limits + Args: + extra (str): The extra string that is placed after the checker name. For example, the classification. + Returns: int: 0 if the amount of warnings is within limits, the count of warnings otherwise (or 1 in case of a count of 0 warnings) ''' if self.count > self._maximum or self.count < self._minimum: - return self._return_error_code() + return self._return_error_code(extra) elif self._minimum == self._maximum and self.count == self._maximum: - print("Number of warnings ({0.count}) is exactly as expected. Well done." - .format(self)) + print(f"{self.name + ':':<10} {extra}number of warnings ({self.count}) is exactly as expected. Well done.") else: - print("Number of warnings ({0.count}) is between limits {0._minimum} and {0._maximum}. Well done." - .format(self)) + print(f"{self.name + ':':<10} {extra}number of warnings ({self.count}) is between limits {self._minimum} " + f"and {self._maximum}. Well done.") return 0 - def _return_error_code(self): + def _return_error_code(self, extra=""): ''' Function for determining the return code and message on failure + Args: + extra (str): The extra string that is placed after the checker name. For example, the classification. + Returns: int: The count of warnings (or 1 in case of a count of 0 warnings) ''' @@ -173,8 +177,10 @@ def _return_error_code(self): error_code = self.count if error_code == 0: error_code = 1 - print("Number of warnings ({0.count}) is {1}. Returning error code {2}." - .format(self, error_reason, error_code)) + string_to_print = f"{self.name + ':':<10} {extra}number of warnings ({self.count}) is {error_reason}." + if self.name not in ["polyspace", "coverity", "robot"]: + string_to_print += f" Returning error code {error_code}." + print(string_to_print) return error_code def parse_config(self, config): From 8b75a73598e6712f179b0e405e4cba0bbf830a93 Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 2 Dec 2024 17:59:02 +0100 Subject: [PATCH 17/86] Update tests to match the reduced prints --- tests/test_integration.py | 17 +++++------------ tests/test_polyspace.py | 36 +++++++++++++++++++----------------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index d3a2cabd..6de4b62f 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -201,18 +201,11 @@ def test_robot_config(self): stdout_log = fake_out.getvalue() self.assertEqual( '\n'.join([ - "Suite 'Suite One': 1 warnings found", - "2 warnings found", - "Suite 'Suite Two': 1 warnings found", - "Suite 'b4d su1te name': 0 warnings found", - "Counted failures for test suite 'Suite One'.", - "Number of warnings (1) is between limits 0 and 1. Well done.", - "Counted failures for all test suites.", - "Number of warnings (2) is higher than the maximum limit (1). Returning error code 2.", - "Counted failures for test suite 'Suite Two'.", - "Number of warnings (1) is between limits 1 and 2. Well done.", - "Counted failures for test suite 'b4d su1te name'.", - "Number of warnings (0) is exactly as expected. Well done.", + "robot: test suite 'Suite One' number of warnings (1) is between limits 0 and 1. Well done.", + "robot: all test suites number of warnings (2) is higher than the maximum limit (1).", + "robot: test suite 'Suite Two' number of warnings (1) is between limits 1 and 2. Well done.", + "robot: test suite 'b4d su1te name' number of warnings (0) is exactly as expected. Well done.", + "Returning error code 4." ]) + '\n', stdout_log ) diff --git a/tests/test_polyspace.py b/tests/test_polyspace.py index a1170c34..9cd36e88 100644 --- a/tests/test_polyspace.py +++ b/tests/test_polyspace.py @@ -30,17 +30,16 @@ def test_code_prover_tsv_file(self): with open(TEST_IN_DIR / 'polyspace.tsv', newline="") as file: with patch('sys.stdout', new=StringIO()) as fake_out: self.warnings.check_logfile(file) - count = self.warnings.return_count() + count = self.warnings.return_check_limits() stdout_log = fake_out.getvalue() - - count_sum = 0 - for checker in self.dut.checkers: - count_sum += checker.count - self.assertIn( - f"{checker.count} warnings found for '{checker.column_name}': '{checker.check_value}'", - stdout_log - ) - self.assertEqual(count, count_sum) + self.assertEqual( + '\n'.join([ + "polyspace: family 'run-time check' color: red number of warnings (0) is exactly as expected. Well done.", + "polyspace: family 'run-time check' color: orange number of warnings (19) is higher than the maximum limit (0).", + "Returning error code 19." + ]) + '\n', + stdout_log + ) self.assertEqual(count, 19) @@ -58,14 +57,17 @@ def test_bug_finder_tsv_file(self): with open(TEST_IN_DIR / 'polyspace.tsv', newline="") as file: with patch('sys.stdout', new=StringIO()) as fake_out: self.warnings.check_logfile(file) - count = self.warnings.return_count() + count = self.warnings.return_check_limits() stdout_log = fake_out.getvalue() - - for checker in self.dut.checkers: - self.assertIn( - f"{checker.count} warnings found for '{checker.column_name}': '{checker.check_value}'", - stdout_log - ) + self.assertEqual( + '\n'.join([ + "polyspace: family 'defect' information: impact: high number of warnings (42) is higher than the maximum limit (0).", + "polyspace: family 'defect' information: impact: medium number of warnings (9) is higher than the maximum limit (0).", + "polyspace: family 'defect' information: impact: low number of warnings (4) is higher than the maximum limit (0).", + "Returning error code 55." + ]) + '\n', + stdout_log + ) self.assertEqual(count, 55) From d304b96043f7722880fc7b82cbe472964cb37703 Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 3 Dec 2024 15:20:13 +0100 Subject: [PATCH 18/86] Fix test; assertNoLogs added in version 3.10 --- tests/test_sphinx.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_sphinx.py b/tests/test_sphinx.py index 50c0ad2f..06f5570b 100644 --- a/tests/test_sphinx.py +++ b/tests/test_sphinx.py @@ -67,8 +67,10 @@ def test_deprecation_warning(self): "app.info() is now deprecated. Use sphinx.util.logging instead. RemovedInSphinx20Warning\n" dut = "This should not be treated as warning2\n" dut += duterr1 - with self.assertNoLogs(level="INFO"): - self.warnings.check(dut) + with self.assertRaises(AssertionError) as err: + with self.assertLogs(level="INFO"): + self.warnings.check(dut) + self.assertEqual(str(err.exception), "no logs of level INFO or higher triggered on root") self.assertEqual(self.warnings.return_count(), 0) def test_deprecation_warning_included(self): From 16e8dad73fd75ebf133d4571f3013e9955a71e77 Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 3 Dec 2024 15:28:29 +0100 Subject: [PATCH 19/86] Use f-string --- src/mlx/warnings/warnings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index 80e86971..f7a68a5b 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -78,7 +78,7 @@ def activate_checker_name(self, name): self.activate_checker(checker) return checker else: - logging.error("Checker %s does not exist" % name) + logging.error(f"Checker {name} does not exist") def get_checker(self, name): ''' Get checker by name @@ -418,7 +418,7 @@ def warnings_logfile(warnings, log): with open(logfile) as file: warnings.check_logfile(file) else: - logging.error("FILE: %s does not exist" % file_wildcard) + logging.error(f"FILE: {file_wildcard} does not exist") return 1 return 0 From d13853bb8195b9e5a915b670a1a0f15153191d4d Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 3 Dec 2024 15:37:21 +0100 Subject: [PATCH 20/86] Update design --- docs/design.rst | 242 +++++++++++++++++++++++++----------------------- 1 file changed, 125 insertions(+), 117 deletions(-) diff --git a/docs/design.rst b/docs/design.rst index f43b9e03..344c3136 100644 --- a/docs/design.rst +++ b/docs/design.rst @@ -10,157 +10,165 @@ Software Design Class Diagram ============= -.. uml generated by `pyreverse mlx.warnings --filter ALL -o plantuml` +.. uml generated by `pyreverse mlx.warnings --filter PUB_ONLY -o plantuml` .. uml:: @startuml classes set namespaceSeparator none class "CoverityChecker" as mlx.warnings.regex_checker.CoverityChecker { - CLASSIFICATION : str - count - name : str - pattern - check(content) + checkers : dict + count : int + counted_warnings + cq_default_path + cq_description_template + cq_findings + name : str + pattern + check(content) + parse_config(config) + return_check_limits() + return_count() + } + class "CoverityClassificationChecker" as mlx.warnings.regex_checker.CoverityClassificationChecker { + SEVERITY_MAP : dict + classification + count + cq_description_template + name : str + add_code_quality_finding(match) + check(content) } class "DoxyChecker" as mlx.warnings.regex_checker.DoxyChecker { - name : str - pattern + name : str + pattern + } + class "Finding" as mlx.warnings.code_quality.Finding { + column + description + fingerprint + fingerprints : dict + line + path + severity + to_dict() } class "JUnitChecker" as mlx.warnings.junit_checker.JUnitChecker { - count - name : str - _check_testcase(testcase) - check(content) - prepare_tree(root_input) + count + name : str + check(content) + prepare_tree(root_input) } class "PolyspaceChecker" as mlx.warnings.polyspace_checker.PolyspaceChecker { - _cq_description_template : Template - checkers : list - count : int - counted_warnings - cq_default_path - cq_description_template - maximum - minimum - name : str - __init__(verbose) - check(content) - parse_config(config) - return_check_limits() - return_count() + checkers : list + count : int + counted_warnings + cq_default_path + cq_description_template + cq_findings + maximum + minimum + name : str + check(content) + parse_config(config) + return_check_limits() + return_count() } class "PolyspaceFamilyChecker" as mlx.warnings.polyspace_checker.PolyspaceFamilyChecker { - _cq_description_template - check_value - code_quality_severity : dict - column_name - count - cq_description_template - cq_findings : list - family_value - __init__(family_value, column_name, check_value) - add_code_quality_finding(row) - check(content) - return_count() + check_value + code_quality_severity : dict + column_name + count + cq_description_template + family_value + name : str + add_code_quality_finding(row) + check(content) } class "RegexChecker" as mlx.warnings.regex_checker.RegexChecker { - SEVERITY_MAP : dict - count - name : str - pattern : NoneType - add_code_quality_finding(match) - check(content) + SEVERITY_MAP : dict + count + name : str + pattern : NoneType + add_code_quality_finding(match) + check(content) } class "RobotChecker" as mlx.warnings.robot_checker.RobotChecker { - checkers : list - count : int - counted_warnings - maximum - minimum - name : str - check(content) - parse_config(config) - return_check_limits() - return_count() + checkers : list + count : int + counted_warnings + maximum + minimum + name : str + check(content) + parse_config(config) + return_check_limits() + return_count() } class "RobotSuiteChecker" as mlx.warnings.robot_checker.RobotSuiteChecker { - check_suite_name : bool - is_valid_suite_name : bool - name - __init__(name, check_suite_name) - _check_testcase(testcase) - check(content) - return_count() + check_suite_name : bool + is_valid_suite_name : bool + name : str + suite_name + check(content) } class "SphinxChecker" as mlx.warnings.regex_checker.SphinxChecker { - name : str - pattern - sphinx_deprecation_regex : str - sphinx_deprecation_regex_in_match : str - include_sphinx_deprecation() + name : str + pattern + sphinx_deprecation_regex : str + sphinx_deprecation_regex_in_match : str + include_sphinx_deprecation() } class "WarningsChecker" as mlx.warnings.warnings_checker.WarningsChecker { - _counted_warnings : list - _cq_description_template : Template - _maximum : int - _minimum : int - count : int - counted_warnings - cq_default_path : str - cq_description_template - cq_enabled : bool - cq_findings : list - exclude_patterns : list - include_patterns : list - maximum - minimum - name : str - verbose : bool - __init__(verbose) - _is_excluded(content) - _return_error_code() - _search_patterns(content, patterns) - add_patterns(regexes, pattern_container) - {abstract}check(content) - parse_config(config) - print_when_verbose(message) - return_check_limits() - return_count() + count : int + counted_warnings + cq_default_path : str + cq_description_template + cq_enabled : bool + cq_findings + exclude_patterns : list + include_patterns : list + maximum + minimum + name : str + verbose : bool + add_patterns(regexes, pattern_container) + {abstract}check(content) + parse_config(config) + return_check_limits(extra) + return_count() } class "WarningsConfigError" as mlx.warnings.exceptions.WarningsConfigError { } class "WarningsPlugin" as mlx.warnings.warnings.WarningsPlugin { - _maximum : int - _minimum : int - activated_checkers : dict - count : int - cq_enabled : bool - printout : bool - public_checkers : list - verbose : bool - __init__(verbose, config_file, cq_enabled) - activate_checker(checker) - activate_checker_name(name) - check(content) - check_logfile(file) - config_parser(config) - configure_maximum(maximum) - configure_minimum(minimum) - get_checker(name) - return_check_limits(name) - return_count(name) - toggle_printout(printout) - write_code_quality_report(out_file) - write_counted_warnings(out_file) + activated_checkers : dict + count : int + cq_enabled : bool + printout : bool + public_checkers : list + verbose : bool + activate_checker(checker) + activate_checker_name(name) + check(content) + check_logfile(file) + config_parser(config) + configure_maximum(maximum) + configure_minimum(minimum) + get_checker(name) + return_check_limits(name) + return_count(name) + toggle_printout(printout) + write_code_quality_report(out_file) + write_counted_warnings(out_file) } class "XMLRunnerChecker" as mlx.warnings.regex_checker.XMLRunnerChecker { - name : str - pattern + name : str + pattern } mlx.warnings.junit_checker.JUnitChecker --|> mlx.warnings.warnings_checker.WarningsChecker mlx.warnings.polyspace_checker.PolyspaceChecker --|> mlx.warnings.warnings_checker.WarningsChecker mlx.warnings.polyspace_checker.PolyspaceFamilyChecker --|> mlx.warnings.warnings_checker.WarningsChecker mlx.warnings.regex_checker.CoverityChecker --|> mlx.warnings.regex_checker.RegexChecker + mlx.warnings.regex_checker.CoverityClassificationChecker --|> mlx.warnings.warnings_checker.WarningsChecker mlx.warnings.regex_checker.DoxyChecker --|> mlx.warnings.regex_checker.RegexChecker mlx.warnings.regex_checker.RegexChecker --|> mlx.warnings.warnings_checker.WarningsChecker mlx.warnings.regex_checker.SphinxChecker --|> mlx.warnings.regex_checker.RegexChecker From 57ecd2f007e6e7266b7ff94d4faadcd4a191fef2 Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 3 Dec 2024 15:44:33 +0100 Subject: [PATCH 21/86] Fix flake8 --- tests/test_config.py | 3 ++- tests/test_polyspace.py | 14 +++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index dcca69e7..384ca8ee 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -234,7 +234,8 @@ def test_partial_robot_config_empty_name(self): count = warnings.return_count() self.assertEqual(count, 1) self.assertEqual(warnings.return_check_limits(), 0) - self.assertEqual([ + self.assertEqual( + [ r"INFO:root:Excluded 'Directory 'C:\\nonexistent' does not exist.' because of configured regex 'does not exist'", "INFO:root:Suite One & Suite Two.Suite Two.Another test", ], diff --git a/tests/test_polyspace.py b/tests/test_polyspace.py index 9cd36e88..260f95bb 100644 --- a/tests/test_polyspace.py +++ b/tests/test_polyspace.py @@ -34,9 +34,9 @@ def test_code_prover_tsv_file(self): stdout_log = fake_out.getvalue() self.assertEqual( '\n'.join([ - "polyspace: family 'run-time check' color: red number of warnings (0) is exactly as expected. Well done.", - "polyspace: family 'run-time check' color: orange number of warnings (19) is higher than the maximum limit (0).", - "Returning error code 19." + "polyspace: family 'run-time check' color: red number of warnings (0) is exactly as expected. Well done.", + "polyspace: family 'run-time check' color: orange number of warnings (19) is higher than the maximum limit (0).", + "Returning error code 19." ]) + '\n', stdout_log ) @@ -61,10 +61,10 @@ def test_bug_finder_tsv_file(self): stdout_log = fake_out.getvalue() self.assertEqual( '\n'.join([ - "polyspace: family 'defect' information: impact: high number of warnings (42) is higher than the maximum limit (0).", - "polyspace: family 'defect' information: impact: medium number of warnings (9) is higher than the maximum limit (0).", - "polyspace: family 'defect' information: impact: low number of warnings (4) is higher than the maximum limit (0).", - "Returning error code 55." + "polyspace: family 'defect' information: impact: high number of warnings (42) is higher than the maximum limit (0).", + "polyspace: family 'defect' information: impact: medium number of warnings (9) is higher than the maximum limit (0).", + "polyspace: family 'defect' information: impact: low number of warnings (4) is higher than the maximum limit (0).", + "Returning error code 55." ]) + '\n', stdout_log ) From 05b63af38b87c914152fba629bb03e19761e46ae Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 3 Dec 2024 15:50:29 +0100 Subject: [PATCH 22/86] Add indentation --- docs/design.rst | 238 ++++++++++++++++++++++++------------------------ 1 file changed, 119 insertions(+), 119 deletions(-) diff --git a/docs/design.rst b/docs/design.rst index 344c3136..5367e3f9 100644 --- a/docs/design.rst +++ b/docs/design.rst @@ -16,153 +16,153 @@ Class Diagram @startuml classes set namespaceSeparator none class "CoverityChecker" as mlx.warnings.regex_checker.CoverityChecker { - checkers : dict - count : int - counted_warnings - cq_default_path - cq_description_template - cq_findings - name : str - pattern - check(content) - parse_config(config) - return_check_limits() - return_count() + checkers : dict + count : int + counted_warnings + cq_default_path + cq_description_template + cq_findings + name : str + pattern + check(content) + parse_config(config) + return_check_limits() + return_count() } class "CoverityClassificationChecker" as mlx.warnings.regex_checker.CoverityClassificationChecker { - SEVERITY_MAP : dict - classification - count - cq_description_template - name : str - add_code_quality_finding(match) - check(content) + SEVERITY_MAP : dict + classification + count + cq_description_template + name : str + add_code_quality_finding(match) + check(content) } class "DoxyChecker" as mlx.warnings.regex_checker.DoxyChecker { - name : str - pattern + name : str + pattern } class "Finding" as mlx.warnings.code_quality.Finding { - column - description - fingerprint - fingerprints : dict - line - path - severity - to_dict() + column + description + fingerprint + fingerprints : dict + line + path + severity + to_dict() } class "JUnitChecker" as mlx.warnings.junit_checker.JUnitChecker { - count - name : str - check(content) - prepare_tree(root_input) + count + name : str + check(content) + prepare_tree(root_input) } class "PolyspaceChecker" as mlx.warnings.polyspace_checker.PolyspaceChecker { - checkers : list - count : int - counted_warnings - cq_default_path - cq_description_template - cq_findings - maximum - minimum - name : str - check(content) - parse_config(config) - return_check_limits() - return_count() + checkers : list + count : int + counted_warnings + cq_default_path + cq_description_template + cq_findings + maximum + minimum + name : str + check(content) + parse_config(config) + return_check_limits() + return_count() } class "PolyspaceFamilyChecker" as mlx.warnings.polyspace_checker.PolyspaceFamilyChecker { - check_value - code_quality_severity : dict - column_name - count - cq_description_template - family_value - name : str - add_code_quality_finding(row) - check(content) + check_value + code_quality_severity : dict + column_name + count + cq_description_template + family_value + name : str + add_code_quality_finding(row) + check(content) } class "RegexChecker" as mlx.warnings.regex_checker.RegexChecker { - SEVERITY_MAP : dict - count - name : str - pattern : NoneType - add_code_quality_finding(match) - check(content) + SEVERITY_MAP : dict + count + name : str + pattern : NoneType + add_code_quality_finding(match) + check(content) } class "RobotChecker" as mlx.warnings.robot_checker.RobotChecker { - checkers : list - count : int - counted_warnings - maximum - minimum - name : str - check(content) - parse_config(config) - return_check_limits() - return_count() + checkers : list + count : int + counted_warnings + maximum + minimum + name : str + check(content) + parse_config(config) + return_check_limits() + return_count() } class "RobotSuiteChecker" as mlx.warnings.robot_checker.RobotSuiteChecker { - check_suite_name : bool - is_valid_suite_name : bool - name : str - suite_name - check(content) + check_suite_name : bool + is_valid_suite_name : bool + name : str + suite_name + check(content) } class "SphinxChecker" as mlx.warnings.regex_checker.SphinxChecker { - name : str - pattern - sphinx_deprecation_regex : str - sphinx_deprecation_regex_in_match : str - include_sphinx_deprecation() + name : str + pattern + sphinx_deprecation_regex : str + sphinx_deprecation_regex_in_match : str + include_sphinx_deprecation() } class "WarningsChecker" as mlx.warnings.warnings_checker.WarningsChecker { - count : int - counted_warnings - cq_default_path : str - cq_description_template - cq_enabled : bool - cq_findings - exclude_patterns : list - include_patterns : list - maximum - minimum - name : str - verbose : bool - add_patterns(regexes, pattern_container) - {abstract}check(content) - parse_config(config) - return_check_limits(extra) - return_count() + count : int + counted_warnings + cq_default_path : str + cq_description_template + cq_enabled : bool + cq_findings + exclude_patterns : list + include_patterns : list + maximum + minimum + name : str + verbose : bool + add_patterns(regexes, pattern_container) + {abstract}check(content) + parse_config(config) + return_check_limits(extra) + return_count() } class "WarningsConfigError" as mlx.warnings.exceptions.WarningsConfigError { } class "WarningsPlugin" as mlx.warnings.warnings.WarningsPlugin { - activated_checkers : dict - count : int - cq_enabled : bool - printout : bool - public_checkers : list - verbose : bool - activate_checker(checker) - activate_checker_name(name) - check(content) - check_logfile(file) - config_parser(config) - configure_maximum(maximum) - configure_minimum(minimum) - get_checker(name) - return_check_limits(name) - return_count(name) - toggle_printout(printout) - write_code_quality_report(out_file) - write_counted_warnings(out_file) + activated_checkers : dict + count : int + cq_enabled : bool + printout : bool + public_checkers : list + verbose : bool + activate_checker(checker) + activate_checker_name(name) + check(content) + check_logfile(file) + config_parser(config) + configure_maximum(maximum) + configure_minimum(minimum) + get_checker(name) + return_check_limits(name) + return_count(name) + toggle_printout(printout) + write_code_quality_report(out_file) + write_counted_warnings(out_file) } class "XMLRunnerChecker" as mlx.warnings.regex_checker.XMLRunnerChecker { - name : str - pattern + name : str + pattern } mlx.warnings.junit_checker.JUnitChecker --|> mlx.warnings.warnings_checker.WarningsChecker mlx.warnings.polyspace_checker.PolyspaceChecker --|> mlx.warnings.warnings_checker.WarningsChecker From a3e6d8da283c857de866dec32d221902cd3ecf07 Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 3 Dec 2024 17:21:14 +0100 Subject: [PATCH 23/86] Fix setting logging level --- src/mlx/warnings/warnings.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index f7a68a5b..3abff2c2 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -284,9 +284,7 @@ def warnings_wrapper(args): code_quality_enabled = bool(args.code_quality) logging.basicConfig(format="%(levelname)s: %(message)s") if args.verbose: - logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.INFO) - else: - logging.basicConfig(format="%(levelname)s: %(message)s") + logging.getLogger().setLevel(logging.INFO) # Read config file if args.configfile is not None: checker_flags = args.sphinx or args.doxygen or args.junit or args.coverity or args.xmlrunner or args.robot From 7e911469e29abd3e9c1a090741d00b6f8a0487e9 Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 5 Dec 2024 17:37:34 +0100 Subject: [PATCH 24/86] Add verbosity tests --- tests/test_integration.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/test_integration.py b/tests/test_integration.py index 6de4b62f..50056e72 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,4 +1,5 @@ import filecmp +import logging import os from io import StringIO from pathlib import Path @@ -10,6 +11,18 @@ TEST_IN_DIR = Path(__file__).parent / 'test_in' TEST_OUT_DIR = Path(__file__).parent / 'test_out' +def run_test_with_logging(args): + buffer = StringIO() + with patch('sys.stdout', new=buffer): + logger = logging.getLogger() + stream_handler = logging.StreamHandler(buffer) + logger.addHandler(stream_handler) + try: + retval = warnings_wrapper(args) + finally: + logger.removeHandler(stream_handler) + return buffer.getvalue(), retval + class TestIntegration(TestCase): def setUp(self): @@ -37,6 +50,21 @@ def test_no_parser_selection(self): warnings_wrapper([]) self.assertEqual(2, ex.exception.code) + def test_verbose(self): + stdout_log, retval = run_test_with_logging(['--verbose', '--junit', 'tests/test_in/junit_single_fail.xml']) + self.assertEqual("test_warn_plugin_single_fail.myfirstfai1ure\n" + "junit: number of warnings (1) is higher than the maximum limit (0). " + "Returning error code 1.\n", + stdout_log) + self.assertEqual(1, retval) + + def test_no_verbose(self): + stdout_log, retval = run_test_with_logging(['--junit', 'tests/test_in/junit_single_fail.xml']) + self.assertEqual("junit: number of warnings (1) is higher than the maximum limit (0). " + "Returning error code 1.\n", + stdout_log) + self.assertEqual(1, retval) + min_ret_val_on_failure = 1 junit_warning_cnt = 3 From cca6fc5793584fae62b19c6db2c757bdeb930238 Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 5 Dec 2024 17:57:56 +0100 Subject: [PATCH 25/86] Capture all prints in other tests --- tests/test_integration.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/tests/test_integration.py b/tests/test_integration.py index 50056e72..4881b20b 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -210,23 +210,21 @@ def test_robot_default_name_arg(self): def test_robot_verbose(self): ''' If no suite name is configured, all suites must be taken into account ''' - with self.assertLogs(level="INFO") as fake_out: - retval = warnings_wrapper(['--verbose', '--robot', '--name', 'Suite Two', 'tests/test_in/robot_double_fail.xml']) - stdout_log = fake_out.output - + stdout_log, retval = run_test_with_logging(['--verbose', + '--robot', + '--name', 'Suite Two', + 'tests/test_in/robot_double_fail.xml']) self.assertEqual(1, retval) - self.assertIn("INFO:root:Suite One & Suite Two.Suite Two.Another test", stdout_log) + self.assertIn("Suite One & Suite Two.Suite Two.Another test\n" + "robot: test suite 'Suite Two' number of warnings (1) is higher than the maximum limit (0).\n" + "Returning error code 1.\n", stdout_log) def test_robot_config(self): os.environ['MIN_ROBOT_WARNINGS'] = '0' os.environ['MAX_ROBOT_WARNINGS'] = '0' - with patch('sys.stdout', new=StringIO()) as fake_out: - retval = warnings_wrapper([ - '--config', - 'tests/test_in/config_example_robot.json', - 'tests/test_in/robot_double_fail.xml', - ]) - stdout_log = fake_out.getvalue() + stdout_log, retval = run_test_with_logging(['--config', + 'tests/test_in/config_example_robot.json', + 'tests/test_in/robot_double_fail.xml']) self.assertEqual( '\n'.join([ "robot: test suite 'Suite One' number of warnings (1) is between limits 0 and 1. Well done.", From 93864a7c4902c5ef4685e11b2d27aa0b2d4d6ece Mon Sep 17 00:00:00 2001 From: JWM Date: Fri, 6 Dec 2024 10:15:34 +0100 Subject: [PATCH 26/86] Use run_test_with_logging in robot test --- tests/test_robot.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/tests/test_robot.py b/tests/test_robot.py index 7608d121..7bd19e0d 100644 --- a/tests/test_robot.py +++ b/tests/test_robot.py @@ -1,6 +1,7 @@ import unittest from mlx.warnings import RobotSuiteChecker, WarningsPlugin +from test_integration import run_test_with_logging class TestRobotWarnings(unittest.TestCase): @@ -30,18 +31,15 @@ def test_single_warning(self): self.assertIn("INFO:root:Suite One & Suite Two.Suite One.First Test", stdout_log) def test_double_warning_and_verbosity(self): - with open('tests/test_in/robot_double_fail.xml') as xmlfile: - with self.assertLogs(level="INFO") as fake_out: - self.warnings.check(xmlfile.read()) - count = self.warnings.return_count() - stdout_log = fake_out.output - - self.assertEqual(count, 2) + stdout_log, retval = run_test_with_logging(['--verbose', + '--robot', + 'tests/test_in/robot_double_fail.xml']) + self.assertEqual(retval, 2) self.assertEqual( - [ - "INFO:root:Suite One & Suite Two.Suite One.First Test", - "INFO:root:Suite One & Suite Two.Suite Two.Another test", - ], + "Suite One & Suite Two.Suite One.First Test\n" + "Suite One & Suite Two.Suite Two.Another test\n" + "robot: all test suites number of warnings (2) is higher than the maximum limit (0).\n" + "Returning error code 2.\n", stdout_log ) From 6e4e05299753c742d4c85afbd6a5a52162945938 Mon Sep 17 00:00:00 2001 From: JWM Date: Fri, 6 Dec 2024 12:11:21 +0100 Subject: [PATCH 27/86] Use capitalized names of checkers --- src/mlx/warnings/warnings_checker.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index d05b488d..622a8acb 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -151,19 +151,22 @@ def return_check_limits(self, extra=""): int: 0 if the amount of warnings is within limits, the count of warnings otherwise (or 1 in case of a count of 0 warnings) ''' + name = self.name.capitalize() if self.name != "junit" else self.name[0].upper() + self.name[1:].capitalize() if self.count > self._maximum or self.count < self._minimum: - return self._return_error_code(extra) + return self._return_error_code(name, extra) elif self._minimum == self._maximum and self.count == self._maximum: - print(f"{self.name + ':':<10} {extra}number of warnings ({self.count}) is exactly as expected. Well done.") + print(f"{name + ':':<10} {extra}number of warnings ({self.count}) is exactly as " + "expected. Well done.") else: - print(f"{self.name + ':':<10} {extra}number of warnings ({self.count}) is between limits {self._minimum} " - f"and {self._maximum}. Well done.") + print(f"{name + ':':<10} {extra}number of warnings ({self.count}) is between limits " + f"{self._minimum} and {self._maximum}. Well done.") return 0 - def _return_error_code(self, extra=""): + def _return_error_code(self, name, extra=""): ''' Function for determining the return code and message on failure Args: + name (str): The capitalized name of the checker extra (str): The extra string that is placed after the checker name. For example, the classification. Returns: @@ -177,7 +180,7 @@ def _return_error_code(self, extra=""): error_code = self.count if error_code == 0: error_code = 1 - string_to_print = f"{self.name + ':':<10} {extra}number of warnings ({self.count}) is {error_reason}." + string_to_print = f"{name + ':':<10} {extra}number of warnings ({self.count}) is {error_reason}." if self.name not in ["polyspace", "coverity", "robot"]: string_to_print += f" Returning error code {error_code}." print(string_to_print) From b61c4edf5f3138a453bb0ce9541716baafbdec9f Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:54:24 +0100 Subject: [PATCH 28/86] Rephrase doctring Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- src/mlx/warnings/warnings_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index 622a8acb..4592ca18 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -145,7 +145,7 @@ def return_check_limits(self, extra=""): ''' Function for checking whether the warning count is within the configured limits Args: - extra (str): The extra string that is placed after the checker name. For example, the classification. + extra (str): Extra information, to insert between the checker name and the message. Returns: int: 0 if the amount of warnings is within limits, the count of warnings otherwise From 3792ed69634531de00d64c6c7809ed1d4cf616b5 Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:54:54 +0100 Subject: [PATCH 29/86] Rephrase doctring Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- src/mlx/warnings/warnings_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index 4592ca18..9b517c01 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -167,7 +167,7 @@ def _return_error_code(self, name, extra=""): Args: name (str): The capitalized name of the checker - extra (str): The extra string that is placed after the checker name. For example, the classification. + extra (str): Extra information, to insert between the checker name and the message. Returns: int: The count of warnings (or 1 in case of a count of 0 warnings) From 51b6afe94e420f150b45d2a1764ad7b3a996c910 Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:57:20 +0100 Subject: [PATCH 30/86] Fix: 0 is no error code Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- src/mlx/warnings/polyspace_checker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index 6a1720f1..9ee0a140 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -119,7 +119,8 @@ def return_check_limits(self): padded_string = [f"{string:<30}" for string in [f"family {checker.family_value!r} ", f"{checker.column_name}: {checker.check_value} "]] count += checker.return_check_limits("".join(padded_string)) - print(f"Returning error code {count}.") + if count: + print(f"Returning error code {count}.") return count def parse_config(self, config): From 4ac22da3262ca49ece0cfc7a3bf26751681b47a3 Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:57:59 +0100 Subject: [PATCH 31/86] Fix: 0 is no error code Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- src/mlx/warnings/regex_checker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 21240182..dd41bec0 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -133,7 +133,8 @@ def return_check_limits(self): for checker in self.checkers.values(): padded_string = [f"{string:<20}" for string in [f"{checker.classification}: "]] count += checker.return_check_limits("".join(padded_string)) - print(f"Returning error code {count}.") + if count: + print(f"Returning error code {count}.") return count def check(self, content): From ed4e8533c21301ea9e538f0d392c87454320b05f Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:58:10 +0100 Subject: [PATCH 32/86] Fix: 0 is no error code Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- src/mlx/warnings/robot_checker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mlx/warnings/robot_checker.py b/src/mlx/warnings/robot_checker.py index ef846c5c..9dc57e6e 100644 --- a/src/mlx/warnings/robot_checker.py +++ b/src/mlx/warnings/robot_checker.py @@ -91,7 +91,8 @@ def return_check_limits(self): else: string = "all test suites" count += checker.return_check_limits(f"{string:<30}") - print(f"Returning error code {self.count}.") + if count: + print(f"Returning error code {count}.") return count def parse_config(self, config): From 83bd470f9ee756c27349310713d52c07d459a08f Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:58:41 +0100 Subject: [PATCH 33/86] Simplify capitalization Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- src/mlx/warnings/warnings_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index 9b517c01..ab1861da 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -151,7 +151,7 @@ def return_check_limits(self, extra=""): int: 0 if the amount of warnings is within limits, the count of warnings otherwise (or 1 in case of a count of 0 warnings) ''' - name = self.name.capitalize() if self.name != "junit" else self.name[0].upper() + self.name[1:].capitalize() + name = self.name.capitalize() if self.name != "junit" else "JUnit" if self.count > self._maximum or self.count < self._minimum: return self._return_error_code(name, extra) elif self._minimum == self._maximum and self.count == self._maximum: From f1217ac12398675d8efa716172855062b4749bf2 Mon Sep 17 00:00:00 2001 From: JWM Date: Fri, 6 Dec 2024 17:06:23 +0100 Subject: [PATCH 34/86] Update test to check all prints --- tests/test_config.py | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 384ca8ee..01d96529 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,6 +1,9 @@ +from io import StringIO +import logging import os from pathlib import Path from unittest import TestCase +from unittest.mock import patch from mlx.warnings import ( DoxyChecker, @@ -14,6 +17,22 @@ TEST_IN_DIR = Path(__file__).parent / 'test_in' +def check_xml_file_with_logging(warnings, file_path): + logging.basicConfig(format="%(levelname)s: %(message)s") + logging.getLogger().setLevel(logging.INFO) + buffer = StringIO() + with patch('sys.stdout', new=buffer): + logger = logging.getLogger() + stream_handler = logging.StreamHandler(buffer) + logger.addHandler(stream_handler) + try: + with open(file_path) as xmlfile: + warnings.check(xmlfile.read()) + retval = warnings.return_check_limits() + finally: + logger.removeHandler(stream_handler) + return buffer.getvalue(), retval + class TestConfig(TestCase): def setUp(self): @@ -198,18 +217,15 @@ def test_partial_robot_config_parsing_exclude_regex(self): } } warnings.config_parser(tmpjson) - with open('tests/test_in/robot_double_fail.xml') as xmlfile: - with self.assertLogs(level="INFO") as verbose_output: - warnings.check(xmlfile.read()) - count = warnings.return_count() - self.assertEqual(count, 1) - self.assertEqual(warnings.return_check_limits(), 0) + stdout_log, retval = check_xml_file_with_logging(warnings, 'tests/test_in/robot_double_fail.xml') + self.assertEqual(warnings.return_count(), 1) + self.assertEqual(retval, 0) self.assertEqual( - [ - r"INFO:root:Excluded 'Directory 'C:\\nonexistent' does not exist.' because of configured regex 'does not exist'", - "INFO:root:Suite One & Suite Two.Suite Two.Another test", - ], - verbose_output.output + "Excluded 'Directory 'C:\\\\nonexistent' does not exist.' because of configured regex 'does not exist'\n" + "Suite One & Suite Two.Suite Two.Another test\n" + "Robot: test suite 'Suite One' number of warnings (0) is exactly as expected. Well done.\n" + "Robot: test suite 'Suite Two' number of warnings (1) is exactly as expected. Well done.\n", + stdout_log ) def test_partial_robot_config_empty_name(self): From 2010f2897d9a926c4d4f3d3d0f483a57914290e5 Mon Sep 17 00:00:00 2001 From: JWM Date: Mon, 9 Dec 2024 12:40:03 +0100 Subject: [PATCH 35/86] Update return_check_limits to accept extra arguments for logger; Use different format for self.logger if nessecary --- src/mlx/warnings/polyspace_checker.py | 13 +++++++++--- src/mlx/warnings/warnings_checker.py | 29 +++++++++++++++------------ 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index 9ee0a140..1a86e20d 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -19,6 +19,10 @@ def __init__(self, verbose): '''Constructor to set the default code quality description template to "Polyspace: $check"''' super().__init__(verbose) self._cq_description_template = Template('Polyspace: $check') + formatter = logging.Formatter(fmt="{checker_name}: {family}: {column_name}: {check_value:<20} | {message:>60}", + style="{") + for handler in self.logger.handlers: + handler.setFormatter(formatter) @property def cq_findings(self): @@ -116,9 +120,12 @@ def return_check_limits(self): ''' count = 0 for checker in self.checkers: - padded_string = [f"{string:<30}" for string in [f"family {checker.family_value!r} ", - f"{checker.column_name}: {checker.check_value} "]] - count += checker.return_check_limits("".join(padded_string)) + extra = { + 'family': checker.family_value, + 'column_name': checker.column_name, + 'check_value': checker.check_value + } + count += checker.return_check_limits(extra) if count: print(f"Returning error code {count}.") return count diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index ab1861da..80b32824 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -51,6 +51,11 @@ def __init__(self, verbose=False): self._cq_description_template = Template('$description') self.exclude_patterns = [] self.include_patterns = [] + self.logger = logging.getLogger() + handler = logging.StreamHandler() + formatter = logging.Formatter(fmt="{checker_name}: {message}", style="{") + handler.setFormatter(formatter) + self.logger.addHandler(handler) @property def cq_findings(self): @@ -141,33 +146,31 @@ def return_count(self): ''' return self.count - def return_check_limits(self, extra=""): + def return_check_limits(self, extra={}): ''' Function for checking whether the warning count is within the configured limits Args: - extra (str): Extra information, to insert between the checker name and the message. + extra (dict): Extra arguments for the logger. Returns: int: 0 if the amount of warnings is within limits, the count of warnings otherwise (or 1 in case of a count of 0 warnings) ''' - name = self.name.capitalize() if self.name != "junit" else "JUnit" + extra["checker_name"] = self.name.capitalize() if self.name != "junit" else "JUnit" if self.count > self._maximum or self.count < self._minimum: - return self._return_error_code(name, extra) + return self._return_error_code(extra) elif self._minimum == self._maximum and self.count == self._maximum: - print(f"{name + ':':<10} {extra}number of warnings ({self.count}) is exactly as " - "expected. Well done.") + self.logger.warning(f"number of warnings ({self.count}) is exactly as expected. Well done.", extra=extra) else: - print(f"{name + ':':<10} {extra}number of warnings ({self.count}) is between limits " - f"{self._minimum} and {self._maximum}. Well done.") + self.logger.warning(f"number of warnings ({self.count}) is between limits {self._minimum} and {self._maximum}. " + "Well done.", extra=extra) return 0 - def _return_error_code(self, name, extra=""): + def _return_error_code(self, extra={}): ''' Function for determining the return code and message on failure Args: - name (str): The capitalized name of the checker - extra (str): Extra information, to insert between the checker name and the message. + extra (dict): Extra arguments for the logger. Returns: int: The count of warnings (or 1 in case of a count of 0 warnings) @@ -180,10 +183,10 @@ def _return_error_code(self, name, extra=""): error_code = self.count if error_code == 0: error_code = 1 - string_to_print = f"{name + ':':<10} {extra}number of warnings ({self.count}) is {error_reason}." + string_to_print = f"number of warnings ({self.count}) is {error_reason}." if self.name not in ["polyspace", "coverity", "robot"]: string_to_print += f" Returning error code {error_code}." - print(string_to_print) + self.logger.warning(string_to_print, extra=extra) return error_code def parse_config(self, config): From 7318f5d39f50b870088d1ce316963f3323ebdaf7 Mon Sep 17 00:00:00 2001 From: JWM Date: Tue, 10 Dec 2024 18:46:20 +0100 Subject: [PATCH 36/86] Use separate logger for output file (remove write_counted_warnings) ; Initiate logger only when checker activated --- src/mlx/warnings/junit_checker.py | 5 ++-- src/mlx/warnings/polyspace_checker.py | 34 ++++++++--------------- src/mlx/warnings/regex_checker.py | 40 +++++++++++++-------------- src/mlx/warnings/robot_checker.py | 23 +++++++-------- src/mlx/warnings/warnings.py | 38 ++++++++++--------------- src/mlx/warnings/warnings_checker.py | 35 ++++++++++++++--------- 6 files changed, 80 insertions(+), 95 deletions(-) diff --git a/src/mlx/warnings/junit_checker.py b/src/mlx/warnings/junit_checker.py index 194c6234..1639b225 100644 --- a/src/mlx/warnings/junit_checker.py +++ b/src/mlx/warnings/junit_checker.py @@ -67,6 +67,7 @@ def _check_testcase(self, testcase): if self._is_excluded(testcase.result.message): return 1 string = f'{testcase.classname}.{testcase.name}' - self.counted_warnings.append(f'{string}: {testcase.result.message}') - logging.info(string) + extra = {"checker_name": self.name.capitalize() if self.name != "junit" else "JUnit"} + self.output_logger.debug(f'{string}: {testcase.result.message}', extra=extra) + self.logger.info(string, extra=extra) return 0 diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index 1a86e20d..cdcb7f8c 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -14,15 +14,12 @@ class PolyspaceChecker(WarningsChecker): name = 'polyspace' checkers = [] + logging_fmt = "{checker_name}: {family}: {column_name}: {check_value:<20} | {message:>60}" - def __init__(self, verbose): + def __init__(self, verbose, output): '''Constructor to set the default code quality description template to "Polyspace: $check"''' - super().__init__(verbose) + super().__init__(verbose, output) self._cq_description_template = Template('Polyspace: $check') - formatter = logging.Formatter(fmt="{checker_name}: {family}: {column_name}: {check_value:<20} | {message:>60}", - style="{") - for handler in self.logger.handlers: - handler.setFormatter(formatter) @property def cq_findings(self): @@ -31,14 +28,6 @@ def cq_findings(self): self._cq_findings.extend(checker.cq_findings) return self._cq_findings - @property - def counted_warnings(self): - '''List[str]: list of counted warnings''' - all_counted_warnings = [] - for checker in self.checkers: - all_counted_warnings.extend(checker.counted_warnings) - return all_counted_warnings - @property def cq_description_template(self): ''' Template: string.Template instance based on the configured template string ''' @@ -121,9 +110,9 @@ def return_check_limits(self): count = 0 for checker in self.checkers: extra = { - 'family': checker.family_value, - 'column_name': checker.column_name, - 'check_value': checker.check_value + "family": checker.family_value, + "column_name": checker.column_name, + "check_value": checker.check_value } count += checker.return_check_limits(extra) if count: @@ -201,6 +190,7 @@ def __init__(self, family_value, column_name, check_value, **kwargs): self.family_value = family_value self.column_name = column_name self.check_value = check_value + self.logger = logging.getLogger(self.name) @property def cq_description_template(self): @@ -244,15 +234,13 @@ def check(self, content): ''' if content[self.column_name].lower() == self.check_value: if content["status"].lower() in ["not a defect", "justified"]: - logging.info(f"Excluded row {content!r} because the status is 'Not a defect' or 'Justified'") + self.logger.info(f"Excluded row {content!r} because the status is 'Not a defect' or 'Justified'", + extra={"checker_name": "Polyspace"}) else: tab_sep_string = "\t".join(content.values()) if not self._is_excluded(tab_sep_string): self.count = self.count + 1 - self.counted_warnings.append('family: {} -> {}: {}'.format( - self.family_value, - self.column_name, - self.check_value - )) + self.output_logger.debug(f'{self.family_value}: {self.column_name}: {self.check_value}', + extra={"checker_name": "Polyspace"}) if self.cq_enabled and content["color"].lower() != "green": self.add_code_quality_finding(content) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index dd41bec0..a18b2b13 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -46,8 +46,9 @@ def check(self, content): if self._is_excluded(match_string): continue self.count += 1 - self.counted_warnings.append(match_string) - logging.info(match_string) + extra = {"checker_name": self.name.capitalize() if self.name != "junit" else "JUnit"} + self.output_logger.debug(match_string, extra=extra) + self.logger.info(match_string, extra=extra) if self.cq_enabled: self.add_code_quality_finding(match) @@ -75,26 +76,19 @@ def add_code_quality_finding(self, match): class CoverityChecker(RegexChecker): name = 'coverity' pattern = coverity_pattern + logging_fmt = "{checker_name}: {classification:<15} | {message:>60}" - def __init__(self, verbose=False): - super().__init__(verbose) + def __init__(self, verbose, output): + super().__init__(verbose, output) self._cq_description_template = Template('Coverity: CID $cid: $checker') self.checkers = { - "unclassified": CoverityClassificationChecker("unclassified", verbose=self.verbose), - "pending": CoverityClassificationChecker("pending", verbose=self.verbose), - "bug": CoverityClassificationChecker("bug", verbose=self.verbose), - "intentional": CoverityClassificationChecker("intentional", verbose=self.verbose), - "false positive": CoverityClassificationChecker("false positive", verbose=self.verbose), + "unclassified": CoverityClassificationChecker("unclassified", verbose=self.verbose, output=output), + "pending": CoverityClassificationChecker("pending", verbose=self.verbose, output=output), + "bug": CoverityClassificationChecker("bug", verbose=self.verbose, output=output), + "intentional": CoverityClassificationChecker("intentional", verbose=self.verbose, output=output), + "false positive": CoverityClassificationChecker("false positive", verbose=self.verbose, output=output), } - @property - def counted_warnings(self): - ''' List[str]: list of counted warnings''' - all_counted_warnings = [] - for checker in self.checkers.values(): - all_counted_warnings.extend(checker.counted_warnings) - return all_counted_warnings - @property def cq_findings(self): ''' List[dict]: list of code quality findings''' @@ -131,8 +125,10 @@ def return_check_limits(self): ''' count = 0 for checker in self.checkers.values(): - padded_string = [f"{string:<20}" for string in [f"{checker.classification}: "]] - count += checker.return_check_limits("".join(padded_string)) + extra = { + "classification": checker.classification, + } + count += checker.return_check_limits(extra) if count: print(f"Returning error code {count}.") return count @@ -196,6 +192,7 @@ def __init__(self, classification, **kwargs): """ super().__init__(**kwargs) self.classification = classification + self.logger = logging.getLogger(self.name) @property def cq_description_template(self): @@ -242,8 +239,9 @@ def check(self, content): match_string = content.group(0).strip() if not self._is_excluded(match_string) and (content.group('curr') == content.group('max')): self.count += 1 - self.counted_warnings.append(match_string) - logging.info(match_string) + self.output_logger.debug(match_string, + extra={"checker_name": "Coverity"}) + self.logger.info(match_string, extra={"checker_name": "Coverity"}) if self.cq_enabled: self.add_code_quality_finding(content) diff --git a/src/mlx/warnings/robot_checker.py b/src/mlx/warnings/robot_checker.py index 9dc57e6e..33875bb0 100644 --- a/src/mlx/warnings/robot_checker.py +++ b/src/mlx/warnings/robot_checker.py @@ -12,14 +12,7 @@ class RobotChecker(WarningsChecker): name = 'robot' checkers = [] - - @property - def counted_warnings(self): - '''List[str]: list of counted warnings''' - all_counted_warnings = [] - for checker in self.checkers: - all_counted_warnings.extend(checker.counted_warnings) - return all_counted_warnings + logging_fmt = "{checker_name}: {suite_name:<20} {message:>60}" @property def minimum(self): @@ -85,12 +78,15 @@ def return_check_limits(self): count = 0 for checker in self.checkers: if checker.suite_name: - string = f"test suite {checker.suite_name!r}" - padded_string = f"{string:<30}" - count += checker.return_check_limits(padded_string) + extra = { + "suite_name": f"test suite {checker.suite_name!r}", + } + count += checker.return_check_limits(extra) else: - string = "all test suites" - count += checker.return_check_limits(f"{string:<30}") + extra = { + "suite_name": "all test suites", + } + count += checker.return_check_limits(extra) if count: print(f"Returning error code {count}.") return count @@ -119,6 +115,7 @@ def __init__(self, suite_name, check_suite_name=False, **kwargs): self.suite_name = suite_name self.check_suite_name = check_suite_name self.is_valid_suite_name = False + self.logger = logging.getLogger(self.name) def _check_testcase(self, testcase): """ Handles the check of a test case element by checking if the result is a failure/error. diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index 3abff2c2..ab073e8c 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -6,6 +6,7 @@ import glob import json import logging +import os import subprocess import sys from importlib.metadata import distribution @@ -24,7 +25,7 @@ class WarningsPlugin: - def __init__(self, verbose=False, config_file=None, cq_enabled=False): + def __init__(self, verbose=False, config_file=None, cq_enabled=False, output=""): ''' Function for initializing the parsers @@ -36,9 +37,10 @@ def __init__(self, verbose=False, config_file=None, cq_enabled=False): self.activated_checkers = {} self.verbose = verbose self.cq_enabled = cq_enabled - self.public_checkers = [SphinxChecker(self.verbose), DoxyChecker(self.verbose), JUnitChecker(self.verbose), - XMLRunnerChecker(self.verbose), CoverityChecker(self.verbose), - RobotChecker(self.verbose), PolyspaceChecker(self.verbose)] + self.public_checkers = [SphinxChecker(self.verbose, output), DoxyChecker(self.verbose, output), + JUnitChecker(self.verbose, output), XMLRunnerChecker(self.verbose, output), + CoverityChecker(self.verbose, output), RobotChecker(self.verbose, output), + PolyspaceChecker(self.verbose, output)] if config_file: with open(config_file, encoding='utf-8') as open_file: @@ -62,6 +64,7 @@ def activate_checker(self, checker): ''' checker.cq_enabled = self.cq_enabled and checker.name in ('doxygen', 'sphinx', 'xmlrunner', 'polyspace', 'coverity') self.activated_checkers[checker.name] = checker + checker.initiate_logger() def activate_checker_name(self, name): ''' @@ -217,17 +220,6 @@ def config_parser(self, config): except KeyError as err: raise WarningsConfigError(f"Incomplete config. Missing: {err}") from err - def write_counted_warnings(self, out_file): - ''' Writes counted warnings to the given file - - Args: - out_file (str): Location for the output file - ''' - Path(out_file).parent.mkdir(parents=True, exist_ok=True) - with open(out_file, 'w', encoding='utf-8', newline='\n') as open_file: - for checker in self.activated_checkers.values(): - open_file.write("\n".join(checker.counted_warnings) + "\n") - def write_code_quality_report(self, out_file): ''' Generates the Code Quality report artifact as a JSON file that implements a subset of the Code Climate spec @@ -266,7 +258,7 @@ def warnings_wrapper(args): help='Config file in JSON or YAML format provides toggle of checkers and their limits') group2.add_argument('--include-sphinx-deprecation', dest='include_sphinx_deprecation', action='store_true', help="Sphinx checker will include warnings matching (RemovedInSphinx\\d+Warning) regex") - parser.add_argument('-o', '--output', + parser.add_argument('-o', '--output', type=Path, help='Output file that contains all counted warnings') parser.add_argument('-C', '--code-quality', help='Output Code Quality report artifact for GitLab CI') @@ -282,9 +274,10 @@ def warnings_wrapper(args): args = parser.parse_args(args) code_quality_enabled = bool(args.code_quality) - logging.basicConfig(format="%(levelname)s: %(message)s") - if args.verbose: - logging.getLogger().setLevel(logging.INFO) + if args.output and args.output.exists(): + os.remove(args.output) + output = args.output if args.output else "" + # Read config file if args.configfile is not None: checker_flags = args.sphinx or args.doxygen or args.junit or args.coverity or args.xmlrunner or args.robot @@ -292,9 +285,10 @@ def warnings_wrapper(args): if checker_flags or warning_args: logging.error("Configfile cannot be provided with other arguments") sys.exit(2) - warnings = WarningsPlugin(verbose=args.verbose, config_file=args.configfile, cq_enabled=code_quality_enabled) + warnings = WarningsPlugin(verbose=args.verbose, config_file=args.configfile, cq_enabled=code_quality_enabled, + output=output) else: - warnings = WarningsPlugin(verbose=args.verbose, cq_enabled=code_quality_enabled) + warnings = WarningsPlugin(verbose=args.verbose, cq_enabled=code_quality_enabled, output=output) if args.sphinx: warnings.activate_checker_name('sphinx') if args.doxygen: @@ -344,8 +338,6 @@ def warnings_wrapper(args): return retval warnings.return_count() - if args.output: - warnings.write_counted_warnings(args.output) if args.code_quality: warnings.write_code_quality_report(args.code_quality) return warnings.return_check_limits() diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index 80b32824..97a13a43 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -32,8 +32,9 @@ def substitute_envvar(checker_config, keys): class WarningsChecker: name = 'checker' + logging_fmt = "{checker_name}: {message}" - def __init__(self, verbose=False): + def __init__(self, verbose, output): ''' Constructor Args: @@ -41,32 +42,22 @@ def __init__(self, verbose=False): verbose (bool): Enable/disable verbose logging ''' self.verbose = verbose + self.output = output self.count = 0 self._minimum = 0 self._maximum = 0 - self._counted_warnings = [] self._cq_findings = [] self.cq_enabled = False self.cq_default_path = '.gitlab-ci.yml' self._cq_description_template = Template('$description') self.exclude_patterns = [] self.include_patterns = [] - self.logger = logging.getLogger() - handler = logging.StreamHandler() - formatter = logging.Formatter(fmt="{checker_name}: {message}", style="{") - handler.setFormatter(formatter) - self.logger.addHandler(handler) @property def cq_findings(self): ''' List[dict]: list of code quality findings''' return self._cq_findings - @property - def counted_warnings(self): - ''' List[str]: list of counted warnings''' - return self._counted_warnings - @property def cq_description_template(self): ''' Template: string.Template instance based on the configured template string ''' @@ -124,6 +115,24 @@ def check(self, content): ''' return + def initiate_logger(self): + self.logger = logging.getLogger(self.name) + self.logger.propagate = False # Do not propagate to parent loggers + handler = logging.StreamHandler() + formatter = logging.Formatter(fmt=self.logging_fmt, style="{") + handler.setFormatter(formatter) + self.logger.addHandler(handler) + if self.verbose: + self.logger.setLevel(logging.INFO) + + self.output_logger = logging.getLogger(f"{self.name}.output") + self.output_logger.propagate = False # Do not propagate to parent loggers + if self.output is not None: + self.output_logger.setLevel(logging.DEBUG) + handler = logging.FileHandler(self.output, "a") + handler.setFormatter(formatter) + self.output_logger.addHandler(handler) + def add_patterns(self, regexes, pattern_container): ''' Adds regexes as patterns to the specified container @@ -212,7 +221,7 @@ def _is_excluded(self, content): ''' matching_exclude_pattern = self._search_patterns(content, self.exclude_patterns) if not self._search_patterns(content, self.include_patterns) and matching_exclude_pattern: - logging.info(f"Excluded {content!r} because of configured regex {matching_exclude_pattern!r}") + self.logger.info(f"Excluded {content!r} because of configured regex {matching_exclude_pattern!r}") return True return False From 530a1464e60fcb7b437ae160a207ac755ada8987 Mon Sep 17 00:00:00 2001 From: JWM <62558419+JokeWaumans@users.noreply.github.com> Date: Wed, 11 Dec 2024 11:23:09 +0100 Subject: [PATCH 37/86] Check if self.checkers exists instead of appending new checkers to the list Co-authored-by: Jasper Craeghs <28319872+JasperCraeghs@users.noreply.github.com> --- src/mlx/warnings/warnings_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index 97a13a43..889a1eb8 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -193,7 +193,7 @@ def _return_error_code(self, extra={}): if error_code == 0: error_code = 1 string_to_print = f"number of warnings ({self.count}) is {error_reason}." - if self.name not in ["polyspace", "coverity", "robot"]: + if getattr(self, "checkers", None): string_to_print += f" Returning error code {error_code}." self.logger.warning(string_to_print, extra=extra) return error_code From cf49d1a6817418d74f047d30d9946df680be87b4 Mon Sep 17 00:00:00 2001 From: JWM Date: Wed, 11 Dec 2024 12:56:56 +0100 Subject: [PATCH 38/86] Use dynamic padding for Polyspace checker --- src/mlx/warnings/polyspace_checker.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index cdcb7f8c..6a187b98 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -14,7 +14,7 @@ class PolyspaceChecker(WarningsChecker): name = 'polyspace' checkers = [] - logging_fmt = "{checker_name}: {family}: {column_name}: {check_value:<20} | {message:>60}" + logging_fmt = "{checker_name}: {column_info:<40} | {message}" def __init__(self, verbose, output): '''Constructor to set the default code quality description template to "Polyspace: $check"''' @@ -109,11 +109,7 @@ def return_check_limits(self): ''' count = 0 for checker in self.checkers: - extra = { - "family": checker.family_value, - "column_name": checker.column_name, - "check_value": checker.check_value - } + extra = {"column_info": f"{checker.family_value}: {checker.column_name}: {checker.check_value}"} count += checker.return_check_limits(extra) if count: print(f"Returning error code {count}.") @@ -132,6 +128,7 @@ def parse_config(self, config): {\n : ,\n min: ,\n max: \n} """ self.checkers = [] + padding = 0 for family_value, data in config.items(): if family_value == "enabled": continue @@ -150,7 +147,9 @@ def parse_config(self, config): continue column_name = key.lower() check_value = value.lower() - checker = PolyspaceFamilyChecker(family_value, column_name, check_value, verbose=self.verbose) + padding = max(padding, len(f"{family_value}: {column_name}: {check_value}")) + checker = PolyspaceFamilyChecker(family_value, column_name, check_value, verbose=self.verbose, + output=self.output) checker.parse_config(check) self.checkers.append(checker) if not (column_name and check_value): @@ -167,6 +166,11 @@ def parse_config(self, config): checker.cq_description_template = self.cq_description_template checker.cq_default_path = self.cq_default_path + self.logging_fmt = self.logging_fmt.replace("40", f"{padding}") + new_format = logging.Formatter(fmt=self.logging_fmt, style="{") + for handler in self.logger.handlers: + handler.setFormatter(new_format) + class PolyspaceFamilyChecker(WarningsChecker): name = 'polyspace' @@ -234,13 +238,16 @@ def check(self, content): ''' if content[self.column_name].lower() == self.check_value: if content["status"].lower() in ["not a defect", "justified"]: - self.logger.info(f"Excluded row {content!r} because the status is 'Not a defect' or 'Justified'", - extra={"checker_name": "Polyspace"}) + extra={"checker_name": "Polyspace", + "column_info": f"{self.family_value}: {self.column_name}: {self.check_value}",}) else: tab_sep_string = "\t".join(content.values()) if not self._is_excluded(tab_sep_string): self.count = self.count + 1 self.output_logger.debug(f'{self.family_value}: {self.column_name}: {self.check_value}', - extra={"checker_name": "Polyspace"}) + extra={"checker_name": "Polyspace", + "column_info": + f"{self.family_value}: {self.column_name}: {self.check_value}", + }) if self.cq_enabled and content["color"].lower() != "green": self.add_code_quality_finding(content) From bcfaeb4e01a32c31835bef12c8f3e7d85016b079 Mon Sep 17 00:00:00 2001 From: JWM Date: Wed, 11 Dec 2024 12:58:17 +0100 Subject: [PATCH 39/86] Shorten the message when excluded by using only the id of the row --- src/mlx/warnings/polyspace_checker.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index 6a187b98..92b97444 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -238,6 +238,8 @@ def check(self, content): ''' if content[self.column_name].lower() == self.check_value: if content["status"].lower() in ["not a defect", "justified"]: + self.logger.info(f"Excluded defect with ID {content.get('id', None)!r} because the status is " + "'Not a defect' or 'Justified'", extra={"checker_name": "Polyspace", "column_info": f"{self.family_value}: {self.column_name}: {self.check_value}",}) else: From d898d7cc8231c364c3e02066fd5e2ad8b417df08 Mon Sep 17 00:00:00 2001 From: JWM Date: Wed, 11 Dec 2024 12:59:05 +0100 Subject: [PATCH 40/86] Add output_logger to sub-checkers --- src/mlx/warnings/polyspace_checker.py | 1 + src/mlx/warnings/regex_checker.py | 1 + src/mlx/warnings/robot_checker.py | 1 + 3 files changed, 3 insertions(+) diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index 92b97444..62fd162d 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -195,6 +195,7 @@ def __init__(self, family_value, column_name, check_value, **kwargs): self.column_name = column_name self.check_value = check_value self.logger = logging.getLogger(self.name) + self.output_logger = logging.getLogger(f"{self.name}.output") @property def cq_description_template(self): diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index a18b2b13..fd5364db 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -193,6 +193,7 @@ def __init__(self, classification, **kwargs): super().__init__(**kwargs) self.classification = classification self.logger = logging.getLogger(self.name) + self.output_logger = logging.getLogger(f"{self.name}.output") @property def cq_description_template(self): diff --git a/src/mlx/warnings/robot_checker.py b/src/mlx/warnings/robot_checker.py index 33875bb0..2779deb2 100644 --- a/src/mlx/warnings/robot_checker.py +++ b/src/mlx/warnings/robot_checker.py @@ -116,6 +116,7 @@ def __init__(self, suite_name, check_suite_name=False, **kwargs): self.check_suite_name = check_suite_name self.is_valid_suite_name = False self.logger = logging.getLogger(self.name) + self.output_logger = logging.getLogger(f"{self.name}.output") def _check_testcase(self, testcase): """ Handles the check of a test case element by checking if the result is a failure/error. From 0f95808dd12bc8243f854bac24c64d5b7ee564f0 Mon Sep 17 00:00:00 2001 From: JWM Date: Wed, 11 Dec 2024 13:02:58 +0100 Subject: [PATCH 41/86] Fix padding of Coverity checker --- src/mlx/warnings/regex_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index fd5364db..e51bb350 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -76,7 +76,7 @@ def add_code_quality_finding(self, match): class CoverityChecker(RegexChecker): name = 'coverity' pattern = coverity_pattern - logging_fmt = "{checker_name}: {classification:<15} | {message:>60}" + logging_fmt = "{checker_name}: {classification:<14} | {message}" def __init__(self, verbose, output): super().__init__(verbose, output) From 8e4828a165a64e5e97704fb07538053f1a5c3553 Mon Sep 17 00:00:00 2001 From: JWM Date: Wed, 11 Dec 2024 13:17:30 +0100 Subject: [PATCH 42/86] Rename initiate_logger to initialize_loggers --- src/mlx/warnings/warnings.py | 2 +- src/mlx/warnings/warnings_checker.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index ab073e8c..00666e36 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -64,7 +64,7 @@ def activate_checker(self, checker): ''' checker.cq_enabled = self.cq_enabled and checker.name in ('doxygen', 'sphinx', 'xmlrunner', 'polyspace', 'coverity') self.activated_checkers[checker.name] = checker - checker.initiate_logger() + checker.initialize_loggers() def activate_checker_name(self, name): ''' diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index 889a1eb8..d17ca143 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -115,7 +115,7 @@ def check(self, content): ''' return - def initiate_logger(self): + def initialize_loggers(self): self.logger = logging.getLogger(self.name) self.logger.propagate = False # Do not propagate to parent loggers handler = logging.StreamHandler() From 65052edb2be6ab8576d9fdfe1c7aa3905cacd2db Mon Sep 17 00:00:00 2001 From: JWM Date: Wed, 11 Dec 2024 14:09:57 +0100 Subject: [PATCH 43/86] Delete verbose and output as arguments of checker instances --- src/mlx/warnings/polyspace_checker.py | 11 +++--- src/mlx/warnings/regex_checker.py | 18 +++++----- src/mlx/warnings/robot_checker.py | 7 ++-- src/mlx/warnings/warnings.py | 49 ++++++++++++++------------- src/mlx/warnings/warnings_checker.py | 24 ++++++------- 5 files changed, 54 insertions(+), 55 deletions(-) diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index 62fd162d..525006cd 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -16,9 +16,9 @@ class PolyspaceChecker(WarningsChecker): checkers = [] logging_fmt = "{checker_name}: {column_info:<40} | {message}" - def __init__(self, verbose, output): + def __init__(self): '''Constructor to set the default code quality description template to "Polyspace: $check"''' - super().__init__(verbose, output) + super().__init__() self._cq_description_template = Template('Polyspace: $check') @property @@ -148,8 +148,7 @@ def parse_config(self, config): column_name = key.lower() check_value = value.lower() padding = max(padding, len(f"{family_value}: {column_name}: {check_value}")) - checker = PolyspaceFamilyChecker(family_value, column_name, check_value, verbose=self.verbose, - output=self.output) + checker = PolyspaceFamilyChecker(family_value, column_name, check_value) checker.parse_config(check) self.checkers.append(checker) if not (column_name and check_value): @@ -182,7 +181,7 @@ class PolyspaceFamilyChecker(WarningsChecker): "orange": "major", } - def __init__(self, family_value, column_name, check_value, **kwargs): + def __init__(self, family_value, column_name, check_value): """Initialize the PolyspaceFamilyChecker Args: @@ -190,7 +189,7 @@ def __init__(self, family_value, column_name, check_value, **kwargs): column_name (str): The name of the column check_value (str): The value to check in the column """ - super().__init__(**kwargs) + super().__init__() self.family_value = family_value self.column_name = column_name self.check_value = check_value diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index e51bb350..76fa5591 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -78,15 +78,15 @@ class CoverityChecker(RegexChecker): pattern = coverity_pattern logging_fmt = "{checker_name}: {classification:<14} | {message}" - def __init__(self, verbose, output): - super().__init__(verbose, output) + def __init__(self): + super().__init__() self._cq_description_template = Template('Coverity: CID $cid: $checker') self.checkers = { - "unclassified": CoverityClassificationChecker("unclassified", verbose=self.verbose, output=output), - "pending": CoverityClassificationChecker("pending", verbose=self.verbose, output=output), - "bug": CoverityClassificationChecker("bug", verbose=self.verbose, output=output), - "intentional": CoverityClassificationChecker("intentional", verbose=self.verbose, output=output), - "false positive": CoverityClassificationChecker("false positive", verbose=self.verbose, output=output), + "unclassified": CoverityClassificationChecker("unclassified"), + "pending": CoverityClassificationChecker("pending"), + "bug": CoverityClassificationChecker("bug"), + "intentional": CoverityClassificationChecker("intentional"), + "false positive": CoverityClassificationChecker("false positive"), } @property @@ -184,13 +184,13 @@ class CoverityClassificationChecker(WarningsChecker): 'pending': 'critical', } - def __init__(self, classification, **kwargs): + def __init__(self, classification): """Initialize the CoverityClassificationChecker: Args: classification (str): The coverity classification """ - super().__init__(**kwargs) + super().__init__() self.classification = classification self.logger = logging.getLogger(self.name) self.output_logger = logging.getLogger(f"{self.name}.output") diff --git a/src/mlx/warnings/robot_checker.py b/src/mlx/warnings/robot_checker.py index 2779deb2..acce16b2 100644 --- a/src/mlx/warnings/robot_checker.py +++ b/src/mlx/warnings/robot_checker.py @@ -95,8 +95,7 @@ def parse_config(self, config): self.checkers = [] check_suite_name = config.get('check_suite_names', True) for suite_config in config['suites']: - checker = RobotSuiteChecker(suite_config['name'], check_suite_name=check_suite_name, - verbose=self.verbose) + checker = RobotSuiteChecker(suite_config['name'], check_suite_name=check_suite_name) checker.parse_config(suite_config) self.checkers.append(checker) @@ -104,14 +103,14 @@ def parse_config(self, config): class RobotSuiteChecker(JUnitChecker): name = 'robot' - def __init__(self, suite_name, check_suite_name=False, **kwargs): + def __init__(self, suite_name, check_suite_name=False): ''' Constructor Args: name (str): Name of the test suite to check the results of check_suite_name (bool): Whether to raise an error when no test in suite with given name is found ''' - super().__init__(**kwargs) + super().__init__() self.suite_name = suite_name self.check_suite_name = check_suite_name self.is_valid_suite_name = False diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index 00666e36..6d68b1a3 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -25,7 +25,7 @@ class WarningsPlugin: - def __init__(self, verbose=False, config_file=None, cq_enabled=False, output=""): + def __init__(self, verbose=False, config_file=None, cq_enabled=False, output=None): ''' Function for initializing the parsers @@ -33,14 +33,12 @@ def __init__(self, verbose=False, config_file=None, cq_enabled=False, output="") verbose (bool): optional - enable verbose logging config_file (Path): optional - configuration file with setup cq_enabled (bool): optional - enable generation of Code Quality report + output (Path/None): optional - path to the output file ''' self.activated_checkers = {} - self.verbose = verbose self.cq_enabled = cq_enabled - self.public_checkers = [SphinxChecker(self.verbose, output), DoxyChecker(self.verbose, output), - JUnitChecker(self.verbose, output), XMLRunnerChecker(self.verbose, output), - CoverityChecker(self.verbose, output), RobotChecker(self.verbose, output), - PolyspaceChecker(self.verbose, output)] + self.public_checkers = [SphinxChecker(), DoxyChecker(), JUnitChecker(), XMLRunnerChecker(), CoverityChecker(), + RobotChecker(), PolyspaceChecker()] if config_file: with open(config_file, encoding='utf-8') as open_file: @@ -48,37 +46,41 @@ def __init__(self, verbose=False, config_file=None, cq_enabled=False, output="") config = YAML().load(open_file) else: config = json.load(open_file) - self.config_parser(config) + self.config_parser(config, verbose=verbose, output=output) self._minimum = 0 self._maximum = 0 self.count = 0 self.printout = False - def activate_checker(self, checker): + def activate_checker(self, checker, verbose, output): ''' Activate additional checkers after initialization Args: checker (WarningsChecker): checker object + verbose (bool): enable verbose logging + output (Path/None): path to the output file ''' checker.cq_enabled = self.cq_enabled and checker.name in ('doxygen', 'sphinx', 'xmlrunner', 'polyspace', 'coverity') self.activated_checkers[checker.name] = checker - checker.initialize_loggers() + checker.initialize_loggers(verbose, output) - def activate_checker_name(self, name): + def activate_checker_name(self, name, verbose, output): ''' Activates checker by name Args: name (str): checker name + verbose (bool): enable verbose logging + output (Path/None): path to the output file Returns: WarningsChecker: activated checker object, or None when no checker with the given name exists ''' for checker in self.public_checkers: if checker.name == name: - self.activate_checker(checker) + self.activate_checker(checker, verbose, output) return checker else: logging.error(f"Checker {name} does not exist") @@ -202,11 +204,13 @@ def toggle_printout(self, printout): ''' self.printout = printout - def config_parser(self, config): + def config_parser(self, config, verbose, output): ''' Parsing configuration dict extracted by previously opened JSON or YAML file Args: config (dict): Content of configuration file + verbose (bool): enable verbose logging + output (Path/None): path to the output file ''' # activate checker for checker in self.public_checkers: @@ -214,7 +218,7 @@ def config_parser(self, config): checker_config = config[checker.name] try: if bool(checker_config['enabled']): - self.activate_checker(checker) + self.activate_checker(checker, verbose, output) checker.parse_config(checker_config) logging.info(f"Config parsing for {checker.name} completed") except KeyError as err: @@ -274,9 +278,8 @@ def warnings_wrapper(args): args = parser.parse_args(args) code_quality_enabled = bool(args.code_quality) - if args.output and args.output.exists(): + if args.output is not None and args.output.exists(): os.remove(args.output) - output = args.output if args.output else "" # Read config file if args.configfile is not None: @@ -286,21 +289,21 @@ def warnings_wrapper(args): logging.error("Configfile cannot be provided with other arguments") sys.exit(2) warnings = WarningsPlugin(verbose=args.verbose, config_file=args.configfile, cq_enabled=code_quality_enabled, - output=output) + output=args.output) else: - warnings = WarningsPlugin(verbose=args.verbose, cq_enabled=code_quality_enabled, output=output) + warnings = WarningsPlugin(verbose=args.verbose, cq_enabled=code_quality_enabled, output=args.output) if args.sphinx: - warnings.activate_checker_name('sphinx') + warnings.activate_checker_name('sphinx', verbose=args.verbose, output=args.output) if args.doxygen: - warnings.activate_checker_name('doxygen') + warnings.activate_checker_name('doxygen', verbose=args.verbose, output=args.output) if args.junit: - warnings.activate_checker_name('junit') + warnings.activate_checker_name('junit', verbose=args.verbose, output=args.output) if args.xmlrunner: - warnings.activate_checker_name('xmlrunner') + warnings.activate_checker_name('xmlrunner', verbose=args.verbose, output=args.output) if args.coverity: - warnings.activate_checker_name('coverity') + warnings.activate_checker_name('coverity', verbose=args.verbose, output=args.output) if args.robot: - robot_checker = warnings.activate_checker_name('robot') + robot_checker = warnings.activate_checker_name('robot', verbose=args.verbose, output=args.output) robot_checker.parse_config({ 'suites': [{'name': args.name, 'min': 0, 'max': 0}], 'check_suite_names': True, diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index d17ca143..63cc9f3e 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -34,15 +34,7 @@ class WarningsChecker: name = 'checker' logging_fmt = "{checker_name}: {message}" - def __init__(self, verbose, output): - ''' Constructor - - Args: - name (str): Name of the checker - verbose (bool): Enable/disable verbose logging - ''' - self.verbose = verbose - self.output = output + def __init__(self): self.count = 0 self._minimum = 0 self._maximum = 0 @@ -115,21 +107,27 @@ def check(self, content): ''' return - def initialize_loggers(self): + def initialize_loggers(self, verbose, output): + """Initialize the loggers + + Args: + verbose (bool): Enable/disable verbose logging + output (Path/None): The path to the output file + """ self.logger = logging.getLogger(self.name) self.logger.propagate = False # Do not propagate to parent loggers handler = logging.StreamHandler() formatter = logging.Formatter(fmt=self.logging_fmt, style="{") handler.setFormatter(formatter) self.logger.addHandler(handler) - if self.verbose: + if verbose: self.logger.setLevel(logging.INFO) self.output_logger = logging.getLogger(f"{self.name}.output") self.output_logger.propagate = False # Do not propagate to parent loggers - if self.output is not None: + if output is not None: self.output_logger.setLevel(logging.DEBUG) - handler = logging.FileHandler(self.output, "a") + handler = logging.FileHandler(output, "a") handler.setFormatter(formatter) self.output_logger.addHandler(handler) From 62bc59bf06aeb4434628432d3085f973aed03e31 Mon Sep 17 00:00:00 2001 From: JWM Date: Wed, 11 Dec 2024 14:31:37 +0100 Subject: [PATCH 44/86] Change the format of output for Polyspace --- src/mlx/warnings/polyspace_checker.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index 525006cd..f6aed30f 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -169,6 +169,8 @@ def parse_config(self, config): new_format = logging.Formatter(fmt=self.logging_fmt, style="{") for handler in self.logger.handlers: handler.setFormatter(new_format) + for handler in self.output_logger.handlers: + handler.setFormatter(new_format) class PolyspaceFamilyChecker(WarningsChecker): @@ -246,7 +248,7 @@ def check(self, content): tab_sep_string = "\t".join(content.values()) if not self._is_excluded(tab_sep_string): self.count = self.count + 1 - self.output_logger.debug(f'{self.family_value}: {self.column_name}: {self.check_value}', + self.output_logger.debug(f"ID {content.get('id', None)!r}", extra={"checker_name": "Polyspace", "column_info": f"{self.family_value}: {self.column_name}: {self.check_value}", From 60e3f6f72f4a0a864b53ccf9eef3ee1f95288b28 Mon Sep 17 00:00:00 2001 From: JWM Date: Wed, 11 Dec 2024 14:34:01 +0100 Subject: [PATCH 45/86] Add checker name to returning error code --- src/mlx/warnings/polyspace_checker.py | 2 +- src/mlx/warnings/regex_checker.py | 2 +- src/mlx/warnings/robot_checker.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index f6aed30f..e26c0cfa 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -112,7 +112,7 @@ def return_check_limits(self): extra = {"column_info": f"{checker.family_value}: {checker.column_name}: {checker.check_value}"} count += checker.return_check_limits(extra) if count: - print(f"Returning error code {count}.") + print(f"Polyspace: Returning error code {count}.") return count def parse_config(self, config): diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 76fa5591..969e99d3 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -130,7 +130,7 @@ def return_check_limits(self): } count += checker.return_check_limits(extra) if count: - print(f"Returning error code {count}.") + print(f"Coverity: Returning error code {count}.") return count def check(self, content): diff --git a/src/mlx/warnings/robot_checker.py b/src/mlx/warnings/robot_checker.py index acce16b2..29718e9f 100644 --- a/src/mlx/warnings/robot_checker.py +++ b/src/mlx/warnings/robot_checker.py @@ -88,7 +88,7 @@ def return_check_limits(self): } count += checker.return_check_limits(extra) if count: - print(f"Returning error code {count}.") + print(f"Robot: Returning error code {count}.") return count def parse_config(self, config): From 8d4d7e4afcfe3eed3ad0575779d06a1e4fb22ef5 Mon Sep 17 00:00:00 2001 From: JWM Date: Wed, 11 Dec 2024 15:54:58 +0100 Subject: [PATCH 46/86] Use repr to get the checker name --- src/mlx/warnings/junit_checker.py | 3 +++ src/mlx/warnings/warnings_checker.py | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/mlx/warnings/junit_checker.py b/src/mlx/warnings/junit_checker.py index 1639b225..f532a925 100644 --- a/src/mlx/warnings/junit_checker.py +++ b/src/mlx/warnings/junit_checker.py @@ -15,6 +15,9 @@ class JUnitChecker(WarningsChecker): name = 'junit' + def __repr__(self): + return "JUnit" + def check(self, content): ''' Function for counting the number of JUnit failures in a specific text diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index 63cc9f3e..1db8ecc2 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -45,6 +45,9 @@ def __init__(self): self.exclude_patterns = [] self.include_patterns = [] + def __repr__(self): + return self.name.capitalize() + @property def cq_findings(self): ''' List[dict]: list of code quality findings''' @@ -163,7 +166,7 @@ def return_check_limits(self, extra={}): int: 0 if the amount of warnings is within limits, the count of warnings otherwise (or 1 in case of a count of 0 warnings) ''' - extra["checker_name"] = self.name.capitalize() if self.name != "junit" else "JUnit" + extra["checker_name"] = repr(self) if self.count > self._maximum or self.count < self._minimum: return self._return_error_code(extra) elif self._minimum == self._maximum and self.count == self._maximum: From d2de66e1ba9af2ce03ae1a432a8e0b606c5b4178 Mon Sep 17 00:00:00 2001 From: JWM Date: Wed, 11 Dec 2024 16:30:25 +0100 Subject: [PATCH 47/86] Fix if statement; Use class variable subchecker to check if a class is a subchecker --- src/mlx/warnings/polyspace_checker.py | 1 + src/mlx/warnings/regex_checker.py | 1 + src/mlx/warnings/robot_checker.py | 1 + src/mlx/warnings/warnings_checker.py | 3 ++- 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index e26c0cfa..9547ff85 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -175,6 +175,7 @@ def parse_config(self, config): class PolyspaceFamilyChecker(WarningsChecker): name = 'polyspace' + subchecker = True code_quality_severity = { "impact: high": "critical", "impact: medium": "major", diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 969e99d3..8792de29 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -176,6 +176,7 @@ def parse_config(self, config): class CoverityClassificationChecker(WarningsChecker): name = 'coverity' + subchecker = True SEVERITY_MAP = { 'false positive': 'info', 'intentional': 'info', diff --git a/src/mlx/warnings/robot_checker.py b/src/mlx/warnings/robot_checker.py index 29718e9f..b1ccda5b 100644 --- a/src/mlx/warnings/robot_checker.py +++ b/src/mlx/warnings/robot_checker.py @@ -102,6 +102,7 @@ def parse_config(self, config): class RobotSuiteChecker(JUnitChecker): name = 'robot' + subchecker = True def __init__(self, suite_name, check_suite_name=False): ''' Constructor diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index 1db8ecc2..5c3743e7 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -32,6 +32,7 @@ def substitute_envvar(checker_config, keys): class WarningsChecker: name = 'checker' + subchecker = False logging_fmt = "{checker_name}: {message}" def __init__(self): @@ -194,7 +195,7 @@ def _return_error_code(self, extra={}): if error_code == 0: error_code = 1 string_to_print = f"number of warnings ({self.count}) is {error_reason}." - if getattr(self, "checkers", None): + if not self.subchecker: string_to_print += f" Returning error code {error_code}." self.logger.warning(string_to_print, extra=extra) return error_code From 8733d39c04e6d55100541960eef6cde8c1d64781 Mon Sep 17 00:00:00 2001 From: JWM Date: Wed, 11 Dec 2024 16:43:27 +0100 Subject: [PATCH 48/86] Document that subcheckers are responsible for printing returning error code --- src/mlx/warnings/warnings_checker.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index 5c3743e7..d3fbd579 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -159,12 +159,14 @@ def return_count(self): def return_check_limits(self, extra={}): ''' Function for checking whether the warning count is within the configured limits + A checker instance with sub-checkers is responsible for printing 'Returning error code X.' + when the exit code is not 0. Args: extra (dict): Extra arguments for the logger. Returns: - int: 0 if the amount of warnings is within limits, the count of warnings otherwise + int: 0 if the amount of warnings is within limits, the count of (the sum of sub-checker) warnings otherwise (or 1 in case of a count of 0 warnings) ''' extra["checker_name"] = repr(self) From 977a30d2a2dd028bc1f468727c8d70c3713ca509 Mon Sep 17 00:00:00 2001 From: JWM Date: Wed, 11 Dec 2024 17:43:44 +0100 Subject: [PATCH 49/86] Use "mlx.warnings.warnings" logger for general logs; use repr(self) --- src/mlx/warnings/junit_checker.py | 7 ++++--- src/mlx/warnings/polyspace_checker.py | 14 ++++++-------- src/mlx/warnings/regex_checker.py | 17 ++++++++++------- src/mlx/warnings/robot_checker.py | 8 ++++++-- src/mlx/warnings/warnings.py | 25 ++++++++++++++++--------- src/mlx/warnings/warnings_checker.py | 11 +++++++---- 6 files changed, 49 insertions(+), 33 deletions(-) diff --git a/src/mlx/warnings/junit_checker.py b/src/mlx/warnings/junit_checker.py index f532a925..72377c53 100644 --- a/src/mlx/warnings/junit_checker.py +++ b/src/mlx/warnings/junit_checker.py @@ -11,6 +11,7 @@ from .warnings_checker import WarningsChecker +LOGGER = logging.getLogger("mlx.warnings.warnings") class JUnitChecker(WarningsChecker): name = 'junit' @@ -35,7 +36,7 @@ def check(self, content): suites.update_statistics() self.count += suites.failures + suites.errors - amount_to_exclude except etree.ParseError as err: - logging.error(err) + LOGGER.error(f"{repr(self)}: {err}") @staticmethod def prepare_tree(root_input): @@ -67,10 +68,10 @@ def _check_testcase(self, testcase): int: 1 if a failure/error is to be subtracted from the final count, 0 otherwise """ if isinstance(testcase.result, (Failure, Error)): - if self._is_excluded(testcase.result.message): + extra={"checker_name": repr(self)} + if self._is_excluded(testcase.result.message, extra=extra): return 1 string = f'{testcase.classname}.{testcase.name}' - extra = {"checker_name": self.name.capitalize() if self.name != "junit" else "JUnit"} self.output_logger.debug(f'{string}: {testcase.result.message}', extra=extra) self.logger.info(string, extra=extra) return 0 diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index 9547ff85..9f5a06f1 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -112,7 +112,7 @@ def return_check_limits(self): extra = {"column_info": f"{checker.family_value}: {checker.column_name}: {checker.check_value}"} count += checker.return_check_limits(extra) if count: - print(f"Polyspace: Returning error code {count}.") + print(f"{repr(self)}: Returning error code {count}.") return count def parse_config(self, config): @@ -240,19 +240,17 @@ def check(self, content): content (dict): The row of the TSV file ''' if content[self.column_name].lower() == self.check_value: + extra = {"checker_name": repr(self), + "column_info": f"{self.family_value}: {self.column_name}: {self.check_value}",} if content["status"].lower() in ["not a defect", "justified"]: self.logger.info(f"Excluded defect with ID {content.get('id', None)!r} because the status is " "'Not a defect' or 'Justified'", - extra={"checker_name": "Polyspace", - "column_info": f"{self.family_value}: {self.column_name}: {self.check_value}",}) + extra=extra) else: tab_sep_string = "\t".join(content.values()) - if not self._is_excluded(tab_sep_string): + if not self._is_excluded(tab_sep_string, extra=extra): self.count = self.count + 1 self.output_logger.debug(f"ID {content.get('id', None)!r}", - extra={"checker_name": "Polyspace", - "column_info": - f"{self.family_value}: {self.column_name}: {self.check_value}", - }) + extra=extra) if self.cq_enabled and content["color"].lower() != "green": self.add_code_quality_finding(content) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 8792de29..cffded20 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -19,6 +19,8 @@ COVERITY_WARNING_REGEX = r"(?P[\w\.\\/\- ]+)(:(?P\d+)(:(?P\d+))?)?: ?CID (?P\d+) \(#(?P\d+) of (?P\d+)\): (?P.+): (?P[\w ]+),.+" coverity_pattern = re.compile(COVERITY_WARNING_REGEX) +LOGGER = logging.getLogger("mlx.warnings.warnings") + class RegexChecker(WarningsChecker): name = 'regex' @@ -46,7 +48,7 @@ def check(self, content): if self._is_excluded(match_string): continue self.count += 1 - extra = {"checker_name": self.name.capitalize() if self.name != "junit" else "JUnit"} + extra = {"checker_name": repr(self)} self.output_logger.debug(match_string, extra=extra) self.logger.info(match_string, extra=extra) if self.cq_enabled: @@ -130,7 +132,7 @@ def return_check_limits(self): } count += checker.return_check_limits(extra) if count: - print(f"Coverity: Returning error code {count}.") + print(f"{repr(self)}: Returning error code {count}.") return count def check(self, content): @@ -151,7 +153,7 @@ def check(self, content): checker.cq_default_path = self.cq_default_path checker.check(match) else: - logging.warning(f"Unrecognized classification {match.group('classification')!r}") + LOGGER.warning(f"{repr(self)}: Unrecognized classification {match.group('classification')!r}") def parse_config(self, config): """Process configuration @@ -171,7 +173,7 @@ def parse_config(self, config): if classification_key in self.checkers: self.checkers[classification_key].parse_config(checker_config) else: - logging.warning(f"Unrecognized classification {classification!r}") + LOGGER.warning(f"{repr(self)}: Unrecognized classification {classification!r}") class CoverityClassificationChecker(WarningsChecker): @@ -238,12 +240,13 @@ def check(self, content): Args: content (re.Match): The regex match ''' + extra={"checker_name": repr(self)} match_string = content.group(0).strip() - if not self._is_excluded(match_string) and (content.group('curr') == content.group('max')): + if not self._is_excluded(match_string, extra) and (content.group('curr') == content.group('max')): self.count += 1 self.output_logger.debug(match_string, - extra={"checker_name": "Coverity"}) - self.logger.info(match_string, extra={"checker_name": "Coverity"}) + extra=extra) + self.logger.info(match_string, extra=extra) if self.cq_enabled: self.add_code_quality_finding(content) diff --git a/src/mlx/warnings/robot_checker.py b/src/mlx/warnings/robot_checker.py index b1ccda5b..33830e95 100644 --- a/src/mlx/warnings/robot_checker.py +++ b/src/mlx/warnings/robot_checker.py @@ -88,7 +88,7 @@ def return_check_limits(self): } count += checker.return_check_limits(extra) if count: - print(f"Robot: Returning error code {count}.") + print(f"{repr(self)}: Returning error code {count}.") return count def parse_config(self, config): @@ -148,5 +148,9 @@ def check(self, content): """ super().check(content) if not self.is_valid_suite_name and self.check_suite_name: - logging.error(f'No suite with name {self.suite_name!r} found. Returning error code -1.') + self.logger.error(f'No suite with name {self.suite_name!r} found. Returning error code -1.', + extra={ + "checker_name": repr(self), + "suite_name": self.suite_name + }) sys.exit(-1) diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index 6d68b1a3..f1506a5d 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -22,6 +22,8 @@ __version__ = distribution('mlx.warnings').version +LOGGER = logging.getLogger(__name__) +logging.basicConfig(format="%(levelname)s: %(message)s") class WarningsPlugin: @@ -35,6 +37,8 @@ def __init__(self, verbose=False, config_file=None, cq_enabled=False, output=Non cq_enabled (bool): optional - enable generation of Code Quality report output (Path/None): optional - path to the output file ''' + if verbose: + LOGGER.setLevel(logging.INFO) self.activated_checkers = {} self.cq_enabled = cq_enabled self.public_checkers = [SphinxChecker(), DoxyChecker(), JUnitChecker(), XMLRunnerChecker(), CoverityChecker(), @@ -83,7 +87,7 @@ def activate_checker_name(self, name, verbose, output): self.activate_checker(checker, verbose, output) return checker else: - logging.error(f"Checker {name} does not exist") + LOGGER.error(f"Checker {name} does not exist") def get_checker(self, name): ''' Get checker by name @@ -105,7 +109,7 @@ def check(self, content): if self.printout: print(content) if not self.activated_checkers: - logging.error("No checkers activated. Please use activate_checker function") + LOGGER.error("No checkers activated. Please use activate_checker function") else: for checker in self.activated_checkers.values(): if checker.name == "polyspace": @@ -121,7 +125,7 @@ def check_logfile(self, file): content (_io.TextIOWrapper): The open file to parse ''' if not self.activated_checkers: - logging.error("No checkers activated. Please use activate_checker function") + LOGGER.error("No checkers activated. Please use activate_checker function") elif "polyspace" in self.activated_checkers: if len(self.activated_checkers) > 1: raise WarningsConfigError("Polyspace checker cannot be combined with other warnings checkers") @@ -220,7 +224,7 @@ def config_parser(self, config, verbose, output): if bool(checker_config['enabled']): self.activate_checker(checker, verbose, output) checker.parse_config(checker_config) - logging.info(f"Config parsing for {checker.name} completed") + LOGGER.info(f"Config parsing for {checker.name} completed") except KeyError as err: raise WarningsConfigError(f"Incomplete config. Missing: {err}") from err @@ -281,12 +285,15 @@ def warnings_wrapper(args): if args.output is not None and args.output.exists(): os.remove(args.output) + if args.verbose: + LOGGER.setLevel(logging.INFO) + # Read config file if args.configfile is not None: checker_flags = args.sphinx or args.doxygen or args.junit or args.coverity or args.xmlrunner or args.robot warning_args = args.maxwarnings or args.minwarnings or args.exact_warnings if checker_flags or warning_args: - logging.error("Configfile cannot be provided with other arguments") + LOGGER.error("Configfile cannot be provided with other arguments") sys.exit(2) warnings = WarningsPlugin(verbose=args.verbose, config_file=args.configfile, cq_enabled=code_quality_enabled, output=args.output) @@ -310,7 +317,7 @@ def warnings_wrapper(args): }) if args.exact_warnings: if args.maxwarnings | args.minwarnings: - logging.error("expected-warnings cannot be provided with maxwarnings or minwarnings") + LOGGER.error("expected-warnings cannot be provided with maxwarnings or minwarnings") sys.exit(2) warnings.configure_maximum(args.exact_warnings) warnings.configure_minimum(args.exact_warnings) @@ -334,7 +341,7 @@ def warnings_wrapper(args): return retval else: if args.flags: - logging.warning(f"Some keyword arguments have been ignored because they followed positional arguments: " + LOGGER.warning(f"Some keyword arguments have been ignored because they followed positional arguments: " f"{' '.join(args.flags)!r}") retval = warnings_logfile(warnings, args.logfile) if retval != 0: @@ -384,7 +391,7 @@ def warnings_command(warnings, cmd): return proc.returncode except OSError as err: if err.errno == errno.ENOENT: - logging.error("It seems like program " + str(cmd) + " is not installed.") + LOGGER.error("It seems like program " + str(cmd) + " is not installed.") raise @@ -411,7 +418,7 @@ def warnings_logfile(warnings, log): with open(logfile) as file: warnings.check_logfile(file) else: - logging.error(f"FILE: {file_wildcard} does not exist") + LOGGER.error(f"FILE: {file_wildcard} does not exist") return 1 return 0 diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index d3fbd579..133de531 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -163,7 +163,7 @@ def return_check_limits(self, extra={}): when the exit code is not 0. Args: - extra (dict): Extra arguments for the logger. + extra (dict): optional - Extra arguments for the logger. Returns: int: 0 if the amount of warnings is within limits, the count of (the sum of sub-checker) warnings otherwise @@ -179,7 +179,7 @@ def return_check_limits(self, extra={}): "Well done.", extra=extra) return 0 - def _return_error_code(self, extra={}): + def _return_error_code(self, extra): ''' Function for determining the return code and message on failure Args: @@ -212,20 +212,23 @@ def parse_config(self, config): if 'cq_description_template' in config: self.cq_description_template = Template(config['cq_description_template']) - def _is_excluded(self, content): + def _is_excluded(self, content, extra={}): ''' Checks if the specific text must be excluded based on the configured regexes for exclusion and inclusion. Inclusion has priority over exclusion. Args: content (str): The content to parse + extra (dict): optional - Extra arguments for the logger. Returns: bool: True for exclusion, False for inclusion ''' + extra["checker_name"] = repr(self) matching_exclude_pattern = self._search_patterns(content, self.exclude_patterns) if not self._search_patterns(content, self.include_patterns) and matching_exclude_pattern: - self.logger.info(f"Excluded {content!r} because of configured regex {matching_exclude_pattern!r}") + self.logger.info(f"Excluded {content!r} because of configured regex {matching_exclude_pattern!r}", + extra=extra) return True return False From c029e0fcbf647b8f1d503bd3e9fb780b825541de Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 12 Dec 2024 10:30:03 +0100 Subject: [PATCH 50/86] Fix verbose for Coverity --- src/mlx/warnings/regex_checker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index cffded20..95412ca3 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -240,7 +240,7 @@ def check(self, content): Args: content (re.Match): The regex match ''' - extra={"checker_name": repr(self)} + extra={"checker_name": repr(self), "classification": self.classification} match_string = content.group(0).strip() if not self._is_excluded(match_string, extra) and (content.group('curr') == content.group('max')): self.count += 1 From 6869e7059e88b58f4a1fd0c1093550c86ccb658b Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 12 Dec 2024 10:46:22 +0100 Subject: [PATCH 51/86] Use property name_rpr instead of class __repr__ --- src/mlx/warnings/junit_checker.py | 11 ++++++----- src/mlx/warnings/polyspace_checker.py | 4 ++-- src/mlx/warnings/regex_checker.py | 10 +++++----- src/mlx/warnings/robot_checker.py | 4 ++-- src/mlx/warnings/warnings_checker.py | 7 ++++--- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/mlx/warnings/junit_checker.py b/src/mlx/warnings/junit_checker.py index 72377c53..26d27227 100644 --- a/src/mlx/warnings/junit_checker.py +++ b/src/mlx/warnings/junit_checker.py @@ -16,9 +16,6 @@ class JUnitChecker(WarningsChecker): name = 'junit' - def __repr__(self): - return "JUnit" - def check(self, content): ''' Function for counting the number of JUnit failures in a specific text @@ -36,7 +33,11 @@ def check(self, content): suites.update_statistics() self.count += suites.failures + suites.errors - amount_to_exclude except etree.ParseError as err: - LOGGER.error(f"{repr(self)}: {err}") + LOGGER.error(f"{self.name_repr}: {err}") + + @property + def name_repr(self): + return "JUnit" @staticmethod def prepare_tree(root_input): @@ -68,7 +69,7 @@ def _check_testcase(self, testcase): int: 1 if a failure/error is to be subtracted from the final count, 0 otherwise """ if isinstance(testcase.result, (Failure, Error)): - extra={"checker_name": repr(self)} + extra={"checker_name": self.name_repr} if self._is_excluded(testcase.result.message, extra=extra): return 1 string = f'{testcase.classname}.{testcase.name}' diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index 9f5a06f1..8e3e5c9c 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -112,7 +112,7 @@ def return_check_limits(self): extra = {"column_info": f"{checker.family_value}: {checker.column_name}: {checker.check_value}"} count += checker.return_check_limits(extra) if count: - print(f"{repr(self)}: Returning error code {count}.") + print(f"{self.name_repr}: Returning error code {count}.") return count def parse_config(self, config): @@ -240,7 +240,7 @@ def check(self, content): content (dict): The row of the TSV file ''' if content[self.column_name].lower() == self.check_value: - extra = {"checker_name": repr(self), + extra = {"checker_name": self.name_repr, "column_info": f"{self.family_value}: {self.column_name}: {self.check_value}",} if content["status"].lower() in ["not a defect", "justified"]: self.logger.info(f"Excluded defect with ID {content.get('id', None)!r} because the status is " diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 95412ca3..47f286cb 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -48,7 +48,7 @@ def check(self, content): if self._is_excluded(match_string): continue self.count += 1 - extra = {"checker_name": repr(self)} + extra = {"checker_name": self.name_repr} self.output_logger.debug(match_string, extra=extra) self.logger.info(match_string, extra=extra) if self.cq_enabled: @@ -132,7 +132,7 @@ def return_check_limits(self): } count += checker.return_check_limits(extra) if count: - print(f"{repr(self)}: Returning error code {count}.") + print(f"{self.name_repr}: Returning error code {count}.") return count def check(self, content): @@ -153,7 +153,7 @@ def check(self, content): checker.cq_default_path = self.cq_default_path checker.check(match) else: - LOGGER.warning(f"{repr(self)}: Unrecognized classification {match.group('classification')!r}") + LOGGER.warning(f"{self.name_repr}: Unrecognized classification {match.group('classification')!r}") def parse_config(self, config): """Process configuration @@ -173,7 +173,7 @@ def parse_config(self, config): if classification_key in self.checkers: self.checkers[classification_key].parse_config(checker_config) else: - LOGGER.warning(f"{repr(self)}: Unrecognized classification {classification!r}") + LOGGER.warning(f"{self.name_repr}: Unrecognized classification {classification!r}") class CoverityClassificationChecker(WarningsChecker): @@ -240,7 +240,7 @@ def check(self, content): Args: content (re.Match): The regex match ''' - extra={"checker_name": repr(self), "classification": self.classification} + extra={"checker_name": self.name_repr, "classification": self.classification} match_string = content.group(0).strip() if not self._is_excluded(match_string, extra) and (content.group('curr') == content.group('max')): self.count += 1 diff --git a/src/mlx/warnings/robot_checker.py b/src/mlx/warnings/robot_checker.py index 33830e95..46bb8edf 100644 --- a/src/mlx/warnings/robot_checker.py +++ b/src/mlx/warnings/robot_checker.py @@ -88,7 +88,7 @@ def return_check_limits(self): } count += checker.return_check_limits(extra) if count: - print(f"{repr(self)}: Returning error code {count}.") + print(f"{self.name_repr}: Returning error code {count}.") return count def parse_config(self, config): @@ -150,7 +150,7 @@ def check(self, content): if not self.is_valid_suite_name and self.check_suite_name: self.logger.error(f'No suite with name {self.suite_name!r} found. Returning error code -1.', extra={ - "checker_name": repr(self), + "checker_name": self.name_repr, "suite_name": self.suite_name }) sys.exit(-1) diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index 133de531..9082dcaa 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -46,7 +46,8 @@ def __init__(self): self.exclude_patterns = [] self.include_patterns = [] - def __repr__(self): + @property + def name_repr(self): return self.name.capitalize() @property @@ -169,7 +170,7 @@ def return_check_limits(self, extra={}): int: 0 if the amount of warnings is within limits, the count of (the sum of sub-checker) warnings otherwise (or 1 in case of a count of 0 warnings) ''' - extra["checker_name"] = repr(self) + extra["checker_name"] = self.name_repr if self.count > self._maximum or self.count < self._minimum: return self._return_error_code(extra) elif self._minimum == self._maximum and self.count == self._maximum: @@ -224,7 +225,7 @@ def _is_excluded(self, content, extra={}): Returns: bool: True for exclusion, False for inclusion ''' - extra["checker_name"] = repr(self) + extra["checker_name"] = self.name_repr matching_exclude_pattern = self._search_patterns(content, self.exclude_patterns) if not self._search_patterns(content, self.include_patterns) and matching_exclude_pattern: self.logger.info(f"Excluded {content!r} because of configured regex {matching_exclude_pattern!r}", From 5cae4b86e9971f83dedd863b61b9197f0a38fd84 Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 12 Dec 2024 11:51:04 +0100 Subject: [PATCH 52/86] Move initialization of loggers to init of WarningsChecker by adding verbose and output arguments; Delete verbose, config_file and output arguments for WarningsPlugin --- src/mlx/warnings/polyspace_checker.py | 4 +- src/mlx/warnings/regex_checker.py | 4 +- src/mlx/warnings/warnings.py | 88 +++++++++++++-------------- src/mlx/warnings/warnings_checker.py | 51 ++++++++-------- 4 files changed, 71 insertions(+), 76 deletions(-) diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index 8e3e5c9c..7511a405 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -16,9 +16,9 @@ class PolyspaceChecker(WarningsChecker): checkers = [] logging_fmt = "{checker_name}: {column_info:<40} | {message}" - def __init__(self): + def __init__(self, verbose=False, output=None): '''Constructor to set the default code quality description template to "Polyspace: $check"''' - super().__init__() + super().__init__(verbose=verbose, output=output) self._cq_description_template = Template('Polyspace: $check') @property diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 47f286cb..5bf7bd3e 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -80,8 +80,8 @@ class CoverityChecker(RegexChecker): pattern = coverity_pattern logging_fmt = "{checker_name}: {classification:<14} | {message}" - def __init__(self): - super().__init__() + def __init__(self, verbose=False, output=None): + super().__init__(verbose=verbose, output=output) self._cq_description_template = Template('Coverity: CID $cid: $checker') self.checkers = { "unclassified": CoverityClassificationChecker("unclassified"), diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index f1506a5d..a1347790 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -27,64 +27,50 @@ class WarningsPlugin: - def __init__(self, verbose=False, config_file=None, cq_enabled=False, output=None): + def __init__(self, cq_enabled=False): ''' Function for initializing the parsers Args: - verbose (bool): optional - enable verbose logging - config_file (Path): optional - configuration file with setup cq_enabled (bool): optional - enable generation of Code Quality report - output (Path/None): optional - path to the output file ''' - if verbose: - LOGGER.setLevel(logging.INFO) self.activated_checkers = {} self.cq_enabled = cq_enabled - self.public_checkers = [SphinxChecker(), DoxyChecker(), JUnitChecker(), XMLRunnerChecker(), CoverityChecker(), - RobotChecker(), PolyspaceChecker()] - - if config_file: - with open(config_file, encoding='utf-8') as open_file: - if config_file.suffix.lower().startswith('.y'): - config = YAML().load(open_file) - else: - config = json.load(open_file) - self.config_parser(config, verbose=verbose, output=output) - + self.public_checkers = (SphinxChecker, DoxyChecker, JUnitChecker, XMLRunnerChecker, CoverityChecker, + RobotChecker, PolyspaceChecker) self._minimum = 0 self._maximum = 0 self.count = 0 self.printout = False - def activate_checker(self, checker, verbose, output): + def activate_checker(self, checker_type, *args): ''' Activate additional checkers after initialization Args: - checker (WarningsChecker): checker object - verbose (bool): enable verbose logging - output (Path/None): path to the output file + checker_type (WarningsChecker): checker class + + Return: + WarningsChecker: activated checker object ''' + checker = checker_type(*args) checker.cq_enabled = self.cq_enabled and checker.name in ('doxygen', 'sphinx', 'xmlrunner', 'polyspace', 'coverity') self.activated_checkers[checker.name] = checker - checker.initialize_loggers(verbose, output) + return checker - def activate_checker_name(self, name, verbose, output): + def activate_checker_name(self, name, *args): ''' Activates checker by name Args: name (str): checker name - verbose (bool): enable verbose logging - output (Path/None): path to the output file Returns: WarningsChecker: activated checker object, or None when no checker with the given name exists ''' - for checker in self.public_checkers: - if checker.name == name: - self.activate_checker(checker, verbose, output) + for checker_type in self.public_checkers: + if checker_type.name == name: + checker = self.activate_checker(checker_type, *args) return checker else: LOGGER.error(f"Checker {name} does not exist") @@ -212,19 +198,26 @@ def config_parser(self, config, verbose, output): ''' Parsing configuration dict extracted by previously opened JSON or YAML file Args: - config (dict): Content of configuration file + config (dict/Path): Content or path of configuration file verbose (bool): enable verbose logging output (Path/None): path to the output file ''' + if isinstance(config, Path): + with open(config, encoding='utf-8') as open_file: + if config.suffix.lower().startswith('.y'): + config = YAML().load(open_file) + else: + config = json.load(open_file) + # activate checker - for checker in self.public_checkers: - if checker.name in config: - checker_config = config[checker.name] + for checker_type in self.public_checkers: + if checker_type.name in config: + checker_config = config[checker_type.name] try: if bool(checker_config['enabled']): - self.activate_checker(checker, verbose, output) + checker = self.activate_checker(checker_type, verbose, output) checker.parse_config(checker_config) - LOGGER.info(f"Config parsing for {checker.name} completed") + LOGGER.info(f"{checker.name_repr}: Config parsing completed") except KeyError as err: raise WarningsConfigError(f"Incomplete config. Missing: {err}") from err @@ -288,6 +281,8 @@ def warnings_wrapper(args): if args.verbose: LOGGER.setLevel(logging.INFO) + checker_options = [args.verbose, args.output] + warnings = WarningsPlugin(cq_enabled=code_quality_enabled) # Read config file if args.configfile is not None: checker_flags = args.sphinx or args.doxygen or args.junit or args.coverity or args.xmlrunner or args.robot @@ -295,26 +290,25 @@ def warnings_wrapper(args): if checker_flags or warning_args: LOGGER.error("Configfile cannot be provided with other arguments") sys.exit(2) - warnings = WarningsPlugin(verbose=args.verbose, config_file=args.configfile, cq_enabled=code_quality_enabled, - output=args.output) + warnings.config_parser(args.configfile, *checker_options) else: - warnings = WarningsPlugin(verbose=args.verbose, cq_enabled=code_quality_enabled, output=args.output) if args.sphinx: - warnings.activate_checker_name('sphinx', verbose=args.verbose, output=args.output) + warnings.activate_checker_name('sphinx', *checker_options) if args.doxygen: - warnings.activate_checker_name('doxygen', verbose=args.verbose, output=args.output) + warnings.activate_checker_name('doxygen', *checker_options) if args.junit: - warnings.activate_checker_name('junit', verbose=args.verbose, output=args.output) + warnings.activate_checker_name('junit', *checker_options) if args.xmlrunner: - warnings.activate_checker_name('xmlrunner', verbose=args.verbose, output=args.output) + warnings.activate_checker_name('xmlrunner', *checker_options) if args.coverity: - warnings.activate_checker_name('coverity', verbose=args.verbose, output=args.output) + warnings.activate_checker_name('coverity', *checker_options) if args.robot: - robot_checker = warnings.activate_checker_name('robot', verbose=args.verbose, output=args.output) - robot_checker.parse_config({ - 'suites': [{'name': args.name, 'min': 0, 'max': 0}], - 'check_suite_names': True, - }) + robot_checker = warnings.activate_checker_name('robot', *checker_options) + if robot_checker is not None: + robot_checker.parse_config({ + 'suites': [{'name': args.name, 'min': 0, 'max': 0}], + 'check_suite_names': True, + }) if args.exact_warnings: if args.maxwarnings | args.minwarnings: LOGGER.error("expected-warnings cannot be provided with maxwarnings or minwarnings") diff --git a/src/mlx/warnings/warnings_checker.py b/src/mlx/warnings/warnings_checker.py index 9082dcaa..9b73e195 100644 --- a/src/mlx/warnings/warnings_checker.py +++ b/src/mlx/warnings/warnings_checker.py @@ -35,7 +35,13 @@ class WarningsChecker: subchecker = False logging_fmt = "{checker_name}: {message}" - def __init__(self): + def __init__(self, verbose=False, output=None): + """Constructor + + Args: + verbose (bool, optional): Enable/disable verbose logging + output (Path/None, optional): The path to the output file + """ self.count = 0 self._minimum = 0 self._maximum = 0 @@ -46,6 +52,25 @@ def __init__(self): self.exclude_patterns = [] self.include_patterns = [] + if not self.subchecker: + self.logger = logging.getLogger(self.name) + self.logger.propagate = False # Do not propagate to parent loggers + handler = logging.StreamHandler() + formatter = logging.Formatter(fmt=self.logging_fmt, style="{") + handler.setFormatter(formatter) + self.logger.addHandler(handler) + if verbose: + self.logger.setLevel(logging.INFO) + + self.output_logger = logging.getLogger(f"{self.name}.output") + self.output_logger.propagate = False # Do not propagate to parent loggers + if output is not None: + self.output_logger.setLevel(logging.DEBUG) + handler = logging.FileHandler(output, "a") + handler.setFormatter(formatter) + self.output_logger.addHandler(handler) + + @property def name_repr(self): return self.name.capitalize() @@ -112,30 +137,6 @@ def check(self, content): ''' return - def initialize_loggers(self, verbose, output): - """Initialize the loggers - - Args: - verbose (bool): Enable/disable verbose logging - output (Path/None): The path to the output file - """ - self.logger = logging.getLogger(self.name) - self.logger.propagate = False # Do not propagate to parent loggers - handler = logging.StreamHandler() - formatter = logging.Formatter(fmt=self.logging_fmt, style="{") - handler.setFormatter(formatter) - self.logger.addHandler(handler) - if verbose: - self.logger.setLevel(logging.INFO) - - self.output_logger = logging.getLogger(f"{self.name}.output") - self.output_logger.propagate = False # Do not propagate to parent loggers - if output is not None: - self.output_logger.setLevel(logging.DEBUG) - handler = logging.FileHandler(output, "a") - handler.setFormatter(formatter) - self.output_logger.addHandler(handler) - def add_patterns(self, regexes, pattern_container): ''' Adds regexes as patterns to the specified container From ff0148ec8a5aec94e3a6b1968d2715154c6fb0c1 Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 12 Dec 2024 12:05:23 +0100 Subject: [PATCH 53/86] Add verbose prints of the ID of defects when a defect is counted in Polyspace --- src/mlx/warnings/polyspace_checker.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index 7511a405..351efab1 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -250,7 +250,7 @@ def check(self, content): tab_sep_string = "\t".join(content.values()) if not self._is_excluded(tab_sep_string, extra=extra): self.count = self.count + 1 - self.output_logger.debug(f"ID {content.get('id', None)!r}", - extra=extra) + self.output_logger.debug(f"ID {content.get('id', None)!r}", extra=extra) + self.logger.info(f"ID {content.get('id', None)!r}", extra=extra) if self.cq_enabled and content["color"].lower() != "green": self.add_code_quality_finding(content) From 5602fc42544d3dc07917ba99a7f0f1c58930d969 Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 12 Dec 2024 13:10:43 +0100 Subject: [PATCH 54/86] Make verbose and output arguments optional --- src/mlx/warnings/warnings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index a1347790..0946c135 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -194,13 +194,13 @@ def toggle_printout(self, printout): ''' self.printout = printout - def config_parser(self, config, verbose, output): + def config_parser(self, config, verbose=False, output=None): ''' Parsing configuration dict extracted by previously opened JSON or YAML file Args: config (dict/Path): Content or path of configuration file - verbose (bool): enable verbose logging - output (Path/None): path to the output file + verbose (bool, optional): enable verbose logging + output (Path/None, optional): path to the output file ''' if isinstance(config, Path): with open(config, encoding='utf-8') as open_file: From 2bf5d10cbe8a9cc9259ecde6031a71ea66b55367 Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 12 Dec 2024 17:54:48 +0100 Subject: [PATCH 55/86] Fix name_repr and add extra argument to _check_testcase for robot (inherits from JUnitChecker) --- src/mlx/warnings/junit_checker.py | 4 ++-- src/mlx/warnings/robot_checker.py | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/mlx/warnings/junit_checker.py b/src/mlx/warnings/junit_checker.py index 26d27227..89b1002b 100644 --- a/src/mlx/warnings/junit_checker.py +++ b/src/mlx/warnings/junit_checker.py @@ -56,7 +56,7 @@ def prepare_tree(root_input): testsuites_root.append(root_input) return testsuites_root - def _check_testcase(self, testcase): + def _check_testcase(self, testcase, extra={}): """ Handles the check of a test case element by checking if the result is a failure/error. If it is to be excluded by a configured regex, 1 is returned. @@ -69,7 +69,7 @@ def _check_testcase(self, testcase): int: 1 if a failure/error is to be subtracted from the final count, 0 otherwise """ if isinstance(testcase.result, (Failure, Error)): - extra={"checker_name": self.name_repr} + extra["checker_name"] = self.name_repr if self._is_excluded(testcase.result.message, extra=extra): return 1 string = f'{testcase.classname}.{testcase.name}' diff --git a/src/mlx/warnings/robot_checker.py b/src/mlx/warnings/robot_checker.py index 46bb8edf..d5956711 100644 --- a/src/mlx/warnings/robot_checker.py +++ b/src/mlx/warnings/robot_checker.py @@ -118,6 +118,10 @@ def __init__(self, suite_name, check_suite_name=False): self.logger = logging.getLogger(self.name) self.output_logger = logging.getLogger(f"{self.name}.output") + @property + def name_repr(self): + return self.name.capitalize() + def _check_testcase(self, testcase): """ Handles the check of a test case element by checking if the result is a failure/error. @@ -132,7 +136,8 @@ def _check_testcase(self, testcase): """ if testcase.classname.endswith(self.suite_name): self.is_valid_suite_name = True - return super()._check_testcase(testcase) + return super()._check_testcase(testcase, extra={"checker_name": self.name_repr, + "suite_name": self.suite_name}) return int(self.suite_name and isinstance(testcase.result, (Failure, Error))) def check(self, content): From 733f8e560711dfd90fc59071542aa007a07e645c Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 12 Dec 2024 17:56:56 +0100 Subject: [PATCH 56/86] Replace prints using logger with name "mlx.warnings.warnings" --- src/mlx/warnings/polyspace_checker.py | 5 ++++- src/mlx/warnings/regex_checker.py | 2 +- src/mlx/warnings/robot_checker.py | 4 +++- src/mlx/warnings/warnings.py | 5 ++--- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/mlx/warnings/polyspace_checker.py b/src/mlx/warnings/polyspace_checker.py index 351efab1..70afc070 100644 --- a/src/mlx/warnings/polyspace_checker.py +++ b/src/mlx/warnings/polyspace_checker.py @@ -10,6 +10,9 @@ from .exceptions import WarningsConfigError from .warnings_checker import WarningsChecker +LOGGER = logging.getLogger("mlx.warnings.warnings") + + class PolyspaceChecker(WarningsChecker): name = 'polyspace' @@ -112,7 +115,7 @@ def return_check_limits(self): extra = {"column_info": f"{checker.family_value}: {checker.column_name}: {checker.check_value}"} count += checker.return_check_limits(extra) if count: - print(f"{self.name_repr}: Returning error code {count}.") + LOGGER.warning(f"{self.name_repr}: Returning error code {count}.") return count def parse_config(self, config): diff --git a/src/mlx/warnings/regex_checker.py b/src/mlx/warnings/regex_checker.py index 5bf7bd3e..dd6ba451 100644 --- a/src/mlx/warnings/regex_checker.py +++ b/src/mlx/warnings/regex_checker.py @@ -132,7 +132,7 @@ def return_check_limits(self): } count += checker.return_check_limits(extra) if count: - print(f"{self.name_repr}: Returning error code {count}.") + LOGGER.warning(f"{self.name_repr}: Returning error code {count}.") return count def check(self, content): diff --git a/src/mlx/warnings/robot_checker.py b/src/mlx/warnings/robot_checker.py index d5956711..ac40dc88 100644 --- a/src/mlx/warnings/robot_checker.py +++ b/src/mlx/warnings/robot_checker.py @@ -8,6 +8,8 @@ from .junit_checker import JUnitChecker from .warnings_checker import WarningsChecker +LOGGER = logging.getLogger("mlx.warnings.warnings") + class RobotChecker(WarningsChecker): name = 'robot' @@ -88,7 +90,7 @@ def return_check_limits(self): } count += checker.return_check_limits(extra) if count: - print(f"{self.name_repr}: Returning error code {count}.") + LOGGER.warning(f"{self.name_repr}: Returning error code {count}.") return count def parse_config(self, config): diff --git a/src/mlx/warnings/warnings.py b/src/mlx/warnings/warnings.py index 0946c135..8651d221 100644 --- a/src/mlx/warnings/warnings.py +++ b/src/mlx/warnings/warnings.py @@ -93,7 +93,7 @@ def check(self, content): content (str): The content to parse ''' if self.printout: - print(content) + LOGGER.warning(content) if not self.activated_checkers: LOGGER.error("No checkers activated. Please use activate_checker function") else: @@ -365,8 +365,7 @@ def warnings_command(warnings, cmd): OSError: When program is not installed. ''' try: - print("Executing: ", end='') - print(cmd) + LOGGER.info(f"Executing: {cmd}") proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, bufsize=1, universal_newlines=True) out, err = proc.communicate() From 920a405fd801ab5c08367c96827adf44390be073 Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 12 Dec 2024 18:02:17 +0100 Subject: [PATCH 57/86] Fix tests --- tests/test_config.py | 70 +++++++-------- tests/test_coverity.py | 24 ++--- tests/test_doxygen.py | 28 +++--- tests/test_in/junit_double_fail_summary.txt | 4 +- .../robot_double_fail_config_summary.txt | 8 +- tests/test_in/robot_double_fail_summary.txt | 4 +- ...inx_double_deprecation_warning_summary.txt | 4 +- tests/test_integration.py | 90 +++++++++---------- tests/test_junit.py | 14 +-- tests/test_limits.py | 4 +- tests/test_polyspace.py | 52 +++++------ tests/test_robot.py | 37 ++++---- tests/test_sphinx.py | 42 ++++----- tests/test_warnings.py | 4 +- tests/test_xmlrunner.py | 18 ++-- 15 files changed, 189 insertions(+), 214 deletions(-) diff --git a/tests/test_config.py b/tests/test_config.py index 01d96529..48f9a79c 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,9 +1,6 @@ -from io import StringIO -import logging import os from pathlib import Path from unittest import TestCase -from unittest.mock import patch from mlx.warnings import ( DoxyChecker, @@ -17,22 +14,6 @@ TEST_IN_DIR = Path(__file__).parent / 'test_in' -def check_xml_file_with_logging(warnings, file_path): - logging.basicConfig(format="%(levelname)s: %(message)s") - logging.getLogger().setLevel(logging.INFO) - buffer = StringIO() - with patch('sys.stdout', new=buffer): - logger = logging.getLogger() - stream_handler = logging.StreamHandler(buffer) - logger.addHandler(stream_handler) - try: - with open(file_path) as xmlfile: - warnings.check(xmlfile.read()) - retval = warnings.return_check_limits() - finally: - logger.removeHandler(stream_handler) - return buffer.getvalue(), retval - class TestConfig(TestCase): def setUp(self): @@ -45,7 +26,8 @@ def tearDown(self): del os.environ[var] def test_configfile_parsing(self): - warnings = WarningsPlugin(config_file=(TEST_IN_DIR / "config_example.json")) + warnings = WarningsPlugin() + warnings.config_parser((TEST_IN_DIR / "config_example.json")) warnings.check('testfile.c:6: warning: group test: ignoring title "Some test functions" that does not match old title "Some freaky test functions"') self.assertEqual(warnings.return_count(), 1) warnings.check('') @@ -60,13 +42,15 @@ def test_configfile_parsing(self): def test_configfile_parsing_missing_envvar(self): del os.environ['MAX_SPHINX_WARNINGS'] with self.assertRaises(WarningsConfigError) as c_m: - WarningsPlugin(config_file=(TEST_IN_DIR / "config_example.json")) + warnings = WarningsPlugin() + warnings.config_parser((TEST_IN_DIR / "config_example.json")) self.assertEqual( str(c_m.exception), "Failed to find environment variable 'MAX_SPHINX_WARNINGS' for configuration value 'max'") def _helper_exclude(self, warnings): - with self.assertLogs(level="INFO") as verbose_output: + logger_name = "sphinx" + with self.assertLogs(logger=logger_name ,level="INFO") as verbose_output: warnings.check('testfile.c:6: warning: group test: ignoring title "Some test functions" that does not match old title "Some freaky test functions"') self.assertEqual(warnings.return_count(), 0) warnings.check('') @@ -84,20 +68,23 @@ def _helper_exclude(self, warnings): warnings.check('ERROR [0.000s]: test_some_error_test (something.anything.somewhere)') self.assertEqual(warnings.return_count(), 1) excluded_toctree_warning = "Excluded {!r} because of configured regex {!r}".format(toctree_warning, "WARNING: toctree") - self.assertIn(f"INFO:root:{excluded_toctree_warning}", verbose_output.output) + self.assertIn(f"INFO:{logger_name}:{excluded_toctree_warning}", verbose_output.output) warning_echo = "home/bljah/test/index.rst:5: WARNING: this warning should not get excluded" - self.assertIn(f"INFO:root:{warning_echo}", verbose_output.output) + self.assertIn(f"INFO:{logger_name}:{warning_echo}", verbose_output.output) def test_configfile_parsing_exclude_json(self): - warnings = WarningsPlugin(verbose=True, config_file=(TEST_IN_DIR / "config_example_exclude.json")) + warnings = WarningsPlugin() + warnings.config_parser((TEST_IN_DIR / "config_example_exclude.json"), verbose=True) self._helper_exclude(warnings) def test_configfile_parsing_exclude_yml(self): - warnings = WarningsPlugin(verbose=True, config_file=(TEST_IN_DIR / "config_example_exclude.yml")) + warnings = WarningsPlugin() + warnings.config_parser((TEST_IN_DIR / "config_example_exclude.yml"), verbose=True) self._helper_exclude(warnings) def test_configfile_parsing_include_priority(self): - warnings = WarningsPlugin(verbose=True, config_file=(TEST_IN_DIR / "config_example_exclude.json")) + warnings = WarningsPlugin() + warnings.config_parser((TEST_IN_DIR / "config_example_exclude.json"), verbose=True) warnings.get_checker('sphinx').include_sphinx_deprecation() deprecation_warning = 'sphinx/application.py:402: RemovedInSphinx20Warning: app.info() is now deprecated. Use sphinx.util.logging instead.' warnings.check(deprecation_warning) @@ -196,7 +183,7 @@ def test_partial_junit_config_parsing_exclude_regex(self): self.assertEqual(warnings.return_count(), 0) def test_partial_robot_config_parsing_exclude_regex(self): - warnings = WarningsPlugin(verbose=True) + warnings = WarningsPlugin() tmpjson = { 'robot': { 'enabled': True, @@ -216,20 +203,23 @@ def test_partial_robot_config_parsing_exclude_regex(self): ] } } - warnings.config_parser(tmpjson) - stdout_log, retval = check_xml_file_with_logging(warnings, 'tests/test_in/robot_double_fail.xml') + warnings.config_parser(tmpjson, verbose=True) + with self.assertLogs(logger="robot", level="INFO") as verbose_output: + with open('tests/test_in/robot_double_fail.xml') as xmlfile: + warnings.check(xmlfile.read()) + retval = warnings.return_check_limits() self.assertEqual(warnings.return_count(), 1) self.assertEqual(retval, 0) self.assertEqual( - "Excluded 'Directory 'C:\\\\nonexistent' does not exist.' because of configured regex 'does not exist'\n" - "Suite One & Suite Two.Suite Two.Another test\n" - "Robot: test suite 'Suite One' number of warnings (0) is exactly as expected. Well done.\n" - "Robot: test suite 'Suite Two' number of warnings (1) is exactly as expected. Well done.\n", - stdout_log + ["INFO:robot:Excluded 'Directory 'C:\\\\nonexistent' does not exist.' because of configured regex 'does not exist'", + "INFO:robot:Suite One & Suite Two.Suite Two.Another test", + "WARNING:robot:number of warnings (0) is exactly as expected. Well done.", + "WARNING:robot:number of warnings (1) is exactly as expected. Well done."], + verbose_output.output ) def test_partial_robot_config_empty_name(self): - warnings = WarningsPlugin(verbose=True) + warnings = WarningsPlugin() tmpjson = { 'robot': { 'enabled': True, @@ -243,17 +233,17 @@ def test_partial_robot_config_empty_name(self): ] } } - warnings.config_parser(tmpjson) + warnings.config_parser(tmpjson, verbose=True) with open('tests/test_in/robot_double_fail.xml') as xmlfile: - with self.assertLogs(level="INFO") as verbose_output: + with self.assertLogs(logger="robot", level="INFO") as verbose_output: warnings.check(xmlfile.read()) count = warnings.return_count() self.assertEqual(count, 1) self.assertEqual(warnings.return_check_limits(), 0) self.assertEqual( [ - r"INFO:root:Excluded 'Directory 'C:\\nonexistent' does not exist.' because of configured regex 'does not exist'", - "INFO:root:Suite One & Suite Two.Suite Two.Another test", + r"INFO:robot:Excluded 'Directory 'C:\\nonexistent' does not exist.' because of configured regex 'does not exist'", + "INFO:robot:Suite One & Suite Two.Suite Two.Another test", ], verbose_output.output ) diff --git a/tests/test_coverity.py b/tests/test_coverity.py index a4292354..cd8a1577 100644 --- a/tests/test_coverity.py +++ b/tests/test_coverity.py @@ -1,7 +1,9 @@ import filecmp +import logging import os from pathlib import Path -from unittest import TestCase, mock +from unittest import TestCase +from unittest.mock import patch from mlx.warnings import Finding, WarningsPlugin, warnings_wrapper @@ -18,7 +20,7 @@ def ordered(obj): return obj -@mock.patch.dict(os.environ, { +@patch.dict(os.environ, { "MIN_UNCLASSIFIED": "8", "MAX_UNCLASSIFIED": "8", "MIN_INTENTIONAL": "1", "MAX_INTENTIONAL": "1", "MIN_FALSE_POSITIVE": "2", "MAX_FALSE_POSITIVE": "2", @@ -26,8 +28,8 @@ def ordered(obj): class TestCoverityWarnings(TestCase): def setUp(self): Finding.fingerprints = {} - self.warnings = WarningsPlugin(verbose=True) - self.warnings.activate_checker_name('coverity') + self.warnings = WarningsPlugin() + self.warnings.activate_checker_name('coverity', True) def test_no_warning_normal_text(self): dut = 'This should not be treated as warning' @@ -41,30 +43,30 @@ def test_no_warning_but_still_command_output(self): def test_single_warning(self): dut = '/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' - with self.assertLogs(level="INFO") as fake_out: + with self.assertLogs(logger="coverity", level="INFO") as fake_out: self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(f"INFO:root:{dut}", fake_out.output) + self.assertIn(f"INFO:coverity:{dut}", fake_out.output) def test_single_warning_count_one(self): dut1 = '/src/somefile.c:80: CID 113396 (#1 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' dut2 = '/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' - with self.assertLogs(level="INFO") as fake_out: + with self.assertLogs(logger="coverity", level="INFO") as fake_out: self.warnings.check(dut1) self.warnings.check(dut2) self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(f"INFO:root:{dut2}", fake_out.output) + self.assertIn(f"INFO:coverity:{dut2}", fake_out.output) def test_single_warning_real_output(self): dut1 = '/src/somefile.c:80: CID 113396 (#1 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' dut2 = '/src/somefile.c:82: CID 113396 (#2 of 2): Coding standard violation (MISRA C-2012 Rule 10.1): Unclassified, Unspecified, Undecided, owner is nobody, first detected on 2017-07-27.' dut3 = 'src/something/src/somefile.c:82: 1. misra_violation: Essential type of the left hand operand "0U" (unsigned) is not the same as that of the right operand "1U"(signed).' - with self.assertLogs(level="INFO") as fake_out: + with self.assertLogs(logger="coverity", level="INFO") as fake_out: self.warnings.check(dut1) self.warnings.check(dut2) self.warnings.check(dut3) self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(f"INFO:root:{dut2}", fake_out.output) + self.assertIn(f"INFO:coverity:{dut2}", fake_out.output) def test_code_quality_without_config(self): filename = 'coverity_cq.json' @@ -90,7 +92,7 @@ def test_code_quality_with_config_pass(self): self.assertEqual(0, retval) self.assertTrue(filecmp.cmp(out_file, ref_file)) - @mock.patch.dict(os.environ, { + @patch.dict(os.environ, { "MIN_UNCLASSIFIED": "11", "MAX_UNCLASSIFIED": "-1", "MIN_FALSE_POSITIVE": "0", "MAX_FALSE_POSITIVE": "1", }) diff --git a/tests/test_doxygen.py b/tests/test_doxygen.py index 3d73f0f3..bcdf76ba 100644 --- a/tests/test_doxygen.py +++ b/tests/test_doxygen.py @@ -5,8 +5,8 @@ class TestDoxygenWarnings(TestCase): def setUp(self): - self.warnings = WarningsPlugin(verbose=True) - self.warnings.activate_checker_name('doxygen') + self.warnings = WarningsPlugin() + self.warnings.activate_checker_name('doxygen', True) def test_no_warning(self): dut = 'This should not be treated as warning' @@ -15,21 +15,21 @@ def test_no_warning(self): def test_single_warning(self): dut = 'testfile.c:6: warning: group test: ignoring title "Some test functions" that does not match old title "Some freaky test functions"' - with self.assertLogs(level="INFO") as fake_out: + with self.assertLogs(logger="doxygen", level="INFO") as fake_out: self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(f"INFO:root:{dut}", fake_out.output) + self.assertIn(f"INFO:doxygen:{dut}", fake_out.output) def test_single_warning_mixed(self): dut1 = 'This1 should not be treated as warning' dut2 = 'testfile.c:6: warning: group test: ignoring title "Some test functions" that does not match old title "Some freaky test functions"' dut3 = 'This should not be treated as warning2' - with self.assertLogs(level="INFO") as fake_out: + with self.assertLogs(logger="doxygen", level="INFO") as fake_out: self.warnings.check(dut1) self.warnings.check(dut2) self.warnings.check(dut3) self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(f"INFO:root:{dut2}", fake_out.output) + self.assertIn(f"INFO:doxygen:{dut2}", fake_out.output) def test_multiline(self): duterr1 = "testfile.c:6: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" @@ -38,11 +38,11 @@ def test_multiline(self): dut += duterr1 dut += "This should not be treated as warning2\n" dut += duterr2 - with self.assertLogs(level="INFO") as fake_out: + with self.assertLogs(logger="doxygen", level="INFO") as fake_out: self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 2) - self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) - self.assertIn(f"INFO:root:{duterr2.strip()}", fake_out.output) + self.assertIn(f"INFO:doxygen:{duterr1.strip()}", fake_out.output) + self.assertIn(f"INFO:doxygen:{duterr2.strip()}", fake_out.output) def test_git_warning(self): duterr1 = "testfile.c:6: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" @@ -51,21 +51,21 @@ def test_git_warning(self): dut += duterr1 dut += "This should not be treated as warning2\n" dut += duterr2 - with self.assertLogs(level="INFO") as fake_out: + with self.assertLogs(logger="doxygen", level="INFO") as fake_out: self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 2) - self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) - self.assertIn(f"INFO:root:{duterr2.strip()}", fake_out.output) + self.assertIn(f"INFO:doxygen:{duterr1.strip()}", fake_out.output) + self.assertIn(f"INFO:doxygen:{duterr2.strip()}", fake_out.output) def test_sphinx_deprecation_warning(self): duterr1 = "testfile.c:6: warning: group test: ignoring title \"Some test functions\" that does not match old title \"Some freaky test functions\"\n" dut = "/usr/local/lib/python3.5/dist-packages/sphinx/application.py:402: RemovedInSphinx20Warning: app.info() "\ "is now deprecated. Use sphinx.util.logging instead. RemovedInSphinx20Warning)\n" dut += duterr1 - with self.assertLogs(level="INFO") as fake_out: + with self.assertLogs(logger="doxygen", level="INFO") as fake_out: self.warnings.check(dut) self.assertEqual(self.warnings.return_count(), 1) - self.assertIn(f"INFO:root:{duterr1.strip()}", fake_out.output) + self.assertIn(f"INFO:doxygen:{duterr1.strip()}", fake_out.output) def test_doxygen_warnings_txt(self): dut_file = 'tests/test_in/doxygen_warnings.txt' diff --git a/tests/test_in/junit_double_fail_summary.txt b/tests/test_in/junit_double_fail_summary.txt index 298fa54c..ca5434ea 100644 --- a/tests/test_in/junit_double_fail_summary.txt +++ b/tests/test_in/junit_double_fail_summary.txt @@ -1,2 +1,2 @@ -test_warn_plugin_double_fail.myfirstfai1ure: Is our warnings plugin able to trace this random failure msg? -test_warn_plugin_no_double_fail.mysecondfai1ure: Second failure +JUnit: test_warn_plugin_double_fail.myfirstfai1ure: Is our warnings plugin able to trace this random failure msg? +JUnit: test_warn_plugin_no_double_fail.mysecondfai1ure: Second failure diff --git a/tests/test_in/robot_double_fail_config_summary.txt b/tests/test_in/robot_double_fail_config_summary.txt index 2809884d..77e4190a 100644 --- a/tests/test_in/robot_double_fail_config_summary.txt +++ b/tests/test_in/robot_double_fail_config_summary.txt @@ -1,4 +1,4 @@ -Suite One & Suite Two.Suite One.First Test: Directory 'C:\nonexistent' does not exist. -Suite One & Suite Two.Suite One.First Test: Directory 'C:\nonexistent' does not exist. -Suite One & Suite Two.Suite Two.Another test: Expected str; got int. -Suite One & Suite Two.Suite Two.Another test: Expected str; got int. +Robot: Suite One Suite One & Suite Two.Suite One.First Test: Directory 'C:\nonexistent' does not exist. +Robot: Suite One & Suite Two.Suite One.First Test: Directory 'C:\nonexistent' does not exist. +Robot: Suite One & Suite Two.Suite Two.Another test: Expected str; got int. +Robot: Suite Two Suite One & Suite Two.Suite Two.Another test: Expected str; got int. diff --git a/tests/test_in/robot_double_fail_summary.txt b/tests/test_in/robot_double_fail_summary.txt index 4e9820d8..d1708e20 100644 --- a/tests/test_in/robot_double_fail_summary.txt +++ b/tests/test_in/robot_double_fail_summary.txt @@ -1,2 +1,2 @@ -Suite One & Suite Two.Suite One.First Test: Directory 'C:\nonexistent' does not exist. -Suite One & Suite Two.Suite Two.Another test: Expected str; got int. +Robot: Suite One & Suite Two.Suite One.First Test: Directory 'C:\nonexistent' does not exist. +Robot: Suite One & Suite Two.Suite Two.Another test: Expected str; got int. diff --git a/tests/test_in/sphinx_double_deprecation_warning_summary.txt b/tests/test_in/sphinx_double_deprecation_warning_summary.txt index 498c10dc..e71712f7 100644 --- a/tests/test_in/sphinx_double_deprecation_warning_summary.txt +++ b/tests/test_in/sphinx_double_deprecation_warning_summary.txt @@ -1,2 +1,2 @@ -/usr/local/lib/python3.7/dist-packages/sphinx/util/docutils.py:286: RemovedInSphinx30Warning: function based directive support is now deprecated. Use class based directive instead. -/usr/local/lib/python3.7/dist-packages/sphinx_rtd_theme/search.html:20: RemovedInSphinx30Warning: To modify script_files in the theme is deprecated. Please insert a