StrictDoc Documentation
strictdoc/backend/gcov/reader.py
Source file coverage
Path:
strictdoc/backend/gcov/reader.py
Lines:
318
Non-empty lines:
292
Non-empty lines covered with requirements:
292 / 292 (100.0%)
Functions:
5
Functions covered by requirements:
5 / 5 (100.0%)
1
"""
2
@relation(SDOC-SRS-144, scope=file)
3
"""
4
 
5
import hashlib
6
import json
7
from dataclasses import dataclass
8
from pathlib import Path
9
from typing import Type
10
 
11
from strictdoc.backend.gcov.helpers import normalize_gcovr_file_path
12
from strictdoc.backend.sdoc.document_reference import DocumentReference
13
from strictdoc.backend.sdoc.models.document import SDocDocument
14
from strictdoc.backend.sdoc.models.document_grammar import DocumentGrammar
15
from strictdoc.backend.sdoc.models.node import SDocNode, SDocNodeField
16
from strictdoc.backend.sdoc.models.reference import (
17
    FileEntry,
18
    FileEntryFormat,
19
    FileReference,
20
)
21
from strictdoc.core.file_system.file_tree import File
22
from strictdoc.core.project_config import ProjectConfig
23
from strictdoc.helpers.file_system import file_open_read_utf8
24
 
25
 
26
@dataclass
27
class GCovStatsObject:
28
    total_number_of_covered_functions: int = 0
29
    total_number_of_non_covered_functions: int = 0
30
 
31
    def add_function(self, covered: bool) -> None:
32
        if covered:
33
            self.total_number_of_covered_functions += 1
34
        else:
35
            self.total_number_of_non_covered_functions += 1
36
 
37
 
38
class GCovJSONReader:
39
    @classmethod
40
    def read_from_file(
41
        cls: Type["GCovJSONReader"],
42
        doc_file: File,
43
        project_config: ProjectConfig,
44
    ) -> SDocDocument:
45
        with file_open_read_utf8(doc_file.full_path) as file:
46
            content = file.read()
47
        return cls.read_from_string(content, doc_file, project_config)
48
 
49
    @classmethod
50
    def read_from_string(
51
        cls: Type["GCovJSONReader"],
52
        content: str,
53
        doc_file: File,  # noqa: ARG003
54
        project_config: ProjectConfig,
55
    ) -> SDocDocument:
56
        if len(content) == 0:
57
            raise RuntimeError("Document is empty")
58
        try:
59
            json_content = json.loads(content)
60
        except Exception as exception:  # pylint: disable=broad-except
61
            raise RuntimeError(str(exception)) from None
62
 
63
        document = SDocDocument(
64
            mid=None,
65
            title="Code coverage report",
66
            config=None,
67
            view=None,
68
            grammar=None,
69
            section_contents=[],
70
            autogen=True,
71
        )
72
        document.ng_including_document_reference = DocumentReference()
73
        grammar = DocumentGrammar.create_for_test_report(document)
74
        document.grammar = grammar
75
        document.config.requirement_style = "Table"
76
 
77
        document_stats = GCovStatsObject()
78
 
79
        """
80
        Parse individual <testcase> elements.
81
        """
82
 
83
        source_root_path = (
84
            project_config.source_root_path
85
            if project_config is not None
86
            else None
87
        )
88
 
89
        json_files = json_content["files"]
90
        for json_file_ in json_files:
91
            stats = GCovStatsObject()
92
 
93
            json_file_name = normalize_gcovr_file_path(
94
                json_file_["file"], source_root_path=source_root_path
95
            )
96
 
97
            file_section = SDocNode.create_section(
98
                parent=document,
99
                document=document,
100
                title=json_file_name,
101
            )
102
            file_section.ng_including_document_reference = DocumentReference()
103
            file_section.ng_document_reference = DocumentReference()
104
            file_section.ng_document_reference.set_document(document)
105
            document.section_contents.append(file_section)
106
 
107
            covered_functions_section = SDocNode.create_section(
108
                parent=document,
109
                document=document,
110
                title="Covered functions",
111
            )
112
            covered_functions_section.ng_including_document_reference = (
113
                DocumentReference()
114
            )
115
            covered_functions_section.ng_document_reference = (
116
                DocumentReference()
117
            )
118
            covered_functions_section.ng_document_reference.set_document(
119
                document
120
            )
121
            file_section.section_contents.append(covered_functions_section)
122
 
123
            non_covered_functions_section = SDocNode.create_section(
124
                parent=document,
125
                document=document,
126
                title="Non-covered functions",
127
            )
128
            non_covered_functions_section.ng_including_document_reference = (
129
                DocumentReference()
130
            )
131
            non_covered_functions_section.ng_document_reference = (
132
                DocumentReference()
133
            )
134
            non_covered_functions_section.ng_document_reference.set_document(
135
                document
136
            )
137
            file_section.section_contents.append(non_covered_functions_section)
138
 
139
            for json_function_ in json_file_["functions"]:
140
                json_function_name = json_function_.get(
141
                    "demangled_name", json_function_.get("name")
142
                )
143
                assert json_function_name is not None, json_function_
144
                is_function_covered = json_function_["execution_count"] > 0
145
                stats.add_function(is_function_covered)
146
 
147
                testcase_node = SDocNode(
148
                    parent=document,
149
                    node_type="TEST_RESULT",
150
                    fields=[],
151
                    relations=[],
152
                )
