junitparser 4.0.2__tar.gz → 5.0.1__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.
Files changed (28) hide show
  1. {junitparser-4.0.2 → junitparser-5.0.1}/LICENSE +2 -2
  2. {junitparser-4.0.2/junitparser.egg-info → junitparser-5.0.1}/PKG-INFO +24 -18
  3. {junitparser-4.0.2 → junitparser-5.0.1}/README.rst +15 -8
  4. {junitparser-4.0.2 → junitparser-5.0.1}/pyproject.toml +13 -12
  5. {junitparser-4.0.2 → junitparser-5.0.1/src}/junitparser/__init__.py +4 -2
  6. {junitparser-4.0.2 → junitparser-5.0.1/src}/junitparser/junitparser.py +48 -19
  7. {junitparser-4.0.2 → junitparser-5.0.1/src}/junitparser/xunit2.py +30 -0
  8. junitparser-4.0.2/MANIFEST.in +0 -1
  9. junitparser-4.0.2/PKG-INFO +0 -317
  10. junitparser-4.0.2/junitparser.egg-info/SOURCES.txt +0 -26
  11. junitparser-4.0.2/junitparser.egg-info/dependency_links.txt +0 -1
  12. junitparser-4.0.2/junitparser.egg-info/entry_points.txt +0 -2
  13. junitparser-4.0.2/junitparser.egg-info/top_level.txt +0 -1
  14. junitparser-4.0.2/setup.cfg +0 -4
  15. junitparser-4.0.2/tests/data/jenkins.xml +0 -23
  16. junitparser-4.0.2/tests/data/no_fails.xml +0 -16
  17. junitparser-4.0.2/tests/data/no_suites_tag.xml +0 -15
  18. junitparser-4.0.2/tests/data/normal.xml +0 -18
  19. junitparser-4.0.2/tests/data/pytest_error.xml +0 -8
  20. junitparser-4.0.2/tests/data/pytest_success.xml +0 -6
  21. junitparser-4.0.2/tests/test_cli.py +0 -107
  22. junitparser-4.0.2/tests/test_fromfile.py +0 -203
  23. junitparser-4.0.2/tests/test_general.py +0 -800
  24. junitparser-4.0.2/tests/test_write.py +0 -284
  25. junitparser-4.0.2/tests/test_xunit2.py +0 -240
  26. {junitparser-4.0.2 → junitparser-5.0.1/src}/junitparser/__main__.py +0 -0
  27. {junitparser-4.0.2 → junitparser-5.0.1/src}/junitparser/cli.py +0 -0
  28. {junitparser-4.0.2 → junitparser-5.0.1/src}/junitparser/py.typed +0 -0
@@ -1,4 +1,4 @@
1
- Copyright 2020 Joel Wang
1
+ Copyright 2025 Weiwei Wang
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
@@ -10,4 +10,4 @@ Copyright 2020 Joel Wang
10
10
  distributed under the License is distributed on an "AS IS" BASIS,
11
11
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  See the License for the specific language governing permissions and
13
- limitations under the License.
13
+ limitations under the License.
@@ -1,22 +1,21 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: junitparser
3
- Version: 4.0.2
3
+ Version: 5.0.1
4
4
  Summary: Manipulates JUnit/xUnit Result XML files
5
- Author-email: Weiwei Wang <gastlygem@gmail.com>
6
- License: Apache-2.0
7
- Project-URL: Homepage, https://github.com/weiwei/junitparser
8
- Project-URL: Issues, https://github.com/weiwei/junitparser/issues
9
- Project-URL: Documentation, https://junitparser.readthedocs.io/
10
5
  Keywords: junit,xunit,xml,parser
6
+ Author: Weiwei Wang
7
+ Author-email: Weiwei Wang <gastlygem@gmail.com>
8
+ License-Expression: Apache-2.0
9
+ License-File: LICENSE
11
10
  Classifier: Development Status :: 5 - Production/Stable
12
11
  Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: Apache Software License
14
12
  Classifier: Topic :: Text Processing
15
13
  Classifier: Programming Language :: Python :: 3
16
- Requires-Python: >=3.6
14
+ Requires-Python: >=3.10
15
+ Project-URL: Homepage, https://github.com/weiwei/junitparser
16
+ Project-URL: Issues, https://github.com/weiwei/junitparser/issues
17
+ Project-URL: Documentation, https://junitparser.readthedocs.io/
17
18
  Description-Content-Type: text/x-rst
