Skip to content

Commit e009f69

Browse files
PYSCAN-27 Re-raising error should not be too verbose by default (#23)
1 parent d2e6eb9 commit e009f69

File tree

7 files changed

+127
-61
lines changed

7 files changed

+127
-61
lines changed

src/py_sonar_scanner/__main__.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,22 @@
1717
# along with this program; if not, write to the Free Software Foundation,
1818
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
#
20-
2120
from py_sonar_scanner.configuration import Configuration
2221
from py_sonar_scanner.environment import Environment
22+
from py_sonar_scanner.logger import ApplicationLogger
2323

2424

2525
def scan():
26+
log = ApplicationLogger.get_logger()
2627
cfg = Configuration()
27-
cfg.setup()
28-
29-
env = Environment(cfg)
30-
env.setup()
31-
env.scanner().scan()
32-
env.cleanup()
28+
try:
29+
cfg.setup()
30+
env = Environment(cfg)
31+
env.scan()
32+
except Exception as e:
33+
log.exception("Error during SonarScanner execution: %s", str(e))
34+
if not cfg.is_debug():
35+
log.info("Re-run SonarScanner using the -X switch to enable full debug logging.")
3336

3437

3538
if __name__ == "__main__":

src/py_sonar_scanner/configuration.py

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,41 +18,55 @@
1818
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
#
2020
from __future__ import annotations
21+
22+
import argparse
2123
import os
2224
import sys
2325
from logging import Logger
26+
from typing import Union
2427

2528
import toml
2629

2730
from py_sonar_scanner.logger import ApplicationLogger
2831

2932

3033
class Configuration:
34+
log: Logger
3135
sonar_scanner_executable_path: str
3236
sonar_scanner_path: str
3337
sonar_scanner_version: str
3438
scan_arguments: list[str]
35-
log: Logger
39+
wrapper_arguments: argparse.Namespace
3640

3741
def __init__(self):
42+
self.log = ApplicationLogger.get_logger()
3843
self.sonar_scanner_path = ".scanner"
3944
self.sonar_scanner_version = "4.6.2.2472"
4045
self.sonar_scanner_executable_path = ""
4146
self.scan_arguments = []
42-
self.log = ApplicationLogger.get_logger()
47+
self.wrapper_arguments = argparse.Namespace(debug=False)
4348

4449
def setup(self) -> None:
4550
"""This is executed when run from the command line"""
51+
self._read_wrapper_arguments()
52+
ApplicationLogger.set_debug(self.is_debug())
4653
self.scan_arguments = sys.argv[1:]
4754
self.scan_arguments.extend(self._read_toml_args())
4855

56+
def _read_wrapper_arguments(self):
57+
argument_parser = argparse.ArgumentParser()
58+
argument_parser.add_argument("-toml.path", "-Dtoml.path", "--toml.path", dest="toml_path")
59+
argument_parser.add_argument("-project.home", "-Dproject.home", "--project.home", dest="project_home")
60+
argument_parser.add_argument("-X", action="store_true", dest="debug")
61+
self.wrapper_arguments, _ = argument_parser.parse_known_args(args=sys.argv[1:])
62+
4963
def _read_toml_args(self) -> list[str]:
5064
scan_arguments: list[str] = []
5165
toml_data: dict = {}
5266
try:
5367
toml_data = self._read_toml_file()
54-
except OSError:
55-
self.log.error("Test error while opening file.")
68+
except OSError as e:
69+
self.log.exception("Error while opening .toml file: %s", str(e))
5670
sonar_properties = self._extract_sonar_properties(toml_data)
5771
for key, value in sonar_properties.items():
5872
self._add_parameter_to_scanner_args(scan_arguments, key, value)
@@ -84,30 +98,12 @@ def _read_toml_file(self) -> dict:
8498
return toml.loads(toml_data)
8599

86100
def _get_toml_file_path(self) -> str:
87-
args = self._get_arguments_dict()
88-
if "-Dtoml.path" in args:
89-
return args["-Dtoml.path"]
90-
elif "-Dproject.home" in args:
91-
return os.path.join(args["-Dproject.home"], "pyproject.toml")
101+
if self.wrapper_arguments.toml_path is not None:
102+
return self.wrapper_arguments.toml_path
103+
elif self.wrapper_arguments.project_home is not None:
104+
return os.path.join(self.wrapper_arguments.project_home, "pyproject.toml")
92105
else:
93106
return os.path.join(os.curdir, "pyproject.toml")
94107

95-
def _get_arguments_dict(self):
96-
args = self.scan_arguments
97-
arguments_dict = {}
98-
i = 0
99-
while i < len(args):
100-
current_arg = args[i]
101-
102-
if "=" in current_arg:
103-
arg_parts = current_arg.split("=", 1)
104-
arguments_dict[arg_parts[0]] = arg_parts[1]
105-
else:
106-
if i + 1 < len(args) and "=" not in args[i + 1]:
107-
arguments_dict[current_arg] = args[i + 1]
108-
i += 1
109-
else:
110-
arguments_dict[current_arg] = None
111-
112-
i += 1
113-
return arguments_dict
108+
def is_debug(self) -> bool:
109+
return self.wrapper_arguments.debug

src/py_sonar_scanner/environment.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ def __init__(self, cfg: Configuration):
4444
self.cfg = cfg
4545
self.log = ApplicationLogger.get_logger()
4646

47+
def scan(self):
48+
try:
49+
self.setup()
50+
self.scanner().scan()
51+
finally:
52+
self.cleanup()
53+
4754
def setup(self):
4855
self.cleanup()
4956
if self._is_sonar_scanner_on_path():

src/py_sonar_scanner/logger.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
# along with this program; if not, write to the Free Software Foundation,
1818
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
#
20+
import functools
2021
import logging
2122
from logging import Logger
2223
from typing import Optional
@@ -37,3 +38,12 @@ def _setup_logger(log: Logger):
3738
log.setLevel(logging.INFO)
3839
handler = logging.StreamHandler()
3940
log.addHandler(handler)
41+
42+
@classmethod
43+
def set_debug(cls, debug: bool) -> None:
44+
if debug:
45+
cls.get_logger().setLevel(logging.DEBUG)
46+
cls.get_logger().exception = functools.partial(cls.get_logger().exception, exc_info=True)
47+
else:
48+
cls.get_logger().setLevel(logging.INFO)
49+
cls.get_logger().exception = functools.partial(cls.get_logger().exception, exc_info=False)

tests/test_configuration.py

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import unittest
2222
from unittest.mock import patch, Mock
2323
from py_sonar_scanner.configuration import Configuration
24+
from py_sonar_scanner.logger import ApplicationLogger
2425

2526
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
2627

@@ -29,6 +30,7 @@ class TestConfiguration(unittest.TestCase):
2930
@patch("py_sonar_scanner.configuration.sys")
3031
def test_argument_parsing(self, mock_sys):
3132
configuration = Configuration()
33+
self.assertFalse(configuration.is_debug())
3234

3335
mock_sys.argv = ["path/to/scanner/py-sonar-scanner"]
3436
configuration.setup()
@@ -71,6 +73,10 @@ def test_argument_parsing(self, mock_sys):
7173
configuration.setup()
7274
self.assertListEqual(configuration.scan_arguments, ["-Dproject.home=tests=2"])
7375

76+
mock_sys.argv = ["path/to/scanner/py-sonar-scanner", "-X"]
77+
configuration.setup()
78+
self.assertTrue(configuration.is_debug())
79+
7480
@patch("py_sonar_scanner.configuration.sys")
7581
def test_dict_with_no_valid_values(self, mock_sys):
7682
configuration = Configuration()
@@ -134,26 +140,24 @@ def test_toml_with_valid_values(self, mock_sys):
134140
],
135141
)
136142

