Skip to content
Open
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
61eb992
mypy junitparser
korbi-web-215 Jun 13, 2025
65dad78
Fix fmt
ubmarco Jun 16, 2025
bcaba18
Remove junitparser from excludes
ubmarco Jun 16, 2025
019810b
Fix all mypy errors in jsonparser.py
korbi-web-215 Jul 20, 2025
4427f07
Fix all mypy errors in junitparser.py
korbi-web-215 Jul 20, 2025
a19b4a0
Fix all mypy errors in test_results.py
korbi-web-215 Jul 20, 2025
612c65f
fix mypy error in environment.py
korbi-web-215 Aug 6, 2025
f3234b8
fix mypy error in exceptions.py
korbi-web-215 Aug 6, 2025
821e126
fix mypy error test_reports.py
korbi-web-215 Aug 6, 2025
aaa35ed
fix mypy error test_case.py
korbi-web-215 Aug 6, 2025
2dcd656
fix mypy error test_common.py
korbi-web-215 Aug 6, 2025
ee02197
fix mypy error test_env.py
korbi-web-215 Aug 6, 2025
25c0ddd
fix mypy error test_flie.py
korbi-web-215 Aug 6, 2025
4e4a511
fix mypy error test_report.py
korbi-web-215 Aug 6, 2025
07ceed5
fix mypy error test_suite.py
korbi-web-215 Aug 6, 2025
1f8c5d0
fix mypy error __init__.py
korbi-web-215 Aug 6, 2025
6a3849d
fix mypy error in environment
korbi-web-215 Aug 30, 2025
4536bec
fix mypy error on test_reports.py
korbi-web-215 Aug 30, 2025
a01a9f3
fix mypy error in test_case.py
korbi-web-215 Aug 30, 2025
189b383
fix mypy errors in test_common.py
korbi-web-215 Aug 30, 2025
dd85b11
fix mypy errors test_env.py
korbi-web-215 Aug 30, 2025
d9fd750
fix mypy errors test_file.py
korbi-web-215 Aug 30, 2025
689bed7
fix mypy errors test_report.py
korbi-web-215 Aug 30, 2025
95621b3
fix one mypy error
korbi-web-215 Aug 30, 2025
1a491f6
fix mypy errors test_suite.py
korbi-web-215 Aug 30, 2025
bb6bf4c
rebasen vom master
korbi-web-215 Oct 18, 2025
4405acf
pre-commits
korbi-web-215 Oct 18, 2025
128702d
mypy fixes
korbi-web-215 Oct 18, 2025
16ac6f5
fix: use getattr for needs_collapse_details in TestCommonDirective
korbi-web-215 Oct 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ exclude = [
"^sphinxcontrib/test_reports/exceptions\\.py$",
"^sphinxcontrib/test_reports/functions",
"^sphinxcontrib/test_reports/jsonparser\\.py$",
"^sphinxcontrib/test_reports/junitparser\\.py$",
"^sphinxcontrib/test_reports/test_reports\\.py$",
]
# Disallow dynamic typing
Expand Down
134 changes: 72 additions & 62 deletions sphinxcontrib/test_reports/directives/test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from sphinxcontrib.test_reports.directives.test_common import TestCommonDirective
from sphinxcontrib.test_reports.exceptions import TestReportInvalidOptionError

from typing import Dict, List, Optional, Match, Tuple, cast


class TestCase(nodes.General, nodes.Element):
pass
Expand Down Expand Up @@ -34,133 +36,141 @@ class TestCaseDirective(TestCommonDirective):

final_argument_whitespace = True

def __init__(self, *args, **kwargs):
def __init__(self, *args: object, **kwargs: object) -> None:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The right fix is to remove the whole constructor. That's the default of what Python would do anyway.

super().__init__(*args, **kwargs)

def run(self, nested=False, suite_count=-1, case_count=-1):
def run(self, nested: bool = False, suite_count: int = -1, case_count: int = -1) -> List[nodes.Element]:
self.prepare_basic_options()
self.load_test_file()

if nested and suite_count >= 0:
# access n-th nested suite here
self.results = self.results[0]["testsuites"][suite_count]

suite_name = self.options.get("suite")
results = self.load_test_file()

suite_name = cast(Optional[str], self.options.get("suite"))
if suite_name is None:
raise TestReportInvalidOptionError("Suite not given!")

case_full_name = self.options.get("case")
class_name = self.options.get("classname")
case_full_name = cast(Optional[str], self.options.get("case"))
class_name = cast(Optional[str], self.options.get("classname"))
if case_full_name is None and class_name is None:
raise TestReportInvalidOptionError("Case or classname not given!")

suite = None
for suite_obj in self.results:
if nested: # nested testsuites
suite = self.results
break

elif suite_obj["name"] == suite_name:
suite = suite_obj
break

if suite is None:
# Typing aliases
TestsuiteDict = Dict[str, object]
TestcaseDict = Dict[str, object]

# Gather candidate suites
candidate_suites: List[TestsuiteDict] = []
if results is not None:
candidate_suites = cast(List[TestsuiteDict], results)

# Handle nested selection if requested
selected_suite: Optional[TestsuiteDict] = None
if nested and suite_count >= 0 and candidate_suites:
root_suite = candidate_suites[0]
nested_suites = cast(List[TestsuiteDict], root_suite.get("testsuites", []))
if 0 <= suite_count < len(nested_suites):
selected_suite = nested_suites[suite_count]

