junitparser 5.0.1__tar.gz → 5.0.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-5.0.1 → junitparser-5.0.2}/PKG-INFO +1 -1
- junitparser-5.0.2/pyproject.toml +45 -0
- junitparser-5.0.1/pyproject.toml → junitparser-5.0.2/pyproject.toml.orig +2 -2
- {junitparser-5.0.1 → junitparser-5.0.2}/src/junitparser/junitparser.py +30 -38
- {junitparser-5.0.1 → junitparser-5.0.2}/LICENSE +0 -0
- {junitparser-5.0.1 → junitparser-5.0.2}/README.rst +0 -0
- {junitparser-5.0.1 → junitparser-5.0.2}/src/junitparser/__init__.py +0 -0
- {junitparser-5.0.1 → junitparser-5.0.2}/src/junitparser/__main__.py +0 -0
- {junitparser-5.0.1 → junitparser-5.0.2}/src/junitparser/cli.py +0 -0
- {junitparser-5.0.1 → junitparser-5.0.2}/src/junitparser/py.typed +0 -0
- {junitparser-5.0.1 → junitparser-5.0.2}/src/junitparser/xunit2.py +0 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.11.7,<0.13.0"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "junitparser"
|
|
7
|
+
version = "5.0.2"
|
|
8
|
+
description = "Manipulates JUnit/xUnit Result XML files"
|
|
9
|
+
readme = "README.rst"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
dependencies = []
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 5 - Production/Stable",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Topic :: Text Processing",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
]
|
|
18
|
+
license = "Apache-2.0"
|
|
19
|
+
license-files = ["LICENSE"]
|
|
20
|
+
keywords = [
|
|
21
|
+
"junit",
|
|
22
|
+
"xunit",
|
|
23
|
+
"xml",
|
|
24
|
+
"parser",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[[project.authors]]
|
|
28
|
+
name = "Weiwei Wang"
|
|
29
|
+
email = "gastlygem@gmail.com"
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/weiwei/junitparser"
|
|
33
|
+
Issues = "https://github.com/weiwei/junitparser/issues"
|
|
34
|
+
Documentation = "https://junitparser.readthedocs.io/"
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
junitparser = "junitparser.cli:main"
|
|
38
|
+
|
|
39
|
+
[dependency-groups]
|
|
40
|
+
dev = [
|
|
41
|
+
"pre-commit>=4.6.0",
|
|
42
|
+
"pytest>=9.0.3",
|
|
43
|
+
"pytest-cov>=7.1.0",
|
|
44
|
+
"ruff>=0.15.12",
|
|
45
|
+
]
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["uv_build>=0.11.7,<0.
|
|
2
|
+
requires = ["uv_build>=0.11.7,<0.13.0"]
|
|
3
3
|
build-backend = "uv_build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "junitparser"
|
|
7
|
-
version = "5.0.
|
|
7
|
+
version = "5.0.2"
|
|
8
8
|
authors = [
|
|
9
9
|
{ name="Weiwei Wang", email="gastlygem@gmail.com" },
|
|
10
10
|
]
|
|
@@ -13,7 +13,7 @@ import io
|
|
|
13
13
|
import itertools
|
|
14
14
|
from copy import deepcopy
|
|
15
15
|
from pathlib import Path
|
|
16
|
-
from typing import List,
|
|
16
|
+
from typing import List, Iterator, IO
|
|
17
17
|
|
|
18
18
|
try:
|
|
19
19
|
from lxml import etree
|
|
@@ -23,7 +23,7 @@ except ImportError:
|
|
|
23
23
|
|
|
24
24
|
def write_xml(
|
|
25
25
|
obj,
|
|
26
|
-
file_or_filename:
|
|
26
|
+
file_or_filename: str | IO | Path | None = None,
|
|
27
27
|
*,
|
|
28
28
|
pretty: bool = False,
|
|
29
29
|
):
|
|
@@ -75,7 +75,7 @@ class JUnitXmlError(Exception):
|
|
|
75
75
|
"""Exception for JUnit XML related errors."""
|
|
76
76
|
|
|
77
77
|
|
|
78
|
-
class Attr
|
|
78
|
+
class Attr:
|
|
79
79
|
"""An attribute for an XML element.
|
|
80
80
|
|
|
81
81
|
By default they are all string values. To support different value types,
|
|
@@ -132,7 +132,7 @@ class FloatAttr(Attr):
|
|
|
132
132
|
return float(result.replace(",", "")) if result else None
|
|
133
133
|
|
|
134
134
|
def __set__(self, instance, value: float):
|
|
135
|
-
if not (isinstance(value, float
|
|
135
|
+
if not (isinstance(value, (float, int))):
|
|
136
136
|
raise TypeError("Expected float value.")
|
|
137
137
|
super().__set__(instance, value)
|
|
138
138
|
|
|
@@ -149,7 +149,7 @@ class junitxml(type):
|
|
|
149
149
|
"""Metaclass to decorate the XML class."""
|
|
150
150
|
|
|
151
151
|
def __new__(meta, name, bases, methods):
|
|
152
|
-
cls = super(
|
|
152
|
+
cls = super().__new__(meta, name, bases, methods)
|
|
153
153
|
cls = attributed(cls)
|
|
154
154
|
return cls
|
|
155
155
|
|
|
@@ -169,12 +169,10 @@ class Element(metaclass=junitxml):
|
|
|
169
169
|
tag = self._elem.tag
|
|
170
170
|
keys = sorted(self._elem.attrib.keys())
|
|
171
171
|
if keys:
|
|
172
|
-
attrs_str = " ".join(
|
|
173
|
-
|
|
174
|
-
)
|
|
175
|
-
return """<Element '%s' %s>""" % (tag, attrs_str)
|
|
172
|
+
attrs_str = " ".join(f'{key}="{self._elem.attrib[key]}"' for key in keys)
|
|
173
|
+
return f"""<Element '{tag}' {attrs_str}>"""
|
|
176
174
|
|
|
177
|
-
return """<Element '
|
|
175
|
+
return f"""<Element '{tag}'>"""
|
|
178
176
|
|
|
179
177
|
def append(self, sub_elem):
|
|
180
178
|
"""Add the element subelement to the end of this elements internal
|
|
@@ -186,7 +184,7 @@ class Element(metaclass=junitxml):
|
|
|
186
184
|
"""Add elements subelement to the end of this elements internal
|
|
187
185
|
list of subelements.
|
|
188
186
|
"""
|
|
189
|
-
self._elem.extend(
|
|
187
|
+
self._elem.extend(sub_elem._elem for sub_elem in sub_elems)
|
|
190
188
|
|
|
191
189
|
@classmethod
|
|
192
190
|
def fromstring(cls, text: str):
|
|
@@ -243,7 +241,7 @@ class Result(Element):
|
|
|
243
241
|
type = Attr()
|
|
244
242
|
|
|
245
243
|
def __init__(self, message: str | None = None, type_: str | None = None):
|
|
246
|
-
super(
|
|
244
|
+
super().__init__(self._tag)
|
|
247
245
|
if message:
|
|
248
246
|
self.message = message
|
|
249
247
|
if type_:
|
|
@@ -344,7 +342,7 @@ class TestCase(Element):
|
|
|
344
342
|
__test__ = False
|
|
345
343
|
|
|
346
344
|
# JUnit TestCase children are final results, SystemOut and SystemErr
|
|
347
|
-
ITER_TYPES = {t._tag: t for t in (Failure, Error, Skipped, SystemOut, SystemErr)}
|
|
345
|
+
ITER_TYPES = {t._tag: t for t in (Failure, Error, Skipped, SystemOut, SystemErr)} # noqa: RUF012
|
|
348
346
|
|
|
349
347
|
def __init__(
|
|
350
348
|
self,
|
|
@@ -363,7 +361,7 @@ class TestCase(Element):
|
|
|
363
361
|
def __hash__(self):
|
|
364
362
|
return super().__hash__()
|
|
365
363
|
|
|
366
|
-
def __iter__(self) -> Iterator[
|
|
364
|
+
def __iter__(self) -> Iterator[Result | System]:
|
|
367
365
|
for elem in self._elem.iter():
|
|
368
366
|
if elem.tag in self.ITER_TYPES:
|
|
369
367
|
yield self.ITER_TYPES[elem.tag].fromelem(elem)
|
|
@@ -393,12 +391,12 @@ class TestCase(Element):
|
|
|
393
391
|
return any(isinstance(r, Skipped) for r in self.result)
|
|
394
392
|
|
|
395
393
|
@property
|
|
396
|
-
def result(self) ->
|
|
394
|
+
def result(self) -> list[FinalResult]:
|
|
397
395
|
"""A list of :class:`Failure`, :class:`Skipped`, or :class:`Error` objects."""
|
|
398
396
|
return [entry for entry in self if isinstance(entry, FinalResult)]
|
|
399
397
|
|
|
400
398
|
@result.setter
|
|
401
|
-
def result(self, value:
|
|
399
|
+
def result(self, value: FinalResult | list[FinalResult]):
|
|
402
400
|
# Check typing
|
|
403
401
|
if not (
|
|
404
402
|
isinstance(value, FinalResult)
|
|
@@ -575,30 +573,30 @@ class TestSuite(Element):
|
|
|
575
573
|
# Merge the two testsuites
|
|
576
574
|
result = deepcopy(self)
|
|
577
575
|
for case in other:
|
|
578
|
-
result._add_testcase_no_update_stats(case)
|
|
576
|
+
result._add_testcase_no_update_stats(deepcopy(case))
|
|
579
577
|
for suite in other.testsuites():
|
|
580
|
-
result.add_testsuite(suite)
|
|
578
|
+
result.add_testsuite(deepcopy(suite))
|
|
581
579
|
result.update_statistics()
|
|
582
580
|
else:
|
|
583
581
|
# Create a new test result containing two testsuites
|
|
584
582
|
result = self.root()
|
|
585
|
-
result.add_testsuite(self)
|
|
586
|
-
result.add_testsuite(other)
|
|
583
|
+
result.add_testsuite(deepcopy(self))
|
|
584
|
+
result.add_testsuite(deepcopy(other))
|
|
587
585
|
return result
|
|
588
586
|
|
|
589
587
|
def __iadd__(self, other):
|
|
590
588
|
if self == other:
|
|
591
589
|
for case in other:
|
|
592
|
-
self._add_testcase_no_update_stats(case)
|
|
590
|
+
self._add_testcase_no_update_stats(deepcopy(case))
|
|
593
591
|
for suite in other.testsuites():
|
|
594
|
-
self.add_testsuite(suite)
|
|
592
|
+
self.add_testsuite(deepcopy(suite))
|
|
595
593
|
self.update_statistics()
|
|
596
594
|
return self
|
|
597
595
|
|
|
598
596
|
result = self.root()
|
|
599
597
|
result.filepath = self.filepath
|
|
600
598
|
result.add_testsuite(self)
|
|
601
|
-
result.add_testsuite(other)
|
|
599
|
+
result.add_testsuite(deepcopy(other))
|
|
602
600
|
return result
|
|
603
601
|
|
|
604
602
|
def remove_testcase(self, testcase: TestCase):
|
|
@@ -668,8 +666,7 @@ class TestSuite(Element):
|
|
|
668
666
|
props = self.child(Properties)
|
|
669
667
|
if props is None:
|
|
670
668
|
return
|
|
671
|
-
|
|
672
|
-
yield prop
|
|
669
|
+
yield from props
|
|
673
670
|
|
|
674
671
|
def remove_property(self, property_: Property):
|
|
675
672
|
"""Remove property *property_* from the testsuite."""
|
|
@@ -684,9 +681,7 @@ class TestSuite(Element):
|
|
|
684
681
|
"""Iterate through all testsuites."""
|
|
685
682
|
yield from self.iterchildren(type(self))
|
|
686
683
|
|
|
687
|
-
def write(
|
|
688
|
-
self, file_or_filename: Optional[Union[str, IO]] = None, *, pretty: bool = False
|
|
689
|
-
):
|
|
684
|
+
def write(self, file_or_filename: str | IO | None = None, *, pretty: bool = False):
|
|
690
685
|
write_xml(self, file_or_filename=file_or_filename, pretty=pretty)
|
|
691
686
|
|
|
692
687
|
|
|
@@ -728,26 +723,25 @@ class JUnitXml(Element):
|
|
|
728
723
|
def __add__(self, other):
|
|
729
724
|
result = type(self)()
|
|
730
725
|
for suite in self:
|
|
731
|
-
result.add_testsuite(suite)
|
|
726
|
+
result.add_testsuite(deepcopy(suite))
|
|
732
727
|
for suite in other:
|
|
733
|
-
result.add_testsuite(suite)
|
|
728
|
+
result.add_testsuite(deepcopy(suite))
|
|
734
729
|
return result
|
|
735
730
|
|
|
736
731
|
def __iadd__(self, other):
|
|
737
732
|
if other._elem.tag == "testsuites":
|
|
738
733
|
for suite in other:
|
|
739
|
-
self.add_testsuite(suite)
|
|
734
|
+
self.add_testsuite(deepcopy(suite))
|
|
740
735
|
elif other._elem.tag == "testsuite":
|
|
741
736
|
suite = self.testsuite(name=other.name)
|
|
742
737
|
for case in other:
|
|
743
|
-
suite._add_testcase_no_update_stats(case)
|
|
738
|
+
suite._add_testcase_no_update_stats(deepcopy(case))
|
|
744
739
|
self.add_testsuite(suite)
|
|
745
740
|
self.update_statistics()
|
|
746
741
|
|
|
747
742
|
return self
|
|
748
743
|
|
|
749
744
|
def add_testsuite(self, suite: TestSuite):
|
|
750
|
-
suite = deepcopy(suite)
|
|
751
745
|
"""Add a testsuite."""
|
|
752
746
|
for existing_suite in self:
|
|
753
747
|
if existing_suite == suite:
|
|
@@ -787,13 +781,13 @@ class JUnitXml(Element):
|
|
|
787
781
|
return instance
|
|
788
782
|
|
|
789
783
|
@classmethod
|
|
790
|
-
def fromstring(cls, text:
|
|
784
|
+
def fromstring(cls, text: str | bytes) -> "JUnitXml":
|
|
791
785
|
"""Construct JUnit objects from an XML string (str or bytes)."""
|
|
792
786
|
root_elem = etree.fromstring(text) # nosec
|
|
793
787
|
return cls.fromroot(root_elem)
|
|
794
788
|
|
|
795
789
|
@classmethod
|
|
796
|
-
def fromfile(cls, file:
|
|
790
|
+
def fromfile(cls, file: str | IO, parse_func=None) -> "JUnitXml":
|
|
797
791
|
"""
|
|
798
792
|
Construct JUnit objects from an XML file.
|
|
799
793
|
|
|
@@ -813,9 +807,7 @@ class JUnitXml(Element):
|
|
|
813
807
|
instance.filepath = file if isinstance(file, str) else None
|
|
814
808
|
return instance
|
|
815
809
|
|
|
816
|
-
def write(
|
|
817
|
-
self, file_or_filename: Optional[Union[str, IO]] = None, *, pretty: bool = False
|
|
818
|
-
):
|
|
810
|
+
def write(self, file_or_filename: str | IO | None = None, *, pretty: bool = False):
|
|
819
811
|
"""Write the object into a JUnit XML file.
|
|
820
812
|
|
|
821
813
|
If `file_or_filename` is not specified, it will write to the original filename.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|