Coverage for pyEDAA/UCIS/Cobertura.py: 96%
133 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 01:05 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 01:05 +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"""
33Data model of the Cobertura format.
35.. mermaid::
37 flowchart LR
38 Coverage --> Package --> Class --> Statement
40"""
41from time import time
42from typing import Dict, Set
44from lxml import etree
45from pyTooling.Decorators import export
48@export
49class CoberturaException(Exception):
50 """Base-class for other Cobertura exceptions"""
53@export
54class DuplicatedLineNumber(CoberturaException):
55 """Raised when statement with specified line number already exists in Cobertura class"""
58@export
59class DuplicatedClassName(CoberturaException):
60 """Raised when class with specified name already exists in Cobertura package"""
63@export
64class DuplicatedPackageName(CoberturaException):
65 """Raised when package with specified name already exists in Cobertura coverage"""
68@export
69class Class:
70 """Represents a code element in the Cobertura coverage data model (Java-focused)."""
72 name: str
73 sourceFile: str
74 lines: Dict[int, int]
75 linesValid: int
76 linesCovered: int
78 def __init__(self, name: str, sourceFile: str):
79 self.name = name
80 self.sourceFile = sourceFile
81 self.lines = {}
82 self.linesValid = 0
83 self.linesCovered = 0
85 def addStatement(self, line: int, hits: int) -> None:
86 if line in self.lines.keys(): 86 ↛ 87line 86 didn't jump to line 87 because the condition on line 86 was never true
87 raise DuplicatedLineNumber(f"Duplicated line number: {line}")
89 self.lines[line] = hits
90 self.linesValid += 1
92 if hits:
93 self.linesCovered += 1
95 def getXmlNode(self) -> etree._Element:
96 classNode = etree.Element("class")
97 classNode.attrib["name"] = self.sourceFile
98 classNode.attrib["filename"] = self.sourceFile
99 classNode.attrib["complexity"] = "0"
100 classNode.attrib["branch-rate"] = "0"
102 try:
103 rate = self.linesCovered / self.linesValid
104 except ZeroDivisionError:
105 rate = 1.0
107 classNode.attrib["line-rate"] = f"{rate:.16g}"
109 classNode.append(etree.Element("methods"))
110 linesNode = etree.SubElement(classNode, "lines")
112 for line in self.lines:
113 etree.SubElement(
114 linesNode,
115 "line",
116 number=str(line),
117 hits=str(self.lines[line]),
118 )
120 return classNode
123@export
124class Package:
125 """Represents a grouping element in the Cobertura coverage data model (Java-focused)."""
127 name: str
128 classes: Dict[str, Class]
129 linesValid: int
130 linesCovered: int
132 def __init__(self, name: str):
133 self.name = name
134 self.classes = {}
135 self.linesValid = 0
136 self.linesCovered = 0
138 def addClass(self, coberturaClass: Class):
139 if coberturaClass.name in self.classes: 139 ↛ 140line 139 didn't jump to line 140 because the condition on line 139 was never true
140 raise DuplicatedClassName(f"Duplicated class name: '{coberturaClass.name}'.")
142 self.classes[coberturaClass.name] = coberturaClass
144 def refreshStatistics(self) -> None:
145 self.linesValid = 0
146 self.linesCovered = 0
148 for coberturaClass in self.classes.values():
149 self.linesCovered += coberturaClass.linesCovered
150 self.linesValid += coberturaClass.linesValid
152 def getXmlNode(self) -> etree._Element:
153 classesNode = etree.Element("classes")
154 packageNode = etree.Element("package")
155 packageNode.attrib["name"] = self.name
156 packageNode.attrib["complexity"] = "0"
157 packageNode.attrib["branch-rate"] = "0"
159 try:
160 rate = self.linesCovered / self.linesValid
161 except ZeroDivisionError:
162 rate = 1.0
164 packageNode.attrib["line-rate"] = f"{rate:.16g}"
166 packageNode.append(classesNode)
168 for coberturaClass in self.classes.values():
169 classesNode.append(coberturaClass.getXmlNode())
171 return packageNode
174@export
175class Coverage:
176 """Represents the root element in the Cobertura coverage data model (Java-focused)."""
178 sources: Set
179 packages: Dict[str, Package]
180 linesValid: int
181 linesCovered: int
183 def __init__(self):
184 self.sources = set()
185 self.packages = {}
186 self.linesValid = 0
187 self.linesCovered = 0
189 def addSource(self, source: str) -> None:
190 self.sources.add(source)
192 def addPackage(self, package: Package) -> None:
193 if package.name in self.packages: 193 ↛ 194line 193 didn't jump to line 194 because the condition on line 193 was never true
194 raise DuplicatedPackageName(f"Duplicated package name: '{package.name}'.")
196 self.packages[package.name] = package
198 def refreshStatistics(self) -> None:
199 self.linesValid = 0
200 self.linesCovered = 0
202 for package in self.packages.values():
203 package.refreshStatistics()
204 self.linesCovered += package.linesCovered
205 self.linesValid += package.linesValid
207 def getXml(self) -> bytes:
208 self.refreshStatistics()
210 coverageNode = etree.Element("coverage")
211 coverageNode.attrib["version"] = "5.5"
212 coverageNode.attrib["timestamp"] = str(int(time()))
213 coverageNode.attrib["branches-valid"] = "0"
214 coverageNode.attrib["branches-covered"] = "0"
215 coverageNode.attrib["branch-rate"] = "0"
216 coverageNode.attrib["complexity"] = "0"
218 sourcesNode = etree.Element("sources")
220 for source in self.sources:
221 etree.SubElement(sourcesNode, "source").text = source
223 coverageNode.append(sourcesNode)
225 packagesNode = etree.Element("packages")
227 for package in self.packages.values():
228 packagesNode.append(package.getXmlNode())
230 coverageNode.append(packagesNode)
232 coverageNode.attrib["lines-valid"] = str(self.linesValid)
233 coverageNode.attrib["lines-covered"] = str(self.linesCovered)
235 try:
236 rate = self.linesCovered / self.linesValid
237 except ZeroDivisionError:
238 rate = 1.0
240 coverageNode.attrib["line-rate"] = f"{rate:.16g}"
242 return etree.tostring(
243 coverageNode, pretty_print=True, encoding="utf-8", xml_declaration=True
244 )