137-
@patch("py_sonar_scanner.configuration.ApplicationLogger.get_logger")
138143
@patch("builtins.open")
139144
@patch("py_sonar_scanner.configuration.sys")
140-
def test_error_while_reading_toml_file(self, mock_sys, mock_open, mock_logger):
145+
def test_error_while_reading_toml_file(self, mock_sys, mock_open):
141146
toml_file_path = os.path.join(CURRENT_DIR, "resources", "test_toml_file.toml")
142147
mock_sys.argv = ["path/to/scanner/py-sonar-scanner", f"-Dtoml.path={toml_file_path}"]
143148

144149
mock_open.side_effect = OSError("Test error while opening file.")
145-
146-
mock_logger_instance = Mock()
147-
mock_logger_instance.error = Mock()
148-
mock_logger.return_value = mock_logger_instance
149-
150-
configuration = Configuration()
151-
configuration.setup()
152-
153-
self.assertListEqual(
154-
configuration.scan_arguments,
155-
[
156-
f"-Dtoml.path={CURRENT_DIR}/resources/test_toml_file.toml",
157-
],
158-
)
159-
mock_logger_instance.error.assert_called_once_with("Test error while opening file.")
150+
with self.assertLogs(ApplicationLogger.get_logger()) as log:
151+
configuration = Configuration()
152+
configuration.setup()
153+
154+
self.assertListEqual(
155+
configuration.scan_arguments,
156+
[
157+
f"-Dtoml.path={CURRENT_DIR}/resources/test_toml_file.toml",
158+
],
159+
)
160+
161+
self.assertEqual(
162+
"Error while opening .toml file: Test error while opening file.", log.records[0].getMessage()
163+
)

