Coverage for pyEDAA/UCIS/UCDB.py: 90%
96 statements
« prev ^ index » next coverage.py v7.6.5, created at 2024-11-15 01:09 +0000
« prev ^ index » next coverage.py v7.6.5, created at 2024-11-15 01:09 +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
39from lxml import etree
40from pyTooling.Decorators import export
42from pyEDAA.UCIS.Cobertura import Class, Coverage, Package
45UCDB_EXCLUDE_PRAGMA = 0x00000020
46UCDB_EXCLUDE_FILE = 0x00000040
47UCDB_EXCLUDE_INST = 0x00000080
48UCDB_EXCLUDE_AUTO = 0x00000100
50UCDB_EXCLUDED = (
51 UCDB_EXCLUDE_FILE | UCDB_EXCLUDE_PRAGMA | UCDB_EXCLUDE_INST | UCDB_EXCLUDE_AUTO
52)
54StatementData = namedtuple(
55 "StatementData",
56 ["file", "line", "index", "instance", "hits"],
57)
60@export
61class UcdbParserException(Exception):
62 """Base-class for other UCDB Parser exceptions"""
65@export
66class InternalErrorOccurred(UcdbParserException):
67 """Raised when internal error occurred"""
70@export
71class Parser:
72 _mergeInstances: bool
73 _tree: etree._ElementTree
74 _nsmap: Dict
75 _coverage: Coverage
76 statementsCount: int
77 statementsCovered: int
79 def __init__(self, ucdbFile: Path, mergeInstances: bool):
80 self._mergeInstances = mergeInstances
82 with ucdbFile.open("r") as filename:
83 self._tree = etree.parse(filename)
85 self._nsmap = {
86 k: v for (k, v) in self._tree.getroot().nsmap.items() if k is not None
87 }
89 self._coverage = Coverage()
91 self.statementsCount = 0
92 self.statementsCovered = 0
94 def getCoberturaModel(self) -> Coverage:
95 self._parseStatementCoverage()
97 return self._coverage
99 def _groupByIndex(self, statements: List[StatementData]) -> List[StatementData]:
100 groupedStmts = []
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))
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 )
116 return groupedStmts
118 def _parseStatementCoverage(self) -> None:
119 scopes = self._tree.xpath(
120 "/ux:ucdb/ux:scope[.//ux:bin[@type='STMTBIN']]", namespaces=self._nsmap
121 )
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__}'.")
126 nodes: List[etree._Element] = []
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__}'.")
132 typeName = scopeNode.get("type")
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.")
137 if typeName.startswith("DU_"):
138 continue
140 statementBins = scopeNode.xpath(".//ux:bin[@type='STMTBIN']", namespaces=self._nsmap)
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__}'.")
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__}'.")
149 nodes.append(statementBin)
151 statements: DefaultDict[str, DefaultDict[int, List]] = defaultdict(lambda: defaultdict(list))
153 for node in nodes:
154 workdir, stmtData = self._parseStatementNode(node)
155 self._coverage.addSource(workdir)
157 flags = node.get("flags")
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.")
162 if int(flags, 16) & UCDB_EXCLUDED:
163 _ = statements[stmtData.file]
164 continue
166 statements[stmtData.file][stmtData.line].append(stmtData)
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)
174 for line, lineStmts in lines.items():
175 if self._mergeInstances:
176 lineStmts = self._groupByIndex(lineStmts)
178 self.statementsCount += len(lineStmts)
180 covered = len(list(filter(attrgetter("hits"), lineStmts)))
181 hit = int(covered == len(lineStmts))
183 self.statementsCovered += covered
185 coberturaClass.addStatement(line, hit)
187 def _parseStatementNode(self, node) -> Tuple[str, StatementData]:
188 srcNode = node.find("./ux:src", namespaces=self._nsmap)
189 workdir = srcNode.get("workdir")
191 instancePath = ".".join(
192 (scope.get("name") for scope in node.iterancestors("{*}scope"))
193 )
195 stmtIndex = int(
196 node.find("./ux:attr[@key='#SINDEX#']", namespaces=self._nsmap).text
197 )
199 count = int(node.find("./ux:count", namespaces=self._nsmap).text)
201 stmtData = StatementData(
202 file=srcNode.get("file"),
203 line=int(srcNode.get("line")),
204 index=stmtIndex,
205 instance=instancePath,
206 hits=count,
207 )
209 return workdir, stmtData