Home | History | Annotate | Download | only in coverage
      1 #!/usr/bin/env python3
      2 
      3 # Copyright (c) 2011-2014, Intel Corporation
      4 # All rights reserved.
      5 #
      6 # Redistribution and use in source and binary forms, with or without modification,
      7 # are permitted provided that the following conditions are met:
      8 #
      9 # 1. Redistributions of source code must retain the above copyright notice, this
     10 # list of conditions and the following disclaimer.
     11 #
     12 # 2. Redistributions in binary form must reproduce the above copyright notice,
     13 # this list of conditions and the following disclaimer in the documentation and/or
     14 # other materials provided with the distribution.
     15 #
     16 # 3. Neither the name of the copyright holder nor the names of its contributors
     17 # may be used to endorse or promote products derived from this software without
     18 # specific prior written permission.
     19 #
     20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
     21 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     22 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     23 # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
     24 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     25 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     26 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
     27 # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     29 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30 
     31 
     32 """
     33 Generate a coverage report by parsing parameter framework log.
     34 
     35 The coverage report contains the:
     36  - domain
     37  - configuration
     38  - rule
     39  - criterion
     40 basic coverage statistics.
     41 """
     42 
     43 import xml.dom.minidom
     44 import sys
     45 import re
     46 import logging
     47 
     48 FORMAT = '%(levelname)s: %(message)s'
     49 logging.basicConfig(stream=sys.stderr, level=logging.WARNING, format=FORMAT)
     50 logger = logging.getLogger("Coverage")
     51 
     52 class CustomError(Exception):
     53     pass
     54 
     55 class ChildError(CustomError):
     56     def __init__(self, parent, child):
     57         self.parent = parent
     58         self.child = child
     59 
     60 class ChildNotFoundError(ChildError):
     61     def __str__(self):
     62         return 'Unable to find the child "%s" in "%s"' % (self.child, self.parent)
     63 
     64 class DuplicatedChildError(ChildError):
     65     def __str__(self):
     66         return 'Add existing child "%s" in "%s".' % (self.child, self.parent)
     67 
     68 class Element():
     69     """Root class for all coverage elements"""
     70     tag = "element"
     71 
     72     def __init__(self, name):
     73 
     74         self.parent = None
     75         self.children = []
     76 
     77         self.nbUse = 0
     78 
     79         self.name = name
     80 
     81         self.debug("New element")
     82 
     83 
     84     def __str__(self):
     85         return  "%s (%s)" % (self.name, self.tag)
     86 
     87     def __eq__(self, compared):
     88         return (self.name == compared.name) and (self.children == compared.children)
     89 
     90     def getName(self, default=""):
     91         return self.name or default
     92 
     93     def hasChildren(self):
     94         return bool(self.children)
     95 
     96     def getChildren(self):
     97         return self.children
     98 
     99     def _getDescendants(self):
    100         for child in self.children:
    101             yield child
    102             for descendant in child._getDescendants() :
    103                 yield descendant
    104 
    105     def getChildFromName(self, childName):
    106 
    107         for child in self.children :
    108 
    109             if child.getName() == childName :
    110                 return child
    111 
    112         self.debug('Child "%s" not found' % childName, logging.ERROR)
    113 
    114         self.debug("Child list :")
    115 
    116         for child in self.children :
    117             self.debug("  - %s" % child)
    118 
    119         raise ChildNotFoundError(self, childName)
    120 
    121 
    122     def addChild(self, child):
    123         self.debug("new child: " + child.name)
    124         self.children.append(child)
    125         child._adoptedBy(self)
    126 
    127     def _adoptedBy(self, parent):
    128         assert(not self.parent)
    129         self.parent = parent
    130 
    131     def _getElementNames(self, elementList):
    132         return (substate.name for substate in elementList)
    133 
    134     def _description(self, withCoverage, withNbUse):
    135         description = self.name
    136 
    137         if withNbUse or withCoverage :
    138             description += " has been used " + str(self.nbUse) + " time"
    139 
    140         if withCoverage :
    141             description += self._coverageFormating(self._getCoverage())
    142 
    143         return description
    144 
    145 
    146     def _getCoverage(self):
    147         """Return the coverage of the element between 0 and 1
    148 
    149         If the element has no coverage dependency (usually child) return 0 or 1.
    150         otherwise the element coverage is the dependency coverage average"""
    151         coverageDependanceElements = list(self._getCoverageDependanceElements())
    152 
    153         nbcoverageDependence = len(coverageDependanceElements)
    154 
    155         if nbcoverageDependence == 0:
    156             if self.nbUse == 0:
    157                 return 0
    158             else:
    159                 return 1
    160 
    161         coverageDependenceValues = (depElement._getCoverage()
    162                 for depElement in coverageDependanceElements)
    163 
    164         return sum(coverageDependenceValues) / nbcoverageDependence
    165 
    166     def _getCoverageDependanceElements(self):
    167         return self.children
    168 
    169     def _coverageFormating(self, coverage):
    170         # If no coverage provided
    171         if not coverage :
    172             return ""
    173 
    174         # Calculate coverage
    175         return " (%s coverage)" % self._number2percent(coverage)
    176 
    177     @staticmethod
    178     def _number2percent(number):
    179         """Format a number to a integer % string
    180 
    181         example: _number2percent(0.6666) -> "67%"
    182         """
    183         return "{0:.0f}%".format(100 * number)
    184 
    185 
    186     def _dumpDescription(self, withCoverage, withNbUse):
    187 
    188         self.debug("yelding description")
    189         yield RankedLine(self._description(withCoverage, withNbUse), lineSuffix="")
    190 
    191         for dumped in self._dumpPropagate(withCoverage, withNbUse) :
    192             yield dumped
    193 
    194     def _dumpPropagate(self, withCoverage, withNbUse):
    195 
    196         for child in self.children :
    197             for dumpedDescription in child._dumpDescription(withCoverage, withNbUse) :
    198                 yield dumpedDescription.increasedRank()
    199 
    200 
    201     def dump(self, withCoverage=False, withNbUse=True):
    202 
    203         return "\n".join(
    204                 str(dumpedDescription) for dumpedDescription in
    205                         self._dumpDescription(withCoverage, withNbUse))
    206 
    207     def exportToXML(self, document, domElement=None):
    208         if domElement == None:
    209             domElement = document.createElement(self.tag)
    210 
    211         self._XMLaddAttributes(domElement)
    212 
    213         for child in self.children :
    214             domElement.appendChild(child.exportToXML(document))
    215 
    216         return domElement
    217 
    218     def _XMLaddAttributes(self, domElement):
    219         attributes = self._getXMLAttributes()
    220 
    221         coverage = self._getCoverage()
    222         if coverage != None :
    223             attributes["Coverage"] = self._number2percent(coverage)
    224 
    225         for key, value in attributes.items():
    226             domElement.setAttribute(key, value)
    227 
    228     def _getXMLAttributes(self):
    229         return {
    230                 "Name": self.name,
    231                 "NbUse": str(self.nbUse)
    232                 }
    233 
    234     def _incNbUse(self):
    235         self.nbUse += 1
    236 
    237     def childUsed(self, child):
    238         self._incNbUse()
    239         # Propagate to parent
    240         self._tellParentThatChildUsed()
    241 
    242     def _tellParentThatChildUsed(self):
    243         if self.parent :
    244             self.parent.childUsed(self)
    245 
    246 
    247     def parentUsed(self):
    248         self._incNbUse()
    249         # Propagate to children
    250         for child in self.children :
    251             child.parentUsed()
    252 
    253     def hasBeenUsed(self):
    254         return self.nbUse > 0
    255 
    256     def operationOnChild(self, path, operation):
    257 
    258         if path:
    259             return self._operationPropagate(path, operation)
    260         else :
    261             self.debug("operating on self")
    262             return operation(self)
    263 
    264     def _operationPropagate(self, path, operation):
    265 
    266         childName = path.pop(0)
    267         child = self.getChildFromName(childName)
    268 
    269         return child.operationOnChild(path, operation)
    270 
    271 
    272 
    273     def debug(self, stringOrFunction, level=logging.DEBUG):
    274         """Print a debug line on stderr in tree form
    275 
    276         If the debug line is expensive to generate, provide callable
    277         object, it will be called if log is enable for this level.
    278         This callable object should return the logline string.
    279         """
    280         if logger.isEnabledFor(level):
    281 
    282             # TODO: use buildin callable if python >= 3.2
    283             if hasattr(stringOrFunction, "__call__"):
    284                 string = stringOrFunction()
    285             else:
    286                 string = stringOrFunction
    287 
    288             rankedLine = DebugRankedLine("%s: %s" % (self, string))
    289             self._logDebug(rankedLine, level)
    290 
    291     def _logDebug(self, rankedLine, level):
    292 
    293         if self.parent:
    294             self.parent._logDebug(rankedLine.increasedRank(), level)
    295         else :
    296             logger.log(level, str(rankedLine))
    297 
    298 
    299 
    300 
    301 class FromDomElement(Element):
    302     def __init__(self, DomElement):
    303         self._initFromDom(DomElement)
    304         super().__init__(self.name)
    305 
    306 
    307     def _initFromDom(self, DomElement):
    308         self.name = DomElement.getAttribute("Name")
    309 
    310 
    311 
    312 class DomElementLocation():
    313     def __init__(self, classConstructor, path=None):
    314         self.classConstructor = classConstructor
    315         if path :
    316             self.path = path
    317         else :
    318             self.path = []
    319 
    320         self.path.append(classConstructor.tag)
    321 
    322 
    323 class DomPopulatedElement(Element):
    324     """Default child populate
    325 
    326     Look for each dom element with tag specified in self.tag
    327     and instantiate it with the dom element
    328     """
    329     childClasses = []
    330 
    331     def populate(self, dom):
    332 
    333         for childDomElementLocation in self.childClasses :
    334 
    335             self.debug("Looking for child %s in path %s" % (
    336                 childDomElementLocation.path[-1], childDomElementLocation.path))
    337 
    338             for childDomElement in self._findChildFromTagPath(dom, childDomElementLocation.path) :
    339 
    340                 childElement = childDomElementLocation.classConstructor(childDomElement)
    341                 self.addChild(childElement)
    342 
    343                 childElement.populate(childDomElement)
    344 
    345     def _findChildFromTagPath(self, dom, path):
    346         if not path :
    347             yield dom
    348         else :
    349             # Copy list
    350             path = list(path)
    351 
    352             tag = path.pop(0)
    353 
    354             # Find element with tag
    355             self.debug("Going to find elements with tag %s in %s" % (tag, dom))
    356             self.debug(lambda: "Nb of solutions: %s" % len(dom.getElementsByTagName(tag)))
    357 
    358             for elementByTag in dom.getElementsByTagName(tag) :
    359 
    360                 self.debug("Found element: %s" % elementByTag)
    361 
    362                 # If the same tag is found
    363                 if elementByTag in dom.childNodes :
    364 
    365                     # Yield next level
    366                     for element in self._findChildFromTagPath(elementByTag, path) :
    367                         yield element
    368 
    369 
    370 class Rule(Element):
    371 
    372     def usedIfApplicable(self, criteria):
    373         childApplicability = (child.usedIfApplicable(criteria)
    374                 for child in self.children)
    375 
    376         isApplicable = self._isApplicable(criteria, childApplicability)
    377 
    378         if isApplicable :
    379             self._incNbUse()
    380 
    381         self.debug("Rule applicability: %s" % isApplicable)
    382         assert(isApplicable == True or isApplicable == False)
    383 
    384         return isApplicable
    385 
    386 
    387     def _isApplicable(self, criteria, childApplicability):
    388         """Return the rule applicability depending on children applicability.
    389 
    390         If at least one child is applicable, return true"""
    391         # Lazy evaluation as in the PFW
    392         return all(childApplicability)
    393 
    394 
    395 class CriterionRule(FromDomElement, DomPopulatedElement, Rule):
    396     tag = "SelectionCriterionRule"
    397     childClasses = []
    398     isApplicableOperations = {
    399                 "Includes" : lambda criterion, value:     criterion.stateIncludes(value),
    400                 "Excludes" : lambda criterion, value: not criterion.stateIncludes(value),
    401                 "Is"       : lambda criterion, value:     criterion.stateIs(value),
    402                 "IsNot"    : lambda criterion, value: not criterion.stateIs(value)
    403             }
    404 
    405     def _initFromDom(self, DomElement):
    406         self.selectionCriterion = DomElement.getAttribute("SelectionCriterion")
    407         self.matchesWhen = DomElement.getAttribute("MatchesWhen")
    408         self.value = DomElement.getAttribute("Value")
    409         self.name = "%s %s %s" % (self.selectionCriterion, self.matchesWhen, self.value)
    410 
    411         applicableOperationWithoutValue = self.isApplicableOperations[self.matchesWhen]
    412         self.isApplicableOperation = lambda criterion: applicableOperationWithoutValue(criterion, self.value)
    413 
    414     def _isApplicable(self, criteria, childApplicability):
    415 
    416         return criteria.operationOnChild([self.selectionCriterion],
    417                 self.isApplicableOperation)
    418 
    419 
    420 class CompoundRule(FromDomElement, DomPopulatedElement, Rule):
    421     """CompoundRule can be of type ALL or ANY"""
    422     tag = "CompoundRule"
    423     # Declare childClasses but define it at first class instantiation
    424     childClasses = None
    425 
    426     def __init__(self, dom):
    427         # Define childClasses at first class instantiation
    428         if self.childClasses == None :
    429             self.childClasses = [DomElementLocation(CriterionRule),
    430                     DomElementLocation(CompoundRule)]
    431         super().__init__(dom)
    432 
    433     def _initFromDom(self, DomElement):
    434 
    435         type = DomElement.getAttribute("Type")
    436         self.ofTypeAll = {"All" : True, "Any" : False}[type]
    437         self.name = type
    438 
    439     def _isApplicable(self, criteria, childApplicability):
    440         if self.ofTypeAll :
    441             applicability = super()._isApplicable(criteria, childApplicability)
    442         else:
    443             # Lazy evaluation as in the PFW
    444             applicability = any(childApplicability)
    445 
    446         return applicability
    447 
    448 class RootRule(DomPopulatedElement, Rule):
    449     tag = "RootRule"
    450     childClasses = [DomElementLocation(CompoundRule)]
    451 
    452     def populate(self, dom):
    453         super().populate(dom)
    454         self.debug("Children: %s" % self.children)
    455         # A configuration can only have one or no rule
    456         assert(len(self.children) <= 1)
    457 
    458     def _getCoverageDependanceElements(self):
    459         return self._getDescendants()
    460 
    461 
    462 class CriteronStates(Element):
    463     """Root of configuration application criterion state"""
    464     tag = "CriterionStates"
    465 
    466     def parentUsed(self, criteria):
    467         """Add criteria to child if not exist, if exist increase it's nbUse"""
    468         self._incNbUse()
    469 
    470         matches = [child for child in self.children if child == criteria]
    471 
    472         assert(len(matches) <= 1)
    473 
    474         if matches :
    475             self.debug("Criteria state has already been encounter")
    476             currentcriteria = matches[0]
    477         else :
    478             self.debug("Criteria state has never been encounter, saving it")
    479             currentcriteria = criteria
    480             self.addChild(criteria)
    481 
    482         currentcriteria.parentUsed()
    483 
    484 
    485 
    486 class Configuration(FromDomElement, DomPopulatedElement):
    487     tag = "Configuration"
    488     childClasses = []
    489 
    490     class IneligibleConfigurationAppliedError(CustomError):
    491 
    492         def __init__(self, configuration, criteria):
    493             self.configuration = configuration
    494             self.criteria = criteria
    495 
    496         def __str__(self):
    497 
    498             return ("Applying ineligible %s, "
    499                 "rule:\n%s\n"
    500                 "Criteria current state:\n%s" % (
    501                     self.configuration,
    502                     self.configuration.rootRule.dump(withCoverage=False, withNbUse=False),
    503                     self.criteria.dump(withCoverage=False, withNbUse=False)
    504                     ))
    505 
    506     def __init__(self, DomElement):
    507         super().__init__(DomElement)
    508 
    509         self.rootRule = RootRule("RootRule")
    510         self.addChild(self.rootRule)
    511 
    512         self.criteronStates = CriteronStates("CriterionStates")
    513         self.addChild(self.criteronStates)
    514 
    515     def populate(self, dom):
    516         # Delegate to rootRule
    517         self.rootRule.populate(dom)
    518 
    519     def _getCoverage(self):
    520         # Delegate to rootRule
    521         return self.rootRule._getCoverage()
    522 
    523     def used(self, criteria):
    524 
    525         self._incNbUse()
    526 
    527         # Propagate use to parents
    528         self._tellParentThatChildUsed()
    529 
    530         # Propagate to criterion coverage
    531         self.criteronStates.parentUsed(criteria.export())
    532 
    533         # Propagate to rules
    534         if not self.rootRule.usedIfApplicable(criteria) :
    535 
    536             self.debug("Applied but rule does not match current "
    537                        "criteria (parent: %s) " % self.parent.name,
    538                     logging.ERROR)
    539 
    540             raise self.IneligibleConfigurationAppliedError(self, criteria.export())
    541 
    542     def _dumpPropagate(self, withCoverage, withNbUse):
    543         self.debug("Going to ask %s for description" % self.rootRule)
    544         for dumpedDescription in self.rootRule._dumpDescription(
    545                 withCoverage=withCoverage,
    546                 withNbUse=withNbUse) :
    547             yield dumpedDescription.increasedRank()
    548 
    549         self.debug("Going to ask %s for description" % self.criteronStates)
    550         for dumpedDescription in self.criteronStates._dumpDescription(
    551                 withCoverage=False,
    552                 withNbUse=withNbUse) :
    553             yield dumpedDescription.increasedRank()
    554 
    555 
    556 class Domain(FromDomElement, DomPopulatedElement):
    557     tag = "ConfigurableDomain"
    558     childClasses = [DomElementLocation(Configuration, ["Configurations"])]
    559 
    560 
    561 class Domains(DomPopulatedElement):
    562     tag = "Domains"
    563     childClasses = [DomElementLocation(Domain, ["ConfigurableDomains"])]
    564 
    565 
    566 class RankedLine():
    567     def __init__(self, string,
    568                 stringPrefix="|-- ",
    569                 rankString="|   ",
    570                 linePrefix="",
    571                 lineSuffix="\n"):
    572         self.string = string
    573         self.rank = 0
    574         self.stringPrefix = stringPrefix
    575         self.rankString = rankString
    576         self.linePrefix = linePrefix
    577         self.lineSuffix = lineSuffix
    578 
    579     def increasedRank(self):
    580         self.rank += 1
    581         return self
    582 
    583     def __str__(self):
    584         return self.linePrefix + \
    585             self.rank * self.rankString + \
    586             self.stringPrefix + \
    587             self.string + \
    588             self.lineSuffix
    589 
    590 class DebugRankedLine(RankedLine):
    591 
    592     def __init__(self, string, lineSuffix=""):
    593         super().__init__(string,
    594                 stringPrefix="",
    595                 rankString="   ",
    596                 linePrefix="",
    597                 lineSuffix=lineSuffix)
    598 
    599 
    600 class CriterionState(Element):
    601     tag = "CriterionState"
    602     def used(self):
    603         self._incNbUse()
    604 
    605 
    606 class Criterion(Element):
    607     tag = "Criterion"
    608     inclusivenessTranslate = {True: "Inclusive", False: "Exclusive"}
    609 
    610     class ChangeRequestToNonAccessibleState(CustomError):
    611         def __init__(self, requestedState, detail):
    612             self.requestedState = requestedState
    613             self.detail = detail
    614 
    615         def __str__(self):
    616             return ("Change request to non accessible state %s. Detail: %s" %
    617                 (self.requestedState, self.detail))
    618 
    619     def __init__(self, name, isInclusif,
    620                 stateNamesList, currentStateNamesList,
    621                 ignoreIntegrity=False):
    622         super().__init__(name)
    623         self.isInclusif = isInclusif
    624 
    625         for state in stateNamesList :
    626             self.addChild(CriterionState(state))
    627 
    628         self.currentState = []
    629         self.initStateNamesList = list(currentStateNamesList)
    630         self.changeState(self.initStateNamesList, ignoreIntegrity)
    631 
    632     def reset(self):
    633         # Set current state as provided at initialisation
    634         self.changeState(self.initStateNamesList, ignoreIntegrity=True)
    635 
    636     def changeState(self, subStateNames, ignoreIntegrity=False):
    637         self.debug("Changing state from: %s to: %s" % (
    638                     list(self._getElementNames(self.currentState)),
    639                     subStateNames))
    640 
    641         if not ignoreIntegrity and not self.isIntegre(subStateNames):
    642             raise self.ChangeRequestToNonAccessibleState(subStateNames,
    643                 "An exclusive criterion must have a non empty state")
    644 
    645         newCurrentState = []
    646         for subStateName in subStateNames :
    647             subState = self.getChildFromName(subStateName)
    648             subState.used()
    649             newCurrentState.append(subState)
    650 
    651         self.currentState = newCurrentState
    652 
    653         self._incNbUse()
    654         self._tellParentThatChildUsed()
    655 
    656     def isIntegre(self, subStateNames):
    657         return self.isInclusif or len(subStateNames) == 1
    658 
    659     def childUsed(self, child):
    660         self.currentState = child
    661         super().childUsed(child)
    662 
    663     def export(self):
    664         subStateNames = self._getElementNames(self.currentState)
    665         return Criterion(self.name, self.isInclusif, subStateNames, subStateNames,
    666             ignoreIntegrity=True)
    667 
    668     def stateIncludes(self, subStateName):
    669         subStateCurrentNames = list(self._getElementNames(self.currentState))
    670 
    671         self.debug("Testing if %s is included in %s" % (subStateName, subStateCurrentNames))
    672 
    673         isIncluded = subStateName in subStateCurrentNames
    674         self.debug("IsIncluded: %s" % isIncluded)
    675 
    676         return isIncluded
    677 
    678 
    679     def stateIs(self, subStateNames):
    680         if len(self.currentState) != 1 :
    681             return False
    682         else :
    683             return self.stateIncludes(subStateNames)
    684 
    685     def _getXMLAttributes(self):
    686         attributes = super()._getXMLAttributes()
    687         attributes["Type"] = self.inclusivenessTranslate[self.isInclusif]
    688         return attributes
    689 
    690 
    691 class Criteria(Element):
    692     tag = "Criteria"
    693 
    694     class DuplicatedCriterionError(DuplicatedChildError):
    695         pass
    696 
    697     def export(self):
    698         self.debug("Exporting criteria")
    699         assert(self.children)
    700 
    701         exported = Criteria(self.name)
    702         for child in self.children :
    703             exported.addChild(child.export())
    704         return exported
    705 
    706     def addChild(self, child):
    707         if child in self.children:
    708             raise self.DuplicatedCriterionError(self, child)
    709         super().addChild(child)
    710 
    711 class ConfigAppliedWithoutCriteriaError(CustomError):
    712     def __init__(self, configurationName, domainName):
    713         self.configurationName = configurationName
    714         self.domainName = domainName
    715     def __str__(self):
    716         return ('Applying configuration "%s" from domain "%s" before declaring criteria' %
    717                 (self.configurationName, self.domainName))
    718 
    719 class ParsePFWlog():
    720     MATCH = "match"
    721     ACTION = "action"
    722 
    723     class ChangeRequestOnUnknownCriterion(CustomError):
    724         def __init__(self, criterion):
    725             self.criterion = criterion
    726 
    727         def __str__(self):
    728             return ("Change request on an unknown criterion %s." %
    729                 self.criterion)
    730 
    731     def __init__(self, domains, criteria, ErrorsToIgnore=()):
    732 
    733         self.domains = domains;
    734         self.criteria = criteria;
    735         self.ErrorsToIgnore = ErrorsToIgnore
    736 
    737         configApplicationRegext = r""".*Applying configuration "(.*)" from domain "([^"]*)"""
    738         matchConfigApplicationLine = re.compile(configApplicationRegext).match
    739 
    740         criterionCreationRegext = ", ".join([
    741                     r""".*Criterion name: (.*)""",
    742                     r"""type kind: (.*)""",
    743                     r"""current state: (.*)""",
    744                     r"""states: {(.*)}"""
    745                 ])
    746         matchCriterionCreationLine = re.compile(criterionCreationRegext).match
    747 
    748         changingCriterionRegext = r""".*Selection criterion changed event: Criterion name: (.*), current state: ([^\n\r]*)"""
    749         matchChangingCriterionLine = re.compile(changingCriterionRegext).match
    750 
    751         self.lineLogTypes = [
    752                     {
    753                         self.MATCH: matchConfigApplicationLine,
    754                         self.ACTION: self._configApplication
    755                     }, {
    756                         self.MATCH: matchCriterionCreationLine,
    757                         self.ACTION: self._criterionCreation
    758                     }, {
    759                         self.MATCH: matchChangingCriterionLine,
    760                         self.ACTION: self._changingCriterion
    761                     }
    762                 ]
    763 
    764     @staticmethod
    765     def _formatCriterionList(liststring, separator):
    766         list = liststring.split(separator)
    767         if len(list) == 1 and list[0] == "<none>":
    768             list = []
    769         return list
    770 
    771     def _criterionCreation(self, matchCriterionCreation):
    772         # Unpack
    773         criterionName, criterionType, currentCriterionStates, criterionStates = matchCriterionCreation.group(1, 2, 3, 4)
    774 
    775         criterionStateList = self._formatCriterionList(criterionStates, ", ")
    776 
    777         criterionIsInclusif = {"exclusive" : False, "inclusive" : True}[criterionType]
    778 
    779         currentcriterionStateList = self._formatCriterionList(currentCriterionStates, "|")
    780 
    781         logger.info("Creating criterion: " + criterionName +
    782                     " (" + criterionType + ") " +
    783                     " with current state: " + str(currentcriterionStateList) +
    784                     ", possible states:" + str(criterionStateList))
    785 
    786         try:
    787             self.criteria.addChild(Criterion(
    788                     criterionName,
    789                     criterionIsInclusif,
    790                     criterionStateList,
    791                     currentcriterionStateList
    792                 ))
    793         except self.criteria.DuplicatedCriterionError as ex:
    794             logger.debug(ex)
    795             logger.warning("Reseting criterion %s. Did you reset the PFW ?" % criterionName)
    796             self.criteria.operationOnChild(
    797                 [criterionName],
    798                 lambda criterion: criterion.reset()
    799             )
    800 
    801 
    802 
    803     def _changingCriterion(self, matchChangingCriterion):
    804         # Unpack
    805         criterionName, newCriterionSubStateNames = matchChangingCriterion.group(1, 2)
    806 
    807         newCriterionState = self._formatCriterionList(newCriterionSubStateNames, "|")
    808 
    809         logger.info("Changing criterion %s to %s" % (criterionName , newCriterionState))
    810 
    811         path = [criterionName]
    812         changeCriterionOperation = lambda criterion : criterion.changeState(newCriterionState)
    813         try:
    814             self.criteria.operationOnChild(path, changeCriterionOperation)
    815         except ChildNotFoundError:
    816             raise self.ChangeRequestOnUnknownCriterion(criterionName)
    817 
    818     def _configApplication(self, matchConfig):
    819         # Unpack
    820         configurationName, domainName = matchConfig.group(1, 2)
    821 
    822         # Check that at least one criterion exist
    823         if not self.criteria.hasChildren() :
    824             logger.error("Applying configuration before declaring criteria")
    825             logger.info("Is the log starting at PFW boot ?")
    826             raise ConfigAppliedWithoutCriteriaError(configurationName, domainName)
    827 
    828         # Change criterion state
    829         path = [domainName, configurationName]
    830         usedOperation = lambda element : element.used(self.criteria)
    831 
    832         logger.info("Applying configuration %s from domain %s" % (
    833                 configurationName, domainName))
    834 
    835         self.domains.operationOnChild(path, usedOperation)
    836 
    837 
    838     def _digest(self, lineLogType, lineLog):
    839 
    840         match = lineLogType[self.MATCH](lineLog)
    841         if match :
    842             lineLogType[self.ACTION](match)
    843             return True
    844         return False
    845 
    846 
    847     def parsePFWlog(self, lines):
    848         for lineNb, lineLog in enumerate(lines, 1): # line number starts at 1
    849 
    850             logger.debug("Parsing line :%s" % lineLog.rstrip())
    851 
    852             digested = (self._digest(lineLogType, lineLog)
    853                     for lineLogType in self.lineLogTypes)
    854 
    855             try:
    856                 success = any(digested)
    857 
    858             # Catch some exception in order to print the current parsing line,
    859             # then raise the exception again if not continue of error
    860             except CustomError as ex:
    861                 logger.error('Error raised while parsing line %s: "%s"' %
    862                             (lineNb, repr(lineLog)))
    863 
    864                 # If exception is a subclass of ErrorsToIgnore, log it and continue
    865                 # otherwise raise it again.
    866                 if not issubclass(type(ex), self.ErrorsToIgnore):
    867                     raise ex
    868                 else:
    869                     logger.error('Ignoring exception:"%s", '
    870                                 'can not guarantee database integrity' % ex)
    871             else:
    872                 if not success:
    873                     logger.debug("Line does not match, dropped")
    874 
    875 
    876 class Root(Element):
    877     tag = "CoverageReport"
    878     def __init__(self, name, dom):
    879         super().__init__(name)
    880         # Create domain tree
    881         self.domains = Domains("Domains")
    882         self.domains.populate(dom)
    883         self.addChild(self.domains)
    884         # Create criterion list
    885         self.criteria = Criteria("CriterionRoot")
    886         self.addChild(self.criteria)
    887 
    888     def exportToXML(self):
    889         """Export tree to an xml document"""
    890         impl = xml.dom.minidom.getDOMImplementation()
    891         document = impl.createDocument(namespaceURI=None, qualifiedName=self.tag, doctype=None)
    892         super().exportToXML(document, document.documentElement)
    893 
    894         return document
    895 
    896 # ============================
    897 # Command line argument parser
    898 # ============================
    899 
    900 
    901 class ArgumentParser:
    902     """class that parse command line arguments with argparse library
    903 
    904     Result of parsing are the class attributes.
    905     """
    906     levelTranslate = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
    907 
    908     def __init__(self):
    909 
    910         try:
    911             # As argparse is only in the stdlib since python 3.2,
    912             # testing its availability
    913             import argparse
    914 
    915         except ImportError:
    916             logger.warning("Unable to import argparse "
    917                            "(parser for command-line options and arguments), "
    918                            "using default argument values:")
    919 
    920             logger.warning(" - InputFile: stdin")
    921             self.inputFile = sys.stdin
    922 
    923             logger.warning(" - OutputFile: stdout")
    924             self.outputFile = sys.stdout
    925 
    926             try:
    927                 self.domainsFile = sys.argv[1]
    928             except IndexError as ex:
    929                 logger.fatal("No domain file provided (first argument)")
    930                 raise ex
    931             else:
    932                 logger.warning(" - Domain file: " + self.domainsFile)
    933 
    934             logger.warning(" - Output format: xml")
    935             self.XMLreport = True
    936 
    937             logger.warning(" - Debug level: error")
    938             self.debugLevel = logging.ERROR
    939         else :
    940 
    941             myArgParser = argparse.ArgumentParser(description='Generate PFW report')
    942 
    943             myArgParser.add_argument(
    944                         'domainsFile',
    945                         type=argparse.FileType('r'),
    946                         help="the PFW domain XML file"
    947                     )
    948             myArgParser.add_argument(
    949                         'pfwlog', nargs='?',
    950                         type=argparse.FileType('r'), default=sys.stdin,
    951                         help="the PFW log file, default stdin"
    952                     )
    953             myArgParser.add_argument(
    954                         '-o', '--output',
    955                         dest="outputFile",
    956                         type=argparse.FileType('w'), default=sys.stdout,
    957                         help="the coverage report output file, default stdout"
    958                     )
    959             myArgParser.add_argument(
    960                         '-v', '--verbose',
    961                         dest="debugLevel", default=0,
    962                         action='count',
    963                         help="print debug warnings from warning (default) to debug (-vv)"
    964                     )
    965 
    966             outputFormatGroupe = myArgParser.add_mutually_exclusive_group(required=False)
    967 
    968             outputFormatGroupe.add_argument(
    969                         '--xml',
    970                         dest="xmlFlag",
    971                         action='store_true',
    972                         help=" XML coverage output report"
    973                     )
    974             outputFormatGroupe.add_argument(
    975                         '--raw',
    976                         dest="rawFlag",
    977                         action='store_true',
    978                         help="raw coverage output report"
    979                     )
    980 
    981             myArgParser.add_argument(
    982                         '--ignore-unknown-criterion',
    983                         dest="unknwonCriterionFlag",
    984                         action='store_true',
    985                         help="ignore unknown criterion"
    986                     )
    987 
    988             myArgParser.add_argument(
    989                         '--ignore-incoherent-criterion-state',
    990                         dest="incoherentCriterionFlag",
    991                         action='store_true',
    992                         help="ignore criterion transition to incoherent state"
    993                     )
    994 
    995             myArgParser.add_argument(
    996                         '--ignore-ineligible-configuration-application',
    997                         dest="ineligibleConfigurationApplicationFlag",
    998                         action='store_true',
    999                         help="ignore application of configuration with a false rule "
   1000                         "(not applicable configuration)"
   1001                     )
   1002 
   1003             # Process command line arguments
   1004             options = myArgParser.parse_args()
   1005 
   1006             # Mapping to attributes
   1007             self.inputFile = options.pfwlog
   1008             self.outputFile = options.outputFile
   1009             self.domainsFile = options.domainsFile
   1010 
   1011             # Output report in xml if flag not set
   1012             self.XMLreport = not options.rawFlag
   1013 
   1014             # Setting logger level
   1015             levelCapped = min(options.debugLevel, len(self.levelTranslate) - 1)
   1016             self.debugLevel = self.levelTranslate[levelCapped]
   1017 
   1018             # Setting ignore options
   1019             errorToIgnore = []
   1020             if options.ineligibleConfigurationApplicationFlag :
   1021                 errorToIgnore.append(Configuration.IneligibleConfigurationAppliedError)
   1022 
   1023             if options.incoherentCriterionFlag:
   1024                 errorToIgnore.append(Criterion.ChangeRequestToNonAccessibleState)
   1025 
   1026             if options.unknwonCriterionFlag:
   1027                 errorToIgnore.append(ParsePFWlog.ChangeRequestOnUnknownCriterion)
   1028 
   1029             self.errorToIgnore = tuple(errorToIgnore)
   1030 
   1031 
   1032 
   1033 def main():
   1034 
   1035     errorDuringLogParsing = -1
   1036     errorDuringArgumentParsing = 1
   1037 
   1038     try:
   1039         commandLineArguments = ArgumentParser()
   1040     except LookupError as ex:
   1041         logger.error("Error during argument parsing")
   1042         logger.debug(str(ex))
   1043         sys.exit(errorDuringArgumentParsing)
   1044 
   1045     # Setting logger level
   1046     logger.setLevel(commandLineArguments.debugLevel)
   1047     logger.info("Log level set to: %s" %
   1048             logging.getLevelName(commandLineArguments.debugLevel))
   1049 
   1050     # Create tree from XML
   1051     dom = xml.dom.minidom.parse(commandLineArguments.domainsFile)
   1052 
   1053     # Create element tree
   1054     root = Root("DomainCoverage", dom)
   1055 
   1056     # Parse PFW events
   1057     parser = ParsePFWlog(root.domains, root.criteria, commandLineArguments.errorToIgnore)
   1058 
   1059     try:
   1060         parser.parsePFWlog(commandLineArguments.inputFile.readlines())
   1061     except CustomError as ex:
   1062         logger.fatal("Error during parsing log file %s: %s" %
   1063             (commandLineArguments.inputFile, ex))
   1064         sys.exit(errorDuringLogParsing)
   1065 
   1066     # Output report
   1067     outputFile = commandLineArguments.outputFile
   1068 
   1069     if not commandLineArguments.XMLreport :
   1070         outputFile.write("%s\n" % root.dump(withCoverage=True, withNbUse=True))
   1071     else :
   1072         outputFile.write(root.exportToXML().toprettyxml())
   1073 
   1074 
   1075 if __name__ == "__main__" :
   1076     """ Execute main if the python interpreter is running this module as the main program """
   1077     main()
   1078 
   1079