From a4e3ca3feb99a8357625a5f01552b736fbaaf10e Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Thu, 10 Oct 2024 10:57:04 +0200 Subject: [PATCH 1/7] TestCase: add properties `is_failure` and `is_error` --- junitparser/junitparser.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/junitparser/junitparser.py b/junitparser/junitparser.py index 6790feb..245fdf1 100644 --- a/junitparser/junitparser.py +++ b/junitparser/junitparser.py @@ -344,6 +344,22 @@ def is_passed(self): """Whether this testcase was a success (i.e. if it isn't skipped, failed, or errored).""" return not self.result + @property + def is_failure(self): + """Whether this testcase failed.""" + for r in self.result: + if isinstance(r, Failure): + return True + return False + + @property + def is_error(self): + """Whether this testcase errored.""" + for r in self.result: + if isinstance(r, Error): + return True + return False + @property def is_skipped(self): """Whether this testcase was skipped.""" From bfbce080d0f8d533b574c5f2efbf737a41069f7c Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Thu, 10 Oct 2024 10:57:30 +0200 Subject: [PATCH 2/7] add test for `is_failure` and `is_error` --- tests/test_general.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_general.py b/tests/test_general.py index 6205a6e..0ee3eb6 100644 --- a/tests/test_general.py +++ b/tests/test_general.py @@ -685,18 +685,32 @@ def test_case_is_skipped(self): case.result = [Skipped()] assert case.is_skipped assert not case.is_passed + assert not case.is_failure + assert not case.is_error def test_case_is_passed(self): case = TestCase() case.result = [] assert not case.is_skipped assert case.is_passed + assert not case.is_failure + assert not case.is_error def test_case_is_failed(self): case = TestCase() case.result = [Failure()] assert not case.is_skipped assert not case.is_passed + assert case.is_failure + assert not case.is_error + + def test_case_is_error(self): + case = TestCase() + case.result = [Error()] + assert not case.is_skipped + assert not case.is_passed + assert not case.is_failure + assert case.is_error class Test_Properties: From cfb028d62285a3a04db8161d9ae7bc95e4c0e264 Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Sun, 29 Dec 2024 18:32:27 +0100 Subject: [PATCH 3/7] TestCase: simplify properties by using `any()` and comprehensions --- junitparser/junitparser.py | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/junitparser/junitparser.py b/junitparser/junitparser.py index 245fdf1..c5cf9a0 100644 --- a/junitparser/junitparser.py +++ b/junitparser/junitparser.py @@ -347,36 +347,22 @@ def is_passed(self): @property def is_failure(self): """Whether this testcase failed.""" - for r in self.result: - if isinstance(r, Failure): - return True - return False + return any(isinstance(r, Failure) for r in self.result) @property def is_error(self): """Whether this testcase errored.""" - for r in self.result: - if isinstance(r, Error): - return True - return False + return any(isinstance(r, Error) for r in self.result) @property def is_skipped(self): """Whether this testcase was skipped.""" - for r in self.result: - if isinstance(r, Skipped): - return True - return False + return any(isinstance(r, Skipped) for r in self.result) @property def result(self): """A list of :class:`Failure`, :class:`Skipped`, or :class:`Error` objects.""" - results = [] - for entry in self: - if isinstance(entry, tuple(POSSIBLE_RESULTS)): - results.append(entry) - - return results + return [r for r in self if isinstance(r, tuple(POSSIBLE_RESULTS))] @result.setter def result(self, value: Union[Result, List[Result]]): From d31ebdac5e11ea418b42a12fb8a00205de782915 Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Thu, 24 Oct 2024 15:46:07 +0200 Subject: [PATCH 4/7] TestCase: simplify the setter for `result` --- junitparser/junitparser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/junitparser/junitparser.py b/junitparser/junitparser.py index c5cf9a0..dfc8e37 100644 --- a/junitparser/junitparser.py +++ b/junitparser/junitparser.py @@ -368,13 +368,13 @@ def result(self): def result(self, value: Union[Result, List[Result]]): # First remove all existing results for entry in self.result: - if any(isinstance(entry, r) for r in POSSIBLE_RESULTS): + if isinstance(entry, tuple(POSSIBLE_RESULTS)): self.remove(entry) if isinstance(value, Result): self.append(value) elif isinstance(value, list): for entry in value: - if any(isinstance(entry, r) for r in POSSIBLE_RESULTS): + if isinstance(entry, tuple(POSSIBLE_RESULTS)): self.append(entry) @property From 386d790557140205f99c7a0648a1e3c8a601acd0 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 20 Dec 2024 18:32:11 +0100 Subject: [PATCH 5/7] Rework types of XUnit2 rerun and JUnit final results --- junitparser/junitparser.py | 23 ++++++++++++++--------- junitparser/xunit2.py | 16 +++++++++------- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/junitparser/junitparser.py b/junitparser/junitparser.py index dfc8e37..8ed7009 100644 --- a/junitparser/junitparser.py +++ b/junitparser/junitparser.py @@ -241,7 +241,12 @@ def text(self, value: str): self._elem.text = value -class Skipped(Result): +class FinalResult(Result): + """Base class for final test result (in contrast to XUnit2 RerunResult).""" + + _tag = None + +class Skipped(FinalResult): """Test result when the case is skipped.""" _tag = "skipped" @@ -250,7 +255,7 @@ def __eq__(self, other): return super().__eq__(other) -class Failure(Result): +class Failure(FinalResult): """Test result when the case failed.""" _tag = "failure" @@ -259,7 +264,7 @@ def __eq__(self, other): return super().__eq__(other) -class Error(Result): +class Error(FinalResult): """Test result when the case has errors during execution.""" _tag = "error" @@ -268,7 +273,7 @@ def __eq__(self, other): return super().__eq__(other) -POSSIBLE_RESULTS = {Failure, Error, Skipped} +FINAL_RESULTS = {Failure, Error, Skipped} class System(Element): @@ -329,7 +334,7 @@ def __hash__(self): return super().__hash__() def __iter__(self): - all_types = set.union(POSSIBLE_RESULTS, {SystemOut}, {SystemErr}) + all_types = set.union(FINAL_RESULTS, {SystemOut}, {SystemErr}) for elem in self._elem.iter(): for entry_type in all_types: if elem.tag == entry_type._tag: @@ -360,21 +365,21 @@ def is_skipped(self): return any(isinstance(r, Skipped) for r in self.result) @property - def result(self): + def result(self) -> list[FinalResult]: """A list of :class:`Failure`, :class:`Skipped`, or :class:`Error` objects.""" - return [r for r in self if isinstance(r, tuple(POSSIBLE_RESULTS))] + return [r for r in self if isinstance(r, FinalResult)] @result.setter def result(self, value: Union[Result, List[Result]]): # First remove all existing results for entry in self.result: - if isinstance(entry, tuple(POSSIBLE_RESULTS)): + if isinstance(entry, FinalResult): self.remove(entry) if isinstance(value, Result): self.append(value) elif isinstance(value, list): for entry in value: - if isinstance(entry, tuple(POSSIBLE_RESULTS)): + if isinstance(entry, FinalResult): self.append(entry) @property diff --git a/junitparser/xunit2.py b/junitparser/xunit2.py index 014d0a2..81ec2e3 100644 --- a/junitparser/xunit2.py +++ b/junitparser/xunit2.py @@ -99,8 +99,10 @@ class StackTrace(junitparser.System): _tag = "stackTrace" -class RerunType(junitparser.Result): - _tag = "rerunType" +class RerunResult(junitparser.Result): + """Base class for intermediate / rerun test result (in contrast to JUnit FinalResult).""" + + _tag = None @property def stack_trace(self): @@ -157,19 +159,19 @@ def system_err(self, value: str): self.append(err) -class RerunFailure(RerunType): +class RerunFailure(RerunResult): _tag = "rerunFailure" -class RerunError(RerunType): +class RerunError(RerunResult): _tag = "rerunError" -class FlakyFailure(RerunType): +class FlakyFailure(RerunResult): _tag = "flakyFailure" -class FlakyError(RerunType): +class FlakyError(RerunResult): _tag = "flakyError" @@ -199,6 +201,6 @@ def flaky_errors(self): """""" return self._rerun_results(FlakyError) - def add_rerun_result(self, result: RerunType): + def add_rerun_result(self, result: RerunResult): """Append a rerun result to the testcase. A testcase can have multiple rerun results.""" self.append(result) From 7101e5910d1139b0ee28368b195480009cb77c45 Mon Sep 17 00:00:00 2001 From: Enrico Minack Date: Fri, 20 Dec 2024 18:57:18 +0100 Subject: [PATCH 6/7] Simplify type checking in TestCase.result, enforce input types --- junitparser/junitparser.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/junitparser/junitparser.py b/junitparser/junitparser.py index 8ed7009..62d40d2 100644 --- a/junitparser/junitparser.py +++ b/junitparser/junitparser.py @@ -365,22 +365,25 @@ def is_skipped(self): return any(isinstance(r, Skipped) for r in self.result) @property - def result(self) -> list[FinalResult]: + def result(self) -> List[FinalResult]: """A list of :class:`Failure`, :class:`Skipped`, or :class:`Error` objects.""" return [r for r in self if isinstance(r, FinalResult)] @result.setter - def result(self, value: Union[Result, List[Result]]): + def result(self, value: Union[FinalResult, List[FinalResult]]): + # Check typing + if not (isinstance(value, FinalResult) or + isinstance(value, list) and all(isinstance(item, FinalResult) for item in value)): + raise ValueError("Value must be either FinalResult or list of FinalResult") + # First remove all existing results for entry in self.result: - if isinstance(entry, FinalResult): - self.remove(entry) - if isinstance(value, Result): + self.remove(entry) + if isinstance(value, FinalResult): self.append(value) - elif isinstance(value, list): + else: for entry in value: - if isinstance(entry, FinalResult): - self.append(entry) + self.append(entry) @property def system_out(self): From a04c5ce7b0de098813619090ac1f8cc83e575726 Mon Sep 17 00:00:00 2001 From: Jan Wille Date: Sun, 29 Dec 2024 16:54:43 +0100 Subject: [PATCH 7/7] remove constant `FINAL_RESULTS` its functionality is now handled by the class `FinalResult`, so it just leads to confusion between the two. The single usage of the constant can also be simplified by using its content directly --- junitparser/junitparser.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/junitparser/junitparser.py b/junitparser/junitparser.py index 62d40d2..48055c0 100644 --- a/junitparser/junitparser.py +++ b/junitparser/junitparser.py @@ -273,9 +273,6 @@ def __eq__(self, other): return super().__eq__(other) -FINAL_RESULTS = {Failure, Error, Skipped} - - class System(Element): """Parent class for :class:`SystemOut` and :class:`SystemErr`. @@ -334,7 +331,7 @@ def __hash__(self): return super().__hash__() def __iter__(self): - all_types = set.union(FINAL_RESULTS, {SystemOut}, {SystemErr}) + all_types = {Failure, Error, Skipped, SystemOut, SystemErr} for elem in self._elem.iter(): for entry_type in all_types: if elem.tag == entry_type._tag: