blob: 0cd2a5d56d2f29cef23dceb9f55712b66ecdcca6 [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):
Kevin Rocarda2d64522013-06-11 11:37:03 +020079 return (self.name == compared.name) and (self.children == compared.children)
Kevin Rocard326e39e2012-12-14 16:11:05 +010080
81 def getName(self, default=""):
82 return self.name or default
83
84 def hasChildren(self):
85 return bool(self.children)
86
87 def getChildren(self):
88 return self.children
89
90 def _getDescendants(self):
91 for child in self.children:
92 yield child
93 for descendant in child._getDescendants() :
94 yield descendant
95
96 def getChildFromName(self, childName):
97
98 for child in self.children :
99
100 if child.getName() == childName :
101 return child
102
103 self.debug("Child %s not found" % childName, logging.ERROR)
104
105 self.debug("Child list :")
106
107 for child in self.children :
108 self.debug(" - %s" % child)
109
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100110 raise ChildNotFoundError(self, childName)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100111
112
113 def addChild(self, child):
114 self.debug("new child: " + child.name)
115 self.children.append(child)
116 child._adoptedBy(self)
117
118 def _adoptedBy(self, parent):
119 assert(not self.parent)
120 self.parent = parent
121
122 def _getElementNames(self, elementList):
123 return (substate.name for substate in elementList)
124
125 def _description(self, withCoverage, withNbUse):
126 description = self.name
127
128 if withNbUse or withCoverage :
129 description += " has been used " + str(self.nbUse) + " time"
130
131 if withCoverage :
132 description += self._coverageFormating(self._getCoverage())
133
134 return description
135
136
137 def _getCoverage(self):
138
139 coverageDependance = list(self._getCoverageDependance())
140
141 nbcoverageDependence = len(coverageDependance)
142
143 if nbcoverageDependence == 0:
144 # Coverage makes no sense without any dependence
145 return None
146
147 nbcoverageDependanceUsed = len([element
148 for element in coverageDependance
149 if element.hasBeenUsed()])
150
151 return nbcoverageDependanceUsed / nbcoverageDependence
152
153 def _getCoverageDependance(self):
154 return self.children
155
156 def _coverageFormating(self, coverage):
157 # If no coverage provided
158 if not coverage :
159 return ""
160
161 # Calculate coverage
162 return " (%s coverage)" % self._number2percent(coverage)
163
164 @staticmethod
165 def _number2percent(number):
166 """Format a number to a integer % string
167
168 example: _number2percent(0.6666) -> "67%"
169 """
170 return "{0:.0f}%".format(100 * number)
171
172
173 def _dumpDescription(self, withCoverage, withNbUse):
174
175 self.debug("yelding description")
176 yield RankedLine(self._description(withCoverage, withNbUse), lineSuffix="")
177
178 for dumped in self._dumpPropagate(withCoverage, withNbUse) :
179 yield dumped
180
181 def _dumpPropagate(self, withCoverage, withNbUse):
182
183 for child in self.children :
184 for dumpedDescription in child._dumpDescription(withCoverage, withNbUse) :
185 yield dumpedDescription.increasedRank()
186
187
188 def dump(self, withCoverage=False, withNbUse=True):
189
190 return "\n".join(
191 str(dumpedDescription) for dumpedDescription in
192 self._dumpDescription(withCoverage, withNbUse))
193
194 def exportToXML(self):
195 domElement = xml.dom.minidom.Element(self.tag)
196 self._XMLaddAttributes(domElement)
197
198 for child in self.children :
199 domElement.appendChild(child.exportToXML())
200
201 return domElement
202
203 def _XMLaddAttributes(self, domElement):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100204 attributes = self._getXMLAttributes()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100205
206 coverage = self._getCoverage()
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100207 if coverage != None :
208 attributes["Coverage"] = self._number2percent(coverage)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100209
210 for key, value in attributes.items():
211 domElement.setAttribute(key, value)
212
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100213 def _getXMLAttributes(self):
214 return {
215 "Name": self.name,
216 "NbUse": str(self.nbUse)
217 }
Kevin Rocard326e39e2012-12-14 16:11:05 +0100218
219 def _incNbUse(self):
220 self.nbUse += 1
221
222 def childUsed(self, child):
223 self._incNbUse()
224 # Propagate to parent
225 self._tellParentThatChildUsed()
226
227 def _tellParentThatChildUsed(self):
228 if self.parent :
229 self.parent.childUsed(self)
230
231
232 def parentUsed(self):
233 self._incNbUse()
234 # Propagate to children
235 for child in self.children :
236 child.parentUsed()
237
238 def hasBeenUsed(self):
239 return self.nbUse > 0
240
241 def operationOnChild(self, path, operation):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100242
243 if path:
244 return self._operationPropagate(path, operation)
245 else :
246 self.debug("operating on self")
247 return operation(self)
248
249 def _operationPropagate(self, path, operation):
250
251 childName = path.pop(0)
252 child = self.getChildFromName(childName)
253
254 return child.operationOnChild(path, operation)
255
256
257
258 def debug(self, stringOrFunction, level=logging.DEBUG):
259 """Print a debug line on stderr in tree form
260
261 If the debug line is expensive to generate, provide callable
262 object, it will be called if log is enable for this level.
263 This callable object should return the logline string.
264 """
265 if logger.isEnabledFor(level):
266
267 # TODO: use buildin callable if python >= 3.2
268 if hasattr(stringOrFunction, "__call__"):
269 string = stringOrFunction()
270 else:
271 string = stringOrFunction
272
273 rankedLine = DebugRankedLine("%s: %s" % (self, string))
274 self._logDebug(rankedLine, level)
275
276 def _logDebug(self, rankedLine, level):
277
278 if self.parent:
279 self.parent._logDebug(rankedLine.increasedRank(), level)
280 else :
281 logger.log(level, str(rankedLine))
282
283
284
285
286class FromDomElement(Element):
287 def __init__(self, DomElement):
288 self._initFromDom(DomElement)
289 super().__init__(self.name)
290
291
292 def _initFromDom(self, DomElement):
293 self.name = DomElement.getAttribute("Name")
294
295
296
297class DomElementLocation():
298 def __init__(self, classConstructor, path=None):
299 self.classConstructor = classConstructor
300 if path :
301 self.path = path
302 else :
303 self.path = []
304
305 self.path.append(classConstructor.tag)
306
307
308class DomPopulatedElement(Element):
309 """Default child populate
310
311 Look for each dom element with tag specified in self.tag
312 and instantiate it with the dom element
313 """
314 childClasses = []
315
316 def populate(self, dom):
317
318 for childDomElementLocation in self.childClasses :
319
320 self.debug("Looking for child %s in path %s" % (
321 childDomElementLocation.path[-1], childDomElementLocation.path))
322
323 for childDomElement in self._findChildFromTagPath(dom, childDomElementLocation.path) :
324
325 childElement = childDomElementLocation.classConstructor(childDomElement)
326 self.addChild(childElement)
327
328 childElement.populate(childDomElement)
329
330 def _findChildFromTagPath(self, dom, path):
331 if not path :
332 yield dom
333 else :
334 # Copy list
335 path = list(path)
336
337 tag = path.pop(0)
338
339 # Find element with tag
340 self.debug("Going to find elements with tag %s in %s" % (tag, dom))
341 self.debug(lambda: "Nb of solutions: %s" % len(dom.getElementsByTagName(tag)))
342
343 for elementByTag in dom.getElementsByTagName(tag) :
344
345 self.debug("Found element: %s" % elementByTag)
346
347 # If the same tag is found
348 if elementByTag in dom.childNodes :
349
350 # Yield next level
351 for element in self._findChildFromTagPath(elementByTag, path) :
352 yield element
353
354
355class Rule(Element):
356
357 def usedIfApplicable(self, criteria):
358 childApplicability = (child.usedIfApplicable(criteria)
359 for child in self.children)
360
361 isApplicable = self._isApplicable(criteria, childApplicability)
362
363 if isApplicable :
364 self._incNbUse()
365
366 self.debug("Rule applicability: %s" % isApplicable)
367 assert(isApplicable == True or isApplicable == False)
368
369 return isApplicable
370
371
372 def _isApplicable(self, criteria, childApplicability):
373 # Forcing evaluation of all child by the list creation
374 return all(list(childApplicability))
375
376
377class CriterionRule(FromDomElement, DomPopulatedElement, Rule):
378 tag = "SelectionCriterionRule"
379 childClasses = []
380 isApplicableOperations = {
381 "Includes" : lambda criterion, value: criterion.stateIncludes(value),
382 "Excludes" : lambda criterion, value: not criterion.stateIncludes(value),
383 "Is" : lambda criterion, value: criterion.stateIs(value),
384 "IsNot" : lambda criterion, value: not criterion.stateIs(value)
385 }
386
387 def _initFromDom(self, DomElement):
388 self.selectionCriterion = DomElement.getAttribute("SelectionCriterion")
389 self.matchesWhen = DomElement.getAttribute("MatchesWhen")
390 self.value = DomElement.getAttribute("Value")
391 self.name = "%s %s %s" % (self.selectionCriterion, self.matchesWhen, self.value)
392
393 applicableOperationWithoutValue = self.isApplicableOperations[self.matchesWhen]
394 self.isApplicableOperation = lambda criterion: applicableOperationWithoutValue(criterion, self.value)
395
396 def _isApplicable(self, criteria, childApplicability):
397
398 return criteria.operationOnChild([self.selectionCriterion],
399 self.isApplicableOperation)
400
401
402class CompoundRule(FromDomElement, DomPopulatedElement, Rule):
403 """CompoundRule can be of type ALL or ANY"""
404 tag = "CompoundRule"
405 # Declare childClasses but define it at first class instantiation
406 childClasses = None
407
408 def __init__(self, dom):
409 # Define childClasses at first class instantiation
410 if self.childClasses == None :
411 self.childClasses = [DomElementLocation(CriterionRule),
412 DomElementLocation(CompoundRule)]
413 super().__init__(dom)
414
415 def _initFromDom(self, DomElement):
416
417 type = DomElement.getAttribute("Type")
418 self.ofTypeAll = {"All" : True, "Any" : False}[type]
419 self.name = type
420
421 def _isApplicable(self, criteria, childApplicability):
422 if self.ofTypeAll :
423 applicability = super()._isApplicable(criteria, childApplicability)
424 else:
425 # Forcing evaluation of all child by the list creation
426 applicability = any(list(childApplicability))
427
428 return applicability
429
430class RootRule(DomPopulatedElement, Rule):
431 tag = "RootRule"
432 childClasses = [DomElementLocation(CompoundRule)]
433
434 def populate(self, dom):
435 super().populate(dom)
436 self.debug("Children: %s" % self.children)
437 # A configuration can only have one or no rule
438 assert(len(self.children) <= 1)
439
440 def _getCoverageDependance(self):
441 return self._getDescendants()
442
443
444class CriteronStates(Element):
445 """Root of configuration application criterion state"""
446 tag = "CriterionStates"
447
448 def parentUsed(self, criteria):
449 """Add criteria to child if not exist, if exist increase it's nbUse"""
450 self._incNbUse()
451
452 matches = [child for child in self.children if child == criteria]
453
454 assert(len(matches) <= 1)
455
456 if matches :
457 self.debug("Criteria state has already been encounter")
458 currentcriteria = matches[0]
459 else :
460 self.debug("Criteria state has never been encounter, saving it")
461 currentcriteria = criteria
462 self.addChild(criteria)
463
464 currentcriteria.parentUsed()
465
Kevin Rocard326e39e2012-12-14 16:11:05 +0100466
467
468class Configuration(FromDomElement, DomPopulatedElement):
469 tag = "Configuration"
470 childClasses = []
471
Kevin Rocard97dbd352013-06-10 15:15:56 +0200472 class IneligibleConfigurationAppliedError(CustomError):
473
474 def __init__(self, configuration, criteria):
475 self.configuration = configuration
476 self.criteria = criteria
477
478 def __str__(self):
479
480 return ("Applying ineligible %s, "
481 "rule:\n%s\n"
482 "Criteria current state:\n%s" % (
483 self.configuration,
484 self.configuration.rootRule.dump(withCoverage=False, withNbUse=False),
485 self.criteria.dump(withCoverage=False, withNbUse=False)
486 ))
487
Kevin Rocard326e39e2012-12-14 16:11:05 +0100488 def __init__(self, DomElement):
489 super().__init__(DomElement)
490
491 self.rootRule = RootRule("RootRule")
492 self.addChild(self.rootRule)
493
494 self.criteronStates = CriteronStates("CriterionStates")
495 self.addChild(self.criteronStates)
496
497 def populate(self, dom):
498 # Delegate to rootRule
499 self.rootRule.populate(dom)
500
501 def _getCoverage(self):
502 # Delegate to rootRule
503 return self.rootRule._getCoverage()
504
505 def used(self, criteria):
506
507 self._incNbUse()
508
509 # Propagate use to parents
510 self._tellParentThatChildUsed()
511
512 # Propagate to criterion coverage
513 self.criteronStates.parentUsed(criteria.export())
514
515 # Propagate to rules
516 if not self.rootRule.usedIfApplicable(criteria) :
517
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100518 self.debug("Applied but rule does not match current "
519 "criteria (parent: %s) " % self.parent.name,
Kevin Rocard925c20b2013-06-10 16:02:28 +0200520 logging.ERROR)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100521
Kevin Rocard97dbd352013-06-10 15:15:56 +0200522 raise self.IneligibleConfigurationAppliedError(self, criteria.export())
Kevin Rocard326e39e2012-12-14 16:11:05 +0100523
524 def _dumpPropagate(self, withCoverage, withNbUse):
525 self.debug("Going to ask %s for description" % self.rootRule)
526 for dumpedDescription in self.rootRule._dumpDescription(
527 withCoverage=withCoverage,
528 withNbUse=withNbUse) :
529 yield dumpedDescription.increasedRank()
530
531 self.debug("Going to ask %s for description" % self.criteronStates)
532 for dumpedDescription in self.criteronStates._dumpDescription(
533 withCoverage=False,
534 withNbUse=withNbUse) :
535 yield dumpedDescription.increasedRank()
536
537
538class Domain(FromDomElement, DomPopulatedElement):
539 tag = "ConfigurableDomain"
540 childClasses = [DomElementLocation(Configuration, ["Configurations"])]
541
542
543class Domains(DomPopulatedElement):
544 tag = "Domains"
545 childClasses = [DomElementLocation(Domain, ["ConfigurableDomains"])]
546
547
548class RankedLine():
549 def __init__(self, string,
550 stringPrefix="|-- ",
551 rankString="| ",
552 linePrefix="",
553 lineSuffix="\n"):
554 self.string = string
555 self.rank = 0
556 self.stringPrefix = stringPrefix
557 self.rankString = rankString
558 self.linePrefix = linePrefix
559 self.lineSuffix = lineSuffix
560
561 def increasedRank(self):
562 self.rank += 1
563 return self
564
565 def __str__(self):
566 return self.linePrefix + \
567 self.rank * self.rankString + \
568 self.stringPrefix + \
569 self.string + \
570 self.lineSuffix
571
572class DebugRankedLine(RankedLine):
573
574 def __init__(self, string, lineSuffix=""):
575 super().__init__(string,
576 stringPrefix="",
577 rankString=" ",
578 linePrefix="",
579 lineSuffix=lineSuffix)
580
581
582class CriterionState(Element):
583 tag = "CriterionState"
584 def used(self):
585 self._incNbUse()
586
587
588class Criterion(Element):
589 tag = "Criterion"
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100590 inclusivenessTranslate = {True: "Inclusive", False: "Exclusive"}
591
Kevin Rocard556538e2013-06-10 15:20:10 +0200592 class ChangeRequestToNonAccessibleState(CustomError):
593 def __init__(self, requestedState, detail):
594 self.requestedState = requestedState
595 self.detail = detail
596
597 def __str__(self):
598 return ("Change request to non accessible state %s. Detail: %s" %
599 (self.requestedState, self.detail))
600
601 def __init__(self, name, isInclusif,
602 stateNamesList, currentStateNamesList,
603 ignoreIntegrity=False):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100604 super().__init__(name)
605 self.isInclusif = isInclusif
606
Kevin Rocard326e39e2012-12-14 16:11:05 +0100607 for state in stateNamesList :
608 self.addChild(CriterionState(state))
609
610 self.currentState = []
Kevin Rocard556538e2013-06-10 15:20:10 +0200611 self.initStateNamesList = list(currentStateNamesList)
612 self.changeState(self.initStateNamesList, ignoreIntegrity)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100613
Kevin Rocard556538e2013-06-10 15:20:10 +0200614 def reset(self):
615 # Set current state as provided at initialisation
616 self.changeState(self.initStateNamesList, ignoreIntegrity=True)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100617
Kevin Rocard556538e2013-06-10 15:20:10 +0200618 def changeState(self, subStateNames, ignoreIntegrity=False):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100619 self.debug("Changing state from: %s to: %s" % (
620 list(self._getElementNames(self.currentState)),
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100621 subStateNames))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100622
Kevin Rocard556538e2013-06-10 15:20:10 +0200623 if not ignoreIntegrity and not self.isIntegre(subStateNames):
624 raise self.ChangeRequestToNonAccessibleState(subStateNames,
625 "An exclusive criterion must have a non empty state")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100626
627 newCurrentState = []
628 for subStateName in subStateNames :
629 subState = self.getChildFromName(subStateName)
630 subState.used()
631 newCurrentState.append(subState)
632
633 self.currentState = newCurrentState
634
635 self._incNbUse()
636 self._tellParentThatChildUsed()
637
Kevin Rocard556538e2013-06-10 15:20:10 +0200638 def isIntegre(self, subStateNames):
639 return self.isInclusif or len(subStateNames) == 1
640
641 def childUsed(self, child):
642 self.currentState = child
643 super().childUsed(child)
644
Kevin Rocard326e39e2012-12-14 16:11:05 +0100645 def export(self):
646 subStateNames = self._getElementNames(self.currentState)
Kevin Rocard556538e2013-06-10 15:20:10 +0200647 return Criterion(self.name, self.isInclusif, subStateNames, subStateNames,
648 ignoreIntegrity=True)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100649
650 def stateIncludes(self, subStateName):
651 subStateCurrentNames = list(self._getElementNames(self.currentState))
652
653 self.debug("Testing if %s is included in %s" % (subStateName, subStateCurrentNames))
654
655 isIncluded = subStateName in subStateCurrentNames
656 self.debug("IsIncluded: %s" % isIncluded)
657
658 return isIncluded
659
660
661 def stateIs(self, subStateNames):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100662 if len(self.currentState) != 1 :
663 return False
664 else :
665 return self.stateIncludes(subStateNames)
666
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100667 def _getXMLAttributes(self):
668 attributes = super()._getXMLAttributes()
669 attributes["Type"] = self.inclusivenessTranslate[self.isInclusif]
670 return attributes
671
Kevin Rocard326e39e2012-12-14 16:11:05 +0100672
673class Criteria(Element):
674 tag = "Criteria"
675
Kevin Rocardd077c552013-06-10 15:31:32 +0200676 class DuplicatedCriterionError(DuplicatedChildError):
677 pass
678
Kevin Rocard326e39e2012-12-14 16:11:05 +0100679 def export(self):
680 self.debug("Exporting criteria")
681 assert(self.children)
682
683 exported = Criteria(self.name)
684 for child in self.children :
685 exported.addChild(child.export())
686 return exported
687
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100688 def addChild(self, child):
689 if child in self.children:
Kevin Rocardd077c552013-06-10 15:31:32 +0200690 raise self.DuplicatedCriterionError(self, child)
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100691 super().addChild(child)
692
693class ConfigAppliedWithoutCriteriaError(CustomError):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100694 def __init__(self, configurationName, domainName):
695 self.configurationName = configurationName
696 self.domainName = domainName
697 def __str__(self):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100698 return ('Applying configuration "%s" from domain "%s" before declaring criteria' %
699 (self.configurationName, self.domainName))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100700
701class ParsePFWlog():
702 MATCH = "match"
703 ACTION = "action"
704
Kevin Rocard9050c812013-06-10 16:01:21 +0200705 def __init__(self, domains, criteria, ErrorsToIgnore=()):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100706
707 self.domains = domains;
708 self.criteria = criteria;
Kevin Rocard9050c812013-06-10 16:01:21 +0200709 self.ErrorsToIgnore = ErrorsToIgnore
Kevin Rocard326e39e2012-12-14 16:11:05 +0100710
711 configApplicationRegext = r""".*Applying configuration "(.*)" from domain "([^"]*)"""
712 matchConfigApplicationLine = re.compile(configApplicationRegext).match
713
714 criterionCreationRegext = ", ".join([
715 r""".*Criterion name: (.*)""",
716 r"""type kind: (.*)""",
717 r"""current state: (.*)""",
718 r"""states: {(.*)}"""
719 ])
720 matchCriterionCreationLine = re.compile(criterionCreationRegext).match
721
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100722 changingCriterionRegext = r""".*Selection criterion changed event: Criterion name: (.*), current state: ([^\n\r]*)"""
Kevin Rocard326e39e2012-12-14 16:11:05 +0100723 matchChangingCriterionLine = re.compile(changingCriterionRegext).match
724
725 self.lineLogTypes = [
726 {
727 self.MATCH: matchConfigApplicationLine,
728 self.ACTION: self._configApplication
729 }, {
730 self.MATCH: matchCriterionCreationLine,
731 self.ACTION: self._criterionCreation
732 }, {
733 self.MATCH: matchChangingCriterionLine,
734 self.ACTION: self._changingCriterion
735 }
736 ]
737
738 @staticmethod
739 def _formatCriterionList(liststring, separator):
740 list = liststring.split(separator)
741 if len(list) == 1 and list[0] == "<none>":
742 list = []
743 return list
744
745 def _criterionCreation(self, matchCriterionCreation):
746 # Unpack
747 criterionName, criterionType, currentCriterionStates, criterionStates = matchCriterionCreation.group(1, 2, 3, 4)
748
749 criterionStateList = self._formatCriterionList(criterionStates, ", ")
750
751 criterionIsInclusif = {"exclusive" : False, "inclusive" : True}[criterionType]
752
753 currentcriterionStateList = self._formatCriterionList(currentCriterionStates, "|")
754
755 logger.info("Creating criterion: " + criterionName +
756 " (" + criterionType + ") " +
757 " with current state: " + str(currentcriterionStateList) +
758 ", possible states:" + str(criterionStateList))
759
Kevin Rocardea874222013-06-10 16:00:08 +0200760 try:
761 self.criteria.addChild(Criterion(
Kevin Rocard326e39e2012-12-14 16:11:05 +0100762 criterionName,
763 criterionIsInclusif,
764 criterionStateList,
765 currentcriterionStateList
766 ))
Kevin Rocardea874222013-06-10 16:00:08 +0200767 except self.criteria.DuplicatedCriterionError as ex:
768 logger.debug(ex)
769 logger.warning("Reseting criterion %s. Did you reset the PFW ?" % criterionName)
770 self.criteria.operationOnChild(
771 [criterionName],
772 lambda criterion: criterion.reset()
773 )
774
775
Kevin Rocard326e39e2012-12-14 16:11:05 +0100776
777 def _changingCriterion(self, matchChangingCriterion):
778 # Unpack
779 criterionName, newCriterionSubStateNames = matchChangingCriterion.group(1, 2)
780
781 newCriterionState = self._formatCriterionList(newCriterionSubStateNames, "|")
782
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100783 logger.info("Changing criterion %s to %s" % (criterionName , newCriterionState))
784
Kevin Rocard326e39e2012-12-14 16:11:05 +0100785 path = [criterionName]
786 changeCriterionOperation = lambda criterion : criterion.changeState(newCriterionState)
787 self.criteria.operationOnChild(path, changeCriterionOperation)
788
789 def _configApplication(self, matchConfig):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100790 # Unpack
791 configurationName, domainName = matchConfig.group(1, 2)
792
Kevin Rocard326e39e2012-12-14 16:11:05 +0100793 # Check that at least one criterion exist
794 if not self.criteria.hasChildren() :
795 logger.error("Applying configuration before declaring criteria")
796 logger.info("Is the log starting at PFW boot ?")
797 raise ConfigAppliedWithoutCriteriaError(configurationName, domainName)
798
Kevin Rocard326e39e2012-12-14 16:11:05 +0100799 # Change criterion state
800 path = [domainName, configurationName]
801 usedOperation = lambda element : element.used(self.criteria)
802
803 logger.info("Applying configuration %s from domain %s" % (
804 configurationName, domainName))
805
806 self.domains.operationOnChild(path, usedOperation)
807
808
809 def _digest(self, lineLogType, lineLog):
Kevin Rocard9050c812013-06-10 16:01:21 +0200810
Kevin Rocard326e39e2012-12-14 16:11:05 +0100811 match = lineLogType[self.MATCH](lineLog)
812 if match :
813 lineLogType[self.ACTION](match)
814 return True
815 return False
816
Kevin Rocard9050c812013-06-10 16:01:21 +0200817
Kevin Rocard326e39e2012-12-14 16:11:05 +0100818 def parsePFWlog(self, lines):
Kevin Rocard9050c812013-06-10 16:01:21 +0200819 for lineNb, lineLog in enumerate(lines):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100820
821 logger.debug("Parsing line :%s" % lineLog.rstrip())
822
823 digested = (self._digest(lineLogType, lineLog)
824 for lineLogType in self.lineLogTypes)
825
Kevin Rocard9050c812013-06-10 16:01:21 +0200826 try:
827 success = any(digested)
828
829 # Catch some exception in order to print the current parsing line,
830 # then raise the exception again if not continue of error
831 except CustomError as ex:
832 logger.error('Error raised while parsing line %s: "%s"' %
833 (lineNb, repr(lineLog)))
834
835 # If exception is a subclass of ErrorsToIgnore, log it and continue
836 # otherwise raise it again.
837 if not issubclass(type(ex), self.ErrorsToIgnore):
838 raise ex
839 else:
840 logger.error('Ignoring exception:"%s", '
841 'can not guarantee database integrity' % ex)
842 else:
843 if not success:
844 logger.debug("Line does not match, dropped")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100845
846
847class Root(Element):
848 tag = "Root"
849 def __init__(self, name, dom):
850 super().__init__(name)
851 # Create domain tree
852 self.domains = Domains("Domains")
853 self.domains.populate(dom)
854 self.addChild(self.domains)
855 # Create criterion list
856 self.criteria = Criteria("CriterionRoot")
857 self.addChild(self.criteria)
858
859 def exportToXML(self):
860 """Export tree to an xml document"""
861 impl = xml.dom.minidom.getDOMImplementation()
862 newdoc = impl.createDocument(None, self.name, None)
863 XMLDocElement = newdoc.documentElement
864
865 for child in self.children:
866 XMLDocElement.appendChild(child.exportToXML())
867
868 return newdoc
869
870# ============================
871# Command line argument parser
872# ============================
873
874
875class ArgumentParser:
876 """class that parse command line arguments with argparse library
877
878 Result of parsing are the class attributes.
879 """
Kevin Rocard925c20b2013-06-10 16:02:28 +0200880 levelTranslate = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100881
882 def __init__(self):
883
884 try:
Kevin Rocard53493b22013-02-04 15:16:10 +0100885 # As argparse is only in the stdlib since python 3.2,
886 # testing its availability
Kevin Rocard326e39e2012-12-14 16:11:05 +0100887 import argparse
888
889 except ImportError:
Kevin Rocard53493b22013-02-04 15:16:10 +0100890 logger.warning("Unable to import argparse "
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100891 "(parser for command-line options and arguments), "
892 "using default argument values:")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100893
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100894 logger.warning(" - InputFile: stdin")
Kevin Rocard53493b22013-02-04 15:16:10 +0100895 self.inputFile = sys.stdin
Kevin Rocard326e39e2012-12-14 16:11:05 +0100896
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100897 logger.warning(" - OutputFile: stdout")
Kevin Rocard53493b22013-02-04 15:16:10 +0100898 self.outputFile = sys.stdout
Kevin Rocard326e39e2012-12-14 16:11:05 +0100899
Kevin Rocard53493b22013-02-04 15:16:10 +0100900 try:
901 self.domainsFile = sys.argv[1]
902 except IndexError as ex:
903 logger.fatal("No domain file provided (first argument)")
904 raise ex
905 else:
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100906 logger.warning(" - Domain file: " + self.domainsFile)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100907
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100908 logger.warning(" - Output format: xml")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100909 self.XMLreport = True
Kevin Rocard326e39e2012-12-14 16:11:05 +0100910
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100911 logger.warning(" - Debug level: error")
Kevin Rocard925c20b2013-06-10 16:02:28 +0200912 self.debugLevel = logging.ERROR
Kevin Rocard326e39e2012-12-14 16:11:05 +0100913 else :
914
915 myArgParser = argparse.ArgumentParser(description='Generate PFW report')
916
917 myArgParser.add_argument(
918 'domainsFile',
919 type=argparse.FileType('r'),
920 help="the PFW domain XML file"
921 )
922 myArgParser.add_argument(
923 'pfwlog', nargs='?',
924 type=argparse.FileType('r'), default=sys.stdin,
925 help="the PFW log file, default stdin"
926 )
927 myArgParser.add_argument(
928 '-o', '--output',
929 dest="outputFile",
930 type=argparse.FileType('w'), default=sys.stdout,
931 help="the coverage report output file, default stdout"
932 )
933 myArgParser.add_argument(
934 '-v', '--verbose',
935 dest="debugLevel", default=0,
936 action='count',
937 help="print debug warnings from warning (default) to debug (-vv)"
938 )
939
940 outputFormatGroupe = myArgParser.add_mutually_exclusive_group(required=False)
941
942 outputFormatGroupe.add_argument(
943 '--xml',
944 dest="xmlFlag",
945 action='store_true',
946 help=" XML coverage output report"
947 )
948 outputFormatGroupe.add_argument(
949 '--raw',
950 dest="rawFlag",
951 action='store_true',
952 help="raw coverage output report"
953 )
954
Kevin Rocard9050c812013-06-10 16:01:21 +0200955 myArgParser.add_argument(
956 '--ignore-incoherent-criterion-state',
957 dest="incoherentCriterionFlag",
958 action='store_true',
959 help="ignore criterion transition to incoherent state"
960 )
961
962 myArgParser.add_argument(
963 '--ignore-ineligible-configuration-application',
964 dest="ineligibleConfigurationApplicationFlag",
965 action='store_true',
966 help="ignore application of configuration with a false rule "
967 "(not applicable configuration)"
968 )
969
Kevin Rocard326e39e2012-12-14 16:11:05 +0100970 # Process command line arguments
971 options = myArgParser.parse_args()
972
973 # Mapping to attributes
974 self.inputFile = options.pfwlog
975 self.outputFile = options.outputFile
976 self.domainsFile = options.domainsFile
977
978 # Output report in xml if flag not set
979 self.XMLreport = not options.rawFlag
980
981 # Setting logger level
982 levelCapped = min(options.debugLevel, len(self.levelTranslate) - 1)
983 self.debugLevel = self.levelTranslate[levelCapped]
984
Kevin Rocard9050c812013-06-10 16:01:21 +0200985 # Setting ignore options
986 errorToIgnore = []
987 if options.ineligibleConfigurationApplicationFlag :
988 errorToIgnore.append(Configuration.IneligibleConfigurationAppliedError)
989
990 if options.incoherentCriterionFlag:
991 errorToIgnore.append(Criterion.ChangeRequestToNonAccessibleState)
992
993 self.errorToIgnore = tuple(errorToIgnore)
994
Kevin Rocard326e39e2012-12-14 16:11:05 +0100995
996
997def main():
998
Kevin Rocard53493b22013-02-04 15:16:10 +0100999 errorDuringLogParsing = -1
1000 errorDuringArgumentParsing = 1
1001
1002 try:
1003 commandLineArguments = ArgumentParser()
1004 except LookupError as ex:
1005 logger.error("Error during argument parsing")
1006 logger.debug(str(ex))
1007 sys.exit(errorDuringArgumentParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001008
1009 # Setting logger level
1010 logger.setLevel(commandLineArguments.debugLevel)
1011 logger.info("Log level set to: %s" %
1012 logging.getLevelName(commandLineArguments.debugLevel))
1013
1014 # Create tree from XML
1015 dom = xml.dom.minidom.parse(commandLineArguments.domainsFile)
1016
Kevin Rocard53493b22013-02-04 15:16:10 +01001017 # Create element tree
Kevin Rocard326e39e2012-12-14 16:11:05 +01001018 root = Root("Coverage", dom)
1019
Kevin Rocard53493b22013-02-04 15:16:10 +01001020 # Parse PFW events
Kevin Rocard9050c812013-06-10 16:01:21 +02001021 parser = ParsePFWlog(root.domains, root.criteria, commandLineArguments.errorToIgnore)
Kevin Rocard53493b22013-02-04 15:16:10 +01001022
Kevin Rocard326e39e2012-12-14 16:11:05 +01001023 try:
1024 parser.parsePFWlog(commandLineArguments.inputFile.readlines())
Kevin Rocard3aa0db42013-02-13 17:15:38 +01001025 except CustomError as ex:
1026 logger.fatal("Error during parsing log file %s: %s" %
Kevin Rocard326e39e2012-12-14 16:11:05 +01001027 (commandLineArguments.inputFile, ex))
Kevin Rocard53493b22013-02-04 15:16:10 +01001028 sys.exit(errorDuringLogParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001029
Kevin Rocard53493b22013-02-04 15:16:10 +01001030 # Output report
Kevin Rocard326e39e2012-12-14 16:11:05 +01001031 outputFile = commandLineArguments.outputFile
1032
1033 if not commandLineArguments.XMLreport :
Kevin Rocard326e39e2012-12-14 16:11:05 +01001034 outputFile.write("%s\n" % root.dump(withCoverage=True, withNbUse=True))
Kevin Rocard326e39e2012-12-14 16:11:05 +01001035 else :
Kevin Rocard326e39e2012-12-14 16:11:05 +01001036 outputFile.write(root.exportToXML().toprettyxml())
1037
1038
Kevin Rocard326e39e2012-12-14 16:11:05 +01001039if __name__ == "__main__" :
Kevin Rocard9050c812013-06-10 16:01:21 +02001040 """ Execute main if the python interpreter is running this module as the main program """
Kevin Rocard326e39e2012-12-14 16:11:05 +01001041 main()
1042