From 588442630ac7bc23e8cb6c94cf5081dab311c409 Mon Sep 17 00:00:00 2001 From: eap Date: Sat, 23 Aug 2025 23:05:28 -0600 Subject: [PATCH 01/11] Add parsing of test case properties --- junitparser/junitparser.py | 150 +++++++++++++++++------------ tests/data/testcase_properties.xml | 13 +++ tests/test_fromfile.py | 21 ++++ 3 files changed, 124 insertions(+), 60 deletions(-) create mode 100644 tests/data/testcase_properties.xml diff --git a/junitparser/junitparser.py b/junitparser/junitparser.py index d77ef5a..82c5c37 100644 --- a/junitparser/junitparser.py +++ b/junitparser/junitparser.py @@ -312,6 +312,66 @@ class SystemErr(System): _tag = "system-err" +class Property(Element): + """A key/value pare that's stored in the testsuite. + + Use it to store anything you find interesting or useful. + + Attributes: + name: The property name. + value: The property value. + """ + + _tag = "property" + name = Attr() + value = Attr() + + def __init__(self, name: str = None, value: str = None): + super().__init__(self._tag) + self.name = name + self.value = value + + def __eq__(self, other): + return self.name == other.name and self.value == other.value + + def __ne__(self, other): + return not self == other + + def __lt__(self, other): + """Supports sort() for properties.""" + return self.name > other.name + + +class Properties(Element): + """A list of properties inside a testsuite. + + See :class:`Property` + """ + + _tag = "properties" + + def __init__(self): + super().__init__(self._tag) + + def add_property(self, property_: Property): + self.append(property_) + + def __iter__(self) -> Iterator[Property]: + return super().iterchildren(Property) + + def __eq__(self, other): + p1 = list(self) + p2 = list(other) + p1.sort() + p2.sort() + if len(p1) != len(p2): + return False + for e1, e2 in zip(p1, p2): + if e1 != e2: + return False + return True + + class TestCase(Element): """Object to store a testcase and its result. @@ -351,6 +411,36 @@ def __eq__(self, other): # TODO: May not work correctly if unreliable hash method is used. return hash(self) == hash(other) + def add_property(self, name: str, value: str): + """Add a property *name* = *value* to the testcase. + + See :class:`Property` and :class:`Properties`. + """ + + props = self.child(Properties) + if props is None: + props = Properties() + self.append(props) + prop = Property(name, value) + props.add_property(prop) + + def properties(self): + """Iterate through all :class:`Property` elements in the testcase.""" + props = self.child(Properties) + if props is None: + return + for prop in props: + yield prop + + def remove_property(self, property_: Property): + """Remove property *property_* from the testcase.""" + props = self.child(Properties) + if props is None: + return + for prop in props: + if prop == property_: + props.remove(property_) + @property def is_passed(self): """Whether this testcase was a success (i.e. if it isn't skipped, failed, or errored).""" @@ -427,66 +517,6 @@ def system_err(self, value: str): self.append(err) -class Property(Element): - """A key/value pare that's stored in the testsuite. - - Use it to store anything you find interesting or useful. - - Attributes: - name: The property name. - value: The property value. - """ - - _tag = "property" - name = Attr() - value = Attr() - - def __init__(self, name: str = None, value: str = None): - super().__init__(self._tag) - self.name = name - self.value = value - - def __eq__(self, other): - return self.name == other.name and self.value == other.value - - def __ne__(self, other): - return not self == other - - def __lt__(self, other): - """Supports sort() for properties.""" - return self.name > other.name - - -class Properties(Element): - """A list of properties inside a testsuite. - - See :class:`Property` - """ - - _tag = "properties" - - def __init__(self): - super().__init__(self._tag) - - def add_property(self, property_: Property): - self.append(property_) - - def __iter__(self) -> Iterator[Property]: - return super().iterchildren(Property) - - def __eq__(self, other): - p1 = list(self) - p2 = list(other) - p1.sort() - p2.sort() - if len(p1) != len(p2): - return False - for e1, e2 in zip(p1, p2): - if e1 != e2: - return False - return True - - class TestSuite(Element): """The object. diff --git a/tests/data/testcase_properties.xml b/tests/data/testcase_properties.xml new file mode 100644 index 0000000..5cdc768 --- /dev/null +++ b/tests/data/testcase_properties.xml @@ -0,0 +1,13 @@ + + + + + + + + +a line of output +another line + + + \ No newline at end of file diff --git a/tests/test_fromfile.py b/tests/test_fromfile.py index 8637623..da6f3fe 100644 --- a/tests/test_fromfile.py +++ b/tests/test_fromfile.py @@ -170,6 +170,27 @@ def test_fromfile_with_testsuite_in_testsuite(): assert len(all_cases[2].result) == 0 +def test_fromfile_testcase_properties(): + xml = JUnitXml.fromfile(os.path.join(os.path.dirname(__file__), "data/testcase_properties.xml")) + assert isinstance(xml, JUnitXml) + suite = list(iter(xml))[0] + assert isinstance(suite, TestSuite) + assert len(list(suite.properties())) == 0 + assert suite.name == "Linux-gcc" + # Check that there is one case in the suite. + cases = list(suite.iterchildren(TestCase)) + assert len(cases) == 1 + case = cases[0] + assert isinstance(case, TestCase) + assert case.system_out == "\na line of output\nanother line\n" + # Check that there are two properties in the case and check the values. + case_properties = list(case.properties()) + assert len(case_properties) == 2 + prop1, prop2 = case_properties + assert prop1.name == "cmake_labels" and prop1.value == "util;script" + assert prop2.name == "TestType" and prop2.value == "LATENCY_EDMA" + + def test_file_is_not_xml(): xmlfile = StringIO("Not really an xml file") with pytest.raises(Exception): From 170d41212630127ee873a3ef7bcb24db331f8abc Mon Sep 17 00:00:00 2001 From: eap Date: Sat, 23 Aug 2025 23:07:18 -0600 Subject: [PATCH 02/11] Fix doc line for props --- junitparser/junitparser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junitparser/junitparser.py b/junitparser/junitparser.py index 82c5c37..f12ea82 100644 --- a/junitparser/junitparser.py +++ b/junitparser/junitparser.py @@ -313,7 +313,7 @@ class SystemErr(System): class Property(Element): - """A key/value pare that's stored in the testsuite. + """A key/value pare that's stored in the testsuite or testcase properties. Use it to store anything you find interesting or useful. @@ -343,7 +343,7 @@ def __lt__(self, other): class Properties(Element): - """A list of properties inside a testsuite. + """A list of properties inside a testsuite or testcase. See :class:`Property` """ From c686e1a17e9a769e0018e6ab81b4e8c5008c9aa0 Mon Sep 17 00:00:00 2001 From: eap Date: Sat, 23 Aug 2025 23:17:17 -0600 Subject: [PATCH 03/11] typo fix --- junitparser/junitparser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/junitparser/junitparser.py b/junitparser/junitparser.py index f12ea82..3bcf7a1 100644 --- a/junitparser/junitparser.py +++ b/junitparser/junitparser.py @@ -313,7 +313,7 @@ class SystemErr(System): class Property(Element): - """A key/value pare that's stored in the testsuite or testcase properties. + """A key/value pair that's stored in the testsuite or testcase properties. Use it to store anything you find interesting or useful. From ce338493af2422eebd238330a53094c81511c8d4 Mon Sep 17 00:00:00 2001 From: eap Date: Sat, 23 Aug 2025 23:33:51 -0600 Subject: [PATCH 04/11] Simplify testing --- tests/data/testcase_properties.xml | 13 ------------- tests/test_fromfile.py | 21 --------------------- tests/test_general.py | 25 +++++++++++++++++++++++++ 3 files changed, 25 insertions(+), 34 deletions(-) delete mode 100644 tests/data/testcase_properties.xml diff --git a/tests/data/testcase_properties.xml b/tests/data/testcase_properties.xml deleted file mode 100644 index 5cdc768..0000000 --- a/tests/data/testcase_properties.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - -a line of output -another line - - - \ No newline at end of file diff --git a/tests/test_fromfile.py b/tests/test_fromfile.py index da6f3fe..8637623 100644 --- a/tests/test_fromfile.py +++ b/tests/test_fromfile.py @@ -170,27 +170,6 @@ def test_fromfile_with_testsuite_in_testsuite(): assert len(all_cases[2].result) == 0 -def test_fromfile_testcase_properties(): - xml = JUnitXml.fromfile(os.path.join(os.path.dirname(__file__), "data/testcase_properties.xml")) - assert isinstance(xml, JUnitXml) - suite = list(iter(xml))[0] - assert isinstance(suite, TestSuite) - assert len(list(suite.properties())) == 0 - assert suite.name == "Linux-gcc" - # Check that there is one case in the suite. - cases = list(suite.iterchildren(TestCase)) - assert len(cases) == 1 - case = cases[0] - assert isinstance(case, TestCase) - assert case.system_out == "\na line of output\nanother line\n" - # Check that there are two properties in the case and check the values. - case_properties = list(case.properties()) - assert len(case_properties) == 2 - prop1, prop2 = case_properties - assert prop1.name == "cmake_labels" and prop1.value == "util;script" - assert prop2.name == "TestType" and prop2.value == "LATENCY_EDMA" - - def test_file_is_not_xml(): xmlfile = StringIO("Not really an xml file") with pytest.raises(Exception): diff --git a/tests/test_general.py b/tests/test_general.py index 6f650d3..a9305b6 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -1,5 +1,6 @@ import locale import os +import textwrap from copy import deepcopy from unittest import skipIf from xml.etree import ElementTree as etree @@ -559,6 +560,30 @@ def test_multi_results(self): case.result += [fail4] assert len(case.result) == 5 + def test_properties_and_output(self): + text = textwrap.dedent(""" + + + + + + + a line of output + another line + + + """) + case = TestCase.fromstring(text) + # no assertion raised + assert case.name == "test_pushstringvector" + assert case.system_out == "\na line of output\nanother line\n" + # Check that there are two properties in the case and check the values. + case_properties = list(case.properties()) + assert len(case_properties) == 2 + prop1, prop2 = case_properties + assert prop1.name == "cmake_labels" and prop1.value == "util;script" + assert prop2.name == "TestType" and prop2.value == "LATENCY_EDMA" + def test_case_attributes(self): case = TestCase() case.name = "testname" From e32f774fab7491baa8c48b582552499a05cad404 Mon Sep 17 00:00:00 2001 From: eap Date: Sat, 23 Aug 2025 23:35:33 -0600 Subject: [PATCH 05/11] Self-review, fix test comments --- tests/test_general.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_general.py b/tests/test_general.py index a9305b6..a0d1a5a 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -574,10 +574,9 @@ def test_properties_and_output(self): """) case = TestCase.fromstring(text) - # no assertion raised assert case.name == "test_pushstringvector" assert case.system_out == "\na line of output\nanother line\n" - # Check that there are two properties in the case and check the values. + # Check that there are two properties in the TestCase, then check the values. case_properties = list(case.properties()) assert len(case_properties) == 2 prop1, prop2 = case_properties From 517830b422e4715420ddad5de00723830e0be32e Mon Sep 17 00:00:00 2001 From: eap Date: Sat, 23 Aug 2025 23:55:34 -0600 Subject: [PATCH 06/11] Test the addition and removal of props in testcases --- tests/test_general.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_general.py b/tests/test_general.py index a0d1a5a..bb29719 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -704,6 +704,18 @@ def test_result_attrs(self): b'', ] + def test_add_remove_property(self): + case = TestCase() + case.add_property("prop1", "foo") + case.add_property("prop2", "bar") + prop_to_remove = Property("prop1", "foo") + case.remove_property(prop_to_remove) + assert len(list(case.properties())) == 1 + assert case.tostring() in [ + b'', + b'', + ] + def test_add_child_element(self): class CustomElement(Element): _tag = "custom" From 8a1d1cc2d6ba850d782733ca45e74ddec76872f4 Mon Sep 17 00:00:00 2001 From: eap Date: Sat, 13 Sep 2025 22:41:09 -0600 Subject: [PATCH 07/11] Move testcase properties parser to xunit2 --- junitparser/junitparser.py | 150 +++++++++++++++---------------------- junitparser/xunit2.py | 31 ++++++++ tests/test_general.py | 36 --------- tests/test_xunit2.py | 37 ++++++++- 4 files changed, 127 insertions(+), 127 deletions(-) diff --git a/junitparser/junitparser.py b/junitparser/junitparser.py index 3bcf7a1..5ca6be9 100644 --- a/junitparser/junitparser.py +++ b/junitparser/junitparser.py @@ -312,66 +312,6 @@ class SystemErr(System): _tag = "system-err" -class Property(Element): - """A key/value pair that's stored in the testsuite or testcase properties. - - Use it to store anything you find interesting or useful. - - Attributes: - name: The property name. - value: The property value. - """ - - _tag = "property" - name = Attr() - value = Attr() - - def __init__(self, name: str = None, value: str = None): - super().__init__(self._tag) - self.name = name - self.value = value - - def __eq__(self, other): - return self.name == other.name and self.value == other.value - - def __ne__(self, other): - return not self == other - - def __lt__(self, other): - """Supports sort() for properties.""" - return self.name > other.name - - -class Properties(Element): - """A list of properties inside a testsuite or testcase. - - See :class:`Property` - """ - - _tag = "properties" - - def __init__(self): - super().__init__(self._tag) - - def add_property(self, property_: Property): - self.append(property_) - - def __iter__(self) -> Iterator[Property]: - return super().iterchildren(Property) - - def __eq__(self, other): - p1 = list(self) - p2 = list(other) - p1.sort() - p2.sort() - if len(p1) != len(p2): - return False - for e1, e2 in zip(p1, p2): - if e1 != e2: - return False - return True - - class TestCase(Element): """Object to store a testcase and its result. @@ -411,36 +351,6 @@ def __eq__(self, other): # TODO: May not work correctly if unreliable hash method is used. return hash(self) == hash(other) - def add_property(self, name: str, value: str): - """Add a property *name* = *value* to the testcase. - - See :class:`Property` and :class:`Properties`. - """ - - props = self.child(Properties) - if props is None: - props = Properties() - self.append(props) - prop = Property(name, value) - props.add_property(prop) - - def properties(self): - """Iterate through all :class:`Property` elements in the testcase.""" - props = self.child(Properties) - if props is None: - return - for prop in props: - yield prop - - def remove_property(self, property_: Property): - """Remove property *property_* from the testcase.""" - props = self.child(Properties) - if props is None: - return - for prop in props: - if prop == property_: - props.remove(property_) - @property def is_passed(self): """Whether this testcase was a success (i.e. if it isn't skipped, failed, or errored).""" @@ -517,6 +427,66 @@ def system_err(self, value: str): self.append(err) +class Property(Element): + """A key/value pair that's stored in the testsuite or testcase properties. + + Use it to store anything you find interesting or useful. + + Attributes: + name: The property name. + value: The property value. + """ + + _tag = "property" + name = Attr() + value = Attr() + + def __init__(self, name: str = None, value: str = None): + super().__init__(self._tag) + self.name = name + self.value = value + + def __eq__(self, other): + return self.name == other.name and self.value == other.value + + def __ne__(self, other): + return not self == other + + def __lt__(self, other): + """Supports sort() for properties.""" + return self.name > other.name + + +class Properties(Element): + """A list of properties inside a testsuite or testcase. + + See :class:`Property` + """ + + _tag = "properties" + + def __init__(self): + super().__init__(self._tag) + + def add_property(self, property_: Property): + self.append(property_) + + def __iter__(self) -> Iterator[Property]: + return super().iterchildren(Property) + + def __eq__(self, other): + p1 = list(self) + p2 = list(other) + p1.sort() + p2.sort() + if len(p1) != len(p2): + return False + for e1, e2 in zip(p1, p2): + if e1 != e2: + return False + return True + + class TestSuite(Element): """The object. diff --git a/junitparser/xunit2.py b/junitparser/xunit2.py index 4f60827..385e43d 100644 --- a/junitparser/xunit2.py +++ b/junitparser/xunit2.py @@ -15,6 +15,7 @@ import itertools from typing import List, Type, TypeVar from . import junitparser +from .junitparser import Properties, Property class StackTrace(junitparser.System): @@ -154,6 +155,36 @@ def add_interim_result(self, result: InterimResult): """Append an interim (rerun or flaky) result to the testcase. A testcase can have multiple interim results.""" self.append(result) + def add_property(self, name: str, value: str): + """Add a property *name* = *value* to the testcase. + + See :class:`Property` and :class:`Properties`. + """ + + props = self.child(Properties) + if props is None: + props = Properties() + self.append(props) + prop = Property(name, value) + props.add_property(prop) + + def properties(self): + """Iterate through all :class:`Property` elements in the testcase.""" + props = self.child(Properties) + if props is None: + return + for prop in props: + yield prop + + def remove_property(self, property_: Property): + """Remove property *property_* from the testcase.""" + props = self.child(Properties) + if props is None: + return + for prop in props: + if prop == property_: + props.remove(property_) + class TestSuite(junitparser.TestSuite): """TestSuite for Pytest, with some different attributes.""" diff --git a/tests/test_general.py b/tests/test_general.py index bb29719..6f650d3 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -1,6 +1,5 @@ import locale import os -import textwrap from copy import deepcopy from unittest import skipIf from xml.etree import ElementTree as etree @@ -560,29 +559,6 @@ def test_multi_results(self): case.result += [fail4] assert len(case.result) == 5 - def test_properties_and_output(self): - text = textwrap.dedent(""" - - - - - - - a line of output - another line - - - """) - case = TestCase.fromstring(text) - assert case.name == "test_pushstringvector" - assert case.system_out == "\na line of output\nanother line\n" - # Check that there are two properties in the TestCase, then check the values. - case_properties = list(case.properties()) - assert len(case_properties) == 2 - prop1, prop2 = case_properties - assert prop1.name == "cmake_labels" and prop1.value == "util;script" - assert prop2.name == "TestType" and prop2.value == "LATENCY_EDMA" - def test_case_attributes(self): case = TestCase() case.name = "testname" @@ -704,18 +680,6 @@ def test_result_attrs(self): b'', ] - def test_add_remove_property(self): - case = TestCase() - case.add_property("prop1", "foo") - case.add_property("prop2", "bar") - prop_to_remove = Property("prop1", "foo") - case.remove_property(prop_to_remove) - assert len(list(case.properties())) == 1 - assert case.tostring() in [ - b'', - b'', - ] - def test_add_child_element(self): class CustomElement(Element): _tag = "custom" diff --git a/tests/test_xunit2.py b/tests/test_xunit2.py index 13d439e..b2f9049 100644 --- a/tests/test_xunit2.py +++ b/tests/test_xunit2.py @@ -1,4 +1,5 @@ -from junitparser.xunit2 import JUnitXml, TestSuite, TestCase, RerunFailure, RerunError, FlakyFailure, FlakyError +import textwrap +from junitparser.xunit2 import JUnitXml, TestSuite, TestCase, RerunFailure, RerunError, FlakyFailure, FlakyError, Property from junitparser import Failure from copy import deepcopy @@ -137,6 +138,40 @@ def test_case_rerun(self): case.add_interim_result(failure2) assert len(case.rerun_failures()) == 2 + def test_properties_and_output(self): + text = textwrap.dedent(""" + + + + + + + a line of output + another line + + + """) + case = TestCase.fromstring(text) + assert case.name == "test_pushstringvector" + assert case.system_out == "\na line of output\nanother line\n" + # Check that there are two properties in the TestCase, then check the values. + case_properties = list(case.properties()) + assert len(case_properties) == 2 + prop1, prop2 = case_properties + assert prop1.name == "cmake_labels" and prop1.value == "util;script" + assert prop2.name == "TestType" and prop2.value == "LATENCY_EDMA" + + def test_add_remove_property(self): + case = TestCase() + case.add_property("prop1", "foo") + case.add_property("prop2", "bar") + prop_to_remove = Property("prop1", "foo") + case.remove_property(prop_to_remove) + assert len(list(case.properties())) == 1 + assert case.tostring() in [ + b'', + b'', + ] class Test_TestSuite: def test_properties(self): From 744785c4d57d2d221ed3781a818132130ec6aab4 Mon Sep 17 00:00:00 2001 From: eap Date: Sat, 13 Sep 2025 22:59:24 -0600 Subject: [PATCH 08/11] Update docs --- README.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index a082467..c4f8790 100644 --- a/README.rst +++ b/README.rst @@ -141,11 +141,14 @@ Junitparser also support extra schemas: .. code-block:: python + # Extended with extra properties/attributes from the xunit2 schema from junitparser.xunit2 import TestCase, TestSuite, RerunFailure - # These classes are redefined to support extra properties and attributes - # of the xunit2 schema. + + # TestSuite supports system_err. suite = TestSuite("mySuite") suite.system_err = "System err" # xunit2 specific property + + # TestCase supports interim results. case = TestCase("myCase") rerun_failure = RerunFailure("Not found", "404") # case property rerun_failure.stack_trace = "Stack" @@ -153,6 +156,9 @@ Junitparser also support extra schemas: rerun_failure.system_out = "NOT FOUND" case.add_interim_result(rerun_failure) + # TestCase supports properties. + case.add_property("cmake_labels", "cuda;tier2") + Currently supported schemas including: - xunit2_, supported by pytest, Erlang/OTP, Maven Surefire, CppTest, etc. From 886b43436eea27346b170e710da2b54e5edccf19 Mon Sep 17 00:00:00 2001 From: eap Date: Sat, 13 Sep 2025 23:00:39 -0600 Subject: [PATCH 09/11] self review --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index c4f8790..90af15f 100644 --- a/README.rst +++ b/README.rst @@ -141,7 +141,7 @@ Junitparser also support extra schemas: .. code-block:: python - # Extended with extra properties/attributes from the xunit2 schema + # Extended with extra properties/attributes from the xunit2 schema. from junitparser.xunit2 import TestCase, TestSuite, RerunFailure # TestSuite supports system_err. From b5e6e6e5316ddf0361b5a58e61fe5b502175959c Mon Sep 17 00:00:00 2001 From: eap Date: Sat, 13 Sep 2025 23:01:36 -0600 Subject: [PATCH 10/11] simplify --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 90af15f..79b6517 100644 --- a/README.rst +++ b/README.rst @@ -146,11 +146,11 @@ Junitparser also support extra schemas: # TestSuite supports system_err. suite = TestSuite("mySuite") - suite.system_err = "System err" # xunit2 specific property + suite.system_err = "System err" # TestCase supports interim results. case = TestCase("myCase") - rerun_failure = RerunFailure("Not found", "404") # case property + rerun_failure = RerunFailure("Not found", "404") rerun_failure.stack_trace = "Stack" rerun_failure.system_err = "E404" rerun_failure.system_out = "NOT FOUND" From 36ad955c3d0a76bd03f1ae677bef106a0e006a9c Mon Sep 17 00:00:00 2001 From: eap Date: Thu, 4 Dec 2025 09:55:59 -0700 Subject: [PATCH 11/11] review comments --- junitparser/xunit2.py | 17 ++++++++--------- tests/test_xunit2.py | 28 +++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/junitparser/xunit2.py b/junitparser/xunit2.py index 385e43d..5f03a6a 100644 --- a/junitparser/xunit2.py +++ b/junitparser/xunit2.py @@ -15,7 +15,6 @@ import itertools from typing import List, Type, TypeVar from . import junitparser -from .junitparser import Properties, Property class StackTrace(junitparser.System): @@ -158,27 +157,27 @@ def add_interim_result(self, result: InterimResult): def add_property(self, name: str, value: str): """Add a property *name* = *value* to the testcase. - See :class:`Property` and :class:`Properties`. + See :class:`junitparser.Property` and :class:`junitparser.Properties`. """ - props = self.child(Properties) + props = self.child(junitparser.Properties) if props is None: - props = Properties() + props = junitparser.Properties() self.append(props) - prop = Property(name, value) + prop = junitparser.Property(name, value) props.add_property(prop) def properties(self): - """Iterate through all :class:`Property` elements in the testcase.""" - props = self.child(Properties) + """Iterate through all :class:`junitparser.Property` elements in the testcase.""" + props = self.child(junitparser.Properties) if props is None: return for prop in props: yield prop - def remove_property(self, property_: Property): + def remove_property(self, property_: junitparser.Property): """Remove property *property_* from the testcase.""" - props = self.child(Properties) + props = self.child(junitparser.Properties) if props is None: return for prop in props: diff --git a/tests/test_xunit2.py b/tests/test_xunit2.py index b2f9049..904b788 100644 --- a/tests/test_xunit2.py +++ b/tests/test_xunit2.py @@ -1,6 +1,6 @@ import textwrap -from junitparser.xunit2 import JUnitXml, TestSuite, TestCase, RerunFailure, RerunError, FlakyFailure, FlakyError, Property -from junitparser import Failure +from junitparser.xunit2 import JUnitXml, TestSuite, TestCase, RerunFailure, RerunError, FlakyFailure, FlakyError +from junitparser import Failure, Property from copy import deepcopy @@ -139,7 +139,7 @@ def test_case_rerun(self): assert len(case.rerun_failures()) == 2 def test_properties_and_output(self): - text = textwrap.dedent(""" + text = textwrap.dedent("""\ @@ -161,6 +161,28 @@ def test_properties_and_output(self): assert prop1.name == "cmake_labels" and prop1.value == "util;script" assert prop2.name == "TestType" and prop2.value == "LATENCY_EDMA" + def test_suite_parses_testcase_properties(self): + text = textwrap.dedent("""\ + + + + + + + + """ + ) + test_suite1 = TestSuite.fromstring(text) + assert test_suite1.name == "suitename1" + cases = list(iter(test_suite1)) + assert len(cases) == 1 + case = cases[0] + case_properties = list(case.properties()) + assert len(case_properties) == 2 + prop1, prop2 = case_properties + assert prop1.name == "labels" and prop1.value == "foo;bar" + assert prop2.name == "test_type" and prop2.value == "latency" + def test_add_remove_property(self): case = TestCase() case.add_property("prop1", "foo")