From 1a495ce3eda1e8259bdd2ab7ddcb1cef47be1388 Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 22 Aug 2024 10:56:32 +0200 Subject: [PATCH 01/11] Make key and value of fake_checkers not equal to each other --- tests/test_coverity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_coverity.py b/tests/test_coverity.py index 7b0b9397..8f26549f 100644 --- a/tests/test_coverity.py +++ b/tests/test_coverity.py @@ -106,8 +106,8 @@ def test_retrieve_checkers(self): self.fake_checkers = { "checkerAttribute": {"name": "checker", "displayName": "Checker"}, "checkerAttributedata": [ - {"key": "MISRA", "value": "MISRA"}, - {"key": "CHECKER", "value": "CHECKER"} + {"key": "MISRA", "value": "M"}, + {"key": "CHECKER", "value": "C"} ], } # initialize what needed for the REST API From 7096447076f6e024574bc1779f4b3a83c29a6bba Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 22 Aug 2024 11:13:32 +0200 Subject: [PATCH 02/11] Add info to the tests --- tests/test_coverity.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_coverity.py b/tests/test_coverity.py index 8f26549f..3c76a04d 100644 --- a/tests/test_coverity.py +++ b/tests/test_coverity.py @@ -63,6 +63,7 @@ def initialize_coverity_service(self, login=False): return coverity_service def test_session_by_stream_validation(self): + """To test the session authentication, the function `validate_stream` is used.""" coverity_service = self.initialize_coverity_service(login=False) with requests_mock.mock() as mocker: mocker.get(self.stream_url, json={}) @@ -74,6 +75,7 @@ def test_session_by_stream_validation(self): @patch("mlx.coverity_services.requests") def test_stream_validation(self, mock_requests): + """Test if the function `validate_stream` is called once with the correct url""" mock_requests.return_value = MagicMock(spec=requests) # Get the base url @@ -87,6 +89,9 @@ def test_stream_validation(self, mock_requests): mock_method.assert_called_with("https://scan.coverity.com/api/v2/streams/test_stream") def test_retrieve_columns(self): + """Test the function `retrieve_column_keys`. + Check if the the columns property is correctly initialized by checking if the name of a column returns + the correct key.""" with open(f"{TEST_FOLDER}/columns_keys.json", "r") as content: column_keys = json.loads(content.read()) # initialize what needed for the REST API @@ -103,6 +108,8 @@ def test_retrieve_columns(self): assert coverity_service.columns["CID"] == "cid" def test_retrieve_checkers(self): + """Test the function `retrieve_checkers`. Check if the returned list of the checkers property is equal to the + keys of checkerAttributedata of the returned data of the request.""" self.fake_checkers = { "checkerAttribute": {"name": "checker", "displayName": "Checker"}, "checkerAttributedata": [ @@ -130,6 +137,10 @@ def test_retrieve_checkers(self): test_defect_filter_3, ]) def test_get_defects(self, filters, column_names, request_data): + """Check get defects with different filters. Check if the response of `get_defects` is the same as expected. + The data is obtained from the filters.py file. + Due to the usage of set in `get_defects` (column_keys), the function `ordered` is used to compare the returned + data of the request where order does not matter.""" with open(f"{TEST_FOLDER}/columns_keys.json", "r") as content: column_keys = json.loads(content.read()) self.fake_checkers = { @@ -160,6 +171,8 @@ def test_get_defects(self, filters, column_names, request_data): assert ordered(data) == ordered(request_data) def test_get_filtered_defects(self): + """Test `get_filtered_defects` of SphinxCoverityConnector. Check if `get_defects` is called once with the + correct arguments.""" sphinx_coverity_connector = SphinxCoverityConnector() sphinx_coverity_connector.coverity_service = self.initialize_coverity_service(login=False) sphinx_coverity_connector.stream = self.fake_stream @@ -177,6 +190,7 @@ def test_get_filtered_defects(self): mock_method.assert_called_once_with(self.fake_stream, fake_node["filters"], column_names) def test_failed_login(self): + """Test a failed login by mocking the status code when validating the stream.""" coverity_conf_service = CoverityDefectService("scan.coverity.com/") stream_url = f"{coverity_conf_service.api_endpoint}/streams/{self.fake_stream}" From 5e964aafb19adc8f1bf78ac69e1ebfa7d0878654 Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 22 Aug 2024 14:12:58 +0200 Subject: [PATCH 03/11] Set logging level default to WARNING in conf.py --- example/conf.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/example/conf.py b/example/conf.py index 8033ffa4..1f25c22f 100644 --- a/example/conf.py +++ b/example/conf.py @@ -17,6 +17,7 @@ import mlx.coverity import mlx.traceability from decouple import config +import logging from pkg_resources import get_distribution pkg_version = get_distribution("mlx.coverity").version @@ -317,3 +318,5 @@ TRACEABILITY_ITEM_ID_REGEX = r"([A-Z_]+-[A-Z0-9_]+)" TRACEABILITY_ITEM_RELINK = {} + +logging.basicConfig(level=logging.WARNING) From 542d4bbea8ecaa45c1d4d956d774aeb10477f903 Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 22 Aug 2024 14:38:34 +0200 Subject: [PATCH 04/11] Check if chart_attribute adds name to column names --- tests/test_coverity.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/test_coverity.py b/tests/test_coverity.py index 3c76a04d..dc6366ef 100644 --- a/tests/test_coverity.py +++ b/tests/test_coverity.py @@ -8,8 +8,7 @@ from pathlib import Path from parameterized import parameterized - -from mlx.coverity import SphinxCoverityConnector +from mlx.coverity import SphinxCoverityConnector, CoverityDefect from mlx.coverity_services import CoverityDefectService from .filters import test_defect_filter_0, test_defect_filter_1, test_defect_filter_2, test_defect_filter_3 @@ -181,12 +180,14 @@ def test_get_filtered_defects(self): "classification": "Intentional,Bug,Pending,Unclassified", "action": None, "component": None, "cwe": None, "cid": None } - column_names = {"Comment", "Checker", "Classification", "CID"} - fake_node = {"col": column_names, - "filters": node_filters} - + column_names = {"Comment", "Classification", "CID"} + fake_node = CoverityDefect() + fake_node["col"] = column_names + fake_node["filters"] = node_filters + fake_node["chart_attribute"] = "Checker" with patch.object(CoverityDefectService, "get_defects") as mock_method: sphinx_coverity_connector.get_filtered_defects(fake_node) + column_names.add("Checker") mock_method.assert_called_once_with(self.fake_stream, fake_node["filters"], column_names) def test_failed_login(self): From 3364edd005c0468d72c99f3c043bd95c1648ed6b Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 22 Aug 2024 15:12:26 +0200 Subject: [PATCH 05/11] Add warning Errno -3 --- warnings_config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/warnings_config.yml b/warnings_config.yml index dfcf6da8..fa28d1dc 100644 --- a/warnings_config.yml +++ b/warnings_config.yml @@ -4,6 +4,7 @@ sphinx: max: 0 exclude: - 'WARNING: Connection failed: HTTPSConnectionPool\(host=.+, port=\d+\): Max retries exceeded with url: /api/v2/[\w/?=&\\]+ \(Caused by NameResolutionError\(\": Failed to resolve .+ \(\[Errno -2\] Name or service not known\)\"\)\)' + - 'WARNING: Connection failed: HTTPSConnectionPool\(host=.+, port=\d+\): Max retries exceeded with url: /api/v2/[\w/?=&\\]+ \(Caused by NameResolutionError\(\": Failed to resolve .+ \(\[Errno -3\] Temporary failure in name resolution\)\"\)\)' - 'WARNING: CID \d+: Could not find item ID .+ in traceability collection.' - 'WARNING: cannot cache unpickable configuration value: .traceability_attributes_sort. \(because it contains a function, class, or module object\)' doxygen: From 89ae364f5f1274ba606965bad9c7bd5d10375a52 Mon Sep 17 00:00:00 2001 From: JWM Date: Thu, 22 Aug 2024 16:01:48 +0200 Subject: [PATCH 06/11] Update test with a node that has chart_attribute or not --- tests/test_coverity.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/test_coverity.py b/tests/test_coverity.py index dc6366ef..208f622e 100644 --- a/tests/test_coverity.py +++ b/tests/test_coverity.py @@ -171,7 +171,8 @@ def test_get_defects(self, filters, column_names, request_data): def test_get_filtered_defects(self): """Test `get_filtered_defects` of SphinxCoverityConnector. Check if `get_defects` is called once with the - correct arguments.""" + correct arguments. + Tests also when `chart_attribute` of the node exists, the name will be added to column_names.""" sphinx_coverity_connector = SphinxCoverityConnector() sphinx_coverity_connector.coverity_service = self.initialize_coverity_service(login=False) sphinx_coverity_connector.stream = self.fake_stream @@ -184,11 +185,13 @@ def test_get_filtered_defects(self): fake_node = CoverityDefect() fake_node["col"] = column_names fake_node["filters"] = node_filters - fake_node["chart_attribute"] = "Checker" with patch.object(CoverityDefectService, "get_defects") as mock_method: sphinx_coverity_connector.get_filtered_defects(fake_node) - column_names.add("Checker") mock_method.assert_called_once_with(self.fake_stream, fake_node["filters"], column_names) + fake_node["chart_attribute"] = "Checker" + column_names.add("Checker") + sphinx_coverity_connector.get_filtered_defects(fake_node) + mock_method.assert_called_with(self.fake_stream, fake_node["filters"], column_names) def test_failed_login(self): """Test a failed login by mocking the status code when validating the stream.""" From 5e97c3b6d25ebb986baa34ef2d7ca2be767963fb Mon Sep 17 00:00:00 2001 From: jce Date: Fri, 23 Aug 2024 12:09:38 +0200 Subject: [PATCH 07/11] Define logger as global variable with level set to WARNING; it can be overridden in conf.py --- mlx/coverity_logging.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/mlx/coverity_logging.py b/mlx/coverity_logging.py index 3e9620ac..31e67e9d 100644 --- a/mlx/coverity_logging.py +++ b/mlx/coverity_logging.py @@ -1,6 +1,10 @@ """Module to provide functions that accommodate logging.""" from sphinx.util.logging import getLogger +from logging import WARNING + +LOGGER = getLogger(__name__) +LOGGER.setLevel(WARNING) def report_warning(msg, docname, lineno=None): @@ -11,11 +15,10 @@ def report_warning(msg, docname, lineno=None): docname (str): Name of the document in which the error occurred lineno (str): Line number in the document on which the error occurred """ - logger = getLogger(__name__) if lineno is not None: - logger.warning(msg, location=(docname, lineno)) + LOGGER.warning(msg, location=(docname, lineno)) else: - logger.warning(msg, location=docname) + LOGGER.warning(msg, location=docname) def report_info(msg, nonl=False): @@ -25,5 +28,4 @@ def report_info(msg, nonl=False): msg (str): Message of the warning nonl (bool): True when no new line at end """ - logger = getLogger(__name__) - logger.info(msg, nonl=nonl) + LOGGER.info(msg, nonl=nonl) From 57b267b2e0235cc0de3bc6891ee65822af7638af Mon Sep 17 00:00:00 2001 From: JWM Date: Fri, 23 Aug 2024 13:04:00 +0200 Subject: [PATCH 08/11] Overwrite loglevel of mlx.coverity_logging by setting LOGLEVEL env variable --- example/Makefile | 4 ++++ example/conf.py | 11 ++++++++++- mlx/coverity_services.py | 18 +++++++++--------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/example/Makefile b/example/Makefile index e4b96021..9c10a408 100644 --- a/example/Makefile +++ b/example/Makefile @@ -8,6 +8,10 @@ PYTHONWARNINGS?= default::DeprecationWarning PAPER ?= BUILDDIR ?= _build +# logging variables +LOGLEVEL =? WARNING +export LOGLEVEL=${LOGLEVEL} + # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter diff --git a/example/conf.py b/example/conf.py index 1f25c22f..acdc9cf7 100644 --- a/example/conf.py +++ b/example/conf.py @@ -18,6 +18,7 @@ import mlx.traceability from decouple import config import logging +from sphinx.util.logging import getLogger from pkg_resources import get_distribution pkg_version = get_distribution("mlx.coverity").version @@ -319,4 +320,12 @@ TRACEABILITY_ITEM_ID_REGEX = r"([A-Z_]+-[A-Z0-9_]+)" TRACEABILITY_ITEM_RELINK = {} -logging.basicConfig(level=logging.WARNING) +log_level = os.environ.get('LOGLEVEL', None) +if log_level: + try: + numeric_level = getattr(logging, log_level.upper(), None) + logger = getLogger("mlx.coverity_logging") + logger.setLevel(level=numeric_level) + except: + raise ValueError(f"Invalid log level: {log_level}") + diff --git a/mlx/coverity_services.py b/mlx/coverity_services.py index 498dbad7..5e2ef0df 100644 --- a/mlx/coverity_services.py +++ b/mlx/coverity_services.py @@ -3,14 +3,14 @@ """Services and other utilities for Coverity scripting""" import csv -import logging import re from collections import namedtuple from urllib.parse import urlencode - import requests from sphinx.util.logging import getLogger +from mlx.coverity_logging import report_info + # Coverity built in Impact statuses IMPACT_LIST = ["High", "Medium", "Low"] @@ -52,7 +52,7 @@ def __init__(self, hostname): self._api_endpoint = f"https://{hostname}/api/{self.version}" self._checkers = [] self._columns = {} - self.logger = getLogger("coverity_logging") + self.logger = getLogger("mlx.coverity_logging") @property def base_url(self): @@ -256,7 +256,7 @@ def get_defects(self, stream, filters, column_names): "rows": list of [list of dictionaries {"key": , "value": }] } """ - logging.info("Querying Coverity for defects in stream [%s] ...", stream) + report_info(f"Querying Coverity for defects in stream [{stream}] ...",) query_filters = [ { "columnKey": "streams", @@ -306,7 +306,7 @@ def get_defects(self, stream, filters, column_names): } } - logging.info("Running Coverity query...") + report_info("Running Coverity query...") return self.retrieve_issues(data) def handle_attribute_filter(self, attribute_values, name, valid_attributes, allow_regex=False): @@ -322,11 +322,11 @@ def handle_attribute_filter(self, attribute_values, name, valid_attributes, allo Returns: set[str]: The attributes values to query with """ - logging.info("Using %s filter [%s]", name, attribute_values) + report_info(f"Using {name} filter [{attribute_values}]") filter_values = set() for field in attribute_values.split(","): if not valid_attributes or field in valid_attributes: - logging.info("Classification [%s] is valid", field) + report_info("Classification [{field}] is valid") filter_values.add(field) elif allow_regex: pattern = re.compile(field) @@ -334,7 +334,7 @@ def handle_attribute_filter(self, attribute_values, name, valid_attributes, allo if pattern.search(element): filter_values.add(element) else: - logging.error("Invalid %s filter: %s", name, field) + self.logger.error(f"Invalid {name} filter: {field}") return filter_values def handle_component_filter(self, attribute_values): @@ -346,7 +346,7 @@ def handle_component_filter(self, attribute_values): Returns: list[str]: The list of attributes """ - logging.info("Using Component filter [%s]", attribute_values) + report_info(f"Using Component filter [{attribute_values}]") parser = csv.reader([attribute_values]) filter_values = [] for fields in parser: From 83316723b91279b1dc588f4b6e5c84c1384a9ebe Mon Sep 17 00:00:00 2001 From: JWM Date: Fri, 23 Aug 2024 13:05:40 +0200 Subject: [PATCH 09/11] Add DEBUG; when set to 1 traceback will be shown --- example/Makefile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/example/Makefile b/example/Makefile index 9c10a408..ec8874c3 100644 --- a/example/Makefile +++ b/example/Makefile @@ -9,13 +9,17 @@ PAPER ?= BUILDDIR ?= _build # logging variables +DEBUG ?= 0 LOGLEVEL =? WARNING export LOGLEVEL=${LOGLEVEL} # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -ET -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +ALLSPHINXOPTS = -E -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +ifeq (${DEBUG}, 1) +ALLSPHINXOPTS += -T +endif # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . From 3ff61231753b656ec869a34baca4783e964d275e Mon Sep 17 00:00:00 2001 From: JWM Date: Fri, 23 Aug 2024 13:33:57 +0200 Subject: [PATCH 10/11] Fix export --- example/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/Makefile b/example/Makefile index ec8874c3..84e012b1 100644 --- a/example/Makefile +++ b/example/Makefile @@ -11,7 +11,7 @@ BUILDDIR ?= _build # logging variables DEBUG ?= 0 LOGLEVEL =? WARNING -export LOGLEVEL=${LOGLEVEL} +export LOGLEVEL # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 From 391e5fb81f2715a9d2f7a291a81aae2fa2798967 Mon Sep 17 00:00:00 2001 From: JWM Date: Fri, 23 Aug 2024 13:46:53 +0200 Subject: [PATCH 11/11] Use export in make recipe --- example/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/Makefile b/example/Makefile index 84e012b1..0e78d882 100644 --- a/example/Makefile +++ b/example/Makefile @@ -11,7 +11,6 @@ BUILDDIR ?= _build # logging variables DEBUG ?= 0 LOGLEVEL =? WARNING -export LOGLEVEL # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 @@ -51,6 +50,7 @@ clean: -rm -rf $(BUILDDIR)/* html: + export LOGLEVEL=$(LOGLEVEL) $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."