blob: e017cf5e4c87ef883b1dd26b4699b894681e02a8 [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 Rocard3aa0db42013-02-13 17:15:38 +010053 return ("Unable to find the child %s in %s" %
54 (self.child, self.parent))
55
56class DuplicatedChildError(ChildError):
57 def __str__(self):
58 return ("Add existing child %s in %s. Did you restart de PFW ?" %
59 (self.child, self.parent))
Kevin Rocard326e39e2012-12-14 16:11:05 +010060
61class Element():
62 """Root class for all coverage elements"""
63 tag = "element"
64
65 def __init__(self, name):
66
67 self.parent = None
68 self.children = []
69
70 self.nbUse = 0
71
72 self.name = name
73
74 self.debug("New element")
75
76
77 def __str__(self):
78 return "%s (%s)" % (self.name, self.tag)
79
80 def __eq__(self, compared):
81 self.debug(lambda : "Comparing:\n%s" % self.dump())
82 self.debug(lambda : "With:\n%s" % compared.dump())
83 result = self.name == compared.name and self.children == compared.children
84 self.debug("Result is %s" % result)
85 return result
86
87
88 def getName(self, default=""):
89 return self.name or default
90
91 def hasChildren(self):
92 return bool(self.children)
93
94 def getChildren(self):
95 return self.children
96
97 def _getDescendants(self):
98 for child in self.children:
99 yield child
100 for descendant in child._getDescendants() :
101 yield descendant
102
103 def getChildFromName(self, childName):
104
105 for child in self.children :
106
107 if child.getName() == childName :
108 return child
109
110 self.debug("Child %s not found" % childName, logging.ERROR)
111
112 self.debug("Child list :")
113
114 for child in self.children :
115 self.debug(" - %s" % child)
116
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100117 raise ChildNotFoundError(self, childName)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100118
119
120 def addChild(self, child):
121 self.debug("new child: " + child.name)
122 self.children.append(child)
123 child._adoptedBy(self)
124
125 def _adoptedBy(self, parent):
126 assert(not self.parent)
127 self.parent = parent
128
129 def _getElementNames(self, elementList):
130 return (substate.name for substate in elementList)
131
132 def _description(self, withCoverage, withNbUse):
133 description = self.name
134
135 if withNbUse or withCoverage :
136 description += " has been used " + str(self.nbUse) + " time"
137
138 if withCoverage :
139 description += self._coverageFormating(self._getCoverage())
140
141 return description
142
143
144 def _getCoverage(self):
145
146 coverageDependance = list(self._getCoverageDependance())
147
148 nbcoverageDependence = len(coverageDependance)
149
150 if nbcoverageDependence == 0:
151 # Coverage makes no sense without any dependence
152 return None
153
154 nbcoverageDependanceUsed = len([element
155 for element in coverageDependance
156 if element.hasBeenUsed()])
157
158 return nbcoverageDependanceUsed / nbcoverageDependence
159
160 def _getCoverageDependance(self):
161 return self.children
162
163 def _coverageFormating(self, coverage):
164 # If no coverage provided
165 if not coverage :
166 return ""
167
168 # Calculate coverage
169 return " (%s coverage)" % self._number2percent(coverage)
170
171 @staticmethod
172 def _number2percent(number):
173 """Format a number to a integer % string
174
175 example: _number2percent(0.6666) -> "67%"
176 """
177 return "{0:.0f}%".format(100 * number)
178
179
180 def _dumpDescription(self, withCoverage, withNbUse):
181
182 self.debug("yelding description")
183 yield RankedLine(self._description(withCoverage, withNbUse), lineSuffix="")
184
185 for dumped in self._dumpPropagate(withCoverage, withNbUse) :
186 yield dumped
187
188 def _dumpPropagate(self, withCoverage, withNbUse):
189
190 for child in self.children :
191 for dumpedDescription in child._dumpDescription(withCoverage, withNbUse) :
192 yield dumpedDescription.increasedRank()
193
194
195 def dump(self, withCoverage=False, withNbUse=True):
196
197 return "\n".join(
198 str(dumpedDescription) for dumpedDescription in
199 self._dumpDescription(withCoverage, withNbUse))
200
201 def exportToXML(self):
202 domElement = xml.dom.minidom.Element(self.tag)
203 self._XMLaddAttributes(domElement)
204
205 for child in self.children :
206 domElement.appendChild(child.exportToXML())
207
208 return domElement
209
210 def _XMLaddAttributes(self, domElement):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100211 attributes = self._getXMLAttributes()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100212
213 coverage = self._getCoverage()
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100214 if coverage != None :
215 attributes["Coverage"] = self._number2percent(coverage)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100216
217 for key, value in attributes.items():
218 domElement.setAttribute(key, value)
219
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100220 def _getXMLAttributes(self):
221 return {
222 "Name": self.name,
223 "NbUse": str(self.nbUse)
224 }
Kevin Rocard326e39e2012-12-14 16:11:05 +0100225
226 def _incNbUse(self):
227 self.nbUse += 1
228
229 def childUsed(self, child):
230 self._incNbUse()
231 # Propagate to parent
232 self._tellParentThatChildUsed()
233
234 def _tellParentThatChildUsed(self):
235 if self.parent :
236 self.parent.childUsed(self)
237
238
239 def parentUsed(self):
240 self._incNbUse()
241 # Propagate to children
242 for child in self.children :
243 child.parentUsed()
244
245 def hasBeenUsed(self):
246 return self.nbUse > 0
247
248 def operationOnChild(self, path, operation):
249 # Stop if path is not consume yet but there is no child
250 assert(self.children or not path)
251
252 if path:
253 return self._operationPropagate(path, operation)
254 else :
255 self.debug("operating on self")
256 return operation(self)
257
258 def _operationPropagate(self, path, operation):
259
260 childName = path.pop(0)
261 child = self.getChildFromName(childName)
262
263 return child.operationOnChild(path, operation)
264
265
266
267 def debug(self, stringOrFunction, level=logging.DEBUG):
268 """Print a debug line on stderr in tree form
269
270 If the debug line is expensive to generate, provide callable
271 object, it will be called if log is enable for this level.
272 This callable object should return the logline string.
273 """
274 if logger.isEnabledFor(level):
275
276 # TODO: use buildin callable if python >= 3.2
277 if hasattr(stringOrFunction, "__call__"):
278 string = stringOrFunction()
279 else:
280 string = stringOrFunction
281
282 rankedLine = DebugRankedLine("%s: %s" % (self, string))
283 self._logDebug(rankedLine, level)
284
285 def _logDebug(self, rankedLine, level):
286
287 if self.parent:
288 self.parent._logDebug(rankedLine.increasedRank(), level)
289 else :
290 logger.log(level, str(rankedLine))
291
292
293
294
295class FromDomElement(Element):
296 def __init__(self, DomElement):
297 self._initFromDom(DomElement)
298 super().__init__(self.name)
299
300
301 def _initFromDom(self, DomElement):
302 self.name = DomElement.getAttribute("Name")
303
304
305
306class DomElementLocation():
307 def __init__(self, classConstructor, path=None):
308 self.classConstructor = classConstructor
309 if path :
310 self.path = path
311 else :
312 self.path = []
313
314 self.path.append(classConstructor.tag)
315
316
317class DomPopulatedElement(Element):
318 """Default child populate
319
320 Look for each dom element with tag specified in self.tag
321 and instantiate it with the dom element
322 """
323 childClasses = []
324
325 def populate(self, dom):
326
327 for childDomElementLocation in self.childClasses :
328
329 self.debug("Looking for child %s in path %s" % (
330 childDomElementLocation.path[-1], childDomElementLocation.path))
331
332 for childDomElement in self._findChildFromTagPath(dom, childDomElementLocation.path) :
333
334 childElement = childDomElementLocation.classConstructor(childDomElement)
335 self.addChild(childElement)
336
337 childElement.populate(childDomElement)
338
339 def _findChildFromTagPath(self, dom, path):
340 if not path :
341 yield dom
342 else :
343 # Copy list
344 path = list(path)
345
346 tag = path.pop(0)
347
348 # Find element with tag
349 self.debug("Going to find elements with tag %s in %s" % (tag, dom))
350 self.debug(lambda: "Nb of solutions: %s" % len(dom.getElementsByTagName(tag)))
351
352 for elementByTag in dom.getElementsByTagName(tag) :
353
354 self.debug("Found element: %s" % elementByTag)
355
356 # If the same tag is found
357 if elementByTag in dom.childNodes :
358
359 # Yield next level
360 for element in self._findChildFromTagPath(elementByTag, path) :
361 yield element
362
363
364class Rule(Element):
365
366 def usedIfApplicable(self, criteria):
367 childApplicability = (child.usedIfApplicable(criteria)
368 for child in self.children)
369
370 isApplicable = self._isApplicable(criteria, childApplicability)
371
372 if isApplicable :
373 self._incNbUse()
374
375 self.debug("Rule applicability: %s" % isApplicable)
376 assert(isApplicable == True or isApplicable == False)
377
378 return isApplicable
379
380
381 def _isApplicable(self, criteria, childApplicability):
382 # Forcing evaluation of all child by the list creation
383 return all(list(childApplicability))
384
385
386class CriterionRule(FromDomElement, DomPopulatedElement, Rule):
387 tag = "SelectionCriterionRule"
388 childClasses = []
389 isApplicableOperations = {
390 "Includes" : lambda criterion, value: criterion.stateIncludes(value),
391 "Excludes" : lambda criterion, value: not criterion.stateIncludes(value),
392 "Is" : lambda criterion, value: criterion.stateIs(value),
393 "IsNot" : lambda criterion, value: not criterion.stateIs(value)
394 }
395
396 def _initFromDom(self, DomElement):
397 self.selectionCriterion = DomElement.getAttribute("SelectionCriterion")
398 self.matchesWhen = DomElement.getAttribute("MatchesWhen")
399 self.value = DomElement.getAttribute("Value")
400 self.name = "%s %s %s" % (self.selectionCriterion, self.matchesWhen, self.value)
401
402 applicableOperationWithoutValue = self.isApplicableOperations[self.matchesWhen]
403 self.isApplicableOperation = lambda criterion: applicableOperationWithoutValue(criterion, self.value)
404
405 def _isApplicable(self, criteria, childApplicability):
406
407 return criteria.operationOnChild([self.selectionCriterion],
408 self.isApplicableOperation)
409
410
411class CompoundRule(FromDomElement, DomPopulatedElement, Rule):
412 """CompoundRule can be of type ALL or ANY"""
413 tag = "CompoundRule"
414 # Declare childClasses but define it at first class instantiation
415 childClasses = None
416
417 def __init__(self, dom):
418 # Define childClasses at first class instantiation
419 if self.childClasses == None :
420 self.childClasses = [DomElementLocation(CriterionRule),
421 DomElementLocation(CompoundRule)]
422 super().__init__(dom)
423
424 def _initFromDom(self, DomElement):
425
426 type = DomElement.getAttribute("Type")
427 self.ofTypeAll = {"All" : True, "Any" : False}[type]
428 self.name = type
429
430 def _isApplicable(self, criteria, childApplicability):
431 if self.ofTypeAll :
432 applicability = super()._isApplicable(criteria, childApplicability)
433 else:
434 # Forcing evaluation of all child by the list creation
435 applicability = any(list(childApplicability))
436
437 return applicability
438
439class RootRule(DomPopulatedElement, Rule):
440 tag = "RootRule"
441 childClasses = [DomElementLocation(CompoundRule)]
442
443 def populate(self, dom):
444 super().populate(dom)
445 self.debug("Children: %s" % self.children)
446 # A configuration can only have one or no rule
447 assert(len(self.children) <= 1)
448
449 def _getCoverageDependance(self):
450 return self._getDescendants()
451
452
453class CriteronStates(Element):
454 """Root of configuration application criterion state"""
455 tag = "CriterionStates"
456
457 def parentUsed(self, criteria):
458 """Add criteria to child if not exist, if exist increase it's nbUse"""
459 self._incNbUse()
460
461 matches = [child for child in self.children if child == criteria]
462
463 assert(len(matches) <= 1)
464
465 if matches :
466 self.debug("Criteria state has already been encounter")
467 currentcriteria = matches[0]
468 else :
469 self.debug("Criteria state has never been encounter, saving it")
470 currentcriteria = criteria
471 self.addChild(criteria)
472
473 currentcriteria.parentUsed()
474
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100475class IneligibleConfigurationAppliedError(CustomError):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100476
477 def __init__(self, configuration, criteria):
478 self.configuration = configuration
479 self.criteria = criteria
480
481 def __str__(self):
482
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100483 return ("Applying ineligible %s, "
484 "rule:\n%s\n"
485 "Criteria current state:\n%s" %
486 (self.configuration, self.configuration.rootRule.dump(), self.criteria.dump()))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100487
488
489class Configuration(FromDomElement, DomPopulatedElement):
490 tag = "Configuration"
491 childClasses = []
492
493 def __init__(self, DomElement):
494 super().__init__(DomElement)
495
496 self.rootRule = RootRule("RootRule")
497 self.addChild(self.rootRule)
498
499 self.criteronStates = CriteronStates("CriterionStates")
500 self.addChild(self.criteronStates)
501
502 def populate(self, dom):
503 # Delegate to rootRule
504 self.rootRule.populate(dom)
505
506 def _getCoverage(self):
507 # Delegate to rootRule
508 return self.rootRule._getCoverage()
509
510 def used(self, criteria):
511
512 self._incNbUse()
513
514 # Propagate use to parents
515 self._tellParentThatChildUsed()
516
517 # Propagate to criterion coverage
518 self.criteronStates.parentUsed(criteria.export())
519
520 # Propagate to rules
521 if not self.rootRule.usedIfApplicable(criteria) :
522
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100523 self.debug("Applied but rule does not match current "
524 "criteria (parent: %s) " % self.parent.name,
Kevin Rocard326e39e2012-12-14 16:11:05 +0100525 logging.FATAL)
526
Kevin Rocard326e39e2012-12-14 16:11:05 +0100527 raise IneligibleConfigurationAppliedError(self, criteria.export())
528
529 def _dumpPropagate(self, withCoverage, withNbUse):
530 self.debug("Going to ask %s for description" % self.rootRule)
531 for dumpedDescription in self.rootRule._dumpDescription(
532 withCoverage=withCoverage,
533 withNbUse=withNbUse) :
534 yield dumpedDescription.increasedRank()
535
536 self.debug("Going to ask %s for description" % self.criteronStates)
537 for dumpedDescription in self.criteronStates._dumpDescription(
538 withCoverage=False,
539 withNbUse=withNbUse) :
540 yield dumpedDescription.increasedRank()
541
542
543class Domain(FromDomElement, DomPopulatedElement):
544 tag = "ConfigurableDomain"
545 childClasses = [DomElementLocation(Configuration, ["Configurations"])]
546
547
548class Domains(DomPopulatedElement):
549 tag = "Domains"
550 childClasses = [DomElementLocation(Domain, ["ConfigurableDomains"])]
551
552
553class RankedLine():
554 def __init__(self, string,
555 stringPrefix="|-- ",
556 rankString="| ",
557 linePrefix="",
558 lineSuffix="\n"):
559 self.string = string
560 self.rank = 0
561 self.stringPrefix = stringPrefix
562 self.rankString = rankString
563 self.linePrefix = linePrefix
564 self.lineSuffix = lineSuffix
565
566 def increasedRank(self):
567 self.rank += 1
568 return self
569
570 def __str__(self):
571 return self.linePrefix + \
572 self.rank * self.rankString + \
573 self.stringPrefix + \
574 self.string + \
575 self.lineSuffix
576
577class DebugRankedLine(RankedLine):
578
579 def __init__(self, string, lineSuffix=""):
580 super().__init__(string,
581 stringPrefix="",
582 rankString=" ",
583 linePrefix="",
584 lineSuffix=lineSuffix)
585
586
587class CriterionState(Element):
588 tag = "CriterionState"
589 def used(self):
590 self._incNbUse()
591
592
593class Criterion(Element):
594 tag = "Criterion"
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100595 inclusivenessTranslate = {True: "Inclusive", False: "Exclusive"}
596
Kevin Rocard326e39e2012-12-14 16:11:05 +0100597 def __init__(self, name, isInclusif, stateNamesList, currentStateNamesList):
598 super().__init__(name)
599 self.isInclusif = isInclusif
600
Kevin Rocard326e39e2012-12-14 16:11:05 +0100601 for state in stateNamesList :
602 self.addChild(CriterionState(state))
603
604 self.currentState = []
605
606 # Set current state as provided
607 self.currentState = [self.getChildFromName(childName)
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100608 for childName in currentStateNamesList]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100609
610 def childUsed(self, child):
611 self.currentState = child
612 super().childUsed(child)
613
614 def changeState(self, subStateNames):
615 self.debug("Changing state from: %s to: %s" % (
616 list(self._getElementNames(self.currentState)),
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100617 subStateNames))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100618
619 assert(len(subStateNames) > 0 or self.isInclusif)
620
621 newCurrentState = []
622 for subStateName in subStateNames :
623 subState = self.getChildFromName(subStateName)
624 subState.used()
625 newCurrentState.append(subState)
626
627 self.currentState = newCurrentState
628
629 self._incNbUse()
630 self._tellParentThatChildUsed()
631
632 def export(self):
633 subStateNames = self._getElementNames(self.currentState)
634 return Criterion(self.name, self.isInclusif, subStateNames, subStateNames)
635
636 def stateIncludes(self, subStateName):
637 subStateCurrentNames = list(self._getElementNames(self.currentState))
638
639 self.debug("Testing if %s is included in %s" % (subStateName, subStateCurrentNames))
640
641 isIncluded = subStateName in subStateCurrentNames
642 self.debug("IsIncluded: %s" % isIncluded)
643
644 return isIncluded
645
646
647 def stateIs(self, subStateNames):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100648 if len(self.currentState) != 1 :
649 return False
650 else :
651 return self.stateIncludes(subStateNames)
652
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100653 def _getXMLAttributes(self):
654 attributes = super()._getXMLAttributes()
655 attributes["Type"] = self.inclusivenessTranslate[self.isInclusif]
656 return attributes
657
Kevin Rocard326e39e2012-12-14 16:11:05 +0100658
659class Criteria(Element):
660 tag = "Criteria"
661
662 def export(self):
663 self.debug("Exporting criteria")
664 assert(self.children)
665
666 exported = Criteria(self.name)
667 for child in self.children :
668 exported.addChild(child.export())
669 return exported
670
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100671 def addChild(self, child):
672 if child in self.children:
673 raise DuplicatedChildError(self, child)
674 super().addChild(child)
675
676class ConfigAppliedWithoutCriteriaError(CustomError):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100677 def __init__(self, configurationName, domainName):
678 self.configurationName = configurationName
679 self.domainName = domainName
680 def __str__(self):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100681 return ('Applying configuration "%s" from domain "%s" before declaring criteria' %
682 (self.configurationName, self.domainName))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100683
684class ParsePFWlog():
685 MATCH = "match"
686 ACTION = "action"
687
688 def __init__(self, domains, criteria):
689
690 self.domains = domains;
691 self.criteria = criteria;
692
693 configApplicationRegext = r""".*Applying configuration "(.*)" from domain "([^"]*)"""
694 matchConfigApplicationLine = re.compile(configApplicationRegext).match
695
696 criterionCreationRegext = ", ".join([
697 r""".*Criterion name: (.*)""",
698 r"""type kind: (.*)""",
699 r"""current state: (.*)""",
700 r"""states: {(.*)}"""
701 ])
702 matchCriterionCreationLine = re.compile(criterionCreationRegext).match
703
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100704 changingCriterionRegext = r""".*Selection criterion changed event: Criterion name: (.*), current state: ([^\n\r]*)"""
Kevin Rocard326e39e2012-12-14 16:11:05 +0100705 matchChangingCriterionLine = re.compile(changingCriterionRegext).match
706
707 self.lineLogTypes = [
708 {
709 self.MATCH: matchConfigApplicationLine,
710 self.ACTION: self._configApplication
711 }, {
712 self.MATCH: matchCriterionCreationLine,
713 self.ACTION: self._criterionCreation
714 }, {
715 self.MATCH: matchChangingCriterionLine,
716 self.ACTION: self._changingCriterion
717 }
718 ]
719
720 @staticmethod
721 def _formatCriterionList(liststring, separator):
722 list = liststring.split(separator)
723 if len(list) == 1 and list[0] == "<none>":
724 list = []
725 return list
726
727 def _criterionCreation(self, matchCriterionCreation):
728 # Unpack
729 criterionName, criterionType, currentCriterionStates, criterionStates = matchCriterionCreation.group(1, 2, 3, 4)
730
731 criterionStateList = self._formatCriterionList(criterionStates, ", ")
732
733 criterionIsInclusif = {"exclusive" : False, "inclusive" : True}[criterionType]
734
735 currentcriterionStateList = self._formatCriterionList(currentCriterionStates, "|")
736
737 logger.info("Creating criterion: " + criterionName +
738 " (" + criterionType + ") " +
739 " with current state: " + str(currentcriterionStateList) +
740 ", possible states:" + str(criterionStateList))
741
742 self.criteria.addChild(Criterion(
743 criterionName,
744 criterionIsInclusif,
745 criterionStateList,
746 currentcriterionStateList
747 ))
748
749 def _changingCriterion(self, matchChangingCriterion):
750 # Unpack
751 criterionName, newCriterionSubStateNames = matchChangingCriterion.group(1, 2)
752
753 newCriterionState = self._formatCriterionList(newCriterionSubStateNames, "|")
754
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100755 logger.info("Changing criterion %s to %s" % (criterionName , newCriterionState))
756
Kevin Rocard326e39e2012-12-14 16:11:05 +0100757 path = [criterionName]
758 changeCriterionOperation = lambda criterion : criterion.changeState(newCriterionState)
759 self.criteria.operationOnChild(path, changeCriterionOperation)
760
761 def _configApplication(self, matchConfig):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100762 # Unpack
763 configurationName, domainName = matchConfig.group(1, 2)
764
Kevin Rocard326e39e2012-12-14 16:11:05 +0100765 # Check that at least one criterion exist
766 if not self.criteria.hasChildren() :
767 logger.error("Applying configuration before declaring criteria")
768 logger.info("Is the log starting at PFW boot ?")
769 raise ConfigAppliedWithoutCriteriaError(configurationName, domainName)
770
Kevin Rocard326e39e2012-12-14 16:11:05 +0100771 # Change criterion state
772 path = [domainName, configurationName]
773 usedOperation = lambda element : element.used(self.criteria)
774
775 logger.info("Applying configuration %s from domain %s" % (
776 configurationName, domainName))
777
778 self.domains.operationOnChild(path, usedOperation)
779
780
781 def _digest(self, lineLogType, lineLog):
782 match = lineLogType[self.MATCH](lineLog)
783 if match :
784 lineLogType[self.ACTION](match)
785 return True
786 return False
787
788 def parsePFWlog(self, lines):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100789 for lineLog in lines:
790
791 logger.debug("Parsing line :%s" % lineLog.rstrip())
792
793 digested = (self._digest(lineLogType, lineLog)
794 for lineLogType in self.lineLogTypes)
795
796 if not any(digested):
797 logger.debug("Line does not match, dropped")
798
799
800class Root(Element):
801 tag = "Root"
802 def __init__(self, name, dom):
803 super().__init__(name)
804 # Create domain tree
805 self.domains = Domains("Domains")
806 self.domains.populate(dom)
807 self.addChild(self.domains)
808 # Create criterion list
809 self.criteria = Criteria("CriterionRoot")
810 self.addChild(self.criteria)
811
812 def exportToXML(self):
813 """Export tree to an xml document"""
814 impl = xml.dom.minidom.getDOMImplementation()
815 newdoc = impl.createDocument(None, self.name, None)
816 XMLDocElement = newdoc.documentElement
817
818 for child in self.children:
819 XMLDocElement.appendChild(child.exportToXML())
820
821 return newdoc
822
823# ============================
824# Command line argument parser
825# ============================
826
827
828class ArgumentParser:
829 """class that parse command line arguments with argparse library
830
831 Result of parsing are the class attributes.
832 """
833 levelTranslate = [logging.WARNING, logging.INFO, logging.DEBUG]
834
835 def __init__(self):
836
837 try:
Kevin Rocard53493b22013-02-04 15:16:10 +0100838 # As argparse is only in the stdlib since python 3.2,
839 # testing its availability
Kevin Rocard326e39e2012-12-14 16:11:05 +0100840 import argparse
841
842 except ImportError:
Kevin Rocard53493b22013-02-04 15:16:10 +0100843 logger.warning("Unable to import argparse "
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100844 "(parser for command-line options and arguments), "
845 "using default argument values:")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100846
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100847 logger.warning(" - InputFile: stdin")
Kevin Rocard53493b22013-02-04 15:16:10 +0100848 self.inputFile = sys.stdin
Kevin Rocard326e39e2012-12-14 16:11:05 +0100849
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100850 logger.warning(" - OutputFile: stdout")
Kevin Rocard53493b22013-02-04 15:16:10 +0100851 self.outputFile = sys.stdout
Kevin Rocard326e39e2012-12-14 16:11:05 +0100852
Kevin Rocard53493b22013-02-04 15:16:10 +0100853 try:
854 self.domainsFile = sys.argv[1]
855 except IndexError as ex:
856 logger.fatal("No domain file provided (first argument)")
857 raise ex
858 else:
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100859 logger.warning(" - Domain file: " + self.domainsFile)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100860
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100861 logger.warning(" - Output format: xml")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100862 self.XMLreport = True
Kevin Rocard326e39e2012-12-14 16:11:05 +0100863
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100864 logger.warning(" - Debug level: error")
865 self.debugLevel = logging.INFO
Kevin Rocard326e39e2012-12-14 16:11:05 +0100866 else :
867
868 myArgParser = argparse.ArgumentParser(description='Generate PFW report')
869
870 myArgParser.add_argument(
871 'domainsFile',
872 type=argparse.FileType('r'),
873 help="the PFW domain XML file"
874 )
875 myArgParser.add_argument(
876 'pfwlog', nargs='?',
877 type=argparse.FileType('r'), default=sys.stdin,
878 help="the PFW log file, default stdin"
879 )
880 myArgParser.add_argument(
881 '-o', '--output',
882 dest="outputFile",
883 type=argparse.FileType('w'), default=sys.stdout,
884 help="the coverage report output file, default stdout"
885 )
886 myArgParser.add_argument(
887 '-v', '--verbose',
888 dest="debugLevel", default=0,
889 action='count',
890 help="print debug warnings from warning (default) to debug (-vv)"
891 )
892
893 outputFormatGroupe = myArgParser.add_mutually_exclusive_group(required=False)
894
895 outputFormatGroupe.add_argument(
896 '--xml',
897 dest="xmlFlag",
898 action='store_true',
899 help=" XML coverage output report"
900 )
901 outputFormatGroupe.add_argument(
902 '--raw',
903 dest="rawFlag",
904 action='store_true',
905 help="raw coverage output report"
906 )
907
908 # Process command line arguments
909 options = myArgParser.parse_args()
910
911 # Mapping to attributes
912 self.inputFile = options.pfwlog
913 self.outputFile = options.outputFile
914 self.domainsFile = options.domainsFile
915
916 # Output report in xml if flag not set
917 self.XMLreport = not options.rawFlag
918
919 # Setting logger level
920 levelCapped = min(options.debugLevel, len(self.levelTranslate) - 1)
921 self.debugLevel = self.levelTranslate[levelCapped]
922
923
924
925def main():
926
Kevin Rocard53493b22013-02-04 15:16:10 +0100927 errorDuringLogParsing = -1
928 errorDuringArgumentParsing = 1
929
930 try:
931 commandLineArguments = ArgumentParser()
932 except LookupError as ex:
933 logger.error("Error during argument parsing")
934 logger.debug(str(ex))
935 sys.exit(errorDuringArgumentParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100936
937 # Setting logger level
938 logger.setLevel(commandLineArguments.debugLevel)
939 logger.info("Log level set to: %s" %
940 logging.getLevelName(commandLineArguments.debugLevel))
941
942 # Create tree from XML
943 dom = xml.dom.minidom.parse(commandLineArguments.domainsFile)
944
Kevin Rocard53493b22013-02-04 15:16:10 +0100945 # Create element tree
Kevin Rocard326e39e2012-12-14 16:11:05 +0100946 root = Root("Coverage", dom)
947
Kevin Rocard53493b22013-02-04 15:16:10 +0100948 # Parse PFW events
Kevin Rocard326e39e2012-12-14 16:11:05 +0100949 parser = ParsePFWlog(root.domains, root.criteria)
Kevin Rocard53493b22013-02-04 15:16:10 +0100950
Kevin Rocard326e39e2012-12-14 16:11:05 +0100951 try:
952 parser.parsePFWlog(commandLineArguments.inputFile.readlines())
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100953 except CustomError as ex:
954 logger.fatal("Error during parsing log file %s: %s" %
Kevin Rocard326e39e2012-12-14 16:11:05 +0100955 (commandLineArguments.inputFile, ex))
Kevin Rocard53493b22013-02-04 15:16:10 +0100956 sys.exit(errorDuringLogParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100957
Kevin Rocard53493b22013-02-04 15:16:10 +0100958 # Output report
Kevin Rocard326e39e2012-12-14 16:11:05 +0100959 outputFile = commandLineArguments.outputFile
960
961 if not commandLineArguments.XMLreport :
Kevin Rocard326e39e2012-12-14 16:11:05 +0100962 outputFile.write("%s\n" % root.dump(withCoverage=True, withNbUse=True))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100963 else :
Kevin Rocard326e39e2012-12-14 16:11:05 +0100964 outputFile.write(root.exportToXML().toprettyxml())
965
966
967# Execute main function if the python interpreter is running this module as the main program
968if __name__ == "__main__" :
969 main()
970