# If not nested, search suite by name
if selected_suite is None:
for suite_obj in candidate_suites:
if str(suite_obj.get("name", "")) == suite_name:
selected_suite = suite_obj
break

if selected_suite is None:
raise TestReportInvalidOptionError(
f"Suite {suite_name} not found in test file {self.test_file}"
)

case = None

for case_obj in suite["testcases"]:
if case_obj["name"] == case_full_name and class_name is None: # noqa: SIM114 # noqa: W503
case = case_obj
# Select testcase
testcases = cast(List[TestcaseDict], selected_suite.get("testcases", []))
selected_case: Optional[TestcaseDict] = None
for case_obj in testcases:
name = str(case_obj.get("name", ""))
classname_val = str(case_obj.get("classname", ""))
if name == (case_full_name or "") and class_name is None:
selected_case = case_obj
break

elif (case_obj["classname"] == class_name and case_full_name is None) or (
case_obj["name"] == case_full_name
and case_obj["classname"] == class_name
elif (classname_val == (class_name or "") and case_full_name is None) or (
name == (case_full_name or "") and classname_val == (class_name or "")
):
case = case_obj
selected_case = case_obj
break

elif nested and case_count >= 0:
# access correct case in list
case = suite["testcases"][case_count]
break
if selected_case is None and nested and case_count >= 0 and 0 <= case_count < len(testcases):
selected_case = testcases[case_count]

elif nested:
case = case_obj
break
if selected_case is None and nested and testcases:
selected_case = testcases[0]

if case is None:
if selected_case is None:
raise TestReportInvalidOptionError(
f"Case {case_full_name} with classname {class_name} not found in test file {self.test_file} "
f"and testsuite {suite_name}"
)

result = case["result"]
content = self.test_content
if case["text"] is not None and len(case["text"]) > 0:
result = str(selected_case.get("result", ""))
content = self.test_content or ""
if selected_case.get("text") is not None and isinstance(selected_case.get("text"), str) and len(cast(str, selected_case.get("text"))) > 0:
content += """

**Text**::

{}

""".format("\n ".join([x.lstrip() for x in case["text"].split("\n")]))
""".format("\n ".join([x.lstrip() for x in cast(str, selected_case.get("text", "")).split("\n")]))

if case["message"] is not None and len(case["message"]) > 0:
if selected_case.get("message") is not None and isinstance(selected_case.get("message"), str) and len(cast(str, selected_case.get("message"))) > 0:
content += """

**Message**::

{}

""".format("\n ".join([x.lstrip() for x in case["message"].split("\n")]))
""".format("\n ".join([x.lstrip() for x in cast(str, selected_case.get("message", "")).split("\n")]))

if case["system-out"] is not None and len(case["system-out"]) > 0:
if selected_case.get("system-out") is not None and isinstance(selected_case.get("system-out"), str) and len(cast(str, selected_case.get("system-out"))) > 0:
content += """

**System-out**::

{}

""".format("\n ".join([x.lstrip() for x in case["system-out"].split("\n")]))
""".format("\n ".join([x.lstrip() for x in cast(str, selected_case.get("system-out", "")).split("\n")]))

time = case["time"]
style = "tr_" + case["result"]
time = float(selected_case.get("time", 0.0))
style = "tr_" + str(selected_case.get("result", ""))

import re

groups = re.match(r"^(?P<name>[^\[]+)($|\[(?P<param>.*)?\])", case["name"])
try:
groups: Optional[Match[str]] = re.match(r"^(?P<name>[^\[]+)($|\[(?P<param>.*)?\])", str(selected_case.get("name", "")))
if groups is not None:
case_name = groups["name"]
case_parameter = groups["param"]
except TypeError:
else:
case_name = case_full_name
case_parameter = ""

if case_parameter is None:
case_parameter = ""

# Set extra data, which is not part of the Sphinx-Test-Reports default options
for key, value in case.items():
for key, value in selected_case.items():
if key == "id" and value not in ["", None]:
self.test_id = str(value)
elif key == "status" and value not in ["", None]:
self.test_status = str(value)
elif key == "tags":
self.test_tags = ",".join([self.test_tags, str(value)])
self.test_tags = ",".join([self.test_tags or "", str(value)])
elif key not in DEFAULT_OPTIONS and value not in ["", None]:
# May overwrite globally set values
self.extra_options[key] = str(value)
if self.extra_options is not None:
self.extra_options[key] = str(value)

docname = self.state.document.settings.env.docname
docname = cast(str, self.state.document.settings.env.docname)

main_section = []
main_section: List[nodes.Element] = []
# Merge all options including extra ones
main_section += add_need(
main_section += cast(List[nodes.Element], add_need(
self.app,
self.state,
docname,
Expand All @@ -174,16 +184,16 @@ def run(self, nested=False, suite_count=-1, case_count=-1):
status=self.test_status,
collapse=self.collapse,
file=self.test_file_given,
suite=suite["name"],
suite=str(selected_suite.get("name", "")),
case=case_full_name,
case_name=case_name,
case_parameter=case_parameter,
classname=class_name,
result=result,
time=time,
style=style,
**self.extra_options,
)
**(self.extra_options or {}),
))

add_doc(self.env, docname)
return main_section
Loading
Loading