Coverage for pyEDAA/UCIS/UCDB.py: 92%

97 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-19 00:50 +0000

1# ==================================================================================================================== # 

2# _____ ____ _ _ _ _ ____ ___ ____ # 

3# _ __ _ _| ____| _ \ / \ / \ | | | |/ ___|_ _/ ___| # 

4# | '_ \| | | | _| | | | |/ _ \ / _ \ | | | | | | |\___ \ # 

5# | |_) | |_| | |___| |_| / ___ \ / ___ \ | |_| | |___ | | ___) | # 

6# | .__/ \__, |_____|____/_/ \_\/_/ \_(_)___/ \____|___|____/ # 

7# |_| |___/ # 

8# ==================================================================================================================== # 

9# Authors: # 

10# Artur Porebski (Aldec Inc.) # 

11# Michal Pacula (Aldec Inc.) # 

12# # 

13# License: # 

14# ==================================================================================================================== # 

15# Copyright 2021-2022 Electronic Design Automation Abstraction (EDA²) # 

16# # 

17# Licensed under the Apache License, Version 2.0 (the "License"); # 

18# you may not use this file except in compliance with the License. # 

19# You may obtain a copy of the License at # 

20# # 

21# http://www.apache.org/licenses/LICENSE-2.0 # 

22# # 

23# Unless required by applicable law or agreed to in writing, software # 

24# distributed under the License is distributed on an "AS IS" BASIS, # 

25# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

26# See the License for the specific language governing permissions and # 

27# limitations under the License. # 

28# # 

29# SPDX-License-Identifier: Apache-2.0 # 

30# ==================================================================================================================== # 

31# 

32"""Data model of the UCDB format.""" 

33from collections import namedtuple, defaultdict 

34from itertools import groupby 

35from operator import attrgetter 

36from pathlib import Path 

37from typing import List, Tuple, Dict, DefaultDict 

38 

39from lxml import etree 

40from pyTooling.Decorators import export 

41 

42from pyEDAA.UCIS.Cobertura import Class, Coverage, Package 

43 

44 

45UCDB_EXCLUDE_PRAGMA = 0x00000020 

46UCDB_EXCLUDE_FILE = 0x00000040 

47UCDB_EXCLUDE_INST = 0x00000080 

48UCDB_EXCLUDE_AUTO = 0x00000100 

49 

50UCDB_EXCLUDED = ( 

51 UCDB_EXCLUDE_FILE | UCDB_EXCLUDE_PRAGMA | UCDB_EXCLUDE_INST | UCDB_EXCLUDE_AUTO 

52) 

53 

54StatementData = namedtuple( 

55 "StatementData", 

56 ["file", "line", "index", "instance", "hits"], 

57) 

58 

59 

60@export 

61class UcdbParserException(Exception): 

62 """Base-class for other UCDB Parser exceptions""" 

63 

64 

65@export 

66class InternalErrorOccurred(UcdbParserException): 

67 """Raised when internal error occurred""" 

68 

69 

70@export 

71class Parser: 

72 _mergeInstances: bool 

73 _tree: etree._ElementTree 

74 _nsmap: Dict 

75 _coverage: Coverage 

76 statementsCount: int 

77 statementsCovered: int 

78 

79 def __init__(self, ucdbFile: Path, mergeInstances: bool): 

80 self._mergeInstances = mergeInstances 

81 

82 with ucdbFile.open("r") as filename: 

83 self._tree = etree.parse(filename) 

84 

85 self._nsmap = { 

86 k: v for (k, v) in self._tree.getroot().nsmap.items() if k is not None 

87 } 

88 

89 self._coverage = Coverage() 

90 

91 self.statementsCount = 0 

92 self.statementsCovered = 0 

93 

94 def getCoberturaModel(self) -> Coverage: 

95 self._parseStatementCoverage() 

96 

97 return self._coverage 

98 

99 def _groupByIndex(self, statements: List[StatementData]) -> List[StatementData]: 

100 groupedStmts = [] 

101 

102 sortedStmts = sorted(statements, key=attrgetter("index")) 

103 for index, stmts in groupby(sortedStmts, key=attrgetter("index")): 

104 hit = any((stmt.hits for stmt in stmts)) 

105 

