1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
|
"""
Render junit reports as HTML
"""
import os
import importlib.util
from typing import TYPE_CHECKING
if TYPE_CHECKING: # pragma: no cover
from os import PathLike
from typing import Optional, List, Dict
from jinja2 import Environment, PackageLoader, select_autoescape, FileSystemLoader, BaseLoader
class LoaderFactory:
"""Obtain jinja template loaders"""
DEFAULT = "default"
pkgname = "junit2htmlreport"
THEME_DIR_ENV = "JUNIT2HTML_THEMES"
THEME_FILES = ["base.html", "matrix.html", "report.html", "styles.css"]
def __init__(self):
self.baseloaders: "Dict[str, BaseLoader]" = {
self.DEFAULT: PackageLoader(self.pkgname, os.path.join("templates", self.DEFAULT)),
}
spec = importlib.util.find_spec(self.pkgname)
if spec and spec.origin:
pkgdir = os.path.dirname(spec.origin)
templates_dir = os.path.join(pkgdir, "templates")
for name in os.listdir(templates_dir):
if name not in self.baseloaders.keys():
self.baseloaders[name] = PackageLoader(self.pkgname, os.path.join("templates", name))
extra_themes = os.getenv(self.THEME_DIR_ENV, None)
if extra_themes and os.path.isdir(extra_themes):
for root, folders, filenames in os.walk(os.path.abspath(extra_themes)):
if filenames:
theme = True
for item in self.THEME_FILES:
if item not in filenames:
theme = False
if theme:
theme_name = os.path.basename(root)
self.baseloaders[theme_name] = FileSystemLoader(root)
self.selected = self.DEFAULT
def add_filesystem_loader(self, name: str, path: "PathLike"):
self.baseloaders[name] = FileSystemLoader(path)
def select_loader(self, name: str) -> None:
if name in self.baseloaders.keys():
self.selected = name
def get_loader(self) -> BaseLoader:
return self.baseloaders[self.selected]
@property
def styles(self) -> list:
return sorted(list(self.baseloaders.keys()))
class JunitLoaderBase:
"""Contains a jinja template BaseLoader"""
def __init__(self):
self._loader_factory: "Optional[LoaderFactory]" = None
@property
def loader_factory(self) -> "LoaderFactory":
if self._loader_factory is None:
return LoaderFactory()
return self._loader_factory
@loader_factory.setter
def loader_factory(self, value):
self._loader_factory = value
class ReportContainer(JunitLoaderBase):
"""
Hold one or more reports
"""
reports: "Dict[str, Junit]"
def __init__(self):
super().__init__()
self.reports = {}
def add_report(self, filename: str) -> None:
raise NotImplementedError()
def failures(self):
"""
Return all the failed test cases
:return:
"""
found: "List[Case]" = []
for report in self.reports:
for suite in self.reports[report].suites:
found.extend(suite.failed())
return found
def skips(self):
"""
Return all the skipped test cases
:return:
"""
found: "List[Case]" = []
for report in self.reports:
for suite in self.reports[report].suites:
found.extend(suite.skipped())
return found
class HTMLReport:
def __init__(self, show_toc: bool=True):
self.show_toc = show_toc
self.title: str = ""
self.report: "Optional[Junit]" = None
def load(self, report: "Junit", title: str="JUnit2HTML Report"):
self.report = report
self.title = title
def __iter__(self):
if self.report is None:
raise Exception("A report must be loaded through `load(...)` first.")
return self.report.__iter__()
def __str__(self) -> str:
loader = self.report.loader_factory.get_loader()
env = Environment(loader=loader, autoescape=select_autoescape(["html"]))
template = env.get_template("report.html")
return template.render(report=self, title=self.title, show_toc=self.show_toc)
|