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

Commit 40dbb1a

Browse files
authored
Use junitparser (#24)
1 parent 871ee1c commit 40dbb1a

File tree

5 files changed

+154
-282
lines changed

5 files changed

+154
-282
lines changed

README.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ Unreleased
5959

6060
- Support Python 3.12
6161
- Include error info from multiple locations
62+
- Added ``junitparser`` as a dependency to write junit output.
6263

6364
2.3.0 - 2023-04-30
6465
^^^^^^^^^^^^^^^^^^

cppcheck_junit.py

Lines changed: 65 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@
66
import collections
77
from dataclasses import dataclass
88
from datetime import datetime
9-
import os
109
from socket import gethostname
1110
import sys
1211
from typing import Dict, List
1312
from xml.etree import ElementTree
1413

1514
from exitstatus import ExitStatus
15+
from junitparser import Error, JUnitXml, TestCase, TestSuite
1616

1717

1818
@dataclass
@@ -100,7 +100,7 @@ def parse_cppcheck(file_name: str) -> Dict[str, List[CppcheckError]]:
100100
file = location.get("file", "")
101101
locations.append(
102102
CppcheckLocation(
103-
location.get("file"),
103+
location.get("file", ""),
104104
int(location.get("line", 0)),
105105
int(location.get("column", 0)),
106106
location.get("info", ""),
@@ -120,71 +120,71 @@ def parse_cppcheck(file_name: str) -> Dict[str, List[CppcheckError]]:
120120
return errors
121121

122122

123-
def generate_test_suite(errors: Dict[str, List[CppcheckError]]) -> ElementTree.ElementTree:
124-
"""Converts parsed Cppcheck errors into JUnit XML tree.
123+
def generate_test_error(error: CppcheckError) -> Error:
124+
"""Converts parsed Cppcheck error into Error.
125125
126126
Args:
127+
error: Cppcheck error
128+
129+
Returns:
130+
Error
131+
"""
132+
133+
jerror = Error(error.message, f"{error.severity}:{error.error_id}")
134+
if len(error.locations) == 0:
135+
jerror.text = error.verbose
136+
elif len(error.locations) == 1 and error.locations[0].info == "":
137+
location = error.locations[0]
138+
jerror.text = f"{location.file}:{location.line}:{location.column}: {error.verbose}"
139+
else:
140+
jerror.text = error.verbose
141+
for location in error.locations:
142+
jerror.text += f"\n{location.file}:{location.line}:{location.column}: {location.info}"
143+
144+
return jerror
145+
146+
147+
def generate_test_case(name: str, class_name: str, errors: List[CppcheckError]) -> TestCase:
148+
"""Converts parsed Cppcheck errors into TestCase.
149+
150+
Args:
151+
name: Name for the test case
152+
class_name: Class for the test case
127153
errors: Parsed cppcheck errors.
128154
129155
Returns:
130-
XML test suite.
156+
TestCase
131157
"""
132-
test_suite = ElementTree.Element("testsuite")
133-
test_suite.attrib["name"] = "Cppcheck errors"
134-
test_suite.attrib["timestamp"] = datetime.isoformat(datetime.now())
135-
test_suite.attrib["hostname"] = gethostname()
136-
test_suite.attrib["tests"] = str(len(errors))
137-
test_suite.attrib["failures"] = str(0)
138-
test_suite.attrib["errors"] = str(len(errors))
139-
test_suite.attrib["time"] = str(1)
140-
141-
for file_name, errors in errors.items():
142-
test_case = ElementTree.SubElement(
143-
test_suite,
144-
"testcase",
145-
name=os.path.relpath(file_name) if file_name else "Cppcheck error",
146-
classname="Cppcheck error",
147-
time=str(1),
148-
)
149-
for error in errors:
150-
error_element = ElementTree.SubElement(
151-
test_case,
152-
"error",
153-
type=error.severity,
154-
file=os.path.relpath(error.file) if error.file else "",
155-
message=f"{error.message}",
156-
)
157-
if len(error.locations) == 0:
158-
error_element.text = error.verbose
159-
elif len(error.locations) == 1 and error.locations[0].info == "":
160-
location = error.locations[0]
161-
file = os.path.relpath(location.file) if location.file else ""
162-
error_element.text = f"{file}:{location.line}:{location.column}: {error.verbose}"
163-
else:
164-
error_element.text = error.verbose
165-
for location in error.locations:
166-
file = os.path.relpath(location.file) if location.file else ""
167-
error_element.text += (
168-
f"\n{file}:{location.line}:{location.column}: {location.info}"
169-
)
170-
171-
return ElementTree.ElementTree(test_suite)
172-
173-
174-
def generate_single_success_test_suite() -> ElementTree.ElementTree:
175-
"""Generates a single successful JUnit XML testcase."""
176-
test_suite = ElementTree.Element("testsuite")
177-
test_suite.attrib["name"] = "Cppcheck errors"
178-
test_suite.attrib["timestamp"] = datetime.isoformat(datetime.now())
179-
test_suite.attrib["hostname"] = gethostname()
180-
test_suite.attrib["tests"] = str(1)
181-
test_suite.attrib["failures"] = str(0)
182-
test_suite.attrib["errors"] = str(0)
183-
test_suite.attrib["time"] = str(1)
184-
ElementTree.SubElement(
185-
test_suite, "testcase", name="Cppcheck success", classname="Cppcheck success", time=str(1)
186-
)
187-
return ElementTree.ElementTree(test_suite)
158+
159+
test_case = TestCase(name if name else "Cppcheck", class_name, 1)
160+
jerrors = []
161+
for error in errors:
162+
jerrors.append(generate_test_error(error))
163+
test_case.result = jerrors
164+
165+
return test_case
166+
167+
168+
def generate_test_suite(errors: Dict[str, List[CppcheckError]]) -> TestSuite:
169+
"""Converts parsed Cppcheck errors into TestSuite.
170+
171+
Args:
172+
errors: Parsed cppcheck errors.
173+
174+
Returns:
175+
TestSuite
176+
"""
177+
test_suite = TestSuite("Cppcheck")
178+
test_suite.timestamp = datetime.isoformat(datetime.now())
179+
test_suite.hostname = gethostname()
180+
181+
if len(errors) == 0:
182+
test_suite.add_testcase(generate_test_case("", "Cppcheck success", []))
183+
184+
for name, cerrors in errors.items():
185+
test_suite.add_testcase(generate_test_case(name, "Cppcheck error", cerrors))
186+
187+
return test_suite
188188

189189

190190
def main() -> ExitStatus: # pragma: no cover
@@ -207,14 +207,10 @@ def main() -> ExitStatus: # pragma: no cover
207207
print(f"{args.input_file} is a malformed XML file. Did you use --xml-version=2?\n{e}")
208208
return ExitStatus.failure
209209

210-
if len(errors) > 0:
211-
tree = generate_test_suite(errors)
212-
tree.write(args.output_file, encoding="utf-8", xml_declaration=True)
213-
return args.error_exitcode
214-
else:
215-
tree = generate_single_success_test_suite()
216-
tree.write(args.output_file, encoding="utf-8", xml_declaration=True)
217-
return ExitStatus.success
210+
tree = JUnitXml("Cppcheck")
211+
tree.add_testsuite(generate_test_suite(errors))
212+
tree.write(args.output_file)
213+
return args.error_exitcode if len(errors) > 0 else ExitStatus.success
218214

219215

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

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
exitstatus>=1.0.0
1+
exitstatus>=1.0.0
2+
junitparser>=3.0.0

0 commit comments

Comments
 (0)