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""" 33Generate a coverage report by parsing parameter framework log. 34 35The coverage report contains the: 36 - domain 37 - configuration 38 - rule 39 - criterion 40basic coverage statistics. 41""" 42 43import xml.dom.minidom 44import sys 45import re 46import logging 47 48FORMAT = '%(levelname)s: %(message)s' 49logging.basicConfig(stream=sys.stderr, level=logging.WARNING, format=FORMAT) 50logger = logging.getLogger("Coverage") 51 52class CustomError(Exception): 53 pass 54 55class ChildError(CustomError): 56 def __init__(self, parent, child): 57 self.parent = parent 58 self.child = child 59 60class ChildNotFoundError(ChildError): 61 def __str__(self): 62 return 'Unable to find the child "%s" in "%s"' % (self.child, self.parent) 63 64class DuplicatedChildError(ChildError): 65 def __str__(self): 66 return 'Add existing child "%s" in "%s".' % (self.child, self.parent) 67 68class 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 301class 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 312class 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 323class 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 370class 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 395class 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 420class 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 448class 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 462class 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 486class 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 556class Domain(FromDomElement, DomPopulatedElement): 557 tag = "ConfigurableDomain" 558 childClasses = [DomElementLocation(Configuration, ["Configurations"])] 559 560 561class Domains(DomPopulatedElement): 562 tag = "Domains" 563 childClasses = [DomElementLocation(Domain, ["ConfigurableDomains"])] 564 565 566class 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 590class DebugRankedLine(RankedLine): 591 592 def __init__(self, string, lineSuffix=""): 593 super().__init__(string, 594 stringPrefix="", 595 rankString=" ", 596 linePrefix="", 597 lineSuffix=lineSuffix) 598 599 600class CriterionState(Element): 601 tag = "CriterionState" 602 def used(self): 603 self._incNbUse() 604 605 606class 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 691class 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 711class 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 719class 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 876class 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 901class 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 1033def 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 1075if __name__ == "__main__" : 1076 """ Execute main if the python interpreter is running this module as the main program """ 1077 main() 1078 1079