blob: 6c4345cc7324a435c9bf66d9e8107f5a853125c9 [file] [log] [blame]
Kevin Rocard326e39e2012-12-14 16:11:05 +01001#!/usr/bin/env python3
2
Kevin Rocardfe753d42013-02-08 11:37:39 +01003# INTEL CONFIDENTIAL
4# Copyright 2013 Intel
5# Corporation All Rights Reserved.
6#
7# The source code contained or described herein and all documents related to
8# the source code ("Material") are owned by Intel Corporation or its suppliers
9# or licensors. Title to the Material remains with Intel Corporation or its
10# suppliers and licensors. The Material contains trade secrets and proprietary
11# and confidential information of Intel or its suppliers and licensors. The
12# Material is protected by worldwide copyright and trade secret laws and
13# treaty provisions. No part of the Material may be used, copied, reproduced,
14# modified, published, uploaded, posted, transmitted, distributed, or
15# disclosed in any way without Intels prior express written permission.
16#
17# No license under any patent, copyright, trade secret or other intellectual
18# property right is granted to or conferred upon you by disclosure or delivery
19# of the Materials, either expressly, by implication, inducement, estoppel or
20# otherwise. Any license under such intellectual property rights must be
21# express and approved by Intel in writing.
22
Kevin Rocard326e39e2012-12-14 16:11:05 +010023"""
24Generate a coverage report by parsing parameter framework log.
25
26The coverage report contains the:
27 - domain
28 - configuration
29 - rule
30 - criterion
31basic coverage statistics.
32"""
33
34import xml.dom.minidom
35import sys
36import re
37import logging
38
39FORMAT = '%(levelname)s: %(message)s'
40logging.basicConfig(stream=sys.stderr, level=logging.WARNING, format=FORMAT)
41logger = logging.getLogger("Coverage")
42
Kevin Rocard3aa0db42013-02-13 17:15:38 +010043class CustomError(Exception):
44 pass
45
46class ChildError(CustomError):
Kevin Rocard326e39e2012-12-14 16:11:05 +010047 def __init__(self, parent, child):
48 self.parent = parent
49 self.child = child
Kevin Rocard3aa0db42013-02-13 17:15:38 +010050
51class ChildNotFoundError(ChildError):
Kevin Rocard326e39e2012-12-14 16:11:05 +010052 def __str__(self):
Kevin Rocard7784f4a2013-06-10 15:09:16 +020053 return "Unable to find the child %s in %s" % (self.child, self.parent)
Kevin Rocard3aa0db42013-02-13 17:15:38 +010054
55class DuplicatedChildError(ChildError):
56 def __str__(self):
Kevin Rocard7784f4a2013-06-10 15:09:16 +020057 return "Add existing child %s in %s." % (self.child, self.parent)
Kevin Rocard326e39e2012-12-14 16:11:05 +010058
59class Element():
60 """Root class for all coverage elements"""
61 tag = "element"
62
63 def __init__(self, name):
64
65 self.parent = None
66 self.children = []
67
68 self.nbUse = 0
69
70 self.name = name
71
72 self.debug("New element")
73
74
75 def __str__(self):
76 return "%s (%s)" % (self.name, self.tag)
77
78 def __eq__(self, compared):
79 self.debug(lambda : "Comparing:\n%s" % self.dump())
80 self.debug(lambda : "With:\n%s" % compared.dump())
81 result = self.name == compared.name and self.children == compared.children
82 self.debug("Result is %s" % result)
83 return result
84
85
86 def getName(self, default=""):
87 return self.name or default
88
89 def hasChildren(self):
90 return bool(self.children)
91
92 def getChildren(self):
93 return self.children
94
95 def _getDescendants(self):
96 for child in self.children:
97 yield child
98 for descendant in child._getDescendants() :
99 yield descendant
100
101 def getChildFromName(self, childName):
102
103 for child in self.children :
104
105 if child.getName() == childName :
106 return child
107
108 self.debug("Child %s not found" % childName, logging.ERROR)
109
110 self.debug("Child list :")
111
112 for child in self.children :
113 self.debug(" - %s" % child)
114
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100115 raise ChildNotFoundError(self, childName)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100116
117
118 def addChild(self, child):
119 self.debug("new child: " + child.name)
120 self.children.append(child)
121 child._adoptedBy(self)
122
123 def _adoptedBy(self, parent):
124 assert(not self.parent)
125 self.parent = parent
126
127 def _getElementNames(self, elementList):
128 return (substate.name for substate in elementList)
129
130 def _description(self, withCoverage, withNbUse):
131 description = self.name
132
133 if withNbUse or withCoverage :
134 description += " has been used " + str(self.nbUse) + " time"
135
136 if withCoverage :
137 description += self._coverageFormating(self._getCoverage())
138
139 return description
140
141
142 def _getCoverage(self):
143
144 coverageDependance = list(self._getCoverageDependance())
145
146 nbcoverageDependence = len(coverageDependance)
147
148 if nbcoverageDependence == 0:
149 # Coverage makes no sense without any dependence
150 return None
151
152 nbcoverageDependanceUsed = len([element
153 for element in coverageDependance
154 if element.hasBeenUsed()])
155
156 return nbcoverageDependanceUsed / nbcoverageDependence
157
158 def _getCoverageDependance(self):
159 return self.children
160
161 def _coverageFormating(self, coverage):
162 # If no coverage provided
163 if not coverage :
164 return ""
165
166 # Calculate coverage
167 return " (%s coverage)" % self._number2percent(coverage)
168
169 @staticmethod
170 def _number2percent(number):
171 """Format a number to a integer % string
172
173 example: _number2percent(0.6666) -> "67%"
174 """
175 return "{0:.0f}%".format(100 * number)
176
177
178 def _dumpDescription(self, withCoverage, withNbUse):
179
180 self.debug("yelding description")
181 yield RankedLine(self._description(withCoverage, withNbUse), lineSuffix="")
182
183 for dumped in self._dumpPropagate(withCoverage, withNbUse) :
184 yield dumped
185
186 def _dumpPropagate(self, withCoverage, withNbUse):
187
188 for child in self.children :
189 for dumpedDescription in child._dumpDescription(withCoverage, withNbUse) :
190 yield dumpedDescription.increasedRank()
191
192
193 def dump(self, withCoverage=False, withNbUse=True):
194
195 return "\n".join(
196 str(dumpedDescription) for dumpedDescription in
197 self._dumpDescription(withCoverage, withNbUse))
198
199 def exportToXML(self):
200 domElement = xml.dom.minidom.Element(self.tag)
201 self._XMLaddAttributes(domElement)
202
203 for child in self.children :
204 domElement.appendChild(child.exportToXML())
205
206 return domElement
207
208 def _XMLaddAttributes(self, domElement):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100209 attributes = self._getXMLAttributes()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100210
211 coverage = self._getCoverage()
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100212 if coverage != None :
213 attributes["Coverage"] = self._number2percent(coverage)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100214
215 for key, value in attributes.items():
216 domElement.setAttribute(key, value)
217
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100218 def _getXMLAttributes(self):
219 return {
220 "Name": self.name,
221 "NbUse": str(self.nbUse)
222 }
Kevin Rocard326e39e2012-12-14 16:11:05 +0100223
224 def _incNbUse(self):
225 self.nbUse += 1
226
227 def childUsed(self, child):
228 self._incNbUse()
229 # Propagate to parent
230 self._tellParentThatChildUsed()
231
232 def _tellParentThatChildUsed(self):
233 if self.parent :
234 self.parent.childUsed(self)
235
236
237 def parentUsed(self):
238 self._incNbUse()
239 # Propagate to children
240 for child in self.children :
241 child.parentUsed()
242
243 def hasBeenUsed(self):
244 return self.nbUse > 0
245
246 def operationOnChild(self, path, operation):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100247
248 if path:
249 return self._operationPropagate(path, operation)
250 else :
251 self.debug("operating on self")
252 return operation(self)
253
254 def _operationPropagate(self, path, operation):
255
256 childName = path.pop(0)
257 child = self.getChildFromName(childName)
258
259 return child.operationOnChild(path, operation)
260
261
262
263 def debug(self, stringOrFunction, level=logging.DEBUG):
264 """Print a debug line on stderr in tree form
265
266 If the debug line is expensive to generate, provide callable
267 object, it will be called if log is enable for this level.
268 This callable object should return the logline string.
269 """
270 if logger.isEnabledFor(level):
271
272 # TODO: use buildin callable if python >= 3.2
273 if hasattr(stringOrFunction, "__call__"):
274 string = stringOrFunction()
275 else:
276 string = stringOrFunction
277
278 rankedLine = DebugRankedLine("%s: %s" % (self, string))
279 self._logDebug(rankedLine, level)
280
281 def _logDebug(self, rankedLine, level):
282
283 if self.parent:
284 self.parent._logDebug(rankedLine.increasedRank(), level)
285 else :
286 logger.log(level, str(rankedLine))
287
288
289
290
291class FromDomElement(Element):
292 def __init__(self, DomElement):
293 self._initFromDom(DomElement)
294 super().__init__(self.name)
295
296
297 def _initFromDom(self, DomElement):
298 self.name = DomElement.getAttribute("Name")
299
300
301
302class DomElementLocation():
303 def __init__(self, classConstructor, path=None):
304 self.classConstructor = classConstructor
305 if path :
306 self.path = path
307 else :
308 self.path = []
309
310 self.path.append(classConstructor.tag)
311
312
313class DomPopulatedElement(Element):
314 """Default child populate
315
316 Look for each dom element with tag specified in self.tag
317 and instantiate it with the dom element
318 """
319 childClasses = []
320
321 def populate(self, dom):
322
323 for childDomElementLocation in self.childClasses :
324
325 self.debug("Looking for child %s in path %s" % (
326 childDomElementLocation.path[-1], childDomElementLocation.path))
327
328 for childDomElement in self._findChildFromTagPath(dom, childDomElementLocation.path) :
329
330 childElement = childDomElementLocation.classConstructor(childDomElement)
331 self.addChild(childElement)
332
333 childElement.populate(childDomElement)
334
335 def _findChildFromTagPath(self, dom, path):
336 if not path :
337 yield dom
338 else :
339 # Copy list
340 path = list(path)
341
342 tag = path.pop(0)
343
344 # Find element with tag
345 self.debug("Going to find elements with tag %s in %s" % (tag, dom))
346 self.debug(lambda: "Nb of solutions: %s" % len(dom.getElementsByTagName(tag)))
347
348 for elementByTag in dom.getElementsByTagName(tag) :
349
350 self.debug("Found element: %s" % elementByTag)
351
352 # If the same tag is found
353 if elementByTag in dom.childNodes :
354
355 # Yield next level
356 for element in self._findChildFromTagPath(elementByTag, path) :
357 yield element
358
359
360class Rule(Element):
361
362 def usedIfApplicable(self, criteria):
363 childApplicability = (child.usedIfApplicable(criteria)
364 for child in self.children)
365
366 isApplicable = self._isApplicable(criteria, childApplicability)
367
368 if isApplicable :
369 self._incNbUse()
370
371 self.debug("Rule applicability: %s" % isApplicable)
372 assert(isApplicable == True or isApplicable == False)
373
374 return isApplicable
375
376
377 def _isApplicable(self, criteria, childApplicability):
378 # Forcing evaluation of all child by the list creation
379 return all(list(childApplicability))
380
381
382class CriterionRule(FromDomElement, DomPopulatedElement, Rule):
383 tag = "SelectionCriterionRule"
384 childClasses = []
385 isApplicableOperations = {
386 "Includes" : lambda criterion, value: criterion.stateIncludes(value),
387 "Excludes" : lambda criterion, value: not criterion.stateIncludes(value),
388 "Is" : lambda criterion, value: criterion.stateIs(value),
389 "IsNot" : lambda criterion, value: not criterion.stateIs(value)
390 }
391
392 def _initFromDom(self, DomElement):
393 self.selectionCriterion = DomElement.getAttribute("SelectionCriterion")
394 self.matchesWhen = DomElement.getAttribute("MatchesWhen")
395 self.value = DomElement.getAttribute("Value")
396 self.name = "%s %s %s" % (self.selectionCriterion, self.matchesWhen, self.value)
397
398 applicableOperationWithoutValue = self.isApplicableOperations[self.matchesWhen]
399 self.isApplicableOperation = lambda criterion: applicableOperationWithoutValue(criterion, self.value)
400
401 def _isApplicable(self, criteria, childApplicability):
402
403 return criteria.operationOnChild([self.selectionCriterion],
404 self.isApplicableOperation)
405
406
407class CompoundRule(FromDomElement, DomPopulatedElement, Rule):
408 """CompoundRule can be of type ALL or ANY"""
409 tag = "CompoundRule"
410 # Declare childClasses but define it at first class instantiation
411 childClasses = None
412
413 def __init__(self, dom):
414 # Define childClasses at first class instantiation
415 if self.childClasses == None :
416 self.childClasses = [DomElementLocation(CriterionRule),
417 DomElementLocation(CompoundRule)]
418 super().__init__(dom)
419
420 def _initFromDom(self, DomElement):
421
422 type = DomElement.getAttribute("Type")
423 self.ofTypeAll = {"All" : True, "Any" : False}[type]
424 self.name = type
425
426 def _isApplicable(self, criteria, childApplicability):
427 if self.ofTypeAll :
428 applicability = super()._isApplicable(criteria, childApplicability)
429 else:
430 # Forcing evaluation of all child by the list creation
431 applicability = any(list(childApplicability))
432
433 return applicability
434
435class RootRule(DomPopulatedElement, Rule):
436 tag = "RootRule"
437 childClasses = [DomElementLocation(CompoundRule)]
438
439 def populate(self, dom):
440 super().populate(dom)
441 self.debug("Children: %s" % self.children)
442 # A configuration can only have one or no rule
443 assert(len(self.children) <= 1)
444
445 def _getCoverageDependance(self):
446 return self._getDescendants()
447
448
449class CriteronStates(Element):
450 """Root of configuration application criterion state"""
451 tag = "CriterionStates"
452
453 def parentUsed(self, criteria):
454 """Add criteria to child if not exist, if exist increase it's nbUse"""
455 self._incNbUse()
456
457 matches = [child for child in self.children if child == criteria]
458
459 assert(len(matches) <= 1)
460
461 if matches :
462 self.debug("Criteria state has already been encounter")
463 currentcriteria = matches[0]
464 else :
465 self.debug("Criteria state has never been encounter, saving it")
466 currentcriteria = criteria
467 self.addChild(criteria)
468
469 currentcriteria.parentUsed()
470
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100471class IneligibleConfigurationAppliedError(CustomError):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100472
473 def __init__(self, configuration, criteria):
474 self.configuration = configuration
475 self.criteria = criteria
476
477 def __str__(self):
478
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100479 return ("Applying ineligible %s, "
480 "rule:\n%s\n"
481 "Criteria current state:\n%s" %
482 (self.configuration, self.configuration.rootRule.dump(), self.criteria.dump()))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100483
484
485class Configuration(FromDomElement, DomPopulatedElement):
486 tag = "Configuration"
487 childClasses = []
488
489 def __init__(self, DomElement):
490 super().__init__(DomElement)
491
492 self.rootRule = RootRule("RootRule")
493 self.addChild(self.rootRule)
494
495 self.criteronStates = CriteronStates("CriterionStates")
496 self.addChild(self.criteronStates)
497
498 def populate(self, dom):
499 # Delegate to rootRule
500 self.rootRule.populate(dom)
501
502 def _getCoverage(self):
503 # Delegate to rootRule
504 return self.rootRule._getCoverage()
505
506 def used(self, criteria):
507
508 self._incNbUse()
509
510 # Propagate use to parents
511 self._tellParentThatChildUsed()
512
513 # Propagate to criterion coverage
514 self.criteronStates.parentUsed(criteria.export())
515
516 # Propagate to rules
517 if not self.rootRule.usedIfApplicable(criteria) :
518
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100519 self.debug("Applied but rule does not match current "
520 "criteria (parent: %s) " % self.parent.name,
Kevin Rocard326e39e2012-12-14 16:11:05 +0100521 logging.FATAL)
522
Kevin Rocard326e39e2012-12-14 16:11:05 +0100523 raise IneligibleConfigurationAppliedError(self, criteria.export())
524
525 def _dumpPropagate(self, withCoverage, withNbUse):
526 self.debug("Going to ask %s for description" % self.rootRule)
527 for dumpedDescription in self.rootRule._dumpDescription(
528 withCoverage=withCoverage,
529 withNbUse=withNbUse) :
530 yield dumpedDescription.increasedRank()
531
532 self.debug("Going to ask %s for description" % self.criteronStates)
533 for dumpedDescription in self.criteronStates._dumpDescription(
534 withCoverage=False,
535 withNbUse=withNbUse) :
536 yield dumpedDescription.increasedRank()
537
538
539class Domain(FromDomElement, DomPopulatedElement):
540 tag = "ConfigurableDomain"
541 childClasses = [DomElementLocation(Configuration, ["Configurations"])]
542
543
544class Domains(DomPopulatedElement):
545 tag = "Domains"
546 childClasses = [DomElementLocation(Domain, ["ConfigurableDomains"])]
547
548
549class RankedLine():
550 def __init__(self, string,
551 stringPrefix="|-- ",
552 rankString="| ",
553 linePrefix="",
554 lineSuffix="\n"):
555 self.string = string
556 self.rank = 0
557 self.stringPrefix = stringPrefix
558 self.rankString = rankString
559 self.linePrefix = linePrefix
560 self.lineSuffix = lineSuffix
561
562 def increasedRank(self):
563 self.rank += 1
564 return self
565
566 def __str__(self):
567 return self.linePrefix + \
568 self.rank * self.rankString + \
569 self.stringPrefix + \
570 self.string + \
571 self.lineSuffix
572
573class DebugRankedLine(RankedLine):
574
575 def __init__(self, string, lineSuffix=""):
576 super().__init__(string,
577 stringPrefix="",
578 rankString=" ",
579 linePrefix="",
580 lineSuffix=lineSuffix)
581
582
583class CriterionState(Element):
584 tag = "CriterionState"
585 def used(self):
586 self._incNbUse()
587
588
589class Criterion(Element):
590 tag = "Criterion"
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100591 inclusivenessTranslate = {True: "Inclusive", False: "Exclusive"}
592
Kevin Rocard326e39e2012-12-14 16:11:05 +0100593 def __init__(self, name, isInclusif, stateNamesList, currentStateNamesList):
594 super().__init__(name)
595 self.isInclusif = isInclusif
596
Kevin Rocard326e39e2012-12-14 16:11:05 +0100597 for state in stateNamesList :
598 self.addChild(CriterionState(state))
599
600 self.currentState = []
601
602 # Set current state as provided
603 self.currentState = [self.getChildFromName(childName)
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100604 for childName in currentStateNamesList]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100605
606 def childUsed(self, child):
607 self.currentState = child
608 super().childUsed(child)
609
610 def changeState(self, subStateNames):
611 self.debug("Changing state from: %s to: %s" % (
612 list(self._getElementNames(self.currentState)),
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100613 subStateNames))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100614
615 assert(len(subStateNames) > 0 or self.isInclusif)
616
617 newCurrentState = []
618 for subStateName in subStateNames :
619 subState = self.getChildFromName(subStateName)
620 subState.used()
621 newCurrentState.append(subState)
622
623 self.currentState = newCurrentState
624
625 self._incNbUse()
626 self._tellParentThatChildUsed()
627
628 def export(self):
629 subStateNames = self._getElementNames(self.currentState)
630 return Criterion(self.name, self.isInclusif, subStateNames, subStateNames)
631
632 def stateIncludes(self, subStateName):
633 subStateCurrentNames = list(self._getElementNames(self.currentState))
634
635 self.debug("Testing if %s is included in %s" % (subStateName, subStateCurrentNames))
636
637 isIncluded = subStateName in subStateCurrentNames
638 self.debug("IsIncluded: %s" % isIncluded)
639
640 return isIncluded
641
642
643 def stateIs(self, subStateNames):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100644 if len(self.currentState) != 1 :
645 return False
646 else :
647 return self.stateIncludes(subStateNames)
648
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100649 def _getXMLAttributes(self):
650 attributes = super()._getXMLAttributes()
651 attributes["Type"] = self.inclusivenessTranslate[self.isInclusif]
652 return attributes
653
Kevin Rocard326e39e2012-12-14 16:11:05 +0100654
655class Criteria(Element):
656 tag = "Criteria"
657
658 def export(self):
659 self.debug("Exporting criteria")
660 assert(self.children)
661
662 exported = Criteria(self.name)
663 for child in self.children :
664 exported.addChild(child.export())
665 return exported
666
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100667 def addChild(self, child):
668 if child in self.children:
669 raise DuplicatedChildError(self, child)
670 super().addChild(child)
671
672class ConfigAppliedWithoutCriteriaError(CustomError):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100673 def __init__(self, configurationName, domainName):
674 self.configurationName = configurationName
675 self.domainName = domainName
676 def __str__(self):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100677 return ('Applying configuration "%s" from domain "%s" before declaring criteria' %
678 (self.configurationName, self.domainName))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100679
680class ParsePFWlog():
681 MATCH = "match"
682 ACTION = "action"
683
684 def __init__(self, domains, criteria):
685
686 self.domains = domains;
687 self.criteria = criteria;
688
689 configApplicationRegext = r""".*Applying configuration "(.*)" from domain "([^"]*)"""
690 matchConfigApplicationLine = re.compile(configApplicationRegext).match
691
692 criterionCreationRegext = ", ".join([
693 r""".*Criterion name: (.*)""",
694 r"""type kind: (.*)""",
695 r"""current state: (.*)""",
696 r"""states: {(.*)}"""
697 ])
698 matchCriterionCreationLine = re.compile(criterionCreationRegext).match
699
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100700 changingCriterionRegext = r""".*Selection criterion changed event: Criterion name: (.*), current state: ([^\n\r]*)"""
Kevin Rocard326e39e2012-12-14 16:11:05 +0100701 matchChangingCriterionLine = re.compile(changingCriterionRegext).match
702
703 self.lineLogTypes = [
704 {
705 self.MATCH: matchConfigApplicationLine,
706 self.ACTION: self._configApplication
707 }, {
708 self.MATCH: matchCriterionCreationLine,
709 self.ACTION: self._criterionCreation
710 }, {
711 self.MATCH: matchChangingCriterionLine,
712 self.ACTION: self._changingCriterion
713 }
714 ]
715
716 @staticmethod
717 def _formatCriterionList(liststring, separator):
718 list = liststring.split(separator)
719 if len(list) == 1 and list[0] == "<none>":
720 list = []
721 return list
722
723 def _criterionCreation(self, matchCriterionCreation):
724 # Unpack
725 criterionName, criterionType, currentCriterionStates, criterionStates = matchCriterionCreation.group(1, 2, 3, 4)
726
727 criterionStateList = self._formatCriterionList(criterionStates, ", ")
728
729 criterionIsInclusif = {"exclusive" : False, "inclusive" : True}[criterionType]
730
731 currentcriterionStateList = self._formatCriterionList(currentCriterionStates, "|")
732
733 logger.info("Creating criterion: " + criterionName +
734 " (" + criterionType + ") " +
735 " with current state: " + str(currentcriterionStateList) +
736 ", possible states:" + str(criterionStateList))
737
738 self.criteria.addChild(Criterion(
739 criterionName,
740 criterionIsInclusif,
741 criterionStateList,
742 currentcriterionStateList
743 ))
744
745 def _changingCriterion(self, matchChangingCriterion):
746 # Unpack
747 criterionName, newCriterionSubStateNames = matchChangingCriterion.group(1, 2)
748
749 newCriterionState = self._formatCriterionList(newCriterionSubStateNames, "|")
750
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100751 logger.info("Changing criterion %s to %s" % (criterionName , newCriterionState))
752
Kevin Rocard326e39e2012-12-14 16:11:05 +0100753 path = [criterionName]
754 changeCriterionOperation = lambda criterion : criterion.changeState(newCriterionState)
755 self.criteria.operationOnChild(path, changeCriterionOperation)
756
757 def _configApplication(self, matchConfig):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100758 # Unpack
759 configurationName, domainName = matchConfig.group(1, 2)
760
Kevin Rocard326e39e2012-12-14 16:11:05 +0100761 # Check that at least one criterion exist
762 if not self.criteria.hasChildren() :
763 logger.error("Applying configuration before declaring criteria")
764 logger.info("Is the log starting at PFW boot ?")
765 raise ConfigAppliedWithoutCriteriaError(configurationName, domainName)
766
Kevin Rocard326e39e2012-12-14 16:11:05 +0100767 # Change criterion state
768 path = [domainName, configurationName]
769 usedOperation = lambda element : element.used(self.criteria)
770
771 logger.info("Applying configuration %s from domain %s" % (
772 configurationName, domainName))
773
774 self.domains.operationOnChild(path, usedOperation)
775
776
777 def _digest(self, lineLogType, lineLog):
778 match = lineLogType[self.MATCH](lineLog)
779 if match :
780 lineLogType[self.ACTION](match)
781 return True
782 return False
783
784 def parsePFWlog(self, lines):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100785 for lineLog in lines:
786
787 logger.debug("Parsing line :%s" % lineLog.rstrip())
788
789 digested = (self._digest(lineLogType, lineLog)
790 for lineLogType in self.lineLogTypes)
791
792 if not any(digested):
793 logger.debug("Line does not match, dropped")
794
795
796class Root(Element):
797 tag = "Root"
798 def __init__(self, name, dom):
799 super().__init__(name)
800 # Create domain tree
801 self.domains = Domains("Domains")
802 self.domains.populate(dom)
803 self.addChild(self.domains)
804 # Create criterion list
805 self.criteria = Criteria("CriterionRoot")
806 self.addChild(self.criteria)
807
808 def exportToXML(self):
809 """Export tree to an xml document"""
810 impl = xml.dom.minidom.getDOMImplementation()
811 newdoc = impl.createDocument(None, self.name, None)
812 XMLDocElement = newdoc.documentElement
813
814 for child in self.children:
815 XMLDocElement.appendChild(child.exportToXML())
816
817 return newdoc
818
819# ============================
820# Command line argument parser
821# ============================
822
823
824class ArgumentParser:
825 """class that parse command line arguments with argparse library
826
827 Result of parsing are the class attributes.
828 """
829 levelTranslate = [logging.WARNING, logging.INFO, logging.DEBUG]
830
831 def __init__(self):
832
833 try:
Kevin Rocard53493b22013-02-04 15:16:10 +0100834 # As argparse is only in the stdlib since python 3.2,
835 # testing its availability
Kevin Rocard326e39e2012-12-14 16:11:05 +0100836 import argparse
837
838 except ImportError:
Kevin Rocard53493b22013-02-04 15:16:10 +0100839 logger.warning("Unable to import argparse "
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100840 "(parser for command-line options and arguments), "
841 "using default argument values:")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100842
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100843 logger.warning(" - InputFile: stdin")
Kevin Rocard53493b22013-02-04 15:16:10 +0100844 self.inputFile = sys.stdin
Kevin Rocard326e39e2012-12-14 16:11:05 +0100845
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100846 logger.warning(" - OutputFile: stdout")
Kevin Rocard53493b22013-02-04 15:16:10 +0100847 self.outputFile = sys.stdout
Kevin Rocard326e39e2012-12-14 16:11:05 +0100848
Kevin Rocard53493b22013-02-04 15:16:10 +0100849 try:
850 self.domainsFile = sys.argv[1]
851 except IndexError as ex:
852 logger.fatal("No domain file provided (first argument)")
853 raise ex
854 else:
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100855 logger.warning(" - Domain file: " + self.domainsFile)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100856
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100857 logger.warning(" - Output format: xml")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100858 self.XMLreport = True
Kevin Rocard326e39e2012-12-14 16:11:05 +0100859
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100860 logger.warning(" - Debug level: error")
861 self.debugLevel = logging.INFO
Kevin Rocard326e39e2012-12-14 16:11:05 +0100862 else :
863
864 myArgParser = argparse.ArgumentParser(description='Generate PFW report')
865
866 myArgParser.add_argument(
867 'domainsFile',
868 type=argparse.FileType('r'),
869 help="the PFW domain XML file"
870 )
871 myArgParser.add_argument(
872 'pfwlog', nargs='?',
873 type=argparse.FileType('r'), default=sys.stdin,
874 help="the PFW log file, default stdin"
875 )
876 myArgParser.add_argument(
877 '-o', '--output',
878 dest="outputFile",
879 type=argparse.FileType('w'), default=sys.stdout,
880 help="the coverage report output file, default stdout"
881 )
882 myArgParser.add_argument(
883 '-v', '--verbose',
884 dest="debugLevel", default=0,
885 action='count',
886 help="print debug warnings from warning (default) to debug (-vv)"
887 )
888
889 outputFormatGroupe = myArgParser.add_mutually_exclusive_group(required=False)
890
891 outputFormatGroupe.add_argument(
892 '--xml',
893 dest="xmlFlag",
894 action='store_true',
895 help=" XML coverage output report"
896 )
897 outputFormatGroupe.add_argument(
898 '--raw',
899 dest="rawFlag",
900 action='store_true',
901 help="raw coverage output report"
902 )
903
904 # Process command line arguments
905 options = myArgParser.parse_args()
906
907 # Mapping to attributes
908 self.inputFile = options.pfwlog
909 self.outputFile = options.outputFile
910 self.domainsFile = options.domainsFile
911
912 # Output report in xml if flag not set
913 self.XMLreport = not options.rawFlag
914
915 # Setting logger level
916 levelCapped = min(options.debugLevel, len(self.levelTranslate) - 1)
917 self.debugLevel = self.levelTranslate[levelCapped]
918
919
920
921def main():
922
Kevin Rocard53493b22013-02-04 15:16:10 +0100923 errorDuringLogParsing = -1
924 errorDuringArgumentParsing = 1
925
926 try:
927 commandLineArguments = ArgumentParser()
928 except LookupError as ex:
929 logger.error("Error during argument parsing")
930 logger.debug(str(ex))
931 sys.exit(errorDuringArgumentParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100932
933 # Setting logger level
934 logger.setLevel(commandLineArguments.debugLevel)
935 logger.info("Log level set to: %s" %
936 logging.getLevelName(commandLineArguments.debugLevel))
937
938 # Create tree from XML
939 dom = xml.dom.minidom.parse(commandLineArguments.domainsFile)
940
Kevin Rocard53493b22013-02-04 15:16:10 +0100941 # Create element tree
Kevin Rocard326e39e2012-12-14 16:11:05 +0100942 root = Root("Coverage", dom)
943
Kevin Rocard53493b22013-02-04 15:16:10 +0100944 # Parse PFW events
Kevin Rocard326e39e2012-12-14 16:11:05 +0100945 parser = ParsePFWlog(root.domains, root.criteria)
Kevin Rocard53493b22013-02-04 15:16:10 +0100946
Kevin Rocard326e39e2012-12-14 16:11:05 +0100947 try:
948 parser.parsePFWlog(commandLineArguments.inputFile.readlines())
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100949 except CustomError as ex:
950 logger.fatal("Error during parsing log file %s: %s" %
Kevin Rocard326e39e2012-12-14 16:11:05 +0100951 (commandLineArguments.inputFile, ex))
Kevin Rocard53493b22013-02-04 15:16:10 +0100952 sys.exit(errorDuringLogParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100953
Kevin Rocard53493b22013-02-04 15:16:10 +0100954 # Output report
Kevin Rocard326e39e2012-12-14 16:11:05 +0100955 outputFile = commandLineArguments.outputFile
956
957 if not commandLineArguments.XMLreport :
Kevin Rocard326e39e2012-12-14 16:11:05 +0100958 outputFile.write("%s\n" % root.dump(withCoverage=True, withNbUse=True))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100959 else :
Kevin Rocard326e39e2012-12-14 16:11:05 +0100960 outputFile.write(root.exportToXML().toprettyxml())
961
962
963# Execute main function if the python interpreter is running this module as the main program
964if __name__ == "__main__" :
965 main()
966