diff options
author | Kevin Rocard <kevinx.rocard@intel.com> | 2013-06-10 19:05:46 +0200 |
---|---|---|
committer | David Wagner <david.wagner@intel.com> | 2014-02-12 17:04:02 +0100 |
commit | 1dff2e64afb47af468cc211efa229627367789b7 (patch) | |
tree | ae67109745402189b765d1ca70fac1ea4bec575c /tools/coverage | |
parent | 02726ec37a9686572f825520a04700061ffe5d06 (diff) | |
download | external_parameter-framework-1dff2e64afb47af468cc211efa229627367789b7.zip external_parameter-framework-1dff2e64afb47af468cc211efa229627367789b7.tar.gz external_parameter-framework-1dff2e64afb47af468cc211efa229627367789b7.tar.bz2 |
[coverage] Move coverage generator in it's own folder
BZ: 115218
The coverage report generated script was in the tools folder.
Other files will be used for coverage reports.
Create a coverage folder in order to contain all coverage report
utilities.
Change-Id: I23ed75fb109cd4a5f6804398ef17d99e046fd49d
Signed-off-by: Kevin Rocard <kevinx.rocard@intel.com>
Reviewed-on: http://android.intel.com:8080/114667
Reviewed-by: Denneulin, Guillaume <guillaume.denneulin@intel.com>
Reviewed-by: Gonzalve, Sebastien <sebastien.gonzalve@intel.com>
Reviewed-by: cactus <cactus@intel.com>
Tested-by: Dixon, CharlesX <charlesx.dixon@intel.com>
Reviewed-by: buildbot <buildbot@intel.com>
Tested-by: buildbot <buildbot@intel.com>
Diffstat (limited to 'tools/coverage')
-rwxr-xr-x | tools/coverage/coverage.py | 1048 |
1 files changed, 1048 insertions, 0 deletions
diff --git a/tools/coverage/coverage.py b/tools/coverage/coverage.py new file mode 100755 index 0000000..df79b5b --- /dev/null +++ b/tools/coverage/coverage.py @@ -0,0 +1,1048 @@ +#!/usr/bin/env python3 + +# INTEL CONFIDENTIAL +# Copyright 2013 Intel +# Corporation All Rights Reserved. +# +# The source code contained or described herein and all documents related to +# the source code ("Material") are owned by Intel Corporation or its suppliers +# or licensors. Title to the Material remains with Intel Corporation or its +# suppliers and licensors. The Material contains trade secrets and proprietary +# and confidential information of Intel or its suppliers and licensors. The +# Material is protected by worldwide copyright and trade secret laws and +# treaty provisions. No part of the Material may be used, copied, reproduced, +# modified, published, uploaded, posted, transmitted, distributed, or +# disclosed in any way without Intels prior express written permission. +# +# No license under any patent, copyright, trade secret or other intellectual +# property right is granted to or conferred upon you by disclosure or delivery +# of the Materials, either expressly, by implication, inducement, estoppel or +# otherwise. Any license under such intellectual property rights must be +# express and approved by Intel in writing. + +""" +Generate a coverage report by parsing parameter framework log. + +The coverage report contains the: + - domain + - configuration + - rule + - criterion +basic coverage statistics. +""" + +import xml.dom.minidom +import sys +import re +import logging + +FORMAT = '%(levelname)s: %(message)s' +logging.basicConfig(stream=sys.stderr, level=logging.WARNING, format=FORMAT) +logger = logging.getLogger("Coverage") + +class CustomError(Exception): + pass + +class ChildError(CustomError): + def __init__(self, parent, child): + self.parent = parent + self.child = child + +class ChildNotFoundError(ChildError): + def __str__(self): + return "Unable to find the child %s in %s" % (self.child, self.parent) + +class DuplicatedChildError(ChildError): + def __str__(self): + return "Add existing child %s in %s." % (self.child, self.parent) + +class Element(): + """Root class for all coverage elements""" + tag = "element" + + def __init__(self, name): + + self.parent = None + self.children = [] + + self.nbUse = 0 + + self.name = name + + self.debug("New element") + + + def __str__(self): + return "%s (%s)" % (self.name, self.tag) + + def __eq__(self, compared): + return (self.name == compared.name) and (self.children == compared.children) + + def getName(self, default=""): + return self.name or default + + def hasChildren(self): + return bool(self.children) + + def getChildren(self): + return self.children + + def _getDescendants(self): + for child in self.children: + yield child + for descendant in child._getDescendants() : + yield descendant + + def getChildFromName(self, childName): + + for child in self.children : + + if child.getName() == childName : + return child + + self.debug("Child %s not found" % childName, logging.ERROR) + + self.debug("Child list :") + + for child in self.children : + self.debug(" - %s" % child) + + raise ChildNotFoundError(self, childName) + + + def addChild(self, child): + self.debug("new child: " + child.name) + self.children.append(child) + child._adoptedBy(self) + + def _adoptedBy(self, parent): + assert(not self.parent) + self.parent = parent + + def _getElementNames(self, elementList): + return (substate.name for substate in elementList) + + def _description(self, withCoverage, withNbUse): + description = self.name + + if withNbUse or withCoverage : + description += " has been used " + str(self.nbUse) + " time" + + if withCoverage : + description += self._coverageFormating(self._getCoverage()) + + return description + + + def _getCoverage(self): + """Return the coverage of the element between 0 and 1 + + If the element has no coverage dependency (usually child) return 0 or 1. + otherwise the element coverage is the dependency coverage average""" + coverageDependanceElements = list(self._getCoverageDependanceElements()) + + nbcoverageDependence = len(coverageDependanceElements) + + if nbcoverageDependence == 0: + if self.nbUse == 0: + return 0 + else: + return 1 + + coverageDependenceValues = (depElement._getCoverage() + for depElement in coverageDependanceElements) + + return sum(coverageDependenceValues) / nbcoverageDependence + + def _getCoverageDependanceElements(self): + return self.children + + def _coverageFormating(self, coverage): + # If no coverage provided + if not coverage : + return "" + + # Calculate coverage + return " (%s coverage)" % self._number2percent(coverage) + + @staticmethod + def _number2percent(number): + """Format a number to a integer % string + + example: _number2percent(0.6666) -> "67%" + """ + return "{0:.0f}%".format(100 * number) + + + def _dumpDescription(self, withCoverage, withNbUse): + + self.debug("yelding description") + yield RankedLine(self._description(withCoverage, withNbUse), lineSuffix="") + + for dumped in self._dumpPropagate(withCoverage, withNbUse) : + yield dumped + + def _dumpPropagate(self, withCoverage, withNbUse): + + for child in self.children : + for dumpedDescription in child._dumpDescription(withCoverage, withNbUse) : + yield dumpedDescription.increasedRank() + + + def dump(self, withCoverage=False, withNbUse=True): + + return "\n".join( + str(dumpedDescription) for dumpedDescription in + self._dumpDescription(withCoverage, withNbUse)) + + def exportToXML(self, domElement=None): + if domElement == None: + domElement = xml.dom.minidom.Element(self.tag) + + self._XMLaddAttributes(domElement) + + for child in self.children : + domElement.appendChild(child.exportToXML()) + + return domElement + + def _XMLaddAttributes(self, domElement): + attributes = self._getXMLAttributes() + + coverage = self._getCoverage() + if coverage != None : + attributes["Coverage"] = self._number2percent(coverage) + + for key, value in attributes.items(): + domElement.setAttribute(key, value) + + def _getXMLAttributes(self): + return { + "Name": self.name, + "NbUse": str(self.nbUse) + } + + def _incNbUse(self): + self.nbUse += 1 + + def childUsed(self, child): + self._incNbUse() + # Propagate to parent + self._tellParentThatChildUsed() + + def _tellParentThatChildUsed(self): + if self.parent : + self.parent.childUsed(self) + + + def parentUsed(self): + self._incNbUse() + # Propagate to children + for child in self.children : + child.parentUsed() + + def hasBeenUsed(self): + return self.nbUse > 0 + + def operationOnChild(self, path, operation): + + if path: + return self._operationPropagate(path, operation) + else : + self.debug("operating on self") + return operation(self) + + def _operationPropagate(self, path, operation): + + childName = path.pop(0) + child = self.getChildFromName(childName) + + return child.operationOnChild(path, operation) + + + + def debug(self, stringOrFunction, level=logging.DEBUG): + """Print a debug line on stderr in tree form + + If the debug line is expensive to generate, provide callable + object, it will be called if log is enable for this level. + This callable object should return the logline string. + """ + if logger.isEnabledFor(level): + + # TODO: use buildin callable if python >= 3.2 + if hasattr(stringOrFunction, "__call__"): + string = stringOrFunction() + else: + string = stringOrFunction + + rankedLine = DebugRankedLine("%s: %s" % (self, string)) + self._logDebug(rankedLine, level) + + def _logDebug(self, rankedLine, level): + + if self.parent: + self.parent._logDebug(rankedLine.increasedRank(), level) + else : + logger.log(level, str(rankedLine)) + + + + +class FromDomElement(Element): + def __init__(self, DomElement): + self._initFromDom(DomElement) + super().__init__(self.name) + + + def _initFromDom(self, DomElement): + self.name = DomElement.getAttribute("Name") + + + +class DomElementLocation(): + def __init__(self, classConstructor, path=None): + self.classConstructor = classConstructor + if path : + self.path = path + else : + self.path = [] + + self.path.append(classConstructor.tag) + + +class DomPopulatedElement(Element): + """Default child populate + + Look for each dom element with tag specified in self.tag + and instantiate it with the dom element + """ + childClasses = [] + + def populate(self, dom): + + for childDomElementLocation in self.childClasses : + + self.debug("Looking for child %s in path %s" % ( + childDomElementLocation.path[-1], childDomElementLocation.path)) + + for childDomElement in self._findChildFromTagPath(dom, childDomElementLocation.path) : + + childElement = childDomElementLocation.classConstructor(childDomElement) + self.addChild(childElement) + + childElement.populate(childDomElement) + + def _findChildFromTagPath(self, dom, path): + if not path : + yield dom + else : + # Copy list + path = list(path) + + tag = path.pop(0) + + # Find element with tag + self.debug("Going to find elements with tag %s in %s" % (tag, dom)) + self.debug(lambda: "Nb of solutions: %s" % len(dom.getElementsByTagName(tag))) + + for elementByTag in dom.getElementsByTagName(tag) : + + self.debug("Found element: %s" % elementByTag) + + # If the same tag is found + if elementByTag in dom.childNodes : + + # Yield next level + for element in self._findChildFromTagPath(elementByTag, path) : + yield element + + +class Rule(Element): + + def usedIfApplicable(self, criteria): + childApplicability = (child.usedIfApplicable(criteria) + for child in self.children) + + isApplicable = self._isApplicable(criteria, childApplicability) + + if isApplicable : + self._incNbUse() + + self.debug("Rule applicability: %s" % isApplicable) + assert(isApplicable == True or isApplicable == False) + + return isApplicable + + + def _isApplicable(self, criteria, childApplicability): + """Return the rule applicability depending on children applicability. + + If at least one child is applicable, return true""" + # Lazy evaluation as in the PFW + return all(childApplicability) + + +class CriterionRule(FromDomElement, DomPopulatedElement, Rule): + tag = "SelectionCriterionRule" + childClasses = [] + isApplicableOperations = { + "Includes" : lambda criterion, value: criterion.stateIncludes(value), + "Excludes" : lambda criterion, value: not criterion.stateIncludes(value), + "Is" : lambda criterion, value: criterion.stateIs(value), + "IsNot" : lambda criterion, value: not criterion.stateIs(value) + } + + def _initFromDom(self, DomElement): + self.selectionCriterion = DomElement.getAttribute("SelectionCriterion") + self.matchesWhen = DomElement.getAttribute("MatchesWhen") + self.value = DomElement.getAttribute("Value") + self.name = "%s %s %s" % (self.selectionCriterion, self.matchesWhen, self.value) + + applicableOperationWithoutValue = self.isApplicableOperations[self.matchesWhen] + self.isApplicableOperation = lambda criterion: applicableOperationWithoutValue(criterion, self.value) + + def _isApplicable(self, criteria, childApplicability): + + return criteria.operationOnChild([self.selectionCriterion], + self.isApplicableOperation) + + +class CompoundRule(FromDomElement, DomPopulatedElement, Rule): + """CompoundRule can be of type ALL or ANY""" + tag = "CompoundRule" + # Declare childClasses but define it at first class instantiation + childClasses = None + + def __init__(self, dom): + # Define childClasses at first class instantiation + if self.childClasses == None : + self.childClasses = [DomElementLocation(CriterionRule), + DomElementLocation(CompoundRule)] + super().__init__(dom) + + def _initFromDom(self, DomElement): + + type = DomElement.getAttribute("Type") + self.ofTypeAll = {"All" : True, "Any" : False}[type] + self.name = type + + def _isApplicable(self, criteria, childApplicability): + if self.ofTypeAll : + applicability = super()._isApplicable(criteria, childApplicability) + else: + # Lazy evaluation as in the PFW + applicability = any(childApplicability) + + return applicability + +class RootRule(DomPopulatedElement, Rule): + tag = "RootRule" + childClasses = [DomElementLocation(CompoundRule)] + + def populate(self, dom): + super().populate(dom) + self.debug("Children: %s" % self.children) + # A configuration can only have one or no rule + assert(len(self.children) <= 1) + + def _getCoverageDependanceElements(self): + return self._getDescendants() + + +class CriteronStates(Element): + """Root of configuration application criterion state""" + tag = "CriterionStates" + + def parentUsed(self, criteria): + """Add criteria to child if not exist, if exist increase it's nbUse""" + self._incNbUse() + + matches = [child for child in self.children if child == criteria] + + assert(len(matches) <= 1) + + if matches : + self.debug("Criteria state has already been encounter") + currentcriteria = matches[0] + else : + self.debug("Criteria state has never been encounter, saving it") + currentcriteria = criteria + self.addChild(criteria) + + currentcriteria.parentUsed() + + + +class Configuration(FromDomElement, DomPopulatedElement): + tag = "Configuration" + childClasses = [] + + class IneligibleConfigurationAppliedError(CustomError): + + def __init__(self, configuration, criteria): + self.configuration = configuration + self.criteria = criteria + + def __str__(self): + + return ("Applying ineligible %s, " + "rule:\n%s\n" + "Criteria current state:\n%s" % ( + self.configuration, + self.configuration.rootRule.dump(withCoverage=False, withNbUse=False), + self.criteria.dump(withCoverage=False, withNbUse=False) + )) + + def __init__(self, DomElement): + super().__init__(DomElement) + + self.rootRule = RootRule("RootRule") + self.addChild(self.rootRule) + + self.criteronStates = CriteronStates("CriterionStates") + self.addChild(self.criteronStates) + + def populate(self, dom): + # Delegate to rootRule + self.rootRule.populate(dom) + + def _getCoverage(self): + # Delegate to rootRule + return self.rootRule._getCoverage() + + def used(self, criteria): + + self._incNbUse() + + # Propagate use to parents + self._tellParentThatChildUsed() + + # Propagate to criterion coverage + self.criteronStates.parentUsed(criteria.export()) + + # Propagate to rules + if not self.rootRule.usedIfApplicable(criteria) : + + self.debug("Applied but rule does not match current " + "criteria (parent: %s) " % self.parent.name, + logging.ERROR) + + raise self.IneligibleConfigurationAppliedError(self, criteria.export()) + + def _dumpPropagate(self, withCoverage, withNbUse): + self.debug("Going to ask %s for description" % self.rootRule) + for dumpedDescription in self.rootRule._dumpDescription( + withCoverage=withCoverage, + withNbUse=withNbUse) : + yield dumpedDescription.increasedRank() + + self.debug("Going to ask %s for description" % self.criteronStates) + for dumpedDescription in self.criteronStates._dumpDescription( + withCoverage=False, + withNbUse=withNbUse) : + yield dumpedDescription.increasedRank() + + +class Domain(FromDomElement, DomPopulatedElement): + tag = "ConfigurableDomain" + childClasses = [DomElementLocation(Configuration, ["Configurations"])] + + +class Domains(DomPopulatedElement): + tag = "Domains" + childClasses = [DomElementLocation(Domain, ["ConfigurableDomains"])] + + +class RankedLine(): + def __init__(self, string, + stringPrefix="|-- ", + rankString="| ", + linePrefix="", + lineSuffix="\n"): + self.string = string + self.rank = 0 + self.stringPrefix = stringPrefix + self.rankString = rankString + self.linePrefix = linePrefix + self.lineSuffix = lineSuffix + + def increasedRank(self): + self.rank += 1 + return self + + def __str__(self): + return self.linePrefix + \ + self.rank * self.rankString + \ + self.stringPrefix + \ + self.string + \ + self.lineSuffix + +class DebugRankedLine(RankedLine): + + def __init__(self, string, lineSuffix=""): + super().__init__(string, + stringPrefix="", + rankString=" ", + linePrefix="", + lineSuffix=lineSuffix) + + +class CriterionState(Element): + tag = "CriterionState" + def used(self): + self._incNbUse() + + +class Criterion(Element): + tag = "Criterion" + inclusivenessTranslate = {True: "Inclusive", False: "Exclusive"} + + class ChangeRequestToNonAccessibleState(CustomError): + def __init__(self, requestedState, detail): + self.requestedState = requestedState + self.detail = detail + + def __str__(self): + return ("Change request to non accessible state %s. Detail: %s" % + (self.requestedState, self.detail)) + + def __init__(self, name, isInclusif, + stateNamesList, currentStateNamesList, + ignoreIntegrity=False): + super().__init__(name) + self.isInclusif = isInclusif + + for state in stateNamesList : + self.addChild(CriterionState(state)) + + self.currentState = [] + self.initStateNamesList = list(currentStateNamesList) + self.changeState(self.initStateNamesList, ignoreIntegrity) + + def reset(self): + # Set current state as provided at initialisation + self.changeState(self.initStateNamesList, ignoreIntegrity=True) + + def changeState(self, subStateNames, ignoreIntegrity=False): + self.debug("Changing state from: %s to: %s" % ( + list(self._getElementNames(self.currentState)), + subStateNames)) + + if not ignoreIntegrity and not self.isIntegre(subStateNames): + raise self.ChangeRequestToNonAccessibleState(subStateNames, + "An exclusive criterion must have a non empty state") + + newCurrentState = [] + for subStateName in subStateNames : + subState = self.getChildFromName(subStateName) + subState.used() + newCurrentState.append(subState) + + self.currentState = newCurrentState + + self._incNbUse() + self._tellParentThatChildUsed() + + def isIntegre(self, subStateNames): + return self.isInclusif or len(subStateNames) == 1 + + def childUsed(self, child): + self.currentState = child + super().childUsed(child) + + def export(self): + subStateNames = self._getElementNames(self.currentState) + return Criterion(self.name, self.isInclusif, subStateNames, subStateNames, + ignoreIntegrity=True) + + def stateIncludes(self, subStateName): + subStateCurrentNames = list(self._getElementNames(self.currentState)) + + self.debug("Testing if %s is included in %s" % (subStateName, subStateCurrentNames)) + + isIncluded = subStateName in subStateCurrentNames + self.debug("IsIncluded: %s" % isIncluded) + + return isIncluded + + + def stateIs(self, subStateNames): + if len(self.currentState) != 1 : + return False + else : + return self.stateIncludes(subStateNames) + + def _getXMLAttributes(self): + attributes = super()._getXMLAttributes() + attributes["Type"] = self.inclusivenessTranslate[self.isInclusif] + return attributes + + +class Criteria(Element): + tag = "Criteria" + + class DuplicatedCriterionError(DuplicatedChildError): + pass + + def export(self): + self.debug("Exporting criteria") + assert(self.children) + + exported = Criteria(self.name) + for child in self.children : + exported.addChild(child.export()) + return exported + + def addChild(self, child): + if child in self.children: + raise self.DuplicatedCriterionError(self, child) + super().addChild(child) + +class ConfigAppliedWithoutCriteriaError(CustomError): + def __init__(self, configurationName, domainName): + self.configurationName = configurationName + self.domainName = domainName + def __str__(self): + return ('Applying configuration "%s" from domain "%s" before declaring criteria' % + (self.configurationName, self.domainName)) + +class ParsePFWlog(): + MATCH = "match" + ACTION = "action" + + def __init__(self, domains, criteria, ErrorsToIgnore=()): + + self.domains = domains; + self.criteria = criteria; + self.ErrorsToIgnore = ErrorsToIgnore + + configApplicationRegext = r""".*Applying configuration "(.*)" from domain "([^"]*)""" + matchConfigApplicationLine = re.compile(configApplicationRegext).match + + criterionCreationRegext = ", ".join([ + r""".*Criterion name: (.*)""", + r"""type kind: (.*)""", + r"""current state: (.*)""", + r"""states: {(.*)}""" + ]) + matchCriterionCreationLine = re.compile(criterionCreationRegext).match + + changingCriterionRegext = r""".*Selection criterion changed event: Criterion name: (.*), current state: ([^\n\r]*)""" + matchChangingCriterionLine = re.compile(changingCriterionRegext).match + + self.lineLogTypes = [ + { + self.MATCH: matchConfigApplicationLine, + self.ACTION: self._configApplication + }, { + self.MATCH: matchCriterionCreationLine, + self.ACTION: self._criterionCreation + }, { + self.MATCH: matchChangingCriterionLine, + self.ACTION: self._changingCriterion + } + ] + + @staticmethod + def _formatCriterionList(liststring, separator): + list = liststring.split(separator) + if len(list) == 1 and list[0] == "<none>": + list = [] + return list + + def _criterionCreation(self, matchCriterionCreation): + # Unpack + criterionName, criterionType, currentCriterionStates, criterionStates = matchCriterionCreation.group(1, 2, 3, 4) + + criterionStateList = self._formatCriterionList(criterionStates, ", ") + + criterionIsInclusif = {"exclusive" : False, "inclusive" : True}[criterionType] + + currentcriterionStateList = self._formatCriterionList(currentCriterionStates, "|") + + logger.info("Creating criterion: " + criterionName + + " (" + criterionType + ") " + + " with current state: " + str(currentcriterionStateList) + + ", possible states:" + str(criterionStateList)) + + try: + self.criteria.addChild(Criterion( + criterionName, + criterionIsInclusif, + criterionStateList, + currentcriterionStateList + )) + except self.criteria.DuplicatedCriterionError as ex: + logger.debug(ex) + logger.warning("Reseting criterion %s. Did you reset the PFW ?" % criterionName) + self.criteria.operationOnChild( + [criterionName], + lambda criterion: criterion.reset() + ) + + + + def _changingCriterion(self, matchChangingCriterion): + # Unpack + criterionName, newCriterionSubStateNames = matchChangingCriterion.group(1, 2) + + newCriterionState = self._formatCriterionList(newCriterionSubStateNames, "|") + + logger.info("Changing criterion %s to %s" % (criterionName , newCriterionState)) + + path = [criterionName] + changeCriterionOperation = lambda criterion : criterion.changeState(newCriterionState) + self.criteria.operationOnChild(path, changeCriterionOperation) + + def _configApplication(self, matchConfig): + # Unpack + configurationName, domainName = matchConfig.group(1, 2) + + # Check that at least one criterion exist + if not self.criteria.hasChildren() : + logger.error("Applying configuration before declaring criteria") + logger.info("Is the log starting at PFW boot ?") + raise ConfigAppliedWithoutCriteriaError(configurationName, domainName) + + # Change criterion state + path = [domainName, configurationName] + usedOperation = lambda element : element.used(self.criteria) + + logger.info("Applying configuration %s from domain %s" % ( + configurationName, domainName)) + + self.domains.operationOnChild(path, usedOperation) + + + def _digest(self, lineLogType, lineLog): + + match = lineLogType[self.MATCH](lineLog) + if match : + lineLogType[self.ACTION](match) + return True + return False + + + def parsePFWlog(self, lines): + for lineNb, lineLog in enumerate(lines): + + logger.debug("Parsing line :%s" % lineLog.rstrip()) + + digested = (self._digest(lineLogType, lineLog) + for lineLogType in self.lineLogTypes) + + try: + success = any(digested) + + # Catch some exception in order to print the current parsing line, + # then raise the exception again if not continue of error + except CustomError as ex: + logger.error('Error raised while parsing line %s: "%s"' % + (lineNb, repr(lineLog))) + + # If exception is a subclass of ErrorsToIgnore, log it and continue + # otherwise raise it again. + if not issubclass(type(ex), self.ErrorsToIgnore): + raise ex + else: + logger.error('Ignoring exception:"%s", ' + 'can not guarantee database integrity' % ex) + else: + if not success: + logger.debug("Line does not match, dropped") + + +class Root(Element): + tag = "CoverageReport" + def __init__(self, name, dom): + super().__init__(name) + # Create domain tree + self.domains = Domains("Domains") + self.domains.populate(dom) + self.addChild(self.domains) + # Create criterion list + self.criteria = Criteria("CriterionRoot") + self.addChild(self.criteria) + + def exportToXML(self): + """Export tree to an xml document""" + impl = xml.dom.minidom.getDOMImplementation() + newdoc = impl.createDocument(namespaceURI=None, qualifiedName=self.tag, doctype=None) + super().exportToXML(newdoc.documentElement) + + return newdoc + +# ============================ +# Command line argument parser +# ============================ + + +class ArgumentParser: + """class that parse command line arguments with argparse library + + Result of parsing are the class attributes. + """ + levelTranslate = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG] + + def __init__(self): + + try: + # As argparse is only in the stdlib since python 3.2, + # testing its availability + import argparse + + except ImportError: + logger.warning("Unable to import argparse " + "(parser for command-line options and arguments), " + "using default argument values:") + + logger.warning(" - InputFile: stdin") + self.inputFile = sys.stdin + + logger.warning(" - OutputFile: stdout") + self.outputFile = sys.stdout + + try: + self.domainsFile = sys.argv[1] + except IndexError as ex: + logger.fatal("No domain file provided (first argument)") + raise ex + else: + logger.warning(" - Domain file: " + self.domainsFile) + + logger.warning(" - Output format: xml") + self.XMLreport = True + + logger.warning(" - Debug level: error") + self.debugLevel = logging.ERROR + else : + + myArgParser = argparse.ArgumentParser(description='Generate PFW report') + + myArgParser.add_argument( + 'domainsFile', + type=argparse.FileType('r'), + help="the PFW domain XML file" + ) + myArgParser.add_argument( + 'pfwlog', nargs='?', + type=argparse.FileType('r'), default=sys.stdin, + help="the PFW log file, default stdin" + ) + myArgParser.add_argument( + '-o', '--output', + dest="outputFile", + type=argparse.FileType('w'), default=sys.stdout, + help="the coverage report output file, default stdout" + ) + myArgParser.add_argument( + '-v', '--verbose', + dest="debugLevel", default=0, + action='count', + help="print debug warnings from warning (default) to debug (-vv)" + ) + + outputFormatGroupe = myArgParser.add_mutually_exclusive_group(required=False) + + outputFormatGroupe.add_argument( + '--xml', + dest="xmlFlag", + action='store_true', + help=" XML coverage output report" + ) + outputFormatGroupe.add_argument( + '--raw', + dest="rawFlag", + action='store_true', + help="raw coverage output report" + ) + + myArgParser.add_argument( + '--ignore-incoherent-criterion-state', + dest="incoherentCriterionFlag", + action='store_true', + help="ignore criterion transition to incoherent state" + ) + + myArgParser.add_argument( + '--ignore-ineligible-configuration-application', + dest="ineligibleConfigurationApplicationFlag", + action='store_true', + help="ignore application of configuration with a false rule " + "(not applicable configuration)" + ) + + # Process command line arguments + options = myArgParser.parse_args() + + # Mapping to attributes + self.inputFile = options.pfwlog + self.outputFile = options.outputFile + self.domainsFile = options.domainsFile + + # Output report in xml if flag not set + self.XMLreport = not options.rawFlag + + # Setting logger level + levelCapped = min(options.debugLevel, len(self.levelTranslate) - 1) + self.debugLevel = self.levelTranslate[levelCapped] + + # Setting ignore options + errorToIgnore = [] + if options.ineligibleConfigurationApplicationFlag : + errorToIgnore.append(Configuration.IneligibleConfigurationAppliedError) + + if options.incoherentCriterionFlag: + errorToIgnore.append(Criterion.ChangeRequestToNonAccessibleState) + + self.errorToIgnore = tuple(errorToIgnore) + + + +def main(): + + errorDuringLogParsing = -1 + errorDuringArgumentParsing = 1 + + try: + commandLineArguments = ArgumentParser() + except LookupError as ex: + logger.error("Error during argument parsing") + logger.debug(str(ex)) + sys.exit(errorDuringArgumentParsing) + + # Setting logger level + logger.setLevel(commandLineArguments.debugLevel) + logger.info("Log level set to: %s" % + logging.getLevelName(commandLineArguments.debugLevel)) + + # Create tree from XML + dom = xml.dom.minidom.parse(commandLineArguments.domainsFile) + + # Create element tree + root = Root("DomainCoverage", dom) + + # Parse PFW events + parser = ParsePFWlog(root.domains, root.criteria, commandLineArguments.errorToIgnore) + + try: + parser.parsePFWlog(commandLineArguments.inputFile.readlines()) + except CustomError as ex: + logger.fatal("Error during parsing log file %s: %s" % + (commandLineArguments.inputFile, ex)) + sys.exit(errorDuringLogParsing) + + # Output report + outputFile = commandLineArguments.outputFile + + if not commandLineArguments.XMLreport : + outputFile.write("%s\n" % root.dump(withCoverage=True, withNbUse=True)) + else : + outputFile.write(root.exportToXML().toprettyxml()) + + +if __name__ == "__main__" : + """ Execute main if the python interpreter is running this module as the main program """ + main() + |