Skip to content

Commit

Permalink
Reconstructing testsuite and testcase hierarchy.
Browse files Browse the repository at this point in the history
  • Loading branch information
Paebbels committed Jan 13, 2024
1 parent 5988bb1 commit 29e9c7c
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 55 deletions.
4 changes: 2 additions & 2 deletions run.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ if ($liveunit)
{ Write-Host -ForegroundColor DarkYellow "[live][UNIT] Running Unit Tests using pytest ..."

$env:ENVIRONMENT_NAME = "Windows (x86-64)"
pytest -raP --color=yes --junitxml=report/unit/unitests.xml --template=html1/index.html --report=report/unit/html/index.html --split-report tests/unit
pytest -raP --color=yes --junitxml=report/unit/unittest.xml --template=html1/index.html --report=report/unit/html/index.html --split-report tests/unit

if ($copyunit)
{ cp -Recurse -Force .\report\unit\html\* .\doc\_build\html\unittests
Expand All @@ -150,7 +150,7 @@ elseif ($unit)
# Run unit tests
$runUnitFunc = {
$env:ENVIRONMENT_NAME = "Windows (x86-64)"
pytest -raP --color=yes --junitxml=report/unit/unitests.xml --template=html1/index.html --report=report/unit/html/index.html --split-report tests/unit
pytest -raP --color=yes --junitxml=report/unit/unittest.xml --template=html1/index.html --report=report/unit/html/index.html --split-report tests/unit
}
$unitJob = Start-Job -Name "UnitTests" -ScriptBlock $runUnitFunc
$jobs += $unitJob
Expand Down
76 changes: 43 additions & 33 deletions sphinx_reports/Adapter/JUnit.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,14 @@
"""
**A Sphinx extension providing uni test results embedded in documentation pages.**
"""
from pathlib import Path
from xml.dom import minidom, Node
from pathlib import Path
from xml.dom import minidom, Node
from xml.dom.minidom import Element

from coverage.results import Numbers
from pyTooling.Configuration.JSON import Configuration
from pyTooling.Decorators import export, readonly

from sphinx_reports.Common import ReportExtensionError
from sphinx_reports.DataModel.CodeCoverage import PackageCoverage, ModuleCoverage, AggregatedCoverage
from sphinx_reports.DataModel.Unittest import Testsuite, Testcase
from sphinx_reports.Common import ReportExtensionError
from sphinx_reports.DataModel.Unittest import Testsuite, Testcase, TestsuiteSummary, Test


@export
Expand All @@ -51,10 +48,10 @@ class UnittestError(ReportExtensionError):

@export
class Analyzer:
_packageName: str
_reportFile: Path
_documentElement: Element
_testsuite: Testsuite
_packageName: str
_reportFile: Path
_documentElement: Element
_testsuiteSummary: TestsuiteSummary

def __init__(self, packageName: str, reportFile: Path):
self._packageName = packageName
Expand All @@ -71,36 +68,49 @@ def __init__(self, packageName: str, reportFile: Path):
except Exception as ex:
raise UnittestError(f"Couldn't open '{self._reportFile}'.") from ex

def Convert(self) -> Testsuite:
return self._ParseRootElement(self._documentElement)
def Convert(self) -> TestsuiteSummary:
self._ParseRootElement(self._documentElement)

def _ParseRootElement(self, root: Element) -> Testsuite:
testsuite = Testsuite("root")
return self._testsuiteSummary

def _ParseRootElement(self, root: Element) -> None:
self._testsuiteSummary = TestsuiteSummary("root")

for rootNode in root.childNodes:
if rootNode.nodeName == "testsuite":
ts = self._ParseTestsuite(rootNode)
testsuite._testsuites[ts._name] = ts

return testsuite

def _ParseTestsuite(self, testsuitesNode: Element) -> Testsuite:
name = testsuitesNode.getAttribute("name")
self._ParseTestsuite(rootNode)
# testsuite._testsuites[ts._name] = ts

testsuite = Testsuite(name)
def _ParseTestsuite(self, testsuitesNode: Element) -> None:
for node in testsuitesNode.childNodes:
if node.nodeType == Node.ELEMENT_NODE:
if node.tagName == "testsuite":
self._ParseTestsuite(node)
elif node.tagName == "testcase":
self._ParseTestcase(node)

for testsuiteNode in testsuitesNode.childNodes:
if testsuiteNode.nodeType == Node.ELEMENT_NODE:
if testsuiteNode.tagName == "testcase":
tc = self._ParseTestcase(testsuiteNode)
# testsuite._testcases[tc._name] = tc

testsuite._testcases[tc._name] = tc
def _ParseTestcase(self, testsuiteNode: Element) -> None:
className = testsuiteNode.getAttribute("classname")
name = testsuiteNode.getAttribute("name")

return testsuite
concurrentSuite = self._testsuiteSummary

def _ParseTestcase(self, testsuiteNode: Element) -> Testcase:
className = testsuiteNode.getAttribute("classname")
testsuitePath = className.split(".")
for testsuiteName in testsuitePath[:-1]:
try:
concurrentSuite = concurrentSuite[testsuiteName]
except KeyError:
new = Testsuite(testsuiteName)
concurrentSuite._testsuites[testsuiteName] = new
concurrentSuite = new

testcase = Testcase(className)
testcaseName = testsuitePath[-1]
try:
testcase = concurrentSuite[testcaseName]
except KeyError:
testcase = Testcase(testcaseName)
concurrentSuite._testcases[testcaseName] = testcase

return testcase
testcase._tests[name] = Test(name)
53 changes: 47 additions & 6 deletions sphinx_reports/DataModel/Unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,27 +58,68 @@ def Name(self) -> str:
return self._name


@export
class Test(Base):
def __init__(self, name: str) -> None:
super().__init__(name)


@export
class Testcase(Base):
_tests: Dict[str, Test]

def __init__(self, name: str) -> None:
super().__init__(name)

self._tests = {}


@export
class Testsuite(Base):
_testcases: Dict[str, Testcase]
class TestsuiteBase(Base):
_testsuites: Dict[str, "Testsuite"]

def __init__(self, name: str) -> None:
super().__init__(name)

self._testcases = {}
self._testsuites = {}

@readonly
def Testcases(self) -> Dict[str, Testcase]:
return self._testcases
def __getitem__(self, key: str) -> "Testsuite":
return self._testsuites[key]

def __contains__(self, key: str) -> bool:
return key in self._testsuites

@readonly
def Testsuites(self) -> Dict[str, "Testsuite"]:
return self._testsuites


@export
class Testsuite(TestsuiteBase):
_testcases: Dict[str, Testcase]

def __init__(self, name: str) -> None:
super().__init__(name)

self._testcases = {}

def __getitem__(self, key: str) -> Union["Testsuite", Testcase]:
try:
return self._testsuites[key]
except KeyError:
return self._testcases[key]

def __contains__(self, key: str) -> bool:
if key not in self._testsuites:
return key in self._testcases

return False

@readonly
def Testcases(self) -> Dict[str, Testcase]:
return self._testcases


@export
class TestsuiteSummary(TestsuiteBase):
pass
58 changes: 44 additions & 14 deletions sphinx_reports/Unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@

from sphinx_reports.Common import ReportExtensionError, LegendPosition
from sphinx_reports.Sphinx import strip, BaseDirective
from sphinx_reports.DataModel.Unittest import Testsuite
from sphinx_reports.DataModel.Unittest import Testsuite, TestsuiteSummary, Testcase
from sphinx_reports.Adapter.JUnit import Analyzer


class package_DictType(TypedDict):
class report_DictType(TypedDict):
name: str
directory: str
xml_report: str


@export
Expand All @@ -71,7 +71,7 @@ class UnittestSummary(BaseDirective):
_legend: LegendPosition
_packageName: str
_xmlReport: Path
_testsuite: Testsuite
_testsuite: TestsuiteSummary

def _CheckOptions(self) -> None:
# Parse all directive options or use default values
Expand All @@ -82,7 +82,7 @@ def _CheckConfiguration(self) -> None:

# Check configuration fields and load necessary values
try:
allTestsuites: Dict[str, package_DictType] = self.config[f"{ReportDomain.name}_{self.configPrefix}_testsuites"]
allTestsuites: Dict[str, report_DictType] = self.config[f"{ReportDomain.name}_{self.configPrefix}_testsuites"]
except (KeyError, AttributeError) as ex:
raise ReportExtensionError(f"Configuration option '{ReportDomain.name}_{self.configPrefix}_testsuites' is not configured.") from ex

Expand Down Expand Up @@ -124,10 +124,10 @@ def sortedValues(d: Mapping[str, Testsuite]) -> Generator[Testsuite, None, None]
for key in sorted(d.keys()):
yield d[key]

def renderlevel(tableBody: nodes.tbody, testsuite: Testsuite, level: int = 0) -> None:
def renderRoot(tableBody: nodes.tbody, testsuite: TestsuiteSummary, level: int = 0) -> None:
tableBody += nodes.row(
"",
nodes.entry("", nodes.paragraph(text=f"{' '*level}{testsuite.Name}")),
nodes.entry("", nodes.paragraph(text=f"{' '*level}{testsuite.Name}")),
nodes.entry("", nodes.paragraph(text=f"")), # {testsuite.Expected}")),
nodes.entry("", nodes.paragraph(text=f"")), # {testsuite.Covered}")),
nodes.entry("", nodes.paragraph(text=f"")), # {testsuite.Uncovered}")),
Expand All @@ -137,21 +137,51 @@ def renderlevel(tableBody: nodes.tbody, testsuite: Testsuite, level: int = 0) ->
)

for ts in sortedValues(testsuite._testsuites):
renderlevel(tableBody, ts, level + 1)
renderTestsuite(tableBody, ts, level + 1)

def renderTestsuite(tableBody: nodes.tbody, testsuite: Testsuite, level: int) -> None:
tableBody += nodes.row(
"",
nodes.entry("", nodes.paragraph(text=f"{' '*level}{testsuite.Name}")),
nodes.entry("", nodes.paragraph(text=f"")), # {testsuite.Expected}")),
nodes.entry("", nodes.paragraph(text=f"")), # {testsuite.Covered}")),
nodes.entry("", nodes.paragraph(text=f"")), # {testsuite.Uncovered}")),
nodes.entry("", nodes.paragraph(text=f"")), # {testsuite.Coverage:.1%}")),
classes=["report-unittest-table-row"],
# style="background: rgba( 0, 200, 82, .2);"
)

for ts in sortedValues(testsuite._testsuites):
renderTestsuite(tableBody, ts, level + 1)

for testcase in sortedValues(testsuite._testcases):
renderTestcase(tableBody, testcase, level + 1)

def renderTestcase(tableBody: nodes.tbody, testcase: Testcase, level: int) -> None:
tableBody += nodes.row(
"",
nodes.entry("", nodes.paragraph(text=f"{' '*level}{testcase.Name}")),
nodes.entry("", nodes.paragraph(text=f"")), # {testsuite.Expected}")),
nodes.entry("", nodes.paragraph(text=f"")), # {testsuite.Covered}")),
nodes.entry("", nodes.paragraph(text=f"")), # {testsuite.Uncovered}")),
nodes.entry("", nodes.paragraph(text=f"")), # {testsuite.Coverage:.1%}")),
classes=["report-unittest-table-row"],
# style="background: rgba( 0, 200, 82, .2);"
)

for test in sortedValues(testcase._tests):
tableBody += nodes.row(
"",
nodes.entry("", nodes.paragraph(text=f"{' '*(level+1)}{testcase.Name}")),
nodes.entry("", nodes.paragraph(text=f"")), # {testcase.Expected}")),
nodes.entry("", nodes.paragraph(text=f"")), # {testcase.Covered}")),
nodes.entry("", nodes.paragraph(text=f"")), # {testcase.Uncovered}")),
nodes.entry("", nodes.paragraph(text=f"")), # {testcase.Coverage :.1%}")),
nodes.entry("", nodes.paragraph(text=f"{' '*(level+1)}{test.Name}")),
nodes.entry("", nodes.paragraph(text=f"")), # {test.Expected}")),
nodes.entry("", nodes.paragraph(text=f"")), # {test.Covered}")),
nodes.entry("", nodes.paragraph(text=f"")), # {test.Uncovered}")),
nodes.entry("", nodes.paragraph(text=f"")), # {test.Coverage :.1%}")),
classes=["report-unittest-table-row"],
# style="background: rgba( 0, 200, 82, .2);"
)

renderlevel(tableBody, self._testsuite)
renderRoot(tableBody, self._testsuite)

# # Add a summary row
# tableBody += nodes.row(
Expand Down

0 comments on commit 29e9c7c

Please sign in to comment.