⬅ genbadge/xunitparser_copy.py source

1 """ A copy of xunitparser, to temporary fix the issue in https://github.com/smarie/python-genbadge/issues/18 """
2 import math
3 import unittest
4 from datetime import timedelta
5  
6 try:
7 # security patch: see https://docs.python.org/3/library/xml.etree.elementtree.html
8 from defusedxml import ElementTree
9 except 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()
17  
18  
19 def to_timedelta(val):
20 if val is None:
21 return None
22  
23 secs = float(val)
24 if math.isnan(secs):
25 return None
26  
27 return timedelta(seconds=secs)
28  
29  
30 class 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)
34  
35  
36 class TestCase(unittest.TestCase):
37 TR_CLASS = TestResult
38 stdout = None
39 stderr = None
40  
41 def __init__(self, classname, methodname):
42 super(TestCase, self).__init__()
43 self.classname = classname
44 self.methodname = methodname
45  
46 def __str__(self):
47 return "%s (%s)" % (self.methodname, self.classname)
48  
49 def __repr__(self):
50 return "<%s testMethod=%s>" % \
51 (self.classname, self.methodname)
52  
53 def __hash__(self):
54 return hash((type(self), self.classname, self.methodname))
55  
56 def id(self):
57 return "%s.%s" % (self.classname, self.methodname)
58  
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
62  
63 def run(self, tr=None):
64 """ Fake run() that produces the seeded result """
65 tr = tr or self.TR_CLASS()
66  
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':
75 tr.addFailure(self, (self.typename, self._textMessage()))
76 tr.stopTest(self)
77  
78 return tr
79  
80 def _textMessage(self):
81 msg = (e for e in (self.message, self.trace) if e)
82 return '\n\n'.join(msg) or None
83  
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
90  
91 def setUp(self):
92 """ Dummy method so __init__ does not fail """
93 pass
94  
95 def tearDown(self):
96 """ Dummy method so __init__ does not fail """
97 pass
98  
99 def runTest(self):
100 """ Dummy method so __init__ does not fail """
101 self.run()
102  
103 @property
104 def basename(self):
105 return self.classname.rpartition('.')[2]
106  
107 @property
108 def success(self):
109 return self.result == 'success'
110  
111 @property
112 def skipped(self):
113 return self.result == 'skipped'
114  
115 @property
116 def failed(self):
117 return self.result == 'failure'
118  
119 @property
120 def errored(self):
121 return self.result == 'error'
122  
123 @property
124 def good(self):
125 return self.skipped or self.success
126  
127 @property
128 def bad(self):
129 return not self.good
130  
131 @property
132 def stdall(self):
133 """ All system output """
134 return '\n'.join([out for out in (self.stdout, self.stderr) if out])
135  
136  
137 class 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
143  
144  
145 class Parser(object):
146 TC_CLASS = TestCase
147 TS_CLASS = TestSuite
148 TR_CLASS = TestResult
149  
150 def parse(self, source):
151 xml = ElementTree.parse(source)
152 root = xml.getroot()
153 return self.parse_root(root)
154  
155 def parse_root(self, root):
156 ts = self.TS_CLASS()
157 if root.tag == 'testsuites':
158 for subroot in root:
159 self.parse_testsuite(subroot, ts)
160 else:
161 self.parse_testsuite(root, ts)
162  
163 tr = ts.run(self.TR_CLASS())
164  
165 tr.time = to_timedelta(root.attrib.get('time'))
166  
167 # check totals if they are in the root XML element
168 if 'errors' in root.attrib:
  • S101 Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
169 assert len(tr.errors) == int(root.attrib['errors'])
170 if 'failures' in root.attrib:
  • S101 Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
171 assert len(tr.failures) == int(root.attrib['failures'])
172 if 'skip' in root.attrib:
  • S101 Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
173 assert len(tr.skipped) == int(root.attrib['skip'])
174 if 'tests' in root.attrib:
  • S101 Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
175 assert len(list(ts)) == int(root.attrib['tests'])
176  
177 return (ts, tr)
178  
179 def parse_testsuite(self, root, ts):
  • S101 Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
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':
185 self.parse_testcase(el, ts)
186 if el.tag == 'properties':
187 self.parse_properties(el, ts)
188 if el.tag == 'system-out' and el.text:
189 ts.stdout = el.text.strip()
190 if el.tag == 'system-err' and el.text:
191 ts.stderr = el.text.strip()
192  
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'):
203 tc = self.TC_CLASS(tc_classname, el.attrib['name'])
204 result = e.tag
205 typename = e.attrib.get('type')
206  
207 # reuse old if empty
208 message = e.attrib.get('message') or message
209 text = e.text or text
210  
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:
214 tc.stdout = e.text.strip()
215 if e.tag == 'system-err' and e.text:
216 tc.stderr = e.text.strip()
217  
218 # add either the original "success" tc or a tc created by elements
219 ts.addTest(tc)
220  
221 def parse_properties(self, el, ts):
222 for e in el:
223 if e.tag == 'property':
  • S101 Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
224 assert e.attrib['name'] not in ts.properties
225 ts.properties[e.attrib['name']] = e.attrib['value']
226  
227  
228 def parse(source):
229 return Parser().parse(source)