Coverage for sphinx_reports/Adapter/Coverage.py: 32%
56 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-09 22:12 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-09 22:12 +0000
1# ==================================================================================================================== #
2# _ _ _ #
3# ___ _ __ | |__ (_)_ __ __ __ _ __ ___ _ __ ___ _ __| |_ ___ #
4# / __| '_ \| '_ \| | '_ \\ \/ /____| '__/ _ \ '_ \ / _ \| '__| __/ __| #
5# \__ \ |_) | | | | | | | |> <_____| | | __/ |_) | (_) | | | |_\__ \ #
6# |___/ .__/|_| |_|_|_| |_/_/\_\ |_| \___| .__/ \___/|_| \__|___/ #
7# |_| |_| #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2023-2025 Patrick Lehmann - Bötzingen, Germany #
15# #
16# Licensed under the Apache License, Version 2.0 (the "License"); #
17# you may not use this file except in compliance with the License. #
18# You may obtain a copy of the License at #
19# #
20# http://www.apache.org/licenses/LICENSE-2.0 #
21# #
22# Unless required by applicable law or agreed to in writing, software #
23# distributed under the License is distributed on an "AS IS" BASIS, #
24# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
25# See the License for the specific language governing permissions and #
26# limitations under the License. #
27# #
28# SPDX-License-Identifier: Apache-2.0 #
29# ==================================================================================================================== #
30#
31"""
32**A Sphinx extension providing code coverage details embedded in documentation pages.**
33"""
34from pathlib import Path
36from pyTooling.Decorators import export, readonly
37from pyTooling.Configuration.JSON import Configuration
39from sphinx_reports.Common import ReportExtensionError
40from sphinx_reports.DataModel.CodeCoverage import PackageCoverage, ModuleCoverage, Coverage
43@export
44class CodeCoverageError(ReportExtensionError):
45 pass
48@export
49class Analyzer:
50 """
51 An analyzer to read and transform code coverage data from JSON format to a generic data model.
53 Coverage.py can provide collected statement and branch coverage metrics as JSON data, which can be converted to a
54 generic code coverage model.
55 """
57 _packageName: str
58 _coverageReport: Configuration
60 def __init__(self, packageName: str, jsonCoverageFile: Path) -> None:
61 """
62 Read a JSON file containing code coverage metrics generated by Coverage.py.
64 :param packageName: Name of the Python package that was analyzed.
65 :param jsonCoverageFile: JSON file containing statement and/or branch coverage.
66 :raises CodeCoverageError: If JSON file doesn't exist.
67 """
68 if not jsonCoverageFile.exists():
69 raise CodeCoverageError(f"JSON coverage report '{jsonCoverageFile}' not found.") from FileNotFoundError(jsonCoverageFile)
71 self._packageName = packageName
72 self._coverageReport = Configuration(jsonCoverageFile)
74 # if int(self._coverageReport["format"]) != 2:
75 # raise CodeCoverageError(f"File format of '{jsonCoverageFile}' is not supported.")
77 @readonly
78 def PackageName(self) -> str:
79 """
80 Read-only property to access the analyzed package's name.
82 :return: Name of the analyzed package.
83 """
84 return self._packageName
86 @readonly
87 def JSONCoverageFile(self) -> Path:
88 """
89 Read-only property to access the parsed JSON file.
91 :return: Path to the parsed JSON file.
92 """
93 return self._coverageReport.ConfigFile
95 @readonly
96 def CoverageReport(self) -> Configuration:
97 return self._coverageReport
99 def Convert(self) -> PackageCoverage:
100 meta = self._coverageReport["meta"]
101 if (version := meta["format"]) == "3":
102 return self._convert_v3()
103 else:
104 raise CodeCoverageError(f"Unsupported coverage format version '{version}'")
106 def _convert_v3(self) -> PackageCoverage:
107 rootPackageCoverage = PackageCoverage(self._packageName, Path("__init__.py"))
109 for fileRecord in self._coverageReport["files"]:
110 moduleFile = Path(fileRecord.Key)
111 coverageSummary = fileRecord["summary"]
113 moduleName = moduleFile.stem
114 modulePath = moduleFile.parent.parts[1:]
116 currentCoverageObject: Coverage = rootPackageCoverage
117 for packageName in modulePath:
118 try:
119 currentCoverageObject = currentCoverageObject[packageName]
120 except KeyError:
121 currentCoverageObject = PackageCoverage(packageName, moduleFile, currentCoverageObject)
123 if moduleName != "__init__":
124 currentCoverageObject = ModuleCoverage(moduleName, moduleFile, currentCoverageObject)
126 currentCoverageObject._totalStatements = int(coverageSummary["num_statements"])
127 currentCoverageObject._excludedStatements = int(coverageSummary["excluded_lines"])
128 currentCoverageObject._coveredStatements = int(coverageSummary["covered_lines"])
129 currentCoverageObject._missingStatements = int(coverageSummary["missing_lines"])
131 currentCoverageObject._totalBranches = int(coverageSummary["num_branches"])
132 currentCoverageObject._coveredBranches = int(coverageSummary["covered_branches"])
133 currentCoverageObject._partialBranches = int(coverageSummary["num_partial_branches"])
134 currentCoverageObject._missingBranches = int(coverageSummary["missing_branches"])
136 currentCoverageObject._coverage = float(coverageSummary["percent_covered"]) / 100.0
138 return rootPackageCoverage