junitparser 4.0.1__tar.gz → 5.0.0__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.1 → junitparser-5.0.0}/LICENSE +2 -2
  2. {junitparser-4.0.1/junitparser.egg-info → junitparser-5.0.0}/PKG-INFO +18 -12
  3. {junitparser-4.0.1 → junitparser-5.0.0}/README.rst +15 -8
  4. {junitparser-4.0.1 → junitparser-5.0.0}/junitparser/__init__.py +1 -1
  5. {junitparser-4.0.1 → junitparser-5.0.0}/junitparser/junitparser.py +15 -11
  6. {junitparser-4.0.1 → junitparser-5.0.0}/junitparser/xunit2.py +30 -0
  7. {junitparser-4.0.1 → junitparser-5.0.0/junitparser.egg-info}/PKG-INFO +18 -12
  8. {junitparser-4.0.1 → junitparser-5.0.0}/pyproject.toml +3 -4
  9. {junitparser-4.0.1 → junitparser-5.0.0}/tests/test_xunit2.py +67 -2
  10. {junitparser-4.0.1 → junitparser-5.0.0}/MANIFEST.in +0 -0
  11. {junitparser-4.0.1 → junitparser-5.0.0}/junitparser/__main__.py +0 -0
  12. {junitparser-4.0.1 → junitparser-5.0.0}/junitparser/cli.py +0 -0
  13. {junitparser-4.0.1 → junitparser-5.0.0}/junitparser/py.typed +0 -0
  14. {junitparser-4.0.1 → junitparser-5.0.0}/junitparser.egg-info/SOURCES.txt +0 -0
  15. {junitparser-4.0.1 → junitparser-5.0.0}/junitparser.egg-info/dependency_links.txt +0 -0
  16. {junitparser-4.0.1 → junitparser-5.0.0}/junitparser.egg-info/entry_points.txt +0 -0
  17. {junitparser-4.0.1 → junitparser-5.0.0}/junitparser.egg-info/top_level.txt +0 -0
  18. {junitparser-4.0.1 → junitparser-5.0.0}/setup.cfg +0 -0
  19. {junitparser-4.0.1 → junitparser-5.0.0}/tests/data/jenkins.xml +0 -0
  20. {junitparser-4.0.1 → junitparser-5.0.0}/tests/data/no_fails.xml +0 -0
  21. {junitparser-4.0.1 → junitparser-5.0.0}/tests/data/no_suites_tag.xml +0 -0
  22. {junitparser-4.0.1 → junitparser-5.0.0}/tests/data/normal.xml +0 -0
  23. {junitparser-4.0.1 → junitparser-5.0.0}/tests/data/pytest_error.xml +0 -0
  24. {junitparser-4.0.1 → junitparser-5.0.0}/tests/data/pytest_success.xml +0 -0
  25. {junitparser-4.0.1 → junitparser-5.0.0}/tests/test_cli.py +0 -0
  26. {junitparser-4.0.1 → junitparser-5.0.0}/tests/test_fromfile.py +0 -0
  27. {junitparser-4.0.1 → junitparser-5.0.0}/tests/test_general.py +0 -0
  28. {junitparser-4.0.1 → junitparser-5.0.0}/tests/test_write.py +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,19 +1,18 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: junitparser
3
- Version: 4.0.1
3
+ Version: 5.0.0
4
4
  Summary: Manipulates JUnit/xUnit Result XML files
5
5
  Author-email: Weiwei Wang <gastlygem@gmail.com>
6
- License: Apache-2.0
6
+ License-Expression: Apache-2.0
7
7
  Project-URL: Homepage, https://github.com/weiwei/junitparser
8
8
  Project-URL: Issues, https://github.com/weiwei/junitparser/issues
9
9
  Project-URL: Documentation, https://junitparser.readthedocs.io/
10
10
  Keywords: junit,xunit,xml,parser
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: Apache Software License
14
13
  Classifier: Topic :: Text Processing
15
14
  Classifier: Programming Language :: Python :: 3
16
- Requires-Python: >=3.6
15
+ Requires-Python: >=3.10
17
16
  Description-Content-Type: text/x-rst
18
17
  License-File: LICENSE
19
18
  Dynamic: license-file
@@ -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.
@@ -16,7 +16,7 @@ from .junitparser import (
16
16
  TestSuite,
17
17
  )
18
18
 
19
- version = "4.0.1"
19
+ version = "5.0.0"
20
20
 
