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

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 

35 

36from pyTooling.Decorators import export, readonly 

37from pyTooling.Configuration.JSON import Configuration 

38 

39from sphinx_reports.Common import ReportExtensionError 

40from sphinx_reports.DataModel.CodeCoverage import PackageCoverage, ModuleCoverage, Coverage 

41 

42 

43@export 

44class CodeCoverageError(ReportExtensionError): 

45 pass 

46 

47 

48@export 

49class Analyzer: 

50 """ 

51 An analyzer to read and transform code coverage data from JSON format to a generic data model. 

52 

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 """ 

56 

57 _packageName: str 

58 _coverageReport: Configuration 

59 

60 def __init__(self, packageName: str, jsonCoverageFile: Path) -> None: 

61 """ 

62 Read a JSON file containing code coverage metrics generated by Coverage.py. 

63 

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) 

70 

71 self._packageName = packageName 

72 self._coverageReport = Configuration(jsonCoverageFile) 

73 

74 # if int(self._coverageReport["format"]) != 2: 

75 # raise CodeCoverageError(f"File format of '{jsonCoverageFile}' is not supported.") 

76 

77 @readonly 

78 def PackageName(self) -> str: 

79 """ 

80 Read-only property to access the analyzed package's name. 

81 

82 :return: Name of the analyzed package. 

83 """ 

84 return self._packageName 

85 

86 @readonly 

87 def JSONCoverageFile(self) -> Path: 

88 """ 

89 Read-only property to access the parsed JSON file. 

90 

91 :return: Path to the parsed JSON file. 

92 """ 

93 return self._coverageReport.ConfigFile 

94 

95 @readonly 

96 def CoverageReport(self) -> Configuration: 

97 return self._coverageReport 

98 

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}'") 

105 

106 def _convert_v3(self) -> PackageCoverage: 

107 rootPackageCoverage = PackageCoverage(self._packageName, Path("__init__.py")) 

108 

109 for fileRecord in self._coverageReport["files"]: 

110 moduleFile = Path(fileRecord.Key) 

111 coverageSummary = fileRecord["summary"] 

112 

113 moduleName = moduleFile.stem 

114 modulePath = moduleFile.parent.parts[1:] 

115 

116 currentCoverageObject: Coverage = rootPackageCoverage 

117 for packageName in modulePath: 

118 try: 

119 currentCoverageObject = currentCoverageObject[packageName] 

120 except KeyError: 

121 currentCoverageObject = PackageCoverage(packageName, moduleFile, currentCoverageObject) 

122 

123 if moduleName != "__init__": 

124 currentCoverageObject = ModuleCoverage(moduleName, moduleFile, currentCoverageObject) 

125 

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"]) 

130 

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"]) 

135 

136 currentCoverageObject._coverage = float(coverageSummary["percent_covered"]) / 100.0 

137 

138 return rootPackageCoverage