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