21
21
  __all__ = [
22
22
  "Attr",
@@ -11,6 +11,7 @@ See the documentation for other supported schemas.
11
11
  import io
12
12
  import itertools
13
13
  from copy import deepcopy
14
+ from pathlib import Path
14
15
  from typing import List, Union, Iterator, IO, Optional
15
16
 
16
17
  try:
@@ -19,7 +20,7 @@ except ImportError:
19
20
  from xml.etree import ElementTree as etree
20
21
 
21
22
 
22
- def write_xml(obj, file_or_filename: Optional[Union[str, IO]] = None, *, pretty: bool = False):
23
+ def write_xml(obj, file_or_filename: Optional[Union[str, IO, Path]] = None, *, pretty: bool = False):
23
24
  tree = etree.ElementTree(obj._elem)
24
25
  if file_or_filename is None:
25
26
  file_or_filename = obj.filepath
@@ -32,6 +33,9 @@ def write_xml(obj, file_or_filename: Optional[Union[str, IO]] = None, *, pretty:
32
33
  text = etree.tostring(obj._elem)
33
34
  xml = parseString(text) # nosec
34
35
  content = xml.toprettyxml(encoding="utf-8")
36
+ if isinstance(file_or_filename, Path):
37
+ file_or_filename = str(file_or_filename)
38
+
35
39
  if isinstance(file_or_filename, str):
36
40
  with open(file_or_filename, mode="wb") as xmlfile:
37
41
  xmlfile.write(content)
@@ -64,7 +68,7 @@ class Attr(object):
64
68
  Also see: :class:`IntAttr`, :class:`FloatAttr`.
65
69
  """
66
70
 
67
- def __init__(self, name: str = None):
71
+ def __init__(self, name: str | None = None):
68
72
  self.name = name
69
73
 
70
74
  def __get__(self, instance, cls):
@@ -137,7 +141,7 @@ class junitxml(type):
137
141
  class Element(metaclass=junitxml):
138
142
  """Base class for all JUnit XML elements."""
139
143
 
140
- def __init__(self, name: str = None):
144
+ def __init__(self, name: str | None = None):
141
145
  if not name:
142
146
  name = self._tag
143
147
  self._elem = etree.Element(name)
@@ -222,7 +226,7 @@ class Result(Element):
222
226
  message = Attr()
223
227
  type = Attr()
224
228
 
225
- def __init__(self, message: str = None, type_: str = None):
229
+ def __init__(self, message: str | None = None, type_: str | None = None):
226
230
  super(Result, self).__init__(self._tag)
227
231
  if message:
228
232
  self.message = message
@@ -287,7 +291,7 @@ class System(Element):
287
291
 
288
292
  _tag = ""
289
293
 
290
- def __init__(self, content: str = None):
294
+ def __init__(self, content: str | None = None):
291
295
  super().__init__(self._tag)
292
296
  self.text = content
293
297
 
@@ -326,7 +330,7 @@ class TestCase(Element):
326
330
  # JUnit TestCase children are final results, SystemOut and SystemErr
327
331
  ITER_TYPES = {t._tag: t for t in (Failure, Error, Skipped, SystemOut, SystemErr)}
328
332
 
329
- def __init__(self, name: str = None, classname: str = None, time: float = None):
333
+ def __init__(self, name: str | None = None, classname: str | None = None, time: float | None = None):
330
334
  super().__init__(self._tag)
331
335
  if name is not None:
332
336
  self.name = name
@@ -424,7 +428,7 @@ class TestCase(Element):
424
428
 
425
429
 
426
430
  class Property(Element):
427
- """A key/value pare that's stored in the testsuite.
431
+ """A key/value pair that's stored in the testsuite or testcase properties.
428
432
 
429
433
  Use it to store anything you find interesting or useful.
430
434
 
@@ -437,7 +441,7 @@ class Property(Element):
437
441
  name = Attr()
438
442
  value = Attr()
439
443
 
440
- def __init__(self, name: str = None, value: str = None):
444
+ def __init__(self, name: str | None = None, value: str | None = None):
441
445
  super().__init__(self._tag)
442
446
  self.name = name
443
447
  self.value = value
@@ -454,7 +458,7 @@ class Property(Element):
454
458
 
455
459
 
456
460
  class Properties(Element):
457
- """A list of properties inside a testsuite.
461
+ """A list of properties inside a testsuite or testcase.
458
462
 
459
463
  See :class:`Property`
460
464
  """
@@ -510,7 +514,7 @@ class TestSuite(Element):
510
514
 
511
515
  testcase = TestCase
512
516
 
513
- def __init__(self, name=None):
517
+ def __init__(self, name: str | None = None):
514
518
  super().__init__(self._tag)
515
519
  self.name = name
516
520
  self.filepath = None
@@ -684,7 +688,7 @@ class JUnitXml(Element):
684
688
 
685
689
  testsuite = TestSuite
686
690
 
687
- def __init__(self, name=None):
691
+ def __init__(self, name: str | None = None):
688
692
  super().__init__(self._tag)
689
693
  self.filepath = None
690
694
  self.name = name
@@ -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,19 +1,18 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: junitparser
3
- Version: 4.0.1
3
+ Version: 5.0.0
4
4
  Summary: Manipulates JUnit/xUnit Result XML files
5
5
  Author-email: Weiwei Wang <gastlygem@gmail.com>
6
- License: Apache-2.0
6
+ License-Expression: Apache-2.0
7
7
  Project-URL: Homepage, https://github.com/weiwei/junitparser
8
8
  Project-URL: Issues, https://github.com/weiwei/junitparser/issues
9
9
  Project-URL: Documentation, https://junitparser.readthedocs.io/
10
10
  Keywords: junit,xunit,xml,parser
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Intended Audience :: Developers
13
- Classifier: License :: OSI Approved :: Apache Software License
14
13
  Classifier: Topic :: Text Processing
15
14
  Classifier: Programming Language :: Python :: 3
16
- Requires-Python: >=3.6
15
+ Requires-Python: >=3.10
17
16
  Description-Content-Type: text/x-rst
18
17
  License-File: LICENSE
19
18
  Dynamic: license-file
@@ -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.
@@ -10,16 +10,16 @@ authors = [
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]
@@ -32,7 +32,6 @@ junitparser = "junitparser.cli:main"
32
32
 
33
33
  [tool.setuptools]
34
34
  packages = ["junitparser"]
35
- license-files = ["LICENSE"]
36
35
 
37
36
  [tool.setuptools.dynamic]
38
37
  version = {attr = "junitparser.version"}
@@ -1,5 +1,14 @@
1
- from junitparser.xunit2 import JUnitXml, TestSuite, TestCase, RerunFailure, RerunError, FlakyFailure, FlakyError
2
- from junitparser import Failure
1
+ import textwrap
2
+ from junitparser.xunit2 import (
3
+ JUnitXml,
4
+ TestSuite,
5
+ TestCase,
6
+ RerunFailure,
7
+ RerunError,
8
+ FlakyFailure,
9
+ FlakyError,
10
+ )
11
+ from junitparser import Failure, Property
3
12
  from copy import deepcopy
4
13
 
5
14
 
@@ -137,6 +146,62 @@ class Test_TestCase:
137
146
  case.add_interim_result(failure2)
138
147
  assert len(case.rerun_failures()) == 2
139
148
 
149
+ def test_properties_and_output(self):
150
+ text = textwrap.dedent("""\
151
+ <testcase name="test_pushstringvector" classname="test_pushstringvector" status="run">
152
+ <properties>
153
+ <property name="cmake_labels" value="util;script"/>
154
+ <property name="TestType" value="LATENCY_EDMA" />
155
+ </properties>
156
+ <system-out>
157
+ a line of output
158
+ another line
159
+ </system-out>
160
+ </testcase>
161
+ """)
162
+ case = TestCase.fromstring(text)
163
+ assert case.name == "test_pushstringvector"
164
+ assert case.system_out == "\na line of output\nanother line\n"
165
+ # Check that there are two properties in the TestCase, then check the values.
166
+ case_properties = list(case.properties())
167
+ assert len(case_properties) == 2
168
+ prop1, prop2 = case_properties
169
+ assert prop1.name == "cmake_labels" and prop1.value == "util;script"
170
+ assert prop2.name == "TestType" and prop2.value == "LATENCY_EDMA"
171
+
172
+ def test_suite_parses_testcase_properties(self):
173
+ text = textwrap.dedent("""\
174
+ <testsuite name="suitename1" tests="1" failures="0">
175
+ <testcase name="testname1">
176
+ <properties>
177
+ <property name="labels" value="foo;bar"/>
178
+ <property name="test_type" value="latency" />
179
+ </properties>
180
+ </testcase>
181
+ </testsuite>""")
182
+ test_suite1 = TestSuite.fromstring(text)
183
+ assert test_suite1.name == "suitename1"
184
+ cases = list(iter(test_suite1))
185
+ assert len(cases) == 1
186
+ case = cases[0]
187
+ case_properties = list(case.properties())
188
+ assert len(case_properties) == 2
189
+ prop1, prop2 = case_properties
190
+ assert prop1.name == "labels" and prop1.value == "foo;bar"
191
+ assert prop2.name == "test_type" and prop2.value == "latency"
192
+
193
+ def test_add_remove_property(self):
194
+ case = TestCase()
195
+ case.add_property("prop1", "foo")
196
+ case.add_property("prop2", "bar")
197
+ prop_to_remove = Property("prop1", "foo")
198
+ case.remove_property(prop_to_remove)
199
+ assert len(list(case.properties())) == 1
200
+ assert case.tostring() in [
201
+ b'<testcase><properties><property name="prop2" value="bar" /></properties></testcase>',
202
+ b'<testcase><properties><property name="prop2" value="bar"/></properties></testcase>',
203
+ ]
204
+
140
205
 
141
206
  class Test_TestSuite:
142
207
  def test_properties(self):
File without changes
File without changes