Skip to content

Commit

Permalink
v0.2.0
Browse files Browse the repository at this point in the history
# New Features

* Added support for exclusion flags (provided by @por3bski)
* Added tests for new features and bugfixes (provided by @por3bski)
* Command line interface testing via mocking:
  Checked commands:
  * `pyedaa-ucis`
  * `pyedaa-ucis help`
  * `pyedaa-ucis help export`
  * `pyedaa-ucis help expand`
  * `pyedaa-ucis expand`
  * `pyedaa-ucis version`
  * `pyedaa-ucis export`
  * `pyedaa-ucis export --ucdb file1.xml --cobertura file2.xml`

# Changes

* Functions/variables names changed to camelCase (provided by @por3bski)

# Bug Fixes

* Fixed problem with multiple instances (provided by @por3bski)
* Fixed PyCharm project file (provided by @por3bski)
  • Loading branch information
umarcor authored Jan 31, 2022
2 parents 5b87566 + 7c368bb commit bf03e20
Show file tree
Hide file tree
Showing 11 changed files with 1,886 additions and 114 deletions.
2 changes: 1 addition & 1 deletion .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 23 additions & 6 deletions pyEDAA/UCIS/CLI/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@
from pathlib import Path
from textwrap import dedent

from pyAttributes.ArgParseAttributes import ArgParseMixin, DefaultAttribute, CommandAttribute, ArgumentAttribute
from pyAttributes.ArgParseAttributes import ArgParseMixin, DefaultAttribute, CommandAttribute, ArgumentAttribute, SwitchArgumentAttribute

from pyTooling.Decorators import export

from pyEDAA.UCIS import __version__, __copyright__, __license__
from pyEDAA.UCIS.UCDB import Parser
from pyEDAA.UCIS.Cobertura import CoberturaException


@export
Expand Down Expand Up @@ -125,6 +127,7 @@ def HandleVersion(self, _) -> None:
@CommandAttribute("export", help="Export data from UCDB.", description="Export data from UCDB.")
@ArgumentAttribute("--ucdb", metavar='UCDBFile', dest="ucdb", type=str, help="UCDB file in UCIS format (XML).")
@ArgumentAttribute("--cobertura", metavar='CoberturaFile', dest="cobertura", type=str, help="Cobertura code coverage file (XML).")
@SwitchArgumentAttribute("--merge-instances", dest="mergeInstances", help="Merge statement coverage data for all instances of the same design unit.")
def HandleExport(self, args) -> None:
"""Handle program calls with command ``export``."""
self._PrintHeadline()
Expand All @@ -151,18 +154,28 @@ def HandleExport(self, args) -> None:
print(f" IN -> UCIS (XML): {ucdbPath}")
print(f" OUT <- Cobertura (XML): {coberturaPath}")

parser = Parser(ucdbPath)
model = parser.get_cobertura_model()
parser = Parser(ucdbPath, args.mergeInstances)
model = parser.getCoberturaModel()

with coberturaPath.open('w') as file:
file.write(model.get_xml().decode("utf-8"))
file.write(model.getXml().decode("utf-8"))

print()

coverage = model.lines_covered / model.lines_valid * 100
try:
lineCoverage = model.linesCovered / model.linesValid * 100
except ZeroDivisionError:
lineCoverage = 100

try:
statementCoverage = parser.statementsCovered / parser.statementsCount * 100
except ZeroDivisionError:
statementCoverage = 100

print(dedent(f"""\
[DONE] Export and conversion complete.
Statement coverage: {coverage} %
Line coverage: {lineCoverage} %
Statement coverage: {statementCoverage} %
""")
)

Expand Down Expand Up @@ -207,6 +220,10 @@ def main():
print()
print(f"[ERROR] {ex}")
exit(1)
except CoberturaException as ex:
print()
print(f"[INTERNAL ERROR] {ex}")
exit(1)


if __name__ == "__main__":
Expand Down
223 changes: 138 additions & 85 deletions pyEDAA/UCIS/Cobertura.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,47 +45,79 @@
from pyTooling.Decorators import export


@export
class CoberturaException(Exception):
"""Base class for other Cobertura exceptions"""


