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

Commit 657f8f5

Browse files
authored
Merge pull request #4 from yamokosk/master
Improves validation against JUnit schema
2 parents 49e06c6 + 1348d4a commit 657f8f5

File tree

2 files changed

+84
-27
lines changed

2 files changed

+84
-27
lines changed

cppcheck_junit.py

100644100755
Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
import argparse
88
import collections
9+
from datetime import datetime
910
import os
11+
from socket import gethostname
1012
import sys
1113
from typing import Dict, List # noqa: F401
1214
from xml.etree import ElementTree
@@ -110,19 +112,24 @@ def generate_test_suite(errors):
110112
XML test suite.
111113
"""
112114
test_suite = ElementTree.Element('testsuite')
113-
test_suite.attrib['errors'] = str(len(errors))
114-
test_suite.attrib['failures'] = str(0)
115115
test_suite.attrib['name'] = 'Cppcheck errors'
116+
test_suite.attrib['timestamp'] = datetime.isoformat(datetime.now())
117+
test_suite.attrib['hostname'] = gethostname()
116118
test_suite.attrib['tests'] = str(len(errors))
119+
test_suite.attrib['failures'] = str(0)
120+
test_suite.attrib['errors'] = str(len(errors))
117121
test_suite.attrib['time'] = str(1)
118122

119123
for file_name, errors in errors.items():
120124
test_case = ElementTree.SubElement(test_suite,
121125
'testcase',
122-
name=os.path.relpath(file_name) if file_name else '')
126+
name=os.path.relpath(file_name) if file_name else '',
127+
classname='',
128+
time=str(1))
123129
for error in errors:
124130
ElementTree.SubElement(test_case,
125131
'error',
132+
type='',
126133
file=os.path.relpath(error.file) if error.file else '',
127134
line=str(error.line),
128135
message='{}: ({}) {}'.format(error.line,
@@ -137,10 +144,17 @@ def generate_single_success_test_suite():
137144
"""Generates a single successful JUnit XML testcase."""
138145
test_suite = ElementTree.Element('testsuite')
139146
test_suite.attrib['name'] = 'Cppcheck errors'
147+
test_suite.attrib['timestamp'] = datetime.isoformat(datetime.now())
148+
test_suite.attrib['hostname'] = gethostname()
140149
test_suite.attrib['tests'] = str(1)
150+
test_suite.attrib['failures'] = str(0)
151+
test_suite.attrib['errors'] = str(0)
152+
test_suite.attrib['time'] = str(1)
141153
ElementTree.SubElement(test_suite,
142154
'testcase',
143-
name='Cppcheck success')
155+
name='Cppcheck success',
156+
classname='',
157+
time=str(1))
144158
return ElementTree.ElementTree(test_suite)
145159

146160

test.py

Lines changed: 66 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ def test_malformed(self): # type: () -> None
102102

103103

104104
class GenerateTestSuiteTestCase(unittest.TestCase):
105+
# Expected attributes from JUnit XSD
106+
# ref: https://raw.githubusercontent.com/windyroad/JUnit-Schema/master/JUnit.xsd
107+
# @TODO: Better thing to do here would be to curl or otherwise access the
108+
# spec above instead of hardcoding pieces of it here
109+
junit_testsuite_attributes = ['name', 'timestamp', 'hostname', 'tests',
110+
'failures', 'errors', 'time']
111+
junit_testcase_attributes = ['name', 'classname', 'time']
112+
junit_error_attributes = ['type']
113+
105114
def test_single(self): # type: () -> None
106115
errors = {'file_name':
107116
[CppcheckError('file_name',
@@ -111,18 +120,27 @@ def test_single(self): # type: () -> None
111120
'error_id',
112121
'verbose error message')]}
113122
tree = generate_test_suite(errors)
114-
root = tree.getroot()
115-
self.assertEqual(root.get('errors'), str(1))
116-
self.assertEqual(root.get('failures'), str(0))
117-
self.assertEqual(root.get('tests'), str(1))
118-
119-
test_case_element = root.find('testcase')
120-
self.assertEqual(test_case_element.get('name'), 'file_name')
121-
122-
error_element = test_case_element.find('error')
123+
testsuite_element = tree.getroot()
124+
self.assertEqual(testsuite_element.get('errors'), str(1))
125+
self.assertEqual(testsuite_element.get('failures'), str(0))
126+
self.assertEqual(testsuite_element.get('tests'), str(1))
127+
# Check that testsuite element is compliant with the spec
128+
for required_attribute in self.junit_testsuite_attributes:
129+
self.assertTrue(required_attribute in testsuite_element.attrib.keys())
130+
131+
testcase_element = testsuite_element.find('testcase')
132+
self.assertEqual(testcase_element.get('name'), 'file_name')
133+
# Check that test_case is compliant with the spec
134+
for required_attribute in self.junit_testcase_attributes:
135+
self.assertTrue(required_attribute in testcase_element.attrib.keys())
136+
137+
error_element = testcase_element.find('error')
123138
self.assertEqual(error_element.get('file'), 'file_name')
124139
self.assertEqual(error_element.get('line'), str(4))
125140
self.assertEqual(error_element.get('message'), '4: (severity) error message')
141+
# Check that error element is compliant with the spec
142+
for required_attribute in self.junit_error_attributes:
143+
self.assertTrue(required_attribute in error_element.attrib.keys())
126144

127145
def test_missing_file(self): # type: () -> None
128146
errors = {'':
@@ -141,32 +159,57 @@ def test_missing_file(self): # type: () -> None
141159
'that may increase the checking time. For more details, '
142160
'use --enable=information.')]}
143161
tree = generate_test_suite(errors)
144-
root = tree.getroot()
145-
self.assertEqual(root.get('errors'), str(1))
146-
self.assertEqual(root.get('failures'), str(0))
147-
self.assertEqual(root.get('tests'), str(1))
148-
149-
test_case_element = root.find('testcase')
150-
self.assertEqual(test_case_element.get('name'), '')
151-
152-
error_element = test_case_element.find('error')
162+
testsuite_element = tree.getroot()
163+
self.assertEqual(testsuite_element.get('errors'), str(1))
164+
self.assertEqual(testsuite_element.get('failures'), str(0))
165+
self.assertEqual(testsuite_element.get('tests'), str(1))
166+
# Check that testsuite element is compliant with the spec
167+
for required_attribute in self.junit_testsuite_attributes:
168+
self.assertTrue(required_attribute in testsuite_element.attrib.keys())
169+
170+
testcase_element = testsuite_element.find('testcase')
171+
self.assertEqual(testcase_element.get('name'), '')
172+
# Check that test_case is compliant with the spec
173+
for required_attribute in self.junit_testcase_attributes:
174+
self.assertTrue(required_attribute in testcase_element.attrib.keys())
175+
176+
error_element = testcase_element.find('error')
153177
self.assertEqual(error_element.get('file'), '')
154178
self.assertEqual(error_element.get('line'), str(0))
155179
self.assertEqual(error_element.get('message'),
156180
'0: (information) Too many #ifdef configurations - cppcheck only checks '
157181
'12 configurations. Use --force to check all '
158182
'configurations. For more details, use '
159183
'--enable=information.')
184+
# Check that error element is compliant with the spec
185+
for required_attribute in self.junit_error_attributes:
186+
self.assertTrue(required_attribute in error_element.attrib.keys())
160187

161188

162189
class GenerateSingleSuccessTestSuite(unittest.TestCase):
190+
# Expected attributes from JUnit XSD
191+
# ref: https://raw.githubusercontent.com/windyroad/JUnit-Schema/master/JUnit.xsd
192+
# @TODO: Better thing to do here would be to curl or otherwise access the
193+
# spec above instead of hardcoding pieces of it here
194+
junit_testsuite_attributes = ['name', 'timestamp', 'hostname', 'tests',
195+
'failures', 'errors', 'time']
196+
junit_testcase_attributes = ['name', 'classname', 'time']
197+
163198
def test(self): # type: () -> None
164199
tree = generate_single_success_test_suite()
165-
root = tree.getroot()
166-
self.assertEqual(root.get('tests'), str(1))
167-
168-
test_case_element = root.find('testcase')
169-
self.assertEqual(test_case_element.get('name'), 'Cppcheck success')
200+
testsuite_element = tree.getroot()
201+
self.assertEqual(testsuite_element.get('tests'), str(1))
202+
self.assertEqual(testsuite_element.get('errors'), str(0))
203+
self.assertEqual(testsuite_element.get('failures'), str(0))
204+
# Check that testsuite element is compliant with the spec
205+
for required_attribute in self.junit_testsuite_attributes:
206+
self.assertTrue(required_attribute in testsuite_element.attrib.keys())
207+
208+
testcase_element = testsuite_element.find('testcase')
209+
self.assertEqual(testcase_element.get('name'), 'Cppcheck success')
210+
# Check that test_case is compliant with the spec
211+
for required_attribute in self.junit_testcase_attributes:
212+
self.assertTrue(required_attribute in testcase_element.attrib.keys())
170213

171214

172215
class ParseArgumentsTestCase(unittest.TestCase):

0 commit comments

Comments
 (0)