66import collections
77from dataclasses import dataclass
88from datetime import datetime
9- import os
109from socket import gethostname
1110import sys
1211from typing import Dict , List
1312from xml .etree import ElementTree
1413
1514from 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
190190def 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
220216if __name__ == "__main__" : # pragma: no cover
0 commit comments