@export
class DuplicatedLineNumber(CoberturaException):
"""Raised when statement with specified line number already exists in Cobertura class"""


@export
class DuplicatedClassName(CoberturaException):
"""Raised when class with specified name already exists in Cobertura package"""


@export
class DuplicatedPackageName(CoberturaException):
"""Raised when package with specified name already exists in Cobertura coverage"""


@export
class Class:
"""Represents a code element in the Cobertura coverage data model (Java-focused)."""

source_file: str
name: str
sourceFile: str
lines: Dict[int, int]
lines_valid: int
lines_covered: int
linesValid: int
linesCovered: int

def __init__(self, source_file: str):
self.source_file = source_file
def __init__(self, name: str, sourceFile: str):
self.name = name
self.sourceFile = sourceFile
self.lines = {}
self.lines_valid = 0
self.lines_covered = 0

def add_statement(self, line: int, count: int) -> None:
assert line not in self.lines.keys()
self.lines[line] = count
self.lines_valid += 1
if count:
self.lines_covered += 1

def get_xml_node(self) -> etree.Element:
class_node = etree.Element("class")
class_node.attrib["name"] = self.source_file
class_node.attrib["filename"] = self.source_file
class_node.attrib["complexity"] = "0"
class_node.attrib["branch-rate"] = "0"
class_node.attrib["line-rate"] = str(self.lines_covered / self.lines_valid)
class_node.append(etree.Element("methods"))
lines_node = etree.SubElement(class_node, "lines")
self.linesValid = 0
self.linesCovered = 0

def addStatement(self, line: int, hits: int) -> None:
if line in self.lines.keys():
raise DuplicatedLineNumber(f"Duplicated line number: {line}")

self.lines[line] = hits
self.linesValid += 1

if hits:
self.linesCovered += 1

def getXmlNode(self) -> etree._Element:
classNode = etree.Element("class")
classNode.attrib["name"] = self.sourceFile
classNode.attrib["filename"] = self.sourceFile
classNode.attrib["complexity"] = "0"
classNode.attrib["branch-rate"] = "0"

try:
rate = self.linesCovered / self.linesValid
except ZeroDivisionError:
rate = 1.0

classNode.attrib["line-rate"] = f"{rate:.16g}"

classNode.append(etree.Element("methods"))
linesNode = etree.SubElement(classNode, "lines")

for line in self.lines:
etree.SubElement(
lines_node,
linesNode,
"line",
number=str(line),
hits=str(self.lines[line]),
)

return class_node
return classNode


@export
Expand All @@ -94,40 +126,49 @@ class Package:

name: str
classes: Dict[str, Class]
lines_valid: int
lines_covered: int
linesValid: int
linesCovered: int

def __init__(self, name: str):
self.name = name
self.classes = {}
self.lines_valid = 0
self.lines_covered = 0
self.linesValid = 0
self.linesCovered = 0

def add_statement(self, class_name: str, source_file: str, line: int, count: int) -> None:
try:
self.classes[class_name].add_statement(line, count)
except KeyError:
self.classes[class_name] = Class(source_file)
self.classes.get(class_name).add_statement(line, count)
def addClass(self, coberturaClass: Class):
if coberturaClass.name in self.classes:
raise DuplicatedClassName(f"Duplicated class name: '{coberturaClass.name}'.")

self.classes[coberturaClass.name] = coberturaClass

def refreshStatistics(self) -> None:
self.linesValid = 0
self.linesCovered = 0

for coberturaClass in self.classes.values():
self.linesCovered += coberturaClass.linesCovered
self.linesValid += coberturaClass.linesValid

def getXmlNode(self) -> etree._Element:
classesNode = etree.Element("classes")
packageNode = etree.Element("package")
packageNode.attrib["name"] = self.name
packageNode.attrib["complexity"] = "0"
packageNode.attrib["branch-rate"] = "0"

self.lines_valid += 1
try:
rate = self.linesCovered / self.linesValid
except ZeroDivisionError:
rate = 1.0

if count:
self.lines_covered += 1
packageNode.attrib["line-rate"] = f"{rate:.16g}"