tests/test_environment.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,17 @@ def test_is_sonar_scanner_on_path(self, mock_shutil):
157157
environment._is_sonar_scanner_on_path()
158158

159159
mock_shutil.which.assert_called_once_with("sonar-scanner")
160+
161+
def test_env_scan(self):
162+
cfg = Configuration()
163+
environment = Environment(cfg)
164+
environment.setup = Mock()
165+
environment.scanner = Mock(side_effect=Exception("Something wrong"))
166+
environment.cleanup = Mock()
167+
168+
with self.assertRaises(Exception):
169+
environment.scan()
170+
171+
environment.setup.assert_called_once()
172+
environment.scanner.assert_called_once()
173+
environment.cleanup.assert_called_once()

tests/test_main.py

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,62 @@
1717
# along with this program; if not, write to the Free Software Foundation,
1818
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1919
#
20+
import logging
2021
import unittest
2122
from unittest.mock import patch, Mock, MagicMock
2223
from py_sonar_scanner.__main__ import scan
23-
from py_sonar_scanner.scanner import Scanner
24+
from py_sonar_scanner.logger import ApplicationLogger
2425

2526

2627
class TestMain(unittest.TestCase):
27-
@patch("py_sonar_scanner.scanner.Scanner")
2828
@patch("py_sonar_scanner.__main__.Environment")
2929
@patch("py_sonar_scanner.__main__.Configuration")
30-
def test_main_scan(self, mock_cfg, mock_env, mock_scanner):
30+
def test_main_scan(self, mock_cfg, mock_env):
3131
configuration_instance = MagicMock()
3232
configuration_instance.setup = Mock()
3333
mock_cfg.return_value = configuration_instance
3434

3535
environment_instance = MagicMock()
36-
environment_instance.setup = Mock()
37-
mock_scanner.scan = Mock()
38-
environment_instance.scanner.return_value = mock_scanner
36+
environment_instance.scan = Mock()
3937
mock_env.return_value = environment_instance
4038

4139
scan()
4240

4341
configuration_instance.setup.assert_called_once()
44-
environment_instance.setup.assert_called_once()
45-
environment_instance.scanner.assert_called_once()
46-
mock_scanner.scan.assert_called_once()
42+
environment_instance.scan.assert_called_once()
43+
44+
@patch("py_sonar_scanner.__main__.Environment")
45+
@patch("py_sonar_scanner.configuration.sys")
46+
def test_main_scan_fail(self, mock_sys, mock_env):
47+
mock_sys.argv = ["path/to/scanner/py-sonar-scanner"]
48+
environment_instance = MagicMock()
49+
environment_instance.scan = Mock(side_effect=Exception("Something"))
50+
mock_env.return_value = environment_instance
51+
52+
with self.assertLogs(ApplicationLogger.get_logger()) as log:
53+
scan()
54+
self.assertEqual(2, len(log.records))
55+
self.assertEqual("Error during SonarScanner execution: Something", log.records[0].getMessage())
56+
self.assertEqual(logging.ERROR, log.records[0].levelno)
57+
self.assertFalse(log.records[0].exc_info)
58+
self.assertEqual(
59+
"Re-run SonarScanner using the -X switch to enable full debug logging.", log.records[1].getMessage()
60+
)
61+
self.assertEqual(logging.INFO, log.records[1].levelno)
62+
63+
@patch("py_sonar_scanner.scanner.Scanner")
64+
@patch("py_sonar_scanner.__main__.Environment")
65+
@patch("py_sonar_scanner.configuration.sys")
66+
def test_main_scan_debug_fail(self, mock_sys, mock_env, mock_scanner):
67+
mock_sys.argv = ["path/to/scanner/py-sonar-scanner", "-X"]
68+
69+
environment_instance = MagicMock()
70+
environment_instance.scan = Mock(side_effect=Exception("Something"))
71+
mock_env.return_value = environment_instance
72+
73+
with self.assertLogs(ApplicationLogger.get_logger()) as log:
74+
scan()
75+
self.assertEqual(1, len(log.records))
76+
self.assertEqual("Error during SonarScanner execution: Something", log.records[0].getMessage())
77+
self.assertEqual(logging.ERROR, log.records[0].levelno)
78+
self.assertTrue(log.records[0].exc_info)

0 commit comments

Comments
 (0)