Path:
strictdoc/backend/sdoc_source_code/test_reports/robot_xml_reader.py
Lines:
239
Non-empty lines:
218
Non-empty lines covered with requirements:
218 / 218 (100.0%)
Functions:
7
Functions covered by requirements:
7 / 7 (100.0%)
1
"""2
@relation(SDOC-SRS-143, scope=file)3
"""4
5
from typing import Dict, Optional, Type, Union
6
7
import robot.result
8
from robot.api import ExecutionResult, ResultVisitor
9
10
from strictdoc.backend.sdoc.document_reference import DocumentReference
11
from strictdoc.backend.sdoc.models.document import SDocDocument
12
from strictdoc.backend.sdoc.models.document_grammar import DocumentGrammar
13
from strictdoc.backend.sdoc.models.node import SDocNode, SDocNodeField
14
from strictdoc.backend.sdoc.models.reference import (
15
FileEntry,
16
FileEntryFormat,
17
FileReference,
18
)19
from strictdoc.core.file_system.file_tree import File
20
from strictdoc.core.project_config import ProjectConfig
21
from strictdoc.helpers.paths import path_to_posix_path
22
23
24
class SdocVisitor(ResultVisitor): # type: ignore[misc]
25
def __init__(self, project_config: ProjectConfig):
26
self.project_config = project_config
27
self.suites: Dict[str, Union[SDocDocument, SDocNode]] = {}
28
self.document: Optional[SDocDocument] = None
29
30
def visit_suite(self, suite: robot.result.TestSuite) -> None:
31
"""
32
Create document for top level suite and sections for nested suites.33
"""34
assert suite.full_name not in self.suites
35
36
if suite.parent is None:
37
self.document = SDocDocument(
38
mid=None,
39
title=f"Test report: {suite.name}",
40
config=None,
41
view=None,
42
grammar=None,
43
section_contents=[],
44
autogen=True,
45
)46
self.document.ng_including_document_reference = DocumentReference()
47
grammar = DocumentGrammar.create_for_test_report(self.document)
48
self.document.grammar = grammar
49
self.document.config.requirement_style = "Table"
50
self.suites[suite.full_name] = self.document
51
summary = self.summary_from_suite(suite, self.document)
52
self.document.section_contents.append(summary)
53
else:
54
assert self.document
55
assert suite.parent.full_name in self.suites, (
56
"depth-first traversal expected"57
)58
parent_sdoc_node = self.suites[suite.parent.full_name]
59
section = SDocNode.create_section(
60
parent=parent_sdoc_node,
61
document=self.document,
62
title=suite.name,
63
)64
section.ng_including_document_reference = DocumentReference()
65
section.ng_document_reference = DocumentReference()
66
section.ng_document_reference.set_document(self.document)
67
self.suites[suite.full_name] = section
68
parent_sdoc_node.section_contents.append(section)
69
summary_sdoc_node = self.summary_from_suite(suite, section)
70
section.section_contents.append(summary_sdoc_node)
71
72
super().visit_suite(suite)
73
74
def visit_test(self, test: robot.result.TestCase) -> None:
75
"""
76
Create TEST_RESULT node for each test case.77
"""78
assert self.document
79
assert test.parent and test.parent.full_name in self.suites, (
80
"depth-first traversal expected"81
)82
parent_section = self.suites[test.parent.full_name]
83
testcase_node = SDocNode(
84
parent=parent_section,
85
node_type="TEST_RESULT",
86
fields=[],
87
relations=[],
88
)89
testcase_node.ng_document_reference = DocumentReference()
90
testcase_node.ng_document_reference.set_document(self.document)
91
testcase_node.ng_including_document_reference = DocumentReference()
92
93
# Only executed tests will become traceable.94
if not test.skipped:
95
testcase_node.set_field_value(
96
field_name="UID",
97
form_field_index=0,
98
value=SDocNodeField(
99
parent=testcase_node,
100
field_name="UID",
101
parts=[test.full_name.upper()],
102
multiline__=None,
103
),104
)105
106
# Absolute path will be replaced with relative path to strictdoc root107
# in FileTraceabilityIndex.108
abs_path_on_exec_machine = path_to_posix_path(str(test.source))
109
if test.source is not None:
110
testcase_node.set_field_value(
111
field_name="TEST_PATH",
112
form_field_index=0,
113
value=SDocNodeField(
114
parent=testcase_node,
115
field_name="TEST_PATH",
116
parts=[abs_path_on_exec_machine],
117
multiline__=None,
118
),119
)120
121
testcase_node.set_field_value(
122
field_name="TEST_FUNCTION",
123
form_field_index=0,
124
value=SDocNodeField(
125
parent=testcase_node,
126
field_name="TEST_FUNCTION",
127
parts=[test.name],
128
multiline__=None,
129
),130
)131
132
testcase_node.set_field_value(
133
field_name="DURATION",
134
form_field_index=0,
135
value=SDocNodeField(
136
parent=testcase_node,
137
field_name="DURATION",
138
parts=[str(test.elapsed_time.total_seconds())],
139
multiline__=None,
140
),141
)142
143
testcase_node.set_field_value(
144
field_name="STATUS",
145
form_field_index=0,
146
value=SDocNodeField(
147
parent=testcase_node,
148
field_name="STATUS",
149
parts=[test.status],
150
multiline__=None,
151
),152
)153
154
testcase_node.set_field_value(
155
field_name="TITLE",
156
form_field_index=0,
157
value=SDocNodeField(
158
parent=testcase_node,
159
field_name="TITLE",
160
parts=[test.name],
161
multiline__=None,
162
),163
)164
165
if not test.skipped:
166
# File path will be resolved in FileTraceabilityIndex.167
testcase_node.relations.append(
168
FileReference(
169
parent=testcase_node,
170
g_file_entry=FileEntry(
171
parent=None,
172
g_file_format=FileEntryFormat.SOURCECODE,
173
g_file_path="#FORWARD#",
174
g_line_range=None,
175
element="function",
176
id=test.name,
177
),178
)179
)180
181
parent_section.section_contents.append(testcase_node)
182
183
def summary_from_suite(
184
self,
185
suite: robot.result.TestSuite,
186
parent: Union[SDocDocument, SDocNode],
187
) -> SDocNode:
188
assert self.document
189
summary_table = f"""\
190
.. list-table:: Test suite summary191
:widths: 25 10192
:header-rows: 0193
194
* - **Number of tests:**195
- {suite.statistics.total}
196
* - **Number of successful tests:**197
- {suite.statistics.passed}
198
* - **Number of failed tests:**199
- {suite.statistics.failed}
200
* - **Number of skipped tests:**201
- {suite.statistics.skipped}
202
"""203
summary_node = SDocNode(
204
parent=parent,
205
node_type="TEXT",
206
fields=[],
207
relations=[],
208
)209
summary_node.ng_document_reference = DocumentReference()
210
summary_node.ng_document_reference.set_document(self.document)
211
summary_node.ng_including_document_reference = DocumentReference()
212
summary_node.set_field_value(
213
field_name="STATEMENT",
214
form_field_index=0,
215
value=SDocNodeField(
216
parent=summary_node,
217
field_name="STATEMENT",
218
parts=[summary_table],
219
multiline__="True",
220
),221
)222
return summary_node
223
224
225
class RobotOutputXMLReader:
226
@classmethod227
def read_from_file(
228
cls: Type["RobotOutputXMLReader"],
229
doc_file: File,
230
project_config: ProjectConfig,
231
) -> SDocDocument:
232
execution_result = ExecutionResult(doc_file.full_path)
233
sdoc_visitor = SdocVisitor(project_config)
234
execution_result.visit(sdoc_visitor)
235
if sdoc_visitor.document is None:
236
raise RuntimeError(
237
f"No test suite could be parsed from {doc_file.full_path}"
238
)239
return sdoc_visitor.document