1079 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			1079 lines
		
	
	
		
			35 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
| #!/usr/bin/env python3
 | |
| 
 | |
| # Copyright (c) 2011-2014, Intel Corporation
 | |
| # All rights reserved.
 | |
| #
 | |
| # Redistribution and use in source and binary forms, with or without modification,
 | |
| # are permitted provided that the following conditions are met:
 | |
| #
 | |
| # 1. Redistributions of source code must retain the above copyright notice, this
 | |
| # list of conditions and the following disclaimer.
 | |
| #
 | |
| # 2. Redistributions in binary form must reproduce the above copyright notice,
 | |
| # this list of conditions and the following disclaimer in the documentation and/or
 | |
| # other materials provided with the distribution.
 | |
| #
 | |
| # 3. Neither the name of the copyright holder nor the names of its contributors
 | |
| # may be used to endorse or promote products derived from this software without
 | |
| # specific prior written permission.
 | |
| #
 | |
| # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 | |
| # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 | |
| # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 | |
| # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 | |
| # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 | |
| # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 | |
| # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 | |
| # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 | |
| # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 | |
| # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | |
| 
 | |
| 
 | |
| """
 | |
| 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, document, domElement=None):
 | |
|         if domElement == None:
 | |
|             domElement = document.createElement(self.tag)
 | |
| 
 | |
|         self._XMLaddAttributes(domElement)
 | |
| 
 | |
|         for child in self.children:
 | |
|             domElement.appendChild(child.exportToXML(document))
 | |
| 
 | |
|         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"
 | |
| 
 | |
|     class ChangeRequestOnUnknownCriterion(CustomError):
 | |
|         def __init__(self, criterion):
 | |
|             self.criterion = criterion
 | |
| 
 | |
|         def __str__(self):
 | |
|             return ("Change request on an unknown criterion %s." %
 | |
|                     self.criterion)
 | |
| 
 | |
|     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)
 | |
|         try:
 | |
|             self.criteria.operationOnChild(path, changeCriterionOperation)
 | |
|         except ChildNotFoundError:
 | |
|             raise self.ChangeRequestOnUnknownCriterion(criterionName)
 | |
| 
 | |
|     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, 1): # line number starts at 1
 | |
| 
 | |
|             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()
 | |
|         document = impl.createDocument(namespaceURI=None, qualifiedName=self.tag, doctype=None)
 | |
|         super().exportToXML(document, document.documentElement)
 | |
| 
 | |
|         return document
 | |
| 
 | |
| # ============================
 | |
| # 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-unknown-criterion',
 | |
|                 dest="unknwonCriterionFlag",
 | |
|                 action='store_true',
 | |
|                 help="ignore unknown criterion"
 | |
|             )
 | |
| 
 | |
|             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)
 | |
| 
 | |
|             if options.unknwonCriterionFlag:
 | |
|                 errorToIgnore.append(ParsePFWlog.ChangeRequestOnUnknownCriterion)
 | |
| 
 | |
|             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()
 | |
| 
 |