18
- License-File: LICENSE
19
- Dynamic: license-file
20
19
 
21
20
  junitparser -- Pythonic JUnit/xUnit Result XML Parser
22
21
  ======================================================
@@ -39,8 +38,11 @@ Features
39
38
  * Merge test result XML files.
40
39
  * Specify XML parser. For example you can use lxml to speed things up.
41
40
  * Invoke from command line, or `python -m junitparser`
42
- * Python 2 and 3 support (As of Nov 2020, 1/4 of the users are still on Python
43
- 2, so there is no plan to drop Python 2 support)
41
+
42
+ Note on version 5
43
+ -----------------
44
+
45
+ Python 3.10 or above is now required. If you are still using Python 3.9 or below, please use version 4.0.2.
44
46
 
45
47
  Note on version 2
46
48
  -----------------
@@ -53,8 +55,6 @@ If you are using this attribute, please update your code accordingly.
53
55
  Installation
54
56
  -------------
55
57
 
56
- ::
57
-
58
58
  pip install junitparser
59
59
 
60
60
  Usage
@@ -161,18 +161,24 @@ Junitparser also support extra schemas:
161
161
 
162
162
  .. code-block:: python
163
163
 
164
+ # Extended with extra properties/attributes from the xunit2 schema.
164
165
  from junitparser.xunit2 import TestCase, TestSuite, RerunFailure
165
- # These classes are redefined to support extra properties and attributes
166
- # of the xunit2 schema.
166
+
167
+ # TestSuite supports system_err.
167
168
  suite = TestSuite("mySuite")
168
- suite.system_err = "System err" # xunit2 specific property
169
+ suite.system_err = "System err"
170
+
171
+ # TestCase supports interim results.
169
172
  case = TestCase("myCase")
170
- rerun_failure = RerunFailure("Not found", "404") # case property
173
+ rerun_failure = RerunFailure("Not found", "404")
171
174
  rerun_failure.stack_trace = "Stack"
172
175
  rerun_failure.system_err = "E404"
173
176
  rerun_failure.system_out = "NOT FOUND"
174
177
  case.add_interim_result(rerun_failure)
175
178
 
179
+ # TestCase supports properties.
180
+ case.add_property("cmake_labels", "cuda;tier2")
181
+
176
182
  Currently supported schemas including:
177
183
 
178
184
  - xunit2_, supported by pytest, Erlang/OTP, Maven Surefire, CppTest, etc.
@@ -19,8 +19,11 @@ Features
19
19
  * Merge test result XML files.
20
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
23
- 2, so there is no plan to drop Python 2 support)
22
+
23
+ Note on version 5
24
+ -----------------
25
+
26
+ Python 3.10 or above is now required. If you are still using Python 3.9 or below, please use version 4.0.2.
24
27
 
25
28
  Note on version 2
26
29
  -----------------
@@ -33,8 +36,6 @@ If you are using this attribute, please update your code accordingly.
33
36
  Installation
34
37
  -------------
35
38
 
36
- ::
37
-
38
39
  pip install junitparser
39
40
 
40
41
  Usage
@@ -141,18 +142,24 @@ Junitparser also support extra schemas:
141
142
 
142
143
  .. code-block:: python
143
144
 
145
+ # Extended with extra properties/attributes from the xunit2 schema.
144
146
  from junitparser.xunit2 import TestCase, TestSuite, RerunFailure
145
- # These classes are redefined to support extra properties and attributes
146
- # of the xunit2 schema.
147
+
148
+ # TestSuite supports system_err.
147
149
  suite = TestSuite("mySuite")
148
- suite.system_err = "System err" # xunit2 specific property
150
+ suite.system_err = "System err"
151
+
152
+ # TestCase supports interim results.
149
153
  case = TestCase("myCase")
150
- rerun_failure = RerunFailure("Not found", "404") # case property
154
+ rerun_failure = RerunFailure("Not found", "404")
151
155
  rerun_failure.stack_trace = "Stack"
152
156
  rerun_failure.system_err = "E404"
153
157
  rerun_failure.system_out = "NOT FOUND"
