StrictDoc Documentation
strictdoc/backend/sdoc_source_code/test_reports/robot_xml_reader.py
Source file coverage
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 root
107
        # 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 summary
191
    :widths: 25 10
192
    :header-rows: 0
193
 
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
    @classmethod
227
    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