106 groupedStmts.append( 

107 StatementData( 

108 file=statements[0].file, 

109 line=statements[0].line, 

110 index=index, 

111 instance="", 

112 hits=hit, 

113 ) 

114 ) 

115 

116 return groupedStmts 

117 

118 def _parseStatementCoverage(self) -> None: 

119 scopes = self._tree.xpath( 

120 "/ux:ucdb/ux:scope[.//ux:bin[@type='STMTBIN']]", namespaces=self._nsmap 

121 ) 

122 

123 if not isinstance(scopes, list): 123 ↛ 124line 123 didn't jump to line 124, because the condition on line 123 was never true

124 raise InternalErrorOccurred(f"Unexpected type: '{scopes.__class__.__name__}'.") 

125 

126 nodes: List[etree._Element] = [] 

127 

128 for scopeNode in scopes: 

129 if not isinstance(scopeNode, etree._Element): 129 ↛ 130line 129 didn't jump to line 130, because the condition on line 129 was never true

130 raise InternalErrorOccurred(f"Unexpected type: '{scopeNode.__class__.__name__}'.") 

131 

132 typeName = scopeNode.get("type") 

133 

134 if typeName is None: 134 ↛ 135line 134 didn't jump to line 135, because the condition on line 134 was never true

135 raise InternalErrorOccurred("Unexpected 'None' value.") 

136 

137 if typeName.startswith("DU_"): 

138 continue 

139 

140 statementBins = scopeNode.xpath(".//ux:bin[@type='STMTBIN']", namespaces=self._nsmap) 

141 

142 if not isinstance(statementBins, list): 142 ↛ 143line 142 didn't jump to line 143, because the condition on line 142 was never true

143 raise InternalErrorOccurred(f"Unexpected type: '{statementBins.__class__.__name__}'.") 

144 

145 for statementBin in statementBins: 

146 if not isinstance(statementBin, etree._Element): 146 ↛ 147line 146 didn't jump to line 147, because the condition on line 146 was never true

147 raise InternalErrorOccurred(f"Unexpected type: '{statementBin.__class__.__name__}'.") 

148 

149 nodes.append(statementBin) 

150 

151 statements: DefaultDict[str, DefaultDict[int, List]] = defaultdict(lambda: defaultdict(list)) 

152 

153 for node in nodes: 

154 workdir, stmtData = self._parseStatementNode(node) 

155 self._coverage.addSource(workdir) 

156 

157 flags = node.get("flags") 

158 

159 if flags is None: 159 ↛ 160line 159 didn't jump to line 160, because the condition on line 159 was never true

160 raise InternalErrorOccurred("Unexpected 'None' value.") 

161 

162 if int(flags, 16) & UCDB_EXCLUDED: 

163 _ = statements[stmtData.file] 

164 continue 

165 

166 statements[stmtData.file][stmtData.line].append(stmtData) 

167 

168 for file, lines in statements.items(): 

169 package = Package(file) 

170 coberturaClass = Class(file, file) 

171 package.addClass(coberturaClass) 

172 self._coverage.addPackage(package) 

173 

174 for line, lineStmts in lines.items(): 

175 if self._mergeInstances: 

176 lineStmts = self._groupByIndex(lineStmts) 

177 

178 self.statementsCount += len(lineStmts) 

179 

180 covered = len(list(filter(attrgetter("hits"), lineStmts))) 

181 hit = int(covered == len(lineStmts)) 

182 

183 self.statementsCovered += covered 

184 

185 coberturaClass.addStatement(line, hit) 

186 

187 def _parseStatementNode(self, node) -> Tuple[str, StatementData]: 

188 srcNode = node.find("./ux:src", namespaces=self._nsmap) 

189 workdir = srcNode.get("workdir") 

190 

191 instancePath = ".".join( 

192 (scope.get("name") for scope in node.iterancestors("{*}scope")) 

193 ) 

194 

195 stmtIndex = int( 

196 node.find("./ux:attr[@key='#SINDEX#']", namespaces=self._nsmap).text 

197 ) 

198 

199 count = int(node.find("./ux:count", namespaces=self._nsmap).text) 

200 

201 stmtData = StatementData( 

202 file=srcNode.get("file"), 

203 line=int(srcNode.get("line")), 

204 index=stmtIndex, 

205 instance=instancePath, 

206 hits=count, 

207 ) 

208 

209 return workdir, stmtData