154
158
  case.add_interim_result(rerun_failure)
155
159
 
160
+ # TestCase supports properties.
161
+ case.add_property("cmake_labels", "cuda;tier2")
162
+
156
163
  Currently supported schemas including:
157
164
 
158
165
  - xunit2_, supported by pytest, Erlang/OTP, Maven Surefire, CppTest, etc.
@@ -1,25 +1,25 @@
1
1
  [build-system]
2
- requires = ["setuptools >= 61.0"]
3
- build-backend = "setuptools.build_meta"
2
+ requires = ["uv_build>=0.11.7,<0.12.0"]
3
+ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "junitparser"
7
- dynamic = ["version"]
7
+ version = "5.0.1"
8
8
  authors = [
9
9
  { name="Weiwei Wang", email="gastlygem@gmail.com" },
10
10
  ]
11
11
  description = "Manipulates JUnit/xUnit Result XML files"
12
12
  readme = "README.rst"
13
- requires-python = ">=3.6"
13
+ requires-python = ">=3.10"
14
14
  dependencies = []
15
15
  classifiers = [
16
16
  "Development Status :: 5 - Production/Stable",
17
17
  "Intended Audience :: Developers",
18
- "License :: OSI Approved :: Apache Software License",
19
18
  "Topic :: Text Processing",
20
19
  "Programming Language :: Python :: 3",
21
20
  ]
22
- license = {text = "Apache-2.0"}
21
+ license = "Apache-2.0"
22
+ license-files = ["LICENSE"]
23
23
  keywords = ["junit", "xunit", "xml", "parser"]
24
24
 
25
25
  [project.urls]
@@ -30,9 +30,10 @@ Documentation = "https://junitparser.readthedocs.io/"
30
30
  [project.scripts]
31
31
  junitparser = "junitparser.cli:main"
32
32
 
33
- [tool.setuptools]
34
- packages = ["junitparser"]
35
- license-files = ["LICENSE"]
36
-
37
- [tool.setuptools.dynamic]
38
- version = {attr = "junitparser.version"}
33
+ [dependency-groups]
34
+ dev = [
35
+ "pre-commit>=4.6.0",
36
+ "pytest>=9.0.3",
37
+ "pytest-cov>=7.1.0",
38
+ "ruff>=0.15.12",
39
+ ]
@@ -1,3 +1,5 @@
1
+ from importlib.metadata import version as _dist_version
2
+
1
3
  from .junitparser import (
2
4
  Attr,
3
5
  Element,
@@ -16,7 +18,7 @@ from .junitparser import (
16
18
  TestSuite,
17
19
  )
18
20
 
19
- version = "4.0.2"
21
+ version = _dist_version("junitparser")
20
22
 
21
23
  __all__ = [
22
24
  "Attr",
@@ -34,5 +36,5 @@ __all__ = [
34
36
  "SystemErr",
35
37
  "TestCase",
36
38
  "TestSuite",
37
- "version"
39
+ "version",
38
40
  ]
@@ -8,6 +8,7 @@ This, according to the document, is Apache Ant's JUnit output.
8
8
 
9
9
  See the documentation for other supported schemas.
10
10
  """
11
+
11
12
  import io
12
13
  import itertools
13
14
  from copy import deepcopy
@@ -20,7 +21,12 @@ except ImportError:
20
21
  from xml.etree import ElementTree as etree
21
22
 
22
23
 
23
- def write_xml(obj, file_or_filename: Optional[Union[str, IO, Path]] = None, *, pretty: bool = False):
24
+ def write_xml(
25
+ obj,
26
+ file_or_filename: Optional[Union[str, IO, Path]] = None,
27
+ *,
28
+ pretty: bool = False,
29
+ ):
24
30
  tree = etree.ElementTree(obj._elem)
25
31
  if file_or_filename is None:
26
32
  file_or_filename = obj.filepath
@@ -41,15 +47,25 @@ def write_xml(obj, file_or_filename: Optional[Union[str, IO, Path]] = None, *, p
41
47
  xmlfile.write(content)
42
48
  else:
43
49
  if isinstance(file_or_filename, io.TextIOWrapper):
44
- if file_or_filename.encoding is not None and file_or_filename.encoding.lower() != "utf-8":
45
- raise ValueError(f"Only utf-8 encoding is supported: {file_or_filename.encoding}")
50
+ if (
51
+ file_or_filename.encoding is not None
52
+ and file_or_filename.encoding.lower() != "utf-8"
53
+ ):
54
+ raise ValueError(
55
+ f"Only utf-8 encoding is supported: {file_or_filename.encoding}"
56
+ )
46
57
  file_or_filename.buffer.write(content)
47
58
  else:
48
59
  file_or_filename.write(content)
49
60
  else:
50
61
  if isinstance(file_or_filename, io.TextIOWrapper):
51
- if file_or_filename.encoding is not None and file_or_filename.encoding.lower() != "utf-8":
52
- raise ValueError(f"Only utf-8 encoding is supported: {file_or_filename.encoding}")
62
+ if (
63
+ file_or_filename.encoding is not None
64
+ and file_or_filename.encoding.lower() != "utf-8"
65
+ ):
66
+ raise ValueError(
67
+ f"Only utf-8 encoding is supported: {file_or_filename.encoding}"
68
+ )
53
69
  tree.write(file_or_filename.buffer, encoding="utf-8", xml_declaration=True)
54
70
  else:
55
71
  tree.write(file_or_filename, encoding="utf-8", xml_declaration=True)
@@ -68,7 +84,7 @@ class Attr(object):
68
84
  Also see: :class:`IntAttr`, :class:`FloatAttr`.
69
85
  """
70
86
 
71
- def __init__(self, name: str = None):
87
+ def __init__(self, name: str | None = None):
72
88
  self.name = name
73
89
 
74
90
  def __get__(self, instance, cls):
@@ -141,7 +157,7 @@ class junitxml(type):
141
157
  class Element(metaclass=junitxml):
142
158
  """Base class for all JUnit XML elements."""
143
159
 
144
- def __init__(self, name: str = None):
160
+ def __init__(self, name: str | None = None):
145
161
  if not name:
146
162
  name = self._tag
147
163
  self._elem = etree.Element(name)
@@ -226,7 +242,7 @@ class Result(Element):
226
242
  message = Attr()
227
243
  type = Attr()
228
244
 
229
- def __init__(self, message: str = None, type_: str = None):
245
+ def __init__(self, message: str | None = None, type_: str | None = None):
230
246
  super(Result, self).__init__(self._tag)
231
247
  if message:
232
248
  self.message = message
@@ -291,7 +307,7 @@ class System(Element):
291
307
 
292
308
  _tag = ""
293
309
 
294
- def __init__(self, content: str = None):
310
+ def __init__(self, content: str | None = None):
295
311
  super().__init__(self._tag)
296
312
  self.text = content
297
313
 
@@ -330,7 +346,12 @@ class TestCase(Element):
330
346
  # JUnit TestCase children are final results, SystemOut and SystemErr
331
347
  ITER_TYPES = {t._tag: t for t in (Failure, Error, Skipped, SystemOut, SystemErr)}
332
348
 
333
- def __init__(self, name: str = None, classname: str = None, time: float = None):
349
+ def __init__(
350
+ self,
351
+ name: str | None = None,
352
+ classname: str | None = None,
353
+ time: float | None = None,
354
+ ):
334
355
  super().__init__(self._tag)
335
356
  if name is not None:
336
357
  self.name = name
@@ -379,8 +400,11 @@ class TestCase(Element):
379
400
  @result.setter
380
401
  def result(self, value: Union[FinalResult, List[FinalResult]]):
381
402
  # Check typing
382
- if not (isinstance(value, FinalResult) or
383
- isinstance(value, list) and all(isinstance(item, FinalResult) for item in value)):
403
+ if not (
404
+ isinstance(value, FinalResult)
405
+ or isinstance(value, list)
406
+ and all(isinstance(item, FinalResult) for item in value)
407
+ ):
384
408
  raise ValueError("Value must be either FinalResult or list of FinalResult")
385
409
 
386
410
  # First remove all existing results
@@ -428,7 +452,7 @@ class TestCase(Element):
428
452
 
429
453
 
430
454
  class Property(Element):
431
- """A key/value pare that's stored in the testsuite.
455
+ """A key/value pair that's stored in the testsuite or testcase properties.
432
456
 
433
457
  Use it to store anything you find interesting or useful.
434
458
 
@@ -441,7 +465,7 @@ class Property(Element):
441
465
  name = Attr()
442
466
  value = Attr()
443
467
 
444
- def __init__(self, name: str = None, value: str = None):
468
+ def __init__(self, name: str | None = None, value: str | None = None):
445
469
  super().__init__(self._tag)
446
470
  self.name = name
447
471
  self.value = value
@@ -458,7 +482,7 @@ class Property(Element):
458
482
 
459
483
 
460
484
  class Properties(Element):
461
- """A list of properties inside a testsuite.
485
+ """A list of properties inside a testsuite or testcase.
462
486
 
463
487
  See :class:`Property`
464
488
  """
@@ -514,7 +538,7 @@ class TestSuite(Element):
514
538
 
515
539
  testcase = TestCase
516
540
 
517
- def __init__(self, name=None):
541
+ def __init__(self, name: str | None = None):
518
542
  super().__init__(self._tag)
519
543
  self.name = name
520
544
  self.filepath = None
@@ -660,7 +684,9 @@ class TestSuite(Element):
660
684
  """Iterate through all testsuites."""
661
685
  yield from self.iterchildren(type(self))
662
686
 
663
- def write(self, file_or_filename: Optional[Union[str, IO]] = None, *, pretty: bool = False):
687
+ def write(
688
+ self, file_or_filename: Optional[Union[str, IO]] = None, *, pretty: bool = False
689
+ ):
664
690
  write_xml(self, file_or_filename=file_or_filename, pretty=pretty)
665
691
 
666
692
 
@@ -688,7 +714,7 @@ class JUnitXml(Element):
688
714
 
689
715
  testsuite = TestSuite
690
716
 
691
- def __init__(self, name=None):
717
+ def __init__(self, name: str | None = None):
692
718
  super().__init__(self._tag)
693
719
  self.filepath = None
694
720
  self.name = name
@@ -721,6 +747,7 @@ class JUnitXml(Element):
721
747
  return self
722
748
 
723
749
  def add_testsuite(self, suite: TestSuite):
750
+ suite = deepcopy(suite)
724
751
  """Add a testsuite."""
725
752
  for existing_suite in self:
726
753
  if existing_suite == suite:
@@ -786,7 +813,9 @@ class JUnitXml(Element):
786
813
  instance.filepath = file if isinstance(file, str) else None
787
814
  return instance
788
815
 
789
- def write(self, file_or_filename: Optional[Union[str, IO]] = None, *, pretty: bool = False):
816
+ def write(
817
+ self, file_or_filename: Optional[Union[str, IO]] = None, *, pretty: bool = False
818
+ ):
790
819
  """Write the object into a JUnit XML file.
791
820
 
792
821
  If `file_or_filename` is not specified, it will write to the original filename.
@@ -154,6 +154,36 @@ class TestCase(junitparser.TestCase):
154
154
  """Append an interim (rerun or flaky) result to the testcase. A testcase can have multiple interim results."""
155
155
  self.append(result)
156
156
 
157
+ def add_property(self, name: str, value: str):
158
+ """Add a property *name* = *value* to the testcase.
159
+
160
+ See :class:`junitparser.Property` and :class:`junitparser.Properties`.
161
+ """
162
+
163
+ props = self.child(junitparser.Properties)
164
+ if props is None:
165
+ props = junitparser.Properties()
166
+ self.append(props)
167
+ prop = junitparser.Property(name, value)
168
+ props.add_property(prop)
169
+
170
+ def properties(self):
171
+ """Iterate through all :class:`junitparser.Property` elements in the testcase."""
172
+ props = self.child(junitparser.Properties)
173
+ if props is None:
174
+ return
175
+ for prop in props:
176
+ yield prop
177
+
178
+ def remove_property(self, property_: junitparser.Property):
179
+ """Remove property *property_* from the testcase."""
180
+ props = self.child(junitparser.Properties)
181
+ if props is None:
182
+ return
183
+ for prop in props:
184
+ if prop == property_:
185
+ props.remove(property_)
186
+
157
187
 
158
188
  class TestSuite(junitparser.TestSuite):
159
189
  """TestSuite for Pytest, with some different attributes."""
@@ -1 +0,0 @@
1
- include tests/data/*.xml