ReportWriter

Summary

Package:

Testing

Doc-string:

documented

File

Expected

Covered

Uncovered

Coverage

ReportWriter.py

22

22

0

100.0%

Source

pyTooling/Testing/ReportWriter.pydocumentedundocumentedignoredexcludednot counted
31"""
32A pytest plugin writing a test report in :ref:`pyTooling's own format <TESTING/ReportFormat>`.
33
34JUnit XML cannot express two things a marked test suite has: test suites **nest**, where JUnit flattens them into a
35dotted ``classname``; and every item carries **four names** - an identifier, a title, a summary and a description -
36where JUnit has one name and a bag of flat ``<property>`` pairs.
37
38This writer is opt-in through ``--pytooling-xml=PATH`` and runs happily alongside ``--junit-xml``, so a pipeline
39keeps the format its dashboard understands while the richer file is produced from the same reports.
40
41The format's version is the version of its schema - :file:`TestReport-v0.1.xsd` today - so a later version is added
42beside it and a reader knows from ``xsi:noNamespaceSchemaLocation`` which one it is holding.
43
44.. todo:: TESTING::ReportWriter Name the generator in a ``<Generator>`` element
45
46   The tool that wrote a report is not part of the format's version, so it is no longer an attribute on the root
47   element. Where it belongs is an element of its own at root level, carrying the tool's name and version and
48   whatever further meta information a tool wants to add.
49
50.. todo:: TESTING::ReportWriter Add a ``<TestRun>`` level between the report and its test suites
51
52   A run happens on one machine, in one environment, at one time - so a report can hold several of them, and the
53   things that describe a run belong to the run rather than to the report:
54
55   .. code-block:: text
56
57      <TestReport>
58        <TestRun timestamp="..." duration="...">
59          <Environment>
60            <Hostname>...</Hostname>
61            <OperatingSystem>...</OperatingSystem>
62          </Environment>
63          <Summary>...</Summary>
64          <Description>...</Description>
65          <Testsuite ... />
66        </TestRun>
67      </TestReport>
68
69   The environment is optional, which is what makes a hostname acceptable there: it is written when whoever
70   generates the report decides it belongs in it, not by default.
71
72.. hint::
73
74   See :ref:`high-level help <TESTING/ReportFormat>` for the schema and an example.
75"""
...
86
88__all__ = ["SCHEMA_VERSION_LATEST", "SCHEMA_FILES", "REPORT_WRITER_KEY"]
89
91SCHEMA_VERSION_LATEST: SemanticVersion = SemanticVersion(0, 1)
92"""Latest version of the report format, and the one this writer produces."""
93
94SCHEMA_FILES: dict[SemanticVersion, Path] = {
95	SemanticVersion(0, 1): Path("TestReport-v0.1.xsd"),
96}
97"""Schema file per format version, so a later version is added beside the one in use, not instead of it."""
98
99REPORT_WRITER_KEY: StashKey["TestReportWriter"] = StashKey()
100"""Where the writer is stashed on the configuration."""
101
103class _Result(TypedDict, total=False):
104	"""What is collected per testcase, assembled from the reports of its phases."""
105
106	nodeID:               str    #: Node ID of the testcase, whose parts name the test suite levels.
107	duration:             float  #: Sum of the durations of the testcase's phases.
108	message:              str    #: Text of the failure, or an empty string.
109	status:               str    #: ``passed``, ``failed``, ``errored`` or ``skipped``.
110	title:                str    #: Title of the testcase, if it is marked.
111	summary:              str    #: Summary of the testcase, if its doc-string has one.
112	description:          str    #: Description of the testcase, if its doc-string has one.
113
115@export
116class TestReportWriter(metaclass=ExtendedType, slots=True):
117	"""
118	Collects the reports of a session and writes them as one nested XML document.
119
120	The nesting comes from the dotted ``classname`` pytest reports: ``tests.unit.Versioning`` becomes three levels
121	of ``<Testsuite>``, so a reader sees the hierarchy the test suite actually has.
122	"""
123
124	_path:      Path                          #: Where the report is written.
125	_results:   dict[str, _Result]            #: Collected results, keyed by node ID.
126	_hierarchy: dict[str, dict[str, str]]     #: Names of every test suite level, keyed by its dotted path.
127
128	def __init__(self, path: Path, hierarchy: Nullable[dict[str, dict[str, str]]] = None) -> None:
129		"""
130		Initializes the writer with the path it writes to.
131
132		:param path:      Path of the report file to write.
133		:param hierarchy: Optional, the names of every test suite level, keyed by its dotted path, as
134		                  :mod:`pyTooling.Testing.PyTest` collects them. Without it - when the marker plugin is not
135		                  registered - the test suite elements carry no names.
136		"""
		...
140
141	def pytest_runtest_logreport(self, report: Any) -> None:
142		"""
143		Collect one phase of one testcase.
144
145		:param report: The report of one phase of one testcase.
146		"""
		...
158
159	def _testsuiteFor(self, root: Element, suites: dict[str, Element], nodeID: str) -> Element:
160		"""
161		Return the ``<Testsuite>`` element a testcase belongs into, creating the missing levels on the way.
162
163		:param root:    The report element every path starts at.
164		:param suites:  Elements created so far, keyed by their dotted path.
165		:param nodeID:  Node ID of the testcase, whose ``::``-separated parts name the levels.
166		:returns:       The innermost test suite element.
167		"""
		...
188
189	def pytest_sessionfinish(self, session: Session, exitstatus: int) -> None:
190		"""
191		Write the collected results as one XML document.
192
193		:param session:    The finished session.
194		:param exitstatus: The exit status the session ended with.
195		"""
		...
230
232@export
233def pytest_addoption(parser: Parser) -> None:
234	"""
235	Add the ``--pytooling-xml`` option selecting the report's path.
236
237	:param parser: The command line parser to add the option to.
238	"""
	...
244
246@export
247def pytest_configure(config: Config) -> None:
248	"""
249	Register the writer, if a path was given.
250
251	:param config: The session's configuration.
252	"""
	...