153
                testcase_node.ng_document_reference = DocumentReference()
154
                testcase_node.ng_document_reference.set_document(document)
155
                testcase_node.ng_including_document_reference = (
156
                    DocumentReference()
157
                )
158
                testcase_node.set_field_value(
159
                    field_name="UID",
160
                    form_field_index=0,
161
                    value=SDocNodeField(
162
                        parent=testcase_node,
163
                        field_name="UID",
164
                        parts=[
165
                            "GCOV:" + json_file_name + ":" + json_function_name
166
                        ],
167
                        multiline__=None,
168
                    ),
169
                )
170
                # FIXME: Remove?
171
                testcase_node.set_field_value(
172
                    field_name="STATUS",
173
                    form_field_index=0,
174
                    value=SDocNodeField(
175
                        parent=testcase_node,
176
                        field_name="STATUS",
177
                        parts=[
178
                            "Covered" if is_function_covered else "Non-covered"
179
                        ],
180
                        multiline__=None,
181
                    ),
182
                )
183
 
184
                gcov_path_hash = hashlib.md5(
185
                    json_file_name.encode("utf-8")
186
                ).hexdigest()
187
                link = (
188
                    "../../../../"  # noqa: ISC003
189
                    + "coverage."
190
                    + Path(json_file_name).name
191
                    + ". "
192
                    + gcov_path_hash
193
                    + ".html"
194
                )
195
                testcase_node.set_field_value(
196
                    field_name="STATEMENT",
197
                    form_field_index=0,
198
                    value=SDocNodeField(
199
                        parent=testcase_node,
200
                        field_name="STATEMENT",
201
                        parts=[
202
                            f"""
203
                            `LINK <{link}>`_
204
                            """
205
                        ],
206
                        multiline__=None,
207
                    ),
208
                )
209
                testcase_node.set_field_value(
210
                    field_name="TITLE",
211
                    form_field_index=0,
212
                    value=SDocNodeField(
213
                        parent=testcase_node,
214
                        field_name="TITLE",
215
                        parts=[json_function_name],
216
                        multiline__=None,
217
                    ),
218
                )
219
                testcase_node.relations.append(
220
                    FileReference(
221
                        parent=testcase_node,
222
                        g_file_entry=FileEntry(
223
                            parent=None,
224
                            g_file_format=FileEntryFormat.SOURCECODE,
225
                            g_file_path=json_file_name,
226
                            g_line_range=None,
227
                            element="function",
228
                            id=json_function_name,
229
                        ),
230
                    )
231
                )
232
                if is_function_covered:
233
                    covered_functions_section.section_contents.append(
234
                        testcase_node
235
                    )
236
                else:
237
                    non_covered_functions_section.section_contents.append(
238
                        testcase_node
239
                    )
240
 
241
            summary_table = f"""\
242
.. list-table:: Coverage summary for {json_file_name}
243
    :widths: 25 10
244
    :header-rows: 0
245
 
246
    * - **Total covered functions:**
247
      - {stats.total_number_of_covered_functions}
248
    * - **Total non-covered functions:**
249
      - {stats.total_number_of_non_covered_functions}
250
        """
251
 
252
            testcase_node = SDocNode(
253
                parent=document,
254
                node_type="TEXT",
255
                fields=[],
256
                relations=[],
257
            )
258
            testcase_node.ng_document_reference = DocumentReference()
259
            testcase_node.ng_document_reference.set_document(document)
260
            testcase_node.ng_including_document_reference = DocumentReference()
261
            testcase_node.set_field_value(
262
                field_name="STATEMENT",
263
                form_field_index=0,
264
                value=SDocNodeField(
265
                    parent=file_section,
266
                    field_name="STATEMENT",
267
                    parts=[summary_table],
268
                    multiline__="True",
269
                ),
270
            )
271
            file_section.section_contents.insert(0, testcase_node)
272
            document_stats.total_number_of_covered_functions += (
273
                stats.total_number_of_covered_functions
274
            )
275
            document_stats.total_number_of_non_covered_functions += (
276
                stats.total_number_of_non_covered_functions
277
            )
278
 
279
        summary_table = f"""\
280
.. list-table:: Coverage summary
281
    :widths: 25 10
282
    :header-rows: 0
283
 
284
    * - **Total number of files :**
285
      - {len(json_files)}
286
    * - **Total covered functions:**
287
      - {document_stats.total_number_of_covered_functions}
288
    * - **Total non-covered functions:**
289
      - {document_stats.total_number_of_non_covered_functions}
290
        """
291
 
292
        testcase_node = SDocNode(
293
            parent=document,
294
            node_type="TEXT",
295
            fields=[],
296
            relations=[],
297
        )
298
        testcase_node.ng_document_reference = DocumentReference()
299
        testcase_node.ng_document_reference.set_document(document)
300
        testcase_node.ng_including_document_reference = DocumentReference()
301
        testcase_node.set_field_value(
302
            field_name="STATEMENT",
303
            form_field_index=0,
304
            value=SDocNodeField(
305
                parent=testcase_node,
306
                field_name="STATEMENT",
307
                parts=[summary_table],
308
                multiline__="True",
309
            ),
310
        )
311
        document.section_contents.insert(0, testcase_node)
312
        document_stats.total_number_of_covered_functions += (
313
            stats.total_number_of_covered_functions
314
        )
315
        document_stats.total_number_of_non_covered_functions += (
316
            stats.total_number_of_non_covered_functions
317
        )
318
        return document