-
Notifications
You must be signed in to change notification settings - Fork 3
SCANPY-80 Read ".coveragerc" configuration to exclude files from coverage #234
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
4a8fec7
7e0278b
ad3748c
639d071
238bb6c
4ecd777
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,82 @@ | ||||||||||||||||||||||||||||||||||||||
# | ||||||||||||||||||||||||||||||||||||||
# Sonar Scanner Python | ||||||||||||||||||||||||||||||||||||||
# Copyright (C) 2011-2024 SonarSource SA. | ||||||||||||||||||||||||||||||||||||||
# mailto:info AT sonarsource DOT com | ||||||||||||||||||||||||||||||||||||||
# | ||||||||||||||||||||||||||||||||||||||
# This program is free software; you can redistribute it and/or | ||||||||||||||||||||||||||||||||||||||
# modify it under the terms of the GNU Lesser General Public | ||||||||||||||||||||||||||||||||||||||
# License as published by the Free Software Foundation; either | ||||||||||||||||||||||||||||||||||||||
# version 3 of the License, or (at your option) any later version. | ||||||||||||||||||||||||||||||||||||||
# This program is distributed in the hope that it will be useful, | ||||||||||||||||||||||||||||||||||||||
# | ||||||||||||||||||||||||||||||||||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||||||||||||||||||||||||||||||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||||||||||||||||||||||||||||||||||||
# Lesser General Public License for more details. | ||||||||||||||||||||||||||||||||||||||
# | ||||||||||||||||||||||||||||||||||||||
# You should have received a copy of the GNU Lesser General Public License | ||||||||||||||||||||||||||||||||||||||
# along with this program; if not, write to the Free Software Foundation, | ||||||||||||||||||||||||||||||||||||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||||||||||||||||||||||||||||||||||||||
# | ||||||||||||||||||||||||||||||||||||||
import configparser | ||||||||||||||||||||||||||||||||||||||
import logging | ||||||||||||||||||||||||||||||||||||||
import pathlib | ||||||||||||||||||||||||||||||||||||||
from typing import Any | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
class CoverageRCConfigurationLoader: | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@staticmethod | ||||||||||||||||||||||||||||||||||||||
def load(base_dir: pathlib.Path) -> dict[str, str]: | ||||||||||||||||||||||||||||||||||||||
config_file_path = base_dir / ".coveragerc" | ||||||||||||||||||||||||||||||||||||||
result_dict: dict[str, str] = {} | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
coverage_properties = CoverageRCConfigurationLoader.__read_config(config_file_path) | ||||||||||||||||||||||||||||||||||||||
if len(coverage_properties) == 0: | ||||||||||||||||||||||||||||||||||||||
return result_dict | ||||||||||||||||||||||||||||||||||||||
exclusion_properties = CoverageRCConfigurationLoader.__read_coverage_exclusions_properties( | ||||||||||||||||||||||||||||||||||||||
config_file_path, coverage_properties | ||||||||||||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||||||||||||
result_dict.update(exclusion_properties) | ||||||||||||||||||||||||||||||||||||||
return result_dict | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor I would simplify
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@staticmethod | ||||||||||||||||||||||||||||||||||||||
def __read_config(config_file_path: pathlib.Path) -> dict[str, Any]: | ||||||||||||||||||||||||||||||||||||||
config_dict: dict[str, Any] = {} | ||||||||||||||||||||||||||||||||||||||
if not config_file_path.exists(): | ||||||||||||||||||||||||||||||||||||||
logging.debug(f"Configuration file not found: {config_file_path}") | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor for coherence with other logs
Suggested change
|
||||||||||||||||||||||||||||||||||||||
return config_dict | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
try: | ||||||||||||||||||||||||||||||||||||||
config_parser = configparser.ConfigParser() | ||||||||||||||||||||||||||||||||||||||
config_parser.read(config_file_path) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
# Iterate over sections and options | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit
Suggested change
|
||||||||||||||||||||||||||||||||||||||
for section in config_parser.sections(): | ||||||||||||||||||||||||||||||||||||||
section_values = {} | ||||||||||||||||||||||||||||||||||||||
for key, value in config_parser.items(section): | ||||||||||||||||||||||||||||||||||||||
section_values[key] = value | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
config_dict[section] = section_values | ||||||||||||||||||||||||||||||||||||||
except Exception as e: | ||||||||||||||||||||||||||||||||||||||
logging.debug(f"Error decoding coverage file {config_file_path}: {e}") | ||||||||||||||||||||||||||||||||||||||
return config_dict | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
@staticmethod | ||||||||||||||||||||||||||||||||||||||
def __read_coverage_exclusions_properties( | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. minor as this function is not reading from the file but only extracting patterns
Suggested change
|
||||||||||||||||||||||||||||||||||||||
config_file_path: pathlib.Path, coverage_properties: dict[str, Any] | ||||||||||||||||||||||||||||||||||||||
) -> dict[str, Any]: | ||||||||||||||||||||||||||||||||||||||
result_dict: dict[str, Any] = {} | ||||||||||||||||||||||||||||||||||||||
if "run" not in coverage_properties: | ||||||||||||||||||||||||||||||||||||||
logging.debug(f"The run key was not found in {config_file_path}") | ||||||||||||||||||||||||||||||||||||||
return result_dict | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
if "omit" not in coverage_properties["run"]: | ||||||||||||||||||||||||||||||||||||||
logging.debug(f"The run.omit key was not found in {config_file_path}") | ||||||||||||||||||||||||||||||||||||||
return result_dict | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
omit_exclusions = coverage_properties["run"]["omit"] | ||||||||||||||||||||||||||||||||||||||
patterns_list = [patterns.strip() for patterns in omit_exclusions.splitlines() if patterns.strip()] | ||||||||||||||||||||||||||||||||||||||
translated_exclusions = ", ".join(patterns_list) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
result_dict["sonar.coverage.exclusions"] = translated_exclusions | ||||||||||||||||||||||||||||||||||||||
return result_dict | ||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't find it relevant in a loader file to build the options that should be passed to the scanner. I think this function should only return the exclusion patterns, so only a string, and the dict should be built outside. Moreover, the dict will always have only one key which seems overkill :) I'd suggest to build the dictionnary in the load function
Suggested change
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
# | ||
# Sonar Scanner Python | ||
# Copyright (C) 2011-2024 SonarSource SA. | ||
# mailto:info AT sonarsource DOT com | ||
# | ||
# This program is free software; you can redistribute it and/or | ||
# modify it under the terms of the GNU Lesser General Public | ||
# License as published by the Free Software Foundation; either | ||
# version 3 of the License, or (at your option) any later version. | ||
# This program is distributed in the hope that it will be useful, | ||
# | ||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
# Lesser General Public License for more details. | ||
# | ||
# You should have received a copy of the GNU Lesser General Public License | ||
# along with this program; if not, write to the Free Software Foundation, | ||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
# | ||
from pathlib import Path | ||
from unittest import mock | ||
from unittest.mock import MagicMock, patch | ||
|
||
from pyfakefs.fake_filesystem_unittest import TestCase | ||
from pysonar_scanner.configuration.pyproject_toml import TomlConfigurationLoader | ||
from pysonar_scanner.configuration.coveragerc_loader import CoverageRCConfigurationLoader | ||
|
||
|
||
class TestCoverageRcFile(TestCase): | ||
def setUp(self): | ||
self.setUpPyfakefs() | ||
|
||
def test_load_coverage_file(self): | ||
self.fs.create_file( | ||
".coveragerc", | ||
contents=""" | ||
[run] | ||
omit = | ||
*/.local/* | ||
/usr/* | ||
utils/tirefire.py | ||
""", | ||
) | ||
properties = CoverageRCConfigurationLoader.load(Path(".")) | ||
|
||
self.assertEqual(properties["sonar.coverage.exclusions"], "*/.local/*, /usr/*, utils/tirefire.py") | ||
|
||
@patch("pysonar_scanner.configuration.coveragerc_loader.logging") | ||
def test_load_missing_file(self, mock_logging): | ||
properties = CoverageRCConfigurationLoader.load(Path(".")) | ||
self.assertEqual(len(properties), 0) | ||
mock_logging.debug.assert_called_with("Configuration file not found: .coveragerc") | ||
|
||
@patch("pysonar_scanner.configuration.coveragerc_loader.logging") | ||
def test_load_without_run_section(self, mock_logging): | ||
self.fs.create_file( | ||
".coveragerc", | ||
contents=""" | ||
[something_else] | ||
""", | ||
) | ||
properties = CoverageRCConfigurationLoader.load(Path(".")) | ||
self.assertEqual(len(properties), 0) | ||
mock_logging.debug.assert_called_with("The run key was not found in .coveragerc") | ||
|
||
@patch("pysonar_scanner.configuration.coveragerc_loader.logging") | ||
def test_load_without_exclusions_property(self, mock_logging): | ||
self.fs.create_file( | ||
".coveragerc", | ||
contents=""" | ||
[run] | ||
""", | ||
) | ||
properties = CoverageRCConfigurationLoader.load(Path(".")) | ||
self.assertEqual(len(properties), 0) | ||
mock_logging.debug.assert_called_with("The run.omit key was not found in .coveragerc") | ||
|
||
@patch("pysonar_scanner.configuration.coveragerc_loader.logging") | ||
def test_load_malformed_file(self, mock_logging): | ||
self.fs.create_file( | ||
".coveragerc", | ||
contents=""" | ||
[run | ||
omit = | ||
""", | ||
) | ||
properties = CoverageRCConfigurationLoader.load(Path(".")) | ||
self.assertEqual(len(properties), 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor let's rename this function as it's not loading the all coverage file but only the exclusions properties