Coverage for genbadge/xunitparser_copy.py: 70%
155 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-11-10 20:37 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-11-10 20:37 +0000
1""" A copy of xunitparser, to temporary fix the issue in https://github.com/smarie/python-genbadge/issues/18 """
2import math
3import unittest
4from datetime import timedelta
6try:
7 # security patch: see https://docs.python.org/3/library/xml.etree.elementtree.html
8 from defusedxml import ElementTree
9except ImportError as e:
10 ee = e # save it
11 class FakeDefusedXmlImport(object): # noqa
12 def __getattribute__(self, item):
13 raise ImportError("Could not import `defusedxml.ElementTree`, please install `defusedxml`. "
14 "Note that all dependencies for the tests command can be installed with "
15 "`pip install genbadge[tests]`. Caught: %r" % ee)
16 ElementTree = FakeDefusedXmlImport()
19def to_timedelta(val):
20 if val is None:
21 return None
23 secs = float(val)
24 if math.isnan(secs): 24 ↛ 25line 24 didn't jump to line 25, because the condition on line 24 was never true
25 return None
27 return timedelta(seconds=secs)
30class TestResult(unittest.TestResult):
31 def _exc_info_to_string(self, err, test):
32 err = (e for e in err if e)
33 return ': '.join(err)
36class TestCase(unittest.TestCase):
37 TR_CLASS = TestResult
38 stdout = None
39 stderr = None
41 def __init__(self, classname, methodname):
42 super(TestCase, self).__init__()
43 self.classname = classname
44 self.methodname = methodname
46 def __str__(self):
47 return "%s (%s)" % (self.methodname, self.classname)
49 def __repr__(self):
50 return "<%s testMethod=%s>" % \
51 (self.classname, self.methodname)
53 def __hash__(self):
54 return hash((type(self), self.classname, self.methodname))
56 def id(self):
57 return "%s.%s" % (self.classname, self.methodname)
59 def seed(self, result, typename=None, message=None, trace=None):
60 """ Provide the expected result """
61 self.result, self.typename, self.message, self.trace = result, typename, message, trace
63 def run(self, tr=None):
64 """ Fake run() that produces the seeded result """
65 tr = tr or self.TR_CLASS()
67 tr.startTest(self)
68 if self.result == 'success':
69 tr.addSuccess(self)
70 elif self.result == 'skipped':
71 tr.addSkip(self, '%s: %s' % (self.typename, self._textMessage()))
72 elif self.result == 'error':
73 tr.addError(self, (self.typename, self._textMessage()))
74 elif self.result == 'failure': 74 ↛ 76line 74 didn't jump to line 76, because the condition on line 74 was never false
75 tr.addFailure(self, (self.typename, self._textMessage()))
76 tr.stopTest(self)
78 return tr
80 def _textMessage(self):
81 msg = (e for e in (self.message, self.trace) if e)
82 return '\n\n'.join(msg) or None
84 @property
85 def alltext(self):
86 err = (e for e in (self.typename, self.message) if e)
87 err = ': '.join(err)
88 txt = (e for e in (err, self.trace) if e)
89 return '\n\n'.join(txt) or None
91 def setUp(self):
92 """ Dummy method so __init__ does not fail """
93 pass
95 def tearDown(self):
96 """ Dummy method so __init__ does not fail """
97 pass
99 def runTest(self):
100 """ Dummy method so __init__ does not fail """
101 self.run()
103 @property
104 def basename(self):
105 return self.classname.rpartition('.')[2]
107 @property
108 def success(self):
109 return self.result == 'success'
111 @property
112 def skipped(self):
113 return self.result == 'skipped'
115 @property
116 def failed(self):
117 return self.result == 'failure'
119 @property
120 def errored(self):
121 return self.result == 'error'
123 @property
124 def good(self):
125 return self.skipped or self.success
127 @property
128 def bad(self):
129 return not self.good
131 @property
132 def stdall(self):
133 """ All system output """
134 return '\n'.join([out for out in (self.stdout, self.stderr) if out])
137class TestSuite(unittest.TestSuite):
138 def __init__(self, *args, **kwargs):
139 super(TestSuite, self).__init__(*args, **kwargs)
140 self.properties = {}
141 self.stdout = None
142 self.stderr = None
145class Parser(object):
146 TC_CLASS = TestCase
147 TS_CLASS = TestSuite
148 TR_CLASS = TestResult
150 def parse(self, source):
151 xml = ElementTree.parse(source)
152 root = xml.getroot()
153 return self.parse_root(root)
155 def parse_root(self, root):
156 ts = self.TS_CLASS()
157 if root.tag == 'testsuites': 157 ↛ 161line 157 didn't jump to line 161, because the condition on line 157 was never false
158 for subroot in root:
159 self.parse_testsuite(subroot, ts)
160 else:
161 self.parse_testsuite(root, ts)
163 tr = ts.run(self.TR_CLASS())
165 tr.time = to_timedelta(root.attrib.get('time'))
167 # check totals if they are in the root XML element
168 if 'errors' in root.attrib: 168 ↛ 169line 168 didn't jump to line 169, because the condition on line 168 was never true
169 assert len(tr.errors) == int(root.attrib['errors'])
170 if 'failures' in root.attrib: 170 ↛ 171line 170 didn't jump to line 171, because the condition on line 170 was never true
171 assert len(tr.failures) == int(root.attrib['failures'])
172 if 'skip' in root.attrib: 172 ↛ 173line 172 didn't jump to line 173, because the condition on line 172 was never true
173 assert len(tr.skipped) == int(root.attrib['skip'])
174 if 'tests' in root.attrib: 174 ↛ 175line 174 didn't jump to line 175, because the condition on line 174 was never true
175 assert len(list(ts)) == int(root.attrib['tests'])
177 return (ts, tr)
179 def parse_testsuite(self, root, ts):
180 assert root.tag == 'testsuite'
181 ts.name = root.attrib.get('name')
182 ts.package = root.attrib.get('package')
183 for el in root:
184 if el.tag == 'testcase': 184 ↛ 186line 184 didn't jump to line 186, because the condition on line 184 was never false
185 self.parse_testcase(el, ts)
186 if el.tag == 'properties': 186 ↛ 187line 186 didn't jump to line 187, because the condition on line 186 was never true
187 self.parse_properties(el, ts)
188 if el.tag == 'system-out' and el.text: 188 ↛ 189line 188 didn't jump to line 189, because the condition on line 188 was never true
189 ts.stdout = el.text.strip()
190 if el.tag == 'system-err' and el.text: 190 ↛ 191line 190 didn't jump to line 191, because the condition on line 190 was never true
191 ts.stderr = el.text.strip()
193 def parse_testcase(self, el, ts):
194 tc_classname = el.attrib.get('classname') or ts.name
195 tc = self.TC_CLASS(tc_classname, el.attrib['name'])
196 tc.seed('success', trace=el.text or None)
197 tc.time = to_timedelta(el.attrib.get('time'))
198 message = None
199 text = None
200 for e in el:
201 # error takes over failure in JUnit 4
202 if e.tag in ('failure', 'error', 'skipped'): 202 ↛ 213line 202 didn't jump to line 213, because the condition on line 202 was never false
203 tc = self.TC_CLASS(tc_classname, el.attrib['name'])
204 result = e.tag
205 typename = e.attrib.get('type')
207 # reuse old if empty
208 message = e.attrib.get('message') or message
209 text = e.text or text
211 tc.seed(result, typename, message, text)
212 tc.time = to_timedelta(el.attrib.get('time'))
213 if e.tag == 'system-out' and e.text: 213 ↛ 214line 213 didn't jump to line 214, because the condition on line 213 was never true
214 tc.stdout = e.text.strip()
215 if e.tag == 'system-err' and e.text: 215 ↛ 216line 215 didn't jump to line 216, because the condition on line 215 was never true
216 tc.stderr = e.text.strip()
218 # add either the original "success" tc or a tc created by elements
219 ts.addTest(tc)
221 def parse_properties(self, el, ts):
222 for e in el:
223 if e.tag == 'property':
224 assert e.attrib['name'] not in ts.properties
225 ts.properties[e.attrib['name']] = e.attrib['value']
228def parse(source):
229 return Parser().parse(source)