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(str(dumpedDescription) for dumpedDescription in 204 self._dumpDescription(withCoverage, withNbUse)) 205 206 def exportToXML(self, document, domElement=None): 207 if domElement == None: 208 domElement = document.createElement(self.tag) 209 210 self._XMLaddAttributes(domElement) 211 212 for child in self.children: 213 domElement.appendChild(child.exportToXML(document)) 214 215 return domElement 216 217 def _XMLaddAttributes(self, domElement): 218 attributes = self._getXMLAttributes() 219 220 coverage = self._getCoverage() 221 if coverage != None: 222 attributes["Coverage"] = self._number2percent(coverage) 223 224 for key, value in attributes.items(): 225 domElement.setAttribute(key, value) 226 227 def _getXMLAttributes(self): 228 return {"Name": self.name, 229 "NbUse": str(self.nbUse) 230 } 231 232 def _incNbUse(self): 233 self.nbUse += 1 234 235 def childUsed(self, child): 236 self._incNbUse() 237 # Propagate to parent 238 self._tellParentThatChildUsed() 239 240 def _tellParentThatChildUsed(self): 241 if self.parent: 242 self.parent.childUsed(self) 243 244 245 def parentUsed(self): 246 self._incNbUse() 247 # Propagate to children 248 for child in self.children: 249 child.parentUsed() 250 251 def hasBeenUsed(self): 252 return self.nbUse > 0 253 254 def operationOnChild(self, path, operation): 255 256 if path: 257 return self._operationPropagate(path, operation) 258 else: 259 self.debug("operating on self") 260 return operation(self) 261 262 def _operationPropagate(self, path, operation): 263 264 childName = path.pop(0) 265 child = self.getChildFromName(childName) 266 267 return child.operationOnChild(path, operation) 268 269 270 271 def debug(self, stringOrFunction, level=logging.DEBUG): 272 """Print a debug line on stderr in tree form 273 274 If the debug line is expensive to generate, provide callable 275 object, it will be called if log is enable for this level. 276 This callable object should return the logline string. 277 """ 278 if logger.isEnabledFor(level): 279 280 # TODO: use buildin callable if python >= 3.2 281 if hasattr(stringOrFunction, "__call__"): 282 string = stringOrFunction() 283 else: 284 string = stringOrFunction 285 286 rankedLine = DebugRankedLine("%s: %s" % (self, string)) 287 self._logDebug(rankedLine, level) 288 289 def _logDebug(self, rankedLine, level): 290 291 if self.parent: 292 self.parent._logDebug(rankedLine.increasedRank(), level) 293 else: 294 logger.log(level, str(rankedLine)) 295 296 297 298 299class FromDomElement(Element): 300 def __init__(self, DomElement): 301 self._initFromDom(DomElement) 302 super().__init__(self.name) 303 304 305 def _initFromDom(self, DomElement): 306 self.name = DomElement.getAttribute("Name") 307 308 309 310class DomElementLocation(): 311 def __init__(self, classConstructor, path=None): 312 self.classConstructor = classConstructor 313 if path: 314 self.path = path 315 else: 316 self.path = [] 317 318 self.path.append(classConstructor.tag) 319 320 321class DomPopulatedElement(Element): 322 """Default child populate 323 324 Look for each dom element with tag specified in self.tag 325 and instantiate it with the dom element 326 """ 327 childClasses = [] 328 329 def populate(self, dom): 330 331 for childDomElementLocation in self.childClasses: 332 333 self.debug("Looking for child %s in path %s" % ( 334 childDomElementLocation.path[-1], childDomElementLocation.path)) 335 336 for childDomElement in self._findChildFromTagPath(dom, childDomElementLocation.path): 337 338 childElement = childDomElementLocation.classConstructor(childDomElement) 339 self.addChild(childElement) 340 341 childElement.populate(childDomElement) 342 343 def _findChildFromTagPath(self, dom, path): 344 if not path: 345 yield dom 346 else: 347 # Copy list 348 path = list(path) 349 350 tag = path.pop(0) 351 352 # Find element with tag 353 self.debug("Going to find elements with tag %s in %s" % (tag, dom)) 354 self.debug(lambda: "Nb of solutions: %s" % len(dom.getElementsByTagName(tag))) 355 356 for elementByTag in dom.getElementsByTagName(tag): 357 358 self.debug("Found element: %s" % elementByTag) 359 360 # If the same tag is found 361 if elementByTag in dom.childNodes: 362 363 # Yield next level 364 for element in self._findChildFromTagPath(elementByTag, path): 365 yield element 366 367 368class Rule(Element): 369 370 def usedIfApplicable(self, criteria): 371 childApplicability = (child.usedIfApplicable(criteria) 372 for child in self.children) 373 374 isApplicable = self._isApplicable(criteria, childApplicability) 375 376 if isApplicable: 377 self._incNbUse() 378 379 self.debug("Rule applicability: %s" % isApplicable) 380 assert(isApplicable == True or isApplicable == False) 381 382 return isApplicable 383 384 385 def _isApplicable(self, criteria, childApplicability): 386 """Return the rule applicability depending on children applicability. 387 388 If at least one child is applicable, return true""" 389 # Lazy evaluation as in the PFW 390 return all(childApplicability) 391 392 393class CriterionRule(FromDomElement, DomPopulatedElement, Rule): 394 tag = "SelectionCriterionRule" 395 childClasses = [] 396 isApplicableOperations = { 397 "Includes" : lambda criterion, value: criterion.stateIncludes(value), 398 "Excludes" : lambda criterion, value: not criterion.stateIncludes(value), 399 "Is" : lambda criterion, value: criterion.stateIs(value), 400 "IsNot" : lambda criterion, value: not criterion.stateIs(value) 401 } 402 403 def _initFromDom(self, DomElement): 404 self.selectionCriterion = DomElement.getAttribute("SelectionCriterion") 405 self.matchesWhen = DomElement.getAttribute("MatchesWhen") 406 self.value = DomElement.getAttribute("Value") 407 self.name = "%s %s %s" % (self.selectionCriterion, self.matchesWhen, self.value) 408 409 applicableOperationWithoutValue = self.isApplicableOperations[self.matchesWhen] 410 self.isApplicableOperation = lambda criterion: applicableOperationWithoutValue(criterion, 411 self.value) 412 413 def _isApplicable(self, criteria, childApplicability): 414 415 return criteria.operationOnChild([self.selectionCriterion], self.isApplicableOperation) 416 417 418class CompoundRule(FromDomElement, DomPopulatedElement, Rule): 419 """CompoundRule can be of type ALL or ANY""" 420 tag = "CompoundRule" 421 # Declare childClasses but define it at first class instantiation 422 childClasses = None 423 424 def __init__(self, dom): 425 # Define childClasses at first class instantiation 426 if self.childClasses == None: 427 self.childClasses = [DomElementLocation(CriterionRule), 428 DomElementLocation(CompoundRule)] 429 super().__init__(dom) 430 431 def _initFromDom(self, DomElement): 432 433 type = DomElement.getAttribute("Type") 434 self.ofTypeAll = {"All" : True, "Any" : False}[type] 435 self.name = type 436 437 def _isApplicable(self, criteria, childApplicability): 438 if self.ofTypeAll: 439 applicability = super()._isApplicable(criteria, childApplicability) 440 else: 441 # Lazy evaluation as in the PFW 442 applicability = any(childApplicability) 443 444 return applicability 445 446class RootRule(DomPopulatedElement, Rule): 447 tag = "RootRule" 448 childClasses = [DomElementLocation(CompoundRule)] 449 450 def populate(self, dom): 451 super().populate(dom) 452 self.debug("Children: %s" % self.children) 453 # A configuration can only have one or no rule 454 assert(len(self.children) <= 1) 455 456 def _getCoverageDependanceElements(self): 457 return self._getDescendants() 458 459 460class CriteronStates(Element): 461 """Root of configuration application criterion state""" 462 tag = "CriterionStates" 463 464 def parentUsed(self, criteria): 465 """Add criteria to child if not exist, if exist increase it's nbUse""" 466 self._incNbUse() 467 468 matches = [child for child in self.children if child == criteria] 469 470 assert(len(matches) <= 1) 471 472 if matches: 473 self.debug("Criteria state has already been encounter") 474 currentcriteria = matches[0] 475 else: 476 self.debug("Criteria state has never been encounter, saving it") 477 currentcriteria = criteria 478 self.addChild(criteria) 479 480 currentcriteria.parentUsed() 481 482 483 484class Configuration(FromDomElement, DomPopulatedElement): 485 tag = "Configuration" 486 childClasses = [] 487 488 class IneligibleConfigurationAppliedError(CustomError): 489 490 def __init__(self, configuration, criteria): 491 self.configuration = configuration 492 self.criteria = criteria 493 494 def __str__(self): 495 496 return ("Applying ineligible %s, " 497 "rule:\n%s\n" 498 "Criteria current state:\n%s" % ( 499 self.configuration, 500 self.configuration.rootRule.dump(withCoverage=False, withNbUse=False), 501 self.criteria.dump(withCoverage=False, withNbUse=False) 502 )) 503 504 def __init__(self, DomElement): 505 super().__init__(DomElement) 506 507 self.rootRule = RootRule("RootRule") 508 self.addChild(self.rootRule) 509 510 self.criteronStates = CriteronStates("CriterionStates") 511 self.addChild(self.criteronStates) 512 513 def populate(self, dom): 514 # Delegate to rootRule 515 self.rootRule.populate(dom) 516 517 def _getCoverage(self): 518 # Delegate to rootRule 519 return self.rootRule._getCoverage() 520 521 def used(self, criteria): 522 523 self._incNbUse() 524 525 # Propagate use to parents 526 self._tellParentThatChildUsed() 527 528 # Propagate to criterion coverage 529 self.criteronStates.parentUsed(criteria.export()) 530 531 # Propagate to rules 532 if not self.rootRule.usedIfApplicable(criteria): 533 534 self.debug("Applied but rule does not match current " 535 "criteria (parent: %s) " % self.parent.name, 536 logging.ERROR) 537 538 raise self.IneligibleConfigurationAppliedError(self, criteria.export()) 539 540 def _dumpPropagate(self, withCoverage, withNbUse): 541 self.debug("Going to ask %s for description" % self.rootRule) 542 for dumpedDescription in self.rootRule._dumpDescription( 543 withCoverage=withCoverage, 544 withNbUse=withNbUse): 545 yield dumpedDescription.increasedRank() 546 547 self.debug("Going to ask %s for description" % self.criteronStates) 548 for dumpedDescription in self.criteronStates._dumpDescription( 549 withCoverage=False, 550 withNbUse=withNbUse): 551 yield dumpedDescription.increasedRank() 552 553 554class Domain(FromDomElement, DomPopulatedElement): 555 tag = "ConfigurableDomain" 556 childClasses = [DomElementLocation(Configuration, ["Configurations"])] 557 558 559class Domains(DomPopulatedElement): 560 tag = "Domains" 561 childClasses = [DomElementLocation(Domain, ["ConfigurableDomains"])] 562 563 564class RankedLine(): 565 def __init__(self, string, 566 stringPrefix="|-- ", 567 rankString="| ", 568 linePrefix="", 569 lineSuffix="\n"): 570 self.string = string 571 self.rank = 0 572 self.stringPrefix = stringPrefix 573 self.rankString = rankString 574 self.linePrefix = linePrefix 575 self.lineSuffix = lineSuffix 576 577 def increasedRank(self): 578 self.rank += 1 579 return self 580 581 def __str__(self): 582 return self.linePrefix + \ 583 self.rank * self.rankString + \ 584 self.stringPrefix + \ 585 self.string + \ 586 self.lineSuffix 587 588class DebugRankedLine(RankedLine): 589 590 def __init__(self, string, lineSuffix=""): 591 super().__init__(string, 592 stringPrefix="", 593 rankString=" ", 594 linePrefix="", 595 lineSuffix=lineSuffix) 596 597 598class CriterionState(Element): 599 tag = "CriterionState" 600 def used(self): 601 self._incNbUse() 602 603 604class Criterion(Element): 605 tag = "Criterion" 606 inclusivenessTranslate = {True: "Inclusive", False: "Exclusive"} 607 608 class ChangeRequestToNonAccessibleState(CustomError): 609 def __init__(self, requestedState, detail): 610 self.requestedState = requestedState 611 self.detail = detail 612 613 def __str__(self): 614 return ("Change request to non accessible state %s. Detail: %s" % 615 (self.requestedState, self.detail)) 616 617 def __init__(self, name, isInclusif, 618 stateNamesList, currentStateNamesList, 619 ignoreIntegrity=False): 620 super().__init__(name) 621 self.isInclusif = isInclusif 622 623 for state in stateNamesList: 624 self.addChild(CriterionState(state)) 625 626 self.currentState = [] 627 self.initStateNamesList = list(currentStateNamesList) 628 self.changeState(self.initStateNamesList, ignoreIntegrity) 629 630 def reset(self): 631 # Set current state as provided at initialisation 632 self.changeState(self.initStateNamesList, ignoreIntegrity=True) 633 634 def changeState(self, subStateNames, ignoreIntegrity=False): 635 self.debug("Changing state from: %s to: %s" % ( 636 list(self._getElementNames(self.currentState)), 637 subStateNames)) 638 639 if not ignoreIntegrity and not self.isIntegre(subStateNames): 640 raise self.ChangeRequestToNonAccessibleState(subStateNames, 641 "An exclusive criterion must have a non \ 642 empty state") 643 644 newCurrentState = [] 645 for subStateName in subStateNames: 646 subState = self.getChildFromName(subStateName) 647 subState.used() 648 newCurrentState.append(subState) 649 650 self.currentState = newCurrentState 651 652 self._incNbUse() 653 self._tellParentThatChildUsed() 654 655 def isIntegre(self, subStateNames): 656 return self.isInclusif or len(subStateNames) == 1 657 658 def childUsed(self, child): 659 self.currentState = child 660 super().childUsed(child) 661 662 def export(self): 663 subStateNames = self._getElementNames(self.currentState) 664 return Criterion(self.name, self.isInclusif, subStateNames, subStateNames, 665 ignoreIntegrity=True) 666 667 def stateIncludes(self, subStateName): 668 subStateCurrentNames = list(self._getElementNames(self.currentState)) 669 670 self.debug("Testing if %s is included in %s" % (subStateName, subStateCurrentNames)) 671 672 isIncluded = subStateName in subStateCurrentNames 673 self.debug("IsIncluded: %s" % isIncluded) 674 675 return isIncluded 676 677 678 def stateIs(self, subStateNames): 679 if len(self.currentState) != 1: 680 return False 681 else: 682 return self.stateIncludes(subStateNames) 683 684 def _getXMLAttributes(self): 685 attributes = super()._getXMLAttributes() 686 attributes["Type"] = self.inclusivenessTranslate[self.isInclusif] 687 return attributes 688 689 690class Criteria(Element): 691 tag = "Criteria" 692 693 class DuplicatedCriterionError(DuplicatedChildError): 694 pass 695 696 def export(self): 697 self.debug("Exporting criteria") 698 assert(self.children) 699 700 exported = Criteria(self.name) 701 for child in self.children: 702 exported.addChild(child.export()) 703 return exported 704 705 def addChild(self, child): 706 if child in self.children: 707 raise self.DuplicatedCriterionError(self, child) 708 super().addChild(child) 709 710class ConfigAppliedWithoutCriteriaError(CustomError): 711 def __init__(self, configurationName, domainName): 712 self.configurationName = configurationName 713 self.domainName = domainName 714 def __str__(self): 715 return ('Applying configuration "%s" from domain "%s" before declaring criteria' % 716 (self.configurationName, self.domainName)) 717 718class ParsePFWlog(): 719 MATCH = "match" 720 ACTION = "action" 721 722 class ChangeRequestOnUnknownCriterion(CustomError): 723 def __init__(self, criterion): 724 self.criterion = criterion 725 726 def __str__(self): 727 return ("Change request on an unknown criterion %s." % 728 self.criterion) 729 730 def __init__(self, domains, criteria, ErrorsToIgnore=()): 731 732 self.domains = domains 733 self.criteria = criteria 734 self.ErrorsToIgnore = ErrorsToIgnore 735 736 configApplicationRegext = r""".*Applying configuration "(.*)" from domain "([^"]*)""" 737 matchConfigApplicationLine = re.compile(configApplicationRegext).match 738 739 criterionCreationRegext = ", ".join([ 740 r""".*Criterion name: (.*)""", 741 r"""type kind: (.*)""", 742 r"""current state: (.*)""", 743 r"""states: {(.*)}""" 744 ]) 745 matchCriterionCreationLine = re.compile(criterionCreationRegext).match 746 747 changingCriterionRegext = r""".*Selection criterion changed event: Criterion name: (.*), \ 748 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