def get_xml_node(self) -> etree.Element:
classes_node = etree.Element("classes")
package_node = etree.Element("package")
package_node.append(classes_node)
package_node.attrib["name"] = self.name
package_node.attrib["complexity"] = "0"
package_node.attrib["branch-rate"] = "0"
package_node.attrib["line-rate"] = str(self.lines_covered / self.lines_valid)
packageNode.append(classesNode)

for (class_name, class_data) in self.classes.items():
classes_node.append(class_data.get_xml_node())
for coberturaClass in self.classes.values():
classesNode.append(coberturaClass.getXmlNode())

return package_node
return packageNode


@export
Expand All @@ -136,56 +177,68 @@ class Coverage:

sources: Set
packages: Dict[str, Package]
lines_valid: int
lines_covered: int
linesValid: int
linesCovered: int

def __init__(self):
self.sources = set()
self.packages = {}
self.lines_valid = 0
self.lines_covered = 0

def add_statement(self, source: str, file: str, line: int, count: int) -> None:
try:
self.packages[source].add_statement(file, file, line, count)
except KeyError:
self.packages[source] = Package(file)
self.packages.get(source).add_statement(file, file, line, count)
self.linesValid = 0
self.linesCovered = 0

def addSource(self, source: str) -> None:
self.sources.add(source)

self.lines_valid += 1
def addPackage(self, package: Package) -> None:
if package.name in self.packages:
raise DuplicatedPackageName(f"Duplicated package name: '{package.name}'.")

self.packages[package.name] = package

def refreshStatistics(self) -> None:
self.linesValid = 0
self.linesCovered = 0

if count:
self.lines_covered += 1
for package in self.packages.values():
package.refreshStatistics()
self.linesCovered += package.linesCovered
self.linesValid += package.linesValid

def add_branch(self) -> None:
pass
def getXml(self) -> bytes:
self.refreshStatistics()

def get_xml(self) -> etree.Element:
coverage_node = etree.Element("coverage")
coverage_node.attrib["version"] = "5.5"
coverage_node.attrib["timestamp"] = str(int(time()))
coverage_node.attrib["branches-valid"] = "0"
coverage_node.attrib["branches-covered"] = "0"
coverage_node.attrib["branch-rate"] = "0"
coverage_node.attrib["complexity"] = "0"
coverage_node.attrib["lines-valid"] = str(self.lines_valid)
coverage_node.attrib["lines-covered"] = str(self.lines_covered)
coverage_node.attrib["line-rate"] = str(self.lines_covered / self.lines_valid)
coverageNode = etree.Element("coverage")
coverageNode.attrib["version"] = "5.5"
coverageNode.attrib["timestamp"] = str(int(time()))
coverageNode.attrib["branches-valid"] = "0"
coverageNode.attrib["branches-covered"] = "0"
coverageNode.attrib["branch-rate"] = "0"
coverageNode.attrib["complexity"] = "0"

sources_node = etree.Element("sources")
sourcesNode = etree.Element("sources")

for source in self.sources:
etree.SubElement(sources_node, "source").text = source
etree.SubElement(sourcesNode, "source").text = source

coverage_node.append(sources_node)
coverageNode.append(sourcesNode)

packages_node = etree.Element("packages")
packagesNode = etree.Element("packages")

for package_name in self.packages:
packages_node.append(self.packages[package_name].get_xml_node())
for package in self.packages.values():
packagesNode.append(package.getXmlNode())

coverageNode.append(packagesNode)

coverageNode.attrib["lines-valid"] = str(self.linesValid)
coverageNode.attrib["lines-covered"] = str(self.linesCovered)

try:
rate = self.linesCovered / self.linesValid
except ZeroDivisionError:
rate = 1.0

coverage_node.append(packages_node)
coverageNode.attrib["line-rate"] = f"{rate:.16g}"

return etree.tostring(coverage_node, pretty_print=True, encoding="utf-8", xml_declaration=True)
return etree.tostring(
coverageNode, pretty_print=True, encoding="utf-8", xml_declaration=True
)
Loading

0 comments on commit bf03e20

Please sign in to comment.