Skip to content
This repository was archived by the owner on Jan 1, 2026. It is now read-only.

Commit 2ce8855

Browse files
authored
Enable mypy (#26)
* enable mypy * unittests for parse_arguments
1 parent 40dbb1a commit 2ce8855

File tree

6 files changed

+70
-42
lines changed

6 files changed

+70
-42
lines changed

README.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,9 @@ Unreleased
5959

6060
- Support Python 3.12
6161
- Include error info from multiple locations
62-
- Added ``junitparser`` as a dependency to write junit output.
62+
- Added ``junitparser`` as a dependency to write junit output
63+
- Update unitests for argument parser
64+
- Enable mypy for type checking
6365

6466
2.3.0 - 2023-04-30
6567
^^^^^^^^^^^^^^^^^^

cppcheck_junit.py

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class CppcheckError:
4949
verbose: str
5050

5151

52-
def parse_arguments() -> argparse.Namespace:
52+
def parse_arguments(args: List[str]) -> argparse.Namespace:
5353
parser = argparse.ArgumentParser(
5454
description="Converts Cppcheck XML version 2 to JUnit XML format.\n"
5555
"Usage:\n"
@@ -63,11 +63,11 @@ def parse_arguments() -> argparse.Namespace:
6363
"error_exitcode",
6464
type=int,
6565
nargs="?",
66-
const=0,
66+
default=ExitStatus.success,
6767
help="If errors are found, "
6868
f"integer <n> is returned instead of default {ExitStatus.success}.",
6969
)
70-
return parser.parse_args()
70+
return parser.parse_args(args)
7171

7272

7373
def parse_cppcheck(file_name: str) -> Dict[str, List[CppcheckError]]:
@@ -86,36 +86,36 @@ def parse_cppcheck(file_name: str) -> Dict[str, List[CppcheckError]]:
8686
"""
8787
root: ElementTree.Element = ElementTree.parse(file_name).getroot()
8888

89-
if root.get("version") is None or int(root.get("version")) != 2:
89+
if int(root.get("version", "0")) != 2:
9090
raise ValueError("Parser only supports Cppcheck XML version 2. Use --xml-version=2.")
9191

9292
error_root = root.find("errors")
93-
9493
errors = collections.defaultdict(list)
95-
for error_element in error_root:
96-
file = error_element.get("file0", "")
97-
locations = []
98-
for location in error_element.findall("location"):
99-
if not file:
100-
file = location.get("file", "")
101-
locations.append(
102-
CppcheckLocation(
103-
location.get("file", ""),
104-
int(location.get("line", 0)),
105-
int(location.get("column", 0)),
106-
location.get("info", ""),
94+
if error_root is not None:
95+
for error_element in error_root:
96+
file = error_element.get("file0", "")
97+
locations = []
98+
for location in error_element.findall("location"):
99+
if not file:
100+
file = location.get("file", "")
101+
locations.append(
102+
CppcheckLocation(
103+
location.get("file", ""),
104+
int(location.get("line", 0)),
105+
int(location.get("column", 0)),
106+
location.get("info", ""),
107+
)
107108
)
108-
)
109109

110-
error = CppcheckError(
111-
file=file,
112-
locations=locations,
113-
message=error_element.get("msg", ""),
114-
severity=error_element.get("severity", ""),
115-
error_id=error_element.get("id", ""),
116-
verbose=error_element.get("verbose", ""),
117-
)
118-
errors[error.file].append(error)
110+
error = CppcheckError(
111+
file=file,
112+
locations=locations,
113+
message=error_element.get("msg", ""),
114+
severity=error_element.get("severity", ""),
115+
error_id=error_element.get("id", ""),
116+
verbose=error_element.get("verbose", ""),
117+
)
118+
errors[error.file].append(error)
119119

120120
return errors
121121

@@ -187,13 +187,13 @@ def generate_test_suite(errors: Dict[str, List[CppcheckError]]) -> TestSuite:
187187
return test_suite
188188

189189

190-
def main() -> ExitStatus: # pragma: no cover
190+
def main() -> int: # pragma: no cover
191191
"""Main function.
192192
193193
Returns:
194194
Exit code.
195195
"""
196-
args = parse_arguments()
196+
args = parse_arguments(sys.argv[1:])
197197

198198
try:
199199
errors = parse_cppcheck(args.input_file)
@@ -210,7 +210,7 @@ def main() -> ExitStatus: # pragma: no cover
210210
tree = JUnitXml("Cppcheck")
211211
tree.add_testsuite(generate_test_suite(errors))
212212
tree.write(args.output_file)
213-
return args.error_exitcode if len(errors) > 0 else ExitStatus.success
213+
return int(args.error_exitcode) if len(errors) > 0 else ExitStatus.success
214214

215215

216216
if __name__ == "__main__": # pragma: no cover

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ build-backend = "setuptools.build_meta"
55
[tool.mypy]
66
ignore_missing_imports = true
77
strict = true
8+
untyped_calls_exclude = "junitparser"
9+
[[tool.mypy.overrides]]
10+
module = "junitparser"
11+
implicit_reexport = true
812

913
[tool.black]
1014
line-length = 99

test.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
from __future__ import absolute_import, division, print_function, unicode_literals
66

77
from copy import deepcopy
8-
import sys
98
import unittest
109
from xml.etree import ElementTree
1110

11+
from exitstatus import ExitStatus
12+
1213
from cppcheck_junit import (
1314
CppcheckError,
1415
CppcheckLocation,
@@ -102,6 +103,10 @@ def test_malformed(self) -> None:
102103
with self.assertRaises(ElementTree.ParseError):
103104
parse_cppcheck("tests/cppcheck-out-malformed.xml")
104105

106+
def test_malformed_no_errors(self) -> None:
107+
errors = parse_cppcheck("tests/cppcheck-malformed-no-errors.xml")
108+
self.assertEqual(errors, {})
109+
105110

106111
class GenerateTestError(unittest.TestCase):
107112
basic_error = CppcheckError("file", [], "message", "severity", "error_id", "verbose")
@@ -189,12 +194,26 @@ def test_multiple(self) -> None:
189194

190195

191196
class ParseArgumentsTestCase(unittest.TestCase):
192-
def test_no_arguments(self) -> None:
197+
def test_empty(self) -> None:
198+
with self.assertRaises(SystemExit):
199+
parse_arguments([])
200+
201+
def test_one_arg(self) -> None:
193202
with self.assertRaises(SystemExit):
194-
# Suppress argparse stderr.
195-
class NullWriter:
196-
def write(self, s: str) -> None:
197-
pass
203+
parse_arguments(["h"])
204+
205+
def test_two_args(self) -> None:
206+
args = parse_arguments(["input", "output"])
207+
self.assertEqual(args.input_file, "input")
208+
self.assertEqual(args.output_file, "output")
209+
self.assertEqual(args.error_exitcode, ExitStatus.success)
198210

199-
sys.stderr = NullWriter()
200-
parse_arguments()
211+
def test_three_args(self) -> None:
212+
args = parse_arguments(["input", "output", "1"])
213+
self.assertEqual(args.input_file, "input")
214+
self.assertEqual(args.output_file, "output")
215+
self.assertEqual(args.error_exitcode, 1)
216+
217+
def test_invalid_exitcode(self) -> None:
218+
with self.assertRaises(SystemExit):
219+
parse_arguments(["input", "output", "a"])
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<results version="2">
3+
<cppcheck version="1.71"/>
4+
</results>

tox.ini

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ extend-exclude =
77
[gh-actions]
88
python =
99
3.8: py38
10-
3.9: py39, fmt-check, lint
10+
3.9: py39, fmt-check, lint, type-check
1111
3.10: py310
1212
3.11: py311
1313
3.12: py312
@@ -29,7 +29,6 @@ deps =
2929
commands =
3030
python -m pytest --cov=./ --cov-report=html --cov-report=term --cov-fail-under=100 test.py
3131

32-
# TODO: Get mypy checking to pass.
3332
[testenv:type-check]
3433
skip_install = true
3534
deps =
@@ -58,7 +57,7 @@ skip_install = true
5857
deps =
5958
-r{toxinidir}/dev-requirements.txt
6059
commands =
61-
isort --check .
60+
isort --check -v .
6261
black --check .
6362

6463
[testenv:build]

0 commit comments

Comments
 (0)