junitparser 3.1.0__tar.gz → 3.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {junitparser-3.1.0 → junitparser-3.1.2}/PKG-INFO +13 -13
- {junitparser-3.1.0 → junitparser-3.1.2}/README.rst +12 -12
- {junitparser-3.1.0 → junitparser-3.1.2}/junitparser/__init__.py +1 -1
- {junitparser-3.1.0 → junitparser-3.1.2}/junitparser/cli.py +3 -3
- {junitparser-3.1.0 → junitparser-3.1.2}/junitparser/junitparser.py +68 -69
- junitparser-3.1.2/junitparser/py.typed +0 -0
- {junitparser-3.1.0 → junitparser-3.1.2}/junitparser/xunit2.py +6 -6
- {junitparser-3.1.0 → junitparser-3.1.2}/junitparser.egg-info/PKG-INFO +13 -13
- {junitparser-3.1.0 → junitparser-3.1.2}/junitparser.egg-info/SOURCES.txt +6 -2
- {junitparser-3.1.0 → junitparser-3.1.2}/setup.py +1 -1
- junitparser-3.1.2/tests/test_cli.py +14 -0
- junitparser-3.1.2/tests/test_fromfile.py +223 -0
- junitparser-3.1.2/tests/test_general.py +770 -0
- junitparser-3.1.2/tests/test_xunit2.py +103 -0
- junitparser-3.1.0/junitparser.egg-info/requires.txt +0 -1
- {junitparser-3.1.0 → junitparser-3.1.2}/LICENSE +0 -0
- {junitparser-3.1.0 → junitparser-3.1.2}/junitparser/__main__.py +0 -0
- {junitparser-3.1.0 → junitparser-3.1.2}/junitparser.egg-info/dependency_links.txt +0 -0
- {junitparser-3.1.0 → junitparser-3.1.2}/junitparser.egg-info/entry_points.txt +0 -0
- {junitparser-3.1.0 → junitparser-3.1.2}/junitparser.egg-info/not-zip-safe +0 -0
- {junitparser-3.1.0 → junitparser-3.1.2}/junitparser.egg-info/top_level.txt +0 -0
- {junitparser-3.1.0 → junitparser-3.1.2}/pyproject.toml +0 -0
- {junitparser-3.1.0 → junitparser-3.1.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: junitparser
|
|
3
|
-
Version: 3.1.
|
|
3
|
+
Version: 3.1.2
|
|
4
4
|
Summary: Manipulates JUnit/xUnit Result XML files
|
|
5
5
|
Home-page: https://github.com/weiwei/junitparser
|
|
6
6
|
Author: Weiwei Wang
|
|
@@ -28,21 +28,21 @@ existing Result XML files, or create new JUnit/xUnit result XMLs from scratch.
|
|
|
28
28
|
Features
|
|
29
29
|
--------
|
|
30
30
|
|
|
31
|
-
* Parse or modify existing JUnit/xUnit
|
|
32
|
-
* Parse or modify non-standard or customized JUnit/xUnit
|
|
31
|
+
* Parse or modify existing JUnit/xUnit XML files.
|
|
32
|
+
* Parse or modify non-standard or customized JUnit/xUnit XML files, by monkey
|
|
33
33
|
patching existing element definitions.
|
|
34
34
|
* Create JUnit/xUnit test results from scratch.
|
|
35
|
-
* Merge test result
|
|
36
|
-
* Specify
|
|
35
|
+
* Merge test result XML files.
|
|
36
|
+
* Specify XML parser. For example you can use lxml to speed things up.
|
|
37
37
|
* Invoke from command line, or `python -m junitparser`
|
|
38
|
-
* Python 2 and 3 support (As of Nov 2020, 1/4 of the users are still on Python
|
|
38
|
+
* Python 2 and 3 support (As of Nov 2020, 1/4 of the users are still on Python
|
|
39
39
|
2, so there is no plan to drop Python 2 support)
|
|
40
40
|
|
|
41
41
|
Note on version 2
|
|
42
42
|
-----------------
|
|
43
43
|
|
|
44
|
-
Version 2 improved support for pytest result
|
|
45
|
-
notably that there could be multiple <Failure> or <Error> entries. There is a
|
|
44
|
+
Version 2 improved support for pytest result XML files by fixing a few issues,
|
|
45
|
+
notably that there could be multiple <Failure> or <Error> entries. There is a
|
|
46
46
|
breaking change that ``TestCase.result`` is now a list instead of a single item.
|
|
47
47
|
If you are using this attribute, please update your code accordingly.
|
|
48
48
|
|
|
@@ -149,7 +149,7 @@ Schema Support
|
|
|
149
149
|
~~~~~~~~~~~~~~~
|
|
150
150
|
|
|
151
151
|
By default junitparser supports the schema of windyroad_, which is a relatively
|
|
152
|
-
simple schema.
|
|
152
|
+
simple schema.
|
|
153
153
|
|
|
154
154
|
.. _windyroad: https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd
|
|
155
155
|
|
|
@@ -248,7 +248,7 @@ read them out:
|
|
|
248
248
|
Command Line
|
|
249
249
|
------------
|
|
250
250
|
|
|
251
|
-
.. code-block::
|
|
251
|
+
.. code-block:: console
|
|
252
252
|
|
|
253
253
|
$ junitparser --help
|
|
254
254
|
usage: junitparser [-h] [-v] {merge} ...
|
|
@@ -265,7 +265,7 @@ Command Line
|
|
|
265
265
|
-v, --version show program's version number and exit
|
|
266
266
|
|
|
267
267
|
|
|
268
|
-
.. code-block::
|
|
268
|
+
.. code-block:: console
|
|
269
269
|
|
|
270
270
|
$ junitparser merge --help
|
|
271
271
|
usage: junitparser merge [-h] [--glob] paths [paths ...] output
|
|
@@ -280,7 +280,7 @@ Command Line
|
|
|
280
280
|
--suite-name SUITE_NAME
|
|
281
281
|
Name added to <testsuites>.
|
|
282
282
|
|
|
283
|
-
.. code-block::
|
|
283
|
+
.. code-block:: console
|
|
284
284
|
|
|
285
285
|
$ junitparser verify --help
|
|
286
286
|
usage: junitparser verify [-h] [--glob] paths [paths ...]
|
|
@@ -295,7 +295,7 @@ Command Line
|
|
|
295
295
|
Test
|
|
296
296
|
----
|
|
297
297
|
|
|
298
|
-
The tests are written with python
|
|
298
|
+
The tests are written with python ``unittest``, to run them, use pytest::
|
|
299
299
|
|
|
300
300
|
pytest test.py
|
|
301
301
|
|
|
@@ -12,21 +12,21 @@ existing Result XML files, or create new JUnit/xUnit result XMLs from scratch.
|
|
|
12
12
|
Features
|
|
13
13
|
--------
|
|
14
14
|
|
|
15
|
-
* Parse or modify existing JUnit/xUnit
|
|
16
|
-
* Parse or modify non-standard or customized JUnit/xUnit
|
|
15
|
+
* Parse or modify existing JUnit/xUnit XML files.
|
|
16
|
+
* Parse or modify non-standard or customized JUnit/xUnit XML files, by monkey
|
|
17
17
|
patching existing element definitions.
|
|
18
18
|
* Create JUnit/xUnit test results from scratch.
|
|
19
|
-
* Merge test result
|
|
20
|
-
* Specify
|
|
19
|
+
* Merge test result XML files.
|
|
20
|
+
* Specify XML parser. For example you can use lxml to speed things up.
|
|
21
21
|
* Invoke from command line, or `python -m junitparser`
|
|
22
|
-
* Python 2 and 3 support (As of Nov 2020, 1/4 of the users are still on Python
|
|
22
|
+
* Python 2 and 3 support (As of Nov 2020, 1/4 of the users are still on Python
|
|
23
23
|
2, so there is no plan to drop Python 2 support)
|
|
24
24
|
|
|
25
25
|
Note on version 2
|
|
26
26
|
-----------------
|
|
27
27
|
|
|
28
|
-
Version 2 improved support for pytest result
|
|
29
|
-
notably that there could be multiple <Failure> or <Error> entries. There is a
|
|
28
|
+
Version 2 improved support for pytest result XML files by fixing a few issues,
|
|
29
|
+
notably that there could be multiple <Failure> or <Error> entries. There is a
|
|
30
30
|
breaking change that ``TestCase.result`` is now a list instead of a single item.
|
|
31
31
|
If you are using this attribute, please update your code accordingly.
|
|
32
32
|
|
|
@@ -133,7 +133,7 @@ Schema Support
|
|
|
133
133
|
~~~~~~~~~~~~~~~
|
|
134
134
|
|
|
135
135
|
By default junitparser supports the schema of windyroad_, which is a relatively
|
|
136
|
-
simple schema.
|
|
136
|
+
simple schema.
|
|
137
137
|
|
|
138
138
|
.. _windyroad: https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd
|
|
139
139
|
|
|
@@ -232,7 +232,7 @@ read them out:
|
|
|
232
232
|
Command Line
|
|
233
233
|
------------
|
|
234
234
|
|
|
235
|
-
.. code-block::
|
|
235
|
+
.. code-block:: console
|
|
236
236
|
|
|
237
237
|
$ junitparser --help
|
|
238
238
|
usage: junitparser [-h] [-v] {merge} ...
|
|
@@ -249,7 +249,7 @@ Command Line
|
|
|
249
249
|
-v, --version show program's version number and exit
|
|
250
250
|
|
|
251
251
|
|
|
252
|
-
.. code-block::
|
|
252
|
+
.. code-block:: console
|
|
253
253
|
|
|
254
254
|
$ junitparser merge --help
|
|
255
255
|
usage: junitparser merge [-h] [--glob] paths [paths ...] output
|
|
@@ -264,7 +264,7 @@ Command Line
|
|
|
264
264
|
--suite-name SUITE_NAME
|
|
265
265
|
Name added to <testsuites>.
|
|
266
266
|
|
|
267
|
-
.. code-block::
|
|
267
|
+
.. code-block:: console
|
|
268
268
|
|
|
269
269
|
$ junitparser verify --help
|
|
270
270
|
usage: junitparser verify [-h] [--glob] paths [paths ...]
|
|
@@ -279,7 +279,7 @@ Command Line
|
|
|
279
279
|
Test
|
|
280
280
|
----
|
|
281
281
|
|
|
282
|
-
The tests are written with python
|
|
282
|
+
The tests are written with python ``unittest``, to run them, use pytest::
|
|
283
283
|
|
|
284
284
|
pytest test.py
|
|
285
285
|
|
|
@@ -6,7 +6,7 @@ from . import JUnitXml, version
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
def merge(paths, output, suite_name):
|
|
9
|
-
"""Merge
|
|
9
|
+
"""Merge XML report."""
|
|
10
10
|
result = JUnitXml()
|
|
11
11
|
for path in paths:
|
|
12
12
|
result += JUnitXml.fromfile(path)
|
|
@@ -45,7 +45,7 @@ def _parser(prog_name=None): # pragma: no cover
|
|
|
45
45
|
|
|
46
46
|
# command: merge
|
|
47
47
|
merge_parser = command_parser.add_parser(
|
|
48
|
-
"merge", help="Merge
|
|
48
|
+
"merge", help="Merge JUnit XML format reports with junitparser."
|
|
49
49
|
)
|
|
50
50
|
merge_parser.add_argument(
|
|
51
51
|
"--glob",
|
|
@@ -56,7 +56,7 @@ def _parser(prog_name=None): # pragma: no cover
|
|
|
56
56
|
)
|
|
57
57
|
merge_parser.add_argument("paths", nargs="+", help="Original XML path(s).")
|
|
58
58
|
merge_parser.add_argument(
|
|
59
|
-
"output", help='Merged XML Path, setting to "-" will output console'
|
|
59
|
+
"output", help='Merged XML Path, setting to "-" will output to the console'
|
|
60
60
|
)
|
|
61
61
|
merge_parser.add_argument(
|
|
62
62
|
"--suite-name",
|
|
@@ -3,13 +3,15 @@ junitparser is a JUnit/xUnit Result XML Parser. Use it to parse and manipulate
|
|
|
3
3
|
existing Result XML files, or create new JUnit/xUnit result XMLs from scratch.
|
|
4
4
|
|
|
5
5
|
Reference schema: https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd
|
|
6
|
-
This according to the document is Apache Ant's JUnit output.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
This, according to the document, is Apache Ant's JUnit output.
|
|
8
|
+
|
|
9
|
+
See the documentation for other supported schemas.
|
|
9
10
|
"""
|
|
10
11
|
|
|
11
12
|
import itertools
|
|
12
13
|
from copy import deepcopy
|
|
14
|
+
from typing import List
|
|
13
15
|
|
|
14
16
|
try:
|
|
15
17
|
from lxml import etree
|
|
@@ -56,14 +58,14 @@ class Attr(object):
|
|
|
56
58
|
By default they are all string values. To support different value types,
|
|
57
59
|
inherit this class and define your own methods.
|
|
58
60
|
|
|
59
|
-
Also see: :class:`
|
|
61
|
+
Also see: :class:`IntAttr`, :class:`FloatAttr`.
|
|
60
62
|
"""
|
|
61
63
|
|
|
62
64
|
def __init__(self, name: str = None):
|
|
63
65
|
self.name = name
|
|
64
66
|
|
|
65
67
|
def __get__(self, instance, cls):
|
|
66
|
-
"""
|
|
68
|
+
"""Get value from attribute, return ``None`` if attribute doesn't exist."""
|
|
67
69
|
return instance._elem.attrib.get(self.name)
|
|
68
70
|
|
|
69
71
|
def __set__(self, instance, value: str):
|
|
@@ -75,7 +77,7 @@ class Attr(object):
|
|
|
75
77
|
class IntAttr(Attr):
|
|
76
78
|
"""An integer attribute for an XML element.
|
|
77
79
|
|
|
78
|
-
This class is used internally for counting
|
|
80
|
+
This class is used internally for counting testcases, but you could use
|
|
79
81
|
it for any specific purpose.
|
|
80
82
|
"""
|
|
81
83
|
|
|
@@ -121,7 +123,7 @@ def attributed(cls):
|
|
|
121
123
|
|
|
122
124
|
|
|
123
125
|
class junitxml(type):
|
|
124
|
-
"""Metaclass to decorate the
|
|
126
|
+
"""Metaclass to decorate the XML class."""
|
|
125
127
|
|
|
126
128
|
def __new__(meta, name, bases, methods):
|
|
127
129
|
cls = super(junitxml, meta).__new__(meta, name, bases, methods)
|
|
@@ -130,7 +132,7 @@ class junitxml(type):
|
|
|
130
132
|
|
|
131
133
|
|
|
132
134
|
class Element(metaclass=junitxml):
|
|
133
|
-
"""Base class for all
|
|
135
|
+
"""Base class for all JUnit XML elements."""
|
|
134
136
|
|
|
135
137
|
def __init__(self, name: str = None):
|
|
136
138
|
if not name:
|
|
@@ -152,27 +154,27 @@ class Element(metaclass=junitxml):
|
|
|
152
154
|
return """<Element '%s'>""" % tag
|
|
153
155
|
|
|
154
156
|
def append(self, sub_elem):
|
|
155
|
-
"""
|
|
157
|
+
"""Add the element subelement to the end of this elements internal
|
|
156
158
|
list of subelements.
|
|
157
159
|
"""
|
|
158
160
|
self._elem.append(sub_elem._elem)
|
|
159
161
|
|
|
160
162
|
def extend(self, sub_elems):
|
|
161
|
-
"""
|
|
163
|
+
"""Add elements subelement to the end of this elements internal
|
|
162
164
|
list of subelements.
|
|
163
165
|
"""
|
|
164
166
|
self._elem.extend((sub_elem._elem for sub_elem in sub_elems))
|
|
165
167
|
|
|
166
168
|
@classmethod
|
|
167
169
|
def fromstring(cls, text: str):
|
|
168
|
-
"""Construct
|
|
170
|
+
"""Construct JUnit object *cls* from XML string *test*."""
|
|
169
171
|
instance = cls()
|
|
170
172
|
instance._elem = etree.fromstring(text) # nosec
|
|
171
173
|
return instance
|
|
172
174
|
|
|
173
175
|
@classmethod
|
|
174
176
|
def fromelem(cls, elem):
|
|
175
|
-
"""
|
|
177
|
+
"""Construct JUnit objects from an ElementTree element *elem*."""
|
|
176
178
|
if elem is None:
|
|
177
179
|
return
|
|
178
180
|
instance = cls()
|
|
@@ -183,25 +185,25 @@ class Element(metaclass=junitxml):
|
|
|
183
185
|
return instance
|
|
184
186
|
|
|
185
187
|
def iterchildren(self, Child):
|
|
186
|
-
"""Iterate through specified Child type elements."""
|
|
188
|
+
"""Iterate through specified *Child* type elements."""
|
|
187
189
|
elems = self._elem.iterfind(Child._tag)
|
|
188
190
|
for elem in elems:
|
|
189
191
|
yield Child.fromelem(elem)
|
|
190
192
|
|
|
191
193
|
def child(self, Child):
|
|
192
|
-
"""Find a single child of specified Child type."""
|
|
194
|
+
"""Find a single child of specified *Child* type."""
|
|
193
195
|
elem = self._elem.find(Child._tag)
|
|
194
196
|
return Child.fromelem(elem)
|
|
195
197
|
|
|
196
198
|
def remove(self, sub_elem):
|
|
197
|
-
"""Remove
|
|
199
|
+
"""Remove subelement *sub_elem*."""
|
|
198
200
|
for elem in self._elem.iterfind(sub_elem._tag):
|
|
199
201
|
child = sub_elem.__class__.fromelem(elem)
|
|
200
202
|
if child == sub_elem:
|
|
201
203
|
self._elem.remove(child._elem)
|
|
202
204
|
|
|
203
205
|
def tostring(self):
|
|
204
|
-
"""
|
|
206
|
+
"""Convert element to XML string."""
|
|
205
207
|
return etree.tostring(self._elem, encoding="utf-8")
|
|
206
208
|
|
|
207
209
|
|
|
@@ -209,8 +211,8 @@ class Result(Element):
|
|
|
209
211
|
"""Base class for test result.
|
|
210
212
|
|
|
211
213
|
Attributes:
|
|
212
|
-
message:
|
|
213
|
-
type:
|
|
214
|
+
message: Result as message string.
|
|
215
|
+
type: Message type.
|
|
214
216
|
"""
|
|
215
217
|
|
|
216
218
|
_tag = None
|
|
@@ -271,10 +273,10 @@ POSSIBLE_RESULTS = {Failure, Error, Skipped}
|
|
|
271
273
|
|
|
272
274
|
|
|
273
275
|
class System(Element):
|
|
274
|
-
"""Parent class for SystemOut and SystemErr
|
|
276
|
+
"""Parent class for :class:`SystemOut` and :class:`SystemErr`.
|
|
275
277
|
|
|
276
278
|
Attributes:
|
|
277
|
-
text:
|
|
279
|
+
text: The output message.
|
|
278
280
|
"""
|
|
279
281
|
|
|
280
282
|
_tag = ""
|
|
@@ -304,20 +306,16 @@ class TestCase(Element):
|
|
|
304
306
|
"""Object to store a testcase and its result.
|
|
305
307
|
|
|
306
308
|
Attributes:
|
|
307
|
-
name:
|
|
308
|
-
classname:
|
|
309
|
-
time:
|
|
310
|
-
|
|
311
|
-
Properties:
|
|
312
|
-
result: Failure, Skipped, or Error
|
|
313
|
-
system_out: stdout
|
|
314
|
-
system_err: stderr
|
|
309
|
+
name: Name of the testcase.
|
|
310
|
+
classname: The parent class of the testcase.
|
|
311
|
+
time: The time consumed by the testcase.
|
|
315
312
|
"""
|
|
316
313
|
|
|
317
314
|
_tag = "testcase"
|
|
318
315
|
name = Attr()
|
|
319
316
|
classname = Attr()
|
|
320
317
|
time = FloatAttr()
|
|
318
|
+
__test__ = False
|
|
321
319
|
|
|
322
320
|
def __init__(self, name: str = None, classname: str = None, time: float = None):
|
|
323
321
|
super().__init__(self._tag)
|
|
@@ -357,7 +355,7 @@ class TestCase(Element):
|
|
|
357
355
|
|
|
358
356
|
@property
|
|
359
357
|
def result(self):
|
|
360
|
-
"""A list of Failure
|
|
358
|
+
"""A list of :class:`Failure`, :class:`Skipped`, or :class:`Error` objects."""
|
|
361
359
|
results = []
|
|
362
360
|
for entry in self:
|
|
363
361
|
if isinstance(entry, tuple(POSSIBLE_RESULTS)):
|
|
@@ -411,13 +409,13 @@ class TestCase(Element):
|
|
|
411
409
|
|
|
412
410
|
|
|
413
411
|
class Property(Element):
|
|
414
|
-
"""A key/value pare that's stored in the
|
|
412
|
+
"""A key/value pare that's stored in the testsuite.
|
|
415
413
|
|
|
416
414
|
Use it to store anything you find interesting or useful.
|
|
417
415
|
|
|
418
416
|
Attributes:
|
|
419
|
-
name:
|
|
420
|
-
value:
|
|
417
|
+
name: The property name.
|
|
418
|
+
value: The property value.
|
|
421
419
|
"""
|
|
422
420
|
|
|
423
421
|
_tag = "property"
|
|
@@ -441,7 +439,7 @@ class Property(Element):
|
|
|
441
439
|
|
|
442
440
|
|
|
443
441
|
class Properties(Element):
|
|
444
|
-
"""A list of properties inside a
|
|
442
|
+
"""A list of properties inside a testsuite.
|
|
445
443
|
|
|
446
444
|
See :class:`Property`
|
|
447
445
|
"""
|
|
@@ -474,14 +472,14 @@ class TestSuite(Element):
|
|
|
474
472
|
"""The <testsuite> object.
|
|
475
473
|
|
|
476
474
|
Attributes:
|
|
477
|
-
name:
|
|
478
|
-
hostname:
|
|
479
|
-
time:
|
|
480
|
-
timestamp:
|
|
481
|
-
tests:
|
|
482
|
-
failures:
|
|
483
|
-
errors:
|
|
484
|
-
skipped:
|
|
475
|
+
name: The name of the testsuite.
|
|
476
|
+
hostname: Name of the test machine.
|
|
477
|
+
time: Time consumed by the testsuite.
|
|
478
|
+
timestamp: When the test was run.
|
|
479
|
+
tests: Total number of tests.
|
|
480
|
+
failures: Number of failed tests.
|
|
481
|
+
errors: Number of cases with errors.
|
|
482
|
+
skipped: Number of skipped cases.
|
|
485
483
|
"""
|
|
486
484
|
|
|
487
485
|
_tag = "testsuite"
|
|
@@ -493,6 +491,7 @@ class TestSuite(Element):
|
|
|
493
491
|
failures = IntAttr()
|
|
494
492
|
errors = IntAttr()
|
|
495
493
|
skipped = IntAttr()
|
|
494
|
+
__test__ = False
|
|
496
495
|
|
|
497
496
|
def __init__(self, name=None):
|
|
498
497
|
super().__init__(self._tag)
|
|
@@ -527,7 +526,7 @@ class TestSuite(Element):
|
|
|
527
526
|
|
|
528
527
|
def __add__(self, other):
|
|
529
528
|
if self == other:
|
|
530
|
-
# Merge the two
|
|
529
|
+
# Merge the two testsuites
|
|
531
530
|
result = deepcopy(self)
|
|
532
531
|
for case in other:
|
|
533
532
|
result._add_testcase_no_update_stats(case)
|
|
@@ -535,7 +534,7 @@ class TestSuite(Element):
|
|
|
535
534
|
result.add_testsuite(suite)
|
|
536
535
|
result.update_statistics()
|
|
537
536
|
else:
|
|
538
|
-
# Create a new test result containing two
|
|
537
|
+
# Create a new test result containing two testsuites
|
|
539
538
|
result = JUnitXml()
|
|
540
539
|
result.add_testsuite(self)
|
|
541
540
|
result.add_testsuite(other)
|
|
@@ -557,14 +556,14 @@ class TestSuite(Element):
|
|
|
557
556
|
return result
|
|
558
557
|
|
|
559
558
|
def remove_testcase(self, testcase: TestCase):
|
|
560
|
-
"""
|
|
559
|
+
"""Remove testcase *testcase* from the testsuite."""
|
|
561
560
|
for case in self:
|
|
562
561
|
if case == testcase:
|
|
563
562
|
super().remove(case)
|
|
564
563
|
self.update_statistics()
|
|
565
564
|
|
|
566
565
|
def update_statistics(self):
|
|
567
|
-
"""
|
|
566
|
+
"""Update test count and test time."""
|
|
568
567
|
tests = errors = failures = skipped = 0
|
|
569
568
|
time = 0
|
|
570
569
|
for case in self:
|
|
@@ -584,10 +583,10 @@ class TestSuite(Element):
|
|
|
584
583
|
self.skipped = skipped
|
|
585
584
|
self.time = round(time, 3)
|
|
586
585
|
|
|
587
|
-
def add_property(self, name, value):
|
|
588
|
-
"""
|
|
586
|
+
def add_property(self, name: str, value: str):
|
|
587
|
+
"""Add a property *name* = *value* to the testsuite.
|
|
589
588
|
|
|
590
|
-
See :class:`Property` and :class:`Properties
|
|
589
|
+
See :class:`Property` and :class:`Properties`.
|
|
591
590
|
"""
|
|
592
591
|
|
|
593
592
|
props = self.child(Properties)
|
|
@@ -597,29 +596,29 @@ class TestSuite(Element):
|
|
|
597
596
|
prop = Property(name, value)
|
|
598
597
|
props.add_property(prop)
|
|
599
598
|
|
|
600
|
-
def add_testcase(self, testcase):
|
|
601
|
-
"""
|
|
599
|
+
def add_testcase(self, testcase: TestCase):
|
|
600
|
+
"""Add a testcase *testcase* to the testsuite."""
|
|
602
601
|
self.append(testcase)
|
|
603
602
|
self.update_statistics()
|
|
604
603
|
|
|
605
|
-
def add_testcases(self, testcases):
|
|
606
|
-
"""
|
|
604
|
+
def add_testcases(self, testcases: List[TestCase]):
|
|
605
|
+
"""Add testcases *testcases* to the testsuite."""
|
|
607
606
|
self.extend(testcases)
|
|
608
607
|
self.update_statistics()
|
|
609
608
|
|
|
610
|
-
def _add_testcase_no_update_stats(self, testcase):
|
|
611
|
-
"""
|
|
612
|
-
|
|
609
|
+
def _add_testcase_no_update_stats(self, testcase: TestCase):
|
|
610
|
+
"""Add *testcase* to the testsuite (without updating statistics).
|
|
611
|
+
|
|
613
612
|
For internal use only to avoid quadratic behaviour in merge.
|
|
614
613
|
"""
|
|
615
614
|
self.append(testcase)
|
|
616
615
|
|
|
617
616
|
def add_testsuite(self, suite):
|
|
618
|
-
"""
|
|
617
|
+
"""Add a testsuite *suite* to the testsuite."""
|
|
619
618
|
self.append(suite)
|
|
620
619
|
|
|
621
620
|
def properties(self):
|
|
622
|
-
"""
|
|
621
|
+
"""Iterate through all :class:`Property` elements in the testsuite."""
|
|
623
622
|
props = self.child(Properties)
|
|
624
623
|
if props is None:
|
|
625
624
|
return
|
|
@@ -627,7 +626,7 @@ class TestSuite(Element):
|
|
|
627
626
|
yield prop
|
|
628
627
|
|
|
629
628
|
def remove_property(self, property_: Property):
|
|
630
|
-
"""
|
|
629
|
+
"""Remove property *property_* from the testsuite."""
|
|
631
630
|
props = self.child(Properties)
|
|
632
631
|
if props is None:
|
|
633
632
|
return
|
|
@@ -636,7 +635,7 @@ class TestSuite(Element):
|
|
|
636
635
|
props.remove(property_)
|
|
637
636
|
|
|
638
637
|
def testsuites(self):
|
|
639
|
-
"""
|
|
638
|
+
"""Iterate through all testsuites."""
|
|
640
639
|
for suite in self.iterchildren(TestSuite):
|
|
641
640
|
yield suite
|
|
642
641
|
|
|
@@ -647,15 +646,15 @@ class TestSuite(Element):
|
|
|
647
646
|
class JUnitXml(Element):
|
|
648
647
|
"""The JUnitXml root object.
|
|
649
648
|
|
|
650
|
-
It may
|
|
649
|
+
It may contain ``<TestSuites>`` or a ``<TestSuite>``.
|
|
651
650
|
|
|
652
651
|
Attributes:
|
|
653
|
-
name:
|
|
654
|
-
time:
|
|
655
|
-
tests:
|
|
656
|
-
failures:
|
|
657
|
-
errors:
|
|
658
|
-
skipped:
|
|
652
|
+
name: Name of the testsuite if it only contains one testsuite.
|
|
653
|
+
time: Time consumed by the testsuites.
|
|
654
|
+
tests: Total number of tests.
|
|
655
|
+
failures: Number of failed cases.
|
|
656
|
+
errors: Number of cases with errors.
|
|
657
|
+
skipped: Number of skipped cases.
|
|
659
658
|
"""
|
|
660
659
|
|
|
661
660
|
_tag = "testsuites"
|
|
@@ -699,7 +698,7 @@ class JUnitXml(Element):
|
|
|
699
698
|
return self
|
|
700
699
|
|
|
701
700
|
def add_testsuite(self, suite: TestSuite):
|
|
702
|
-
"""Add a
|
|
701
|
+
"""Add a testsuite."""
|
|
703
702
|
for existing_suite in self:
|
|
704
703
|
if existing_suite == suite:
|
|
705
704
|
for case in suite:
|
|
@@ -726,7 +725,7 @@ class JUnitXml(Element):
|
|
|
726
725
|
|
|
727
726
|
@classmethod
|
|
728
727
|
def fromroot(cls, root_elem: Element):
|
|
729
|
-
"""
|
|
728
|
+
"""Construct JUnit objects from an elementTree root element."""
|
|
730
729
|
if root_elem.tag == "testsuites":
|
|
731
730
|
instance = cls()
|
|
732
731
|
elif root_elem.tag == "testsuite":
|
|
@@ -738,7 +737,7 @@ class JUnitXml(Element):
|
|
|
738
737
|
|
|
739
738
|
@classmethod
|
|
740
739
|
def fromstring(cls, text: str):
|
|
741
|
-
"""Construct
|
|
740
|
+
"""Construct JUnit objects from an XML string."""
|
|
742
741
|
root_elem = etree.fromstring(text) # nosec
|
|
743
742
|
return cls.fromroot(root_elem)
|
|
744
743
|
|
|
@@ -755,7 +754,7 @@ class JUnitXml(Element):
|
|
|
755
754
|
return instance
|
|
756
755
|
|
|
757
756
|
def write(self, filepath: str = None, pretty=False, to_console=False):
|
|
758
|
-
"""Write the object into a
|
|
757
|
+
"""Write the object into a JUnit XML file.
|
|
759
758
|
|
|
760
759
|
If `file_path` is not specified, it will write to the original file.
|
|
761
760
|
If `pretty` is True, the result file will be more human friendly.
|
|
File without changes
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
The flavor based on Jenkins xunit plugin:
|
|
3
3
|
https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
|
|
4
4
|
|
|
5
|
-
According to the internet,
|
|
5
|
+
According to the internet, the schema is compatible with:
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- Pytest (as default, though it also supports a "legacy" xunit1 flavor)
|
|
8
8
|
- Erlang/OTP
|
|
9
9
|
- Maven Surefire
|
|
10
10
|
- CppTest
|
|
@@ -20,7 +20,7 @@ T = TypeVar("T")
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class JUnitXml(junitparser.JUnitXml):
|
|
23
|
-
# Pytest and xunit schema doesn't have skipped in testsuites
|
|
23
|
+
# Pytest and xunit schema doesn't have "skipped" in testsuites
|
|
24
24
|
skipped = None
|
|
25
25
|
|
|
26
26
|
def update_statistics(self):
|
|
@@ -40,7 +40,7 @@ class JUnitXml(junitparser.JUnitXml):
|
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
class TestSuite(junitparser.TestSuite):
|
|
43
|
-
"""
|
|
43
|
+
"""TestSuite for Pytest, with some different attributes."""
|
|
44
44
|
|
|
45
45
|
group = junitparser.Attr()
|
|
46
46
|
id = junitparser.Attr()
|
|
@@ -194,9 +194,9 @@ class TestCase(junitparser.TestCase):
|
|
|
194
194
|
return self._rerun_results(FlakyFailure)
|
|
195
195
|
|
|
196
196
|
def flaky_errors(self):
|
|
197
|
-
"""<
|
|
197
|
+
"""<flakyError>"""
|
|
198
198
|
return self._rerun_results(FlakyError)
|
|
199
199
|
|
|
200
200
|
def add_rerun_result(self, result: RerunType):
|
|
201
|
-
"""Append a rerun result to the
|
|
201
|
+
"""Append a rerun result to the testcase. A testcase can have multiple rerun results."""
|
|
202
202
|
self.append(result)
|