blob: ead5e085253a3155568b629db5c49341a4f713b8 [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 Rocard326e39e2012-12-14 16:11:05 +0100471
472
473class Configuration(FromDomElement, DomPopulatedElement):
474 tag = "Configuration"
475 childClasses = []
476
Kevin Rocard97dbd352013-06-10 15:15:56 +0200477 class IneligibleConfigurationAppliedError(CustomError):
478
479 def __init__(self, configuration, criteria):
480 self.configuration = configuration
481 self.criteria = criteria
482
483 def __str__(self):
484
485 return ("Applying ineligible %s, "
486 "rule:\n%s\n"
487 "Criteria current state:\n%s" % (
488 self.configuration,
489 self.configuration.rootRule.dump(withCoverage=False, withNbUse=False),
490 self.criteria.dump(withCoverage=False, withNbUse=False)
491 ))
492
Kevin Rocard326e39e2012-12-14 16:11:05 +0100493 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 Rocard97dbd352013-06-10 15:15:56 +0200527 raise self.IneligibleConfigurationAppliedError(self, criteria.export())
Kevin Rocard326e39e2012-12-14 16:11:05 +0100528
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 Rocard556538e2013-06-10 15:20:10 +0200597 class ChangeRequestToNonAccessibleState(CustomError):
598 def __init__(self, requestedState, detail):
599 self.requestedState = requestedState
600 self.detail = detail
601
602 def __str__(self):
603 return ("Change request to non accessible state %s. Detail: %s" %
604 (self.requestedState, self.detail))
605
606 def __init__(self, name, isInclusif,
607 stateNamesList, currentStateNamesList,
608 ignoreIntegrity=False):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100609 super().__init__(name)
610 self.isInclusif = isInclusif
611
Kevin Rocard326e39e2012-12-14 16:11:05 +0100612 for state in stateNamesList :
613 self.addChild(CriterionState(state))
614
615 self.currentState = []
Kevin Rocard556538e2013-06-10 15:20:10 +0200616 self.initStateNamesList = list(currentStateNamesList)
617 self.changeState(self.initStateNamesList, ignoreIntegrity)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100618
Kevin Rocard556538e2013-06-10 15:20:10 +0200619 def reset(self):
620 # Set current state as provided at initialisation
621 self.changeState(self.initStateNamesList, ignoreIntegrity=True)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100622
Kevin Rocard556538e2013-06-10 15:20:10 +0200623 def changeState(self, subStateNames, ignoreIntegrity=False):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100624 self.debug("Changing state from: %s to: %s" % (
625 list(self._getElementNames(self.currentState)),
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100626 subStateNames))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100627
Kevin Rocard556538e2013-06-10 15:20:10 +0200628 if not ignoreIntegrity and not self.isIntegre(subStateNames):
629 raise self.ChangeRequestToNonAccessibleState(subStateNames,
630 "An exclusive criterion must have a non empty state")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100631
632 newCurrentState = []
633 for subStateName in subStateNames :
634 subState = self.getChildFromName(subStateName)
635 subState.used()
636 newCurrentState.append(subState)
637
638 self.currentState = newCurrentState
639
640 self._incNbUse()
641 self._tellParentThatChildUsed()
642
Kevin Rocard556538e2013-06-10 15:20:10 +0200643 def isIntegre(self, subStateNames):
644 return self.isInclusif or len(subStateNames) == 1
645
646 def childUsed(self, child):
647 self.currentState = child
648 super().childUsed(child)
649
Kevin Rocard326e39e2012-12-14 16:11:05 +0100650 def export(self):
651 subStateNames = self._getElementNames(self.currentState)
Kevin Rocard556538e2013-06-10 15:20:10 +0200652 return Criterion(self.name, self.isInclusif, subStateNames, subStateNames,
653 ignoreIntegrity=True)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100654
655 def stateIncludes(self, subStateName):
656 subStateCurrentNames = list(self._getElementNames(self.currentState))
657
658 self.debug("Testing if %s is included in %s" % (subStateName, subStateCurrentNames))
659
660 isIncluded = subStateName in subStateCurrentNames
661 self.debug("IsIncluded: %s" % isIncluded)
662
663 return isIncluded
664
665
666 def stateIs(self, subStateNames):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100667 if len(self.currentState) != 1 :
668 return False
669 else :
670 return self.stateIncludes(subStateNames)
671
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100672 def _getXMLAttributes(self):
673 attributes = super()._getXMLAttributes()
674 attributes["Type"] = self.inclusivenessTranslate[self.isInclusif]
675 return attributes
676
Kevin Rocard326e39e2012-12-14 16:11:05 +0100677
678class Criteria(Element):
679 tag = "Criteria"
680
Kevin Rocardd077c552013-06-10 15:31:32 +0200681 class DuplicatedCriterionError(DuplicatedChildError):
682 pass
683
Kevin Rocard326e39e2012-12-14 16:11:05 +0100684 def export(self):
685 self.debug("Exporting criteria")
686 assert(self.children)
687
688 exported = Criteria(self.name)
689 for child in self.children :
690 exported.addChild(child.export())
691 return exported
692
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100693 def addChild(self, child):
694 if child in self.children:
Kevin Rocardd077c552013-06-10 15:31:32 +0200695 raise self.DuplicatedCriterionError(self, child)
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100696 super().addChild(child)
697
698class ConfigAppliedWithoutCriteriaError(CustomError):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100699 def __init__(self, configurationName, domainName):
700 self.configurationName = configurationName
701 self.domainName = domainName
702 def __str__(self):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100703 return ('Applying configuration "%s" from domain "%s" before declaring criteria' %
704 (self.configurationName, self.domainName))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100705
706class ParsePFWlog():
707 MATCH = "match"
708 ACTION = "action"
709
Kevin Rocard9050c812013-06-10 16:01:21 +0200710 def __init__(self, domains, criteria, ErrorsToIgnore=()):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100711
712 self.domains = domains;
713 self.criteria = criteria;
Kevin Rocard9050c812013-06-10 16:01:21 +0200714 self.ErrorsToIgnore = ErrorsToIgnore
Kevin Rocard326e39e2012-12-14 16:11:05 +0100715
716 configApplicationRegext = r""".*Applying configuration "(.*)" from domain "([^"]*)"""
717 matchConfigApplicationLine = re.compile(configApplicationRegext).match
718
719 criterionCreationRegext = ", ".join([
720 r""".*Criterion name: (.*)""",
721 r"""type kind: (.*)""",
722 r"""current state: (.*)""",
723 r"""states: {(.*)}"""
724 ])
725 matchCriterionCreationLine = re.compile(criterionCreationRegext).match
726
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100727 changingCriterionRegext = r""".*Selection criterion changed event: Criterion name: (.*), current state: ([^\n\r]*)"""
Kevin Rocard326e39e2012-12-14 16:11:05 +0100728 matchChangingCriterionLine = re.compile(changingCriterionRegext).match
729
730 self.lineLogTypes = [
731 {
732 self.MATCH: matchConfigApplicationLine,
733 self.ACTION: self._configApplication
734 }, {
735 self.MATCH: matchCriterionCreationLine,
736 self.ACTION: self._criterionCreation
737 }, {
738 self.MATCH: matchChangingCriterionLine,
739 self.ACTION: self._changingCriterion
740 }
741 ]
742
743 @staticmethod
744 def _formatCriterionList(liststring, separator):
745 list = liststring.split(separator)
746 if len(list) == 1 and list[0] == "<none>":
747 list = []
748 return list
749
750 def _criterionCreation(self, matchCriterionCreation):
751 # Unpack
752 criterionName, criterionType, currentCriterionStates, criterionStates = matchCriterionCreation.group(1, 2, 3, 4)
753
754 criterionStateList = self._formatCriterionList(criterionStates, ", ")
755
756 criterionIsInclusif = {"exclusive" : False, "inclusive" : True}[criterionType]
757
758 currentcriterionStateList = self._formatCriterionList(currentCriterionStates, "|")
759
760 logger.info("Creating criterion: " + criterionName +
761 " (" + criterionType + ") " +
762 " with current state: " + str(currentcriterionStateList) +
763 ", possible states:" + str(criterionStateList))
764
Kevin Rocardea874222013-06-10 16:00:08 +0200765 try:
766 self.criteria.addChild(Criterion(
Kevin Rocard326e39e2012-12-14 16:11:05 +0100767 criterionName,
768 criterionIsInclusif,
769 criterionStateList,
770 currentcriterionStateList
771 ))
Kevin Rocardea874222013-06-10 16:00:08 +0200772 except self.criteria.DuplicatedCriterionError as ex:
773 logger.debug(ex)
774 logger.warning("Reseting criterion %s. Did you reset the PFW ?" % criterionName)
775 self.criteria.operationOnChild(
776 [criterionName],
777 lambda criterion: criterion.reset()
778 )
779
780
Kevin Rocard326e39e2012-12-14 16:11:05 +0100781
782 def _changingCriterion(self, matchChangingCriterion):
783 # Unpack
784 criterionName, newCriterionSubStateNames = matchChangingCriterion.group(1, 2)
785
786 newCriterionState = self._formatCriterionList(newCriterionSubStateNames, "|")
787
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100788 logger.info("Changing criterion %s to %s" % (criterionName , newCriterionState))
789
Kevin Rocard326e39e2012-12-14 16:11:05 +0100790 path = [criterionName]
791 changeCriterionOperation = lambda criterion : criterion.changeState(newCriterionState)
792 self.criteria.operationOnChild(path, changeCriterionOperation)
793
794 def _configApplication(self, matchConfig):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100795 # Unpack
796 configurationName, domainName = matchConfig.group(1, 2)
797
Kevin Rocard326e39e2012-12-14 16:11:05 +0100798 # Check that at least one criterion exist
799 if not self.criteria.hasChildren() :
800 logger.error("Applying configuration before declaring criteria")
801 logger.info("Is the log starting at PFW boot ?")
802 raise ConfigAppliedWithoutCriteriaError(configurationName, domainName)
803
Kevin Rocard326e39e2012-12-14 16:11:05 +0100804 # Change criterion state
805 path = [domainName, configurationName]
806 usedOperation = lambda element : element.used(self.criteria)
807
808 logger.info("Applying configuration %s from domain %s" % (
809 configurationName, domainName))
810
811 self.domains.operationOnChild(path, usedOperation)
812
813
814 def _digest(self, lineLogType, lineLog):
Kevin Rocard9050c812013-06-10 16:01:21 +0200815
Kevin Rocard326e39e2012-12-14 16:11:05 +0100816 match = lineLogType[self.MATCH](lineLog)
817 if match :
818 lineLogType[self.ACTION](match)
819 return True
820 return False
821
Kevin Rocard9050c812013-06-10 16:01:21 +0200822
Kevin Rocard326e39e2012-12-14 16:11:05 +0100823 def parsePFWlog(self, lines):
Kevin Rocard9050c812013-06-10 16:01:21 +0200824 for lineNb, lineLog in enumerate(lines):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100825
826 logger.debug("Parsing line :%s" % lineLog.rstrip())
827
828 digested = (self._digest(lineLogType, lineLog)
829 for lineLogType in self.lineLogTypes)
830
Kevin Rocard9050c812013-06-10 16:01:21 +0200831 try:
832 success = any(digested)
833
834 # Catch some exception in order to print the current parsing line,
835 # then raise the exception again if not continue of error
836 except CustomError as ex:
837 logger.error('Error raised while parsing line %s: "%s"' %
838 (lineNb, repr(lineLog)))
839
840 # If exception is a subclass of ErrorsToIgnore, log it and continue
841 # otherwise raise it again.
842 if not issubclass(type(ex), self.ErrorsToIgnore):
843 raise ex
844 else:
845 logger.error('Ignoring exception:"%s", '
846 'can not guarantee database integrity' % ex)
847 else:
848 if not success:
849 logger.debug("Line does not match, dropped")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100850
851
852class Root(Element):
853 tag = "Root"
854 def __init__(self, name, dom):
855 super().__init__(name)
856 # Create domain tree
857 self.domains = Domains("Domains")
858 self.domains.populate(dom)
859 self.addChild(self.domains)
860 # Create criterion list
861 self.criteria = Criteria("CriterionRoot")
862 self.addChild(self.criteria)
863
864 def exportToXML(self):
865 """Export tree to an xml document"""
866 impl = xml.dom.minidom.getDOMImplementation()
867 newdoc = impl.createDocument(None, self.name, None)
868 XMLDocElement = newdoc.documentElement
869
870 for child in self.children:
871 XMLDocElement.appendChild(child.exportToXML())
872
873 return newdoc
874
875# ============================
876# Command line argument parser
877# ============================
878
879
880class ArgumentParser:
881 """class that parse command line arguments with argparse library
882
883 Result of parsing are the class attributes.
884 """
885 levelTranslate = [logging.WARNING, logging.INFO, logging.DEBUG]
886
887 def __init__(self):
888
889 try:
Kevin Rocard53493b22013-02-04 15:16:10 +0100890 # As argparse is only in the stdlib since python 3.2,
891 # testing its availability
Kevin Rocard326e39e2012-12-14 16:11:05 +0100892 import argparse
893
894 except ImportError:
Kevin Rocard53493b22013-02-04 15:16:10 +0100895 logger.warning("Unable to import argparse "
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100896 "(parser for command-line options and arguments), "
897 "using default argument values:")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100898
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100899 logger.warning(" - InputFile: stdin")
Kevin Rocard53493b22013-02-04 15:16:10 +0100900 self.inputFile = sys.stdin
Kevin Rocard326e39e2012-12-14 16:11:05 +0100901
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100902 logger.warning(" - OutputFile: stdout")
Kevin Rocard53493b22013-02-04 15:16:10 +0100903 self.outputFile = sys.stdout
Kevin Rocard326e39e2012-12-14 16:11:05 +0100904
Kevin Rocard53493b22013-02-04 15:16:10 +0100905 try:
906 self.domainsFile = sys.argv[1]
907 except IndexError as ex:
908 logger.fatal("No domain file provided (first argument)")
909 raise ex
910 else:
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100911 logger.warning(" - Domain file: " + self.domainsFile)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100912
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100913 logger.warning(" - Output format: xml")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100914 self.XMLreport = True
Kevin Rocard326e39e2012-12-14 16:11:05 +0100915
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100916 logger.warning(" - Debug level: error")
917 self.debugLevel = logging.INFO
Kevin Rocard326e39e2012-12-14 16:11:05 +0100918 else :
919
920 myArgParser = argparse.ArgumentParser(description='Generate PFW report')
921
922 myArgParser.add_argument(
923 'domainsFile',
924 type=argparse.FileType('r'),
925 help="the PFW domain XML file"
926 )
927 myArgParser.add_argument(
928 'pfwlog', nargs='?',
929 type=argparse.FileType('r'), default=sys.stdin,
930 help="the PFW log file, default stdin"
931 )
932 myArgParser.add_argument(
933 '-o', '--output',
934 dest="outputFile",
935 type=argparse.FileType('w'), default=sys.stdout,
936 help="the coverage report output file, default stdout"
937 )
938 myArgParser.add_argument(
939 '-v', '--verbose',
940 dest="debugLevel", default=0,
941 action='count',
942 help="print debug warnings from warning (default) to debug (-vv)"
943 )
944
945 outputFormatGroupe = myArgParser.add_mutually_exclusive_group(required=False)
946
947 outputFormatGroupe.add_argument(
948 '--xml',
949 dest="xmlFlag",
950 action='store_true',
951 help=" XML coverage output report"
952 )
953 outputFormatGroupe.add_argument(
954 '--raw',
955 dest="rawFlag",
956 action='store_true',
957 help="raw coverage output report"
958 )
959
Kevin Rocard9050c812013-06-10 16:01:21 +0200960 myArgParser.add_argument(
961 '--ignore-incoherent-criterion-state',
962 dest="incoherentCriterionFlag",
963 action='store_true',
964 help="ignore criterion transition to incoherent state"
965 )
966
967 myArgParser.add_argument(
968 '--ignore-ineligible-configuration-application',
969 dest="ineligibleConfigurationApplicationFlag",
970 action='store_true',
971 help="ignore application of configuration with a false rule "
972 "(not applicable configuration)"
973 )
974
Kevin Rocard326e39e2012-12-14 16:11:05 +0100975 # Process command line arguments
976 options = myArgParser.parse_args()
977
978 # Mapping to attributes
979 self.inputFile = options.pfwlog
980 self.outputFile = options.outputFile
981 self.domainsFile = options.domainsFile
982
983 # Output report in xml if flag not set
984 self.XMLreport = not options.rawFlag
985
986 # Setting logger level
987 levelCapped = min(options.debugLevel, len(self.levelTranslate) - 1)
988 self.debugLevel = self.levelTranslate[levelCapped]
989
Kevin Rocard9050c812013-06-10 16:01:21 +0200990 # Setting ignore options
991 errorToIgnore = []
992 if options.ineligibleConfigurationApplicationFlag :
993 errorToIgnore.append(Configuration.IneligibleConfigurationAppliedError)
994
995 if options.incoherentCriterionFlag:
996 errorToIgnore.append(Criterion.ChangeRequestToNonAccessibleState)
997
998 self.errorToIgnore = tuple(errorToIgnore)
999
Kevin Rocard326e39e2012-12-14 16:11:05 +01001000
1001
1002def main():
1003
Kevin Rocard53493b22013-02-04 15:16:10 +01001004 errorDuringLogParsing = -1
1005 errorDuringArgumentParsing = 1
1006
1007 try:
1008 commandLineArguments = ArgumentParser()
1009 except LookupError as ex:
1010 logger.error("Error during argument parsing")
1011 logger.debug(str(ex))
1012 sys.exit(errorDuringArgumentParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001013
1014 # Setting logger level
1015 logger.setLevel(commandLineArguments.debugLevel)
1016 logger.info("Log level set to: %s" %
1017 logging.getLevelName(commandLineArguments.debugLevel))
1018
1019 # Create tree from XML
1020 dom = xml.dom.minidom.parse(commandLineArguments.domainsFile)
1021
Kevin Rocard53493b22013-02-04 15:16:10 +01001022 # Create element tree
Kevin Rocard326e39e2012-12-14 16:11:05 +01001023 root = Root("Coverage", dom)
1024
Kevin Rocard53493b22013-02-04 15:16:10 +01001025 # Parse PFW events
Kevin Rocard9050c812013-06-10 16:01:21 +02001026 parser = ParsePFWlog(root.domains, root.criteria, commandLineArguments.errorToIgnore)
Kevin Rocard53493b22013-02-04 15:16:10 +01001027
Kevin Rocard326e39e2012-12-14 16:11:05 +01001028 try:
1029 parser.parsePFWlog(commandLineArguments.inputFile.readlines())
Kevin Rocard3aa0db42013-02-13 17:15:38 +01001030 except CustomError as ex:
1031 logger.fatal("Error during parsing log file %s: %s" %
Kevin Rocard326e39e2012-12-14 16:11:05 +01001032 (commandLineArguments.inputFile, ex))
Kevin Rocard53493b22013-02-04 15:16:10 +01001033 sys.exit(errorDuringLogParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001034
Kevin Rocard53493b22013-02-04 15:16:10 +01001035 # Output report
Kevin Rocard326e39e2012-12-14 16:11:05 +01001036 outputFile = commandLineArguments.outputFile
1037
1038 if not commandLineArguments.XMLreport :
Kevin Rocard326e39e2012-12-14 16:11:05 +01001039 outputFile.write("%s\n" % root.dump(withCoverage=True, withNbUse=True))
Kevin Rocard326e39e2012-12-14 16:11:05 +01001040 else :
Kevin Rocard326e39e2012-12-14 16:11:05 +01001041 outputFile.write(root.exportToXML().toprettyxml())
1042
1043
Kevin Rocard326e39e2012-12-14 16:11:05 +01001044if __name__ == "__main__" :
Kevin Rocard9050c812013-06-10 16:01:21 +02001045 """ Execute main if the python interpreter is running this module as the main program """
Kevin Rocard326e39e2012-12-14 16:11:05 +01001046 main()
1047