blob: a6d6fa6caebaadbd19275a03d45e54e38ac3c9ed [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):
Kevin Rocard2e404522013-06-14 17:09:32 +0200138 """Return the coverage of the element between 0 and 1
Kevin Rocard326e39e2012-12-14 16:11:05 +0100139
Kevin Rocard2e404522013-06-14 17:09:32 +0200140 If the element has no coverage dependency (usually child) return 0 or 1.
141 otherwise the element coverage is the dependency coverage average"""
142 coverageDependanceElements = list(self._getCoverageDependanceElements())
Kevin Rocard326e39e2012-12-14 16:11:05 +0100143
Kevin Rocard2e404522013-06-14 17:09:32 +0200144 nbcoverageDependence = len(coverageDependanceElements)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100145
146 if nbcoverageDependence == 0:
Kevin Rocard2e404522013-06-14 17:09:32 +0200147 if self.nbUse == 0:
148 return 0
149 else:
150 return 1
Kevin Rocard326e39e2012-12-14 16:11:05 +0100151
Kevin Rocard2e404522013-06-14 17:09:32 +0200152 coverageDependenceValues = (depElement._getCoverage()
153 for depElement in coverageDependanceElements)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100154
Kevin Rocard2e404522013-06-14 17:09:32 +0200155 return sum(coverageDependenceValues) / nbcoverageDependence
Kevin Rocard326e39e2012-12-14 16:11:05 +0100156
Kevin Rocard2e404522013-06-14 17:09:32 +0200157 def _getCoverageDependanceElements(self):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100158 return self.children
159
160 def _coverageFormating(self, coverage):
161 # If no coverage provided
162 if not coverage :
163 return ""
164
165 # Calculate coverage
166 return " (%s coverage)" % self._number2percent(coverage)
167
168 @staticmethod
169 def _number2percent(number):
170 """Format a number to a integer % string
171
172 example: _number2percent(0.6666) -> "67%"
173 """
174 return "{0:.0f}%".format(100 * number)
175
176
177 def _dumpDescription(self, withCoverage, withNbUse):
178
179 self.debug("yelding description")
180 yield RankedLine(self._description(withCoverage, withNbUse), lineSuffix="")
181
182 for dumped in self._dumpPropagate(withCoverage, withNbUse) :
183 yield dumped
184
185 def _dumpPropagate(self, withCoverage, withNbUse):
186
187 for child in self.children :
188 for dumpedDescription in child._dumpDescription(withCoverage, withNbUse) :
189 yield dumpedDescription.increasedRank()
190
191
192 def dump(self, withCoverage=False, withNbUse=True):
193
194 return "\n".join(
195 str(dumpedDescription) for dumpedDescription in
196 self._dumpDescription(withCoverage, withNbUse))
197
198 def exportToXML(self):
199 domElement = xml.dom.minidom.Element(self.tag)
200 self._XMLaddAttributes(domElement)
201
202 for child in self.children :
203 domElement.appendChild(child.exportToXML())
204
205 return domElement
206
207 def _XMLaddAttributes(self, domElement):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100208 attributes = self._getXMLAttributes()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100209
210 coverage = self._getCoverage()
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100211 if coverage != None :
212 attributes["Coverage"] = self._number2percent(coverage)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100213
214 for key, value in attributes.items():
215 domElement.setAttribute(key, value)
216
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100217 def _getXMLAttributes(self):
218 return {
219 "Name": self.name,
220 "NbUse": str(self.nbUse)
221 }
Kevin Rocard326e39e2012-12-14 16:11:05 +0100222
223 def _incNbUse(self):
224 self.nbUse += 1
225
226 def childUsed(self, child):
227 self._incNbUse()
228 # Propagate to parent
229 self._tellParentThatChildUsed()
230
231 def _tellParentThatChildUsed(self):
232 if self.parent :
233 self.parent.childUsed(self)
234
235
236 def parentUsed(self):
237 self._incNbUse()
238 # Propagate to children
239 for child in self.children :
240 child.parentUsed()
241
242 def hasBeenUsed(self):
243 return self.nbUse > 0
244
245 def operationOnChild(self, path, operation):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100246
247 if path:
248 return self._operationPropagate(path, operation)
249 else :
250 self.debug("operating on self")
251 return operation(self)
252
253 def _operationPropagate(self, path, operation):
254
255 childName = path.pop(0)
256 child = self.getChildFromName(childName)
257
258 return child.operationOnChild(path, operation)
259
260
261
262 def debug(self, stringOrFunction, level=logging.DEBUG):
263 """Print a debug line on stderr in tree form
264
265 If the debug line is expensive to generate, provide callable
266 object, it will be called if log is enable for this level.
267 This callable object should return the logline string.
268 """
269 if logger.isEnabledFor(level):
270
271 # TODO: use buildin callable if python >= 3.2
272 if hasattr(stringOrFunction, "__call__"):
273 string = stringOrFunction()
274 else:
275 string = stringOrFunction
276
277 rankedLine = DebugRankedLine("%s: %s" % (self, string))
278 self._logDebug(rankedLine, level)
279
280 def _logDebug(self, rankedLine, level):
281
282 if self.parent:
283 self.parent._logDebug(rankedLine.increasedRank(), level)
284 else :
285 logger.log(level, str(rankedLine))
286
287
288
289
290class FromDomElement(Element):
291 def __init__(self, DomElement):
292 self._initFromDom(DomElement)
293 super().__init__(self.name)
294
295
296 def _initFromDom(self, DomElement):
297 self.name = DomElement.getAttribute("Name")
298
299
300
301class DomElementLocation():
302 def __init__(self, classConstructor, path=None):
303 self.classConstructor = classConstructor
304 if path :
305 self.path = path
306 else :
307 self.path = []
308
309 self.path.append(classConstructor.tag)
310
311
312class DomPopulatedElement(Element):
313 """Default child populate
314
315 Look for each dom element with tag specified in self.tag
316 and instantiate it with the dom element
317 """
318 childClasses = []
319
320 def populate(self, dom):
321
322 for childDomElementLocation in self.childClasses :
323
324 self.debug("Looking for child %s in path %s" % (
325 childDomElementLocation.path[-1], childDomElementLocation.path))
326
327 for childDomElement in self._findChildFromTagPath(dom, childDomElementLocation.path) :
328
329 childElement = childDomElementLocation.classConstructor(childDomElement)
330 self.addChild(childElement)
331
332 childElement.populate(childDomElement)
333
334 def _findChildFromTagPath(self, dom, path):
335 if not path :
336 yield dom
337 else :
338 # Copy list
339 path = list(path)
340
341 tag = path.pop(0)
342
343 # Find element with tag
344 self.debug("Going to find elements with tag %s in %s" % (tag, dom))
345 self.debug(lambda: "Nb of solutions: %s" % len(dom.getElementsByTagName(tag)))
346
347 for elementByTag in dom.getElementsByTagName(tag) :
348
349 self.debug("Found element: %s" % elementByTag)
350
351 # If the same tag is found
352 if elementByTag in dom.childNodes :
353
354 # Yield next level
355 for element in self._findChildFromTagPath(elementByTag, path) :
356 yield element
357
358
359class Rule(Element):
360
361 def usedIfApplicable(self, criteria):
362 childApplicability = (child.usedIfApplicable(criteria)
363 for child in self.children)
364
365 isApplicable = self._isApplicable(criteria, childApplicability)
366
367 if isApplicable :
368 self._incNbUse()
369
370 self.debug("Rule applicability: %s" % isApplicable)
371 assert(isApplicable == True or isApplicable == False)
372
373 return isApplicable
374
375
376 def _isApplicable(self, criteria, childApplicability):
377 # Forcing evaluation of all child by the list creation
378 return all(list(childApplicability))
379
380
381class CriterionRule(FromDomElement, DomPopulatedElement, Rule):
382 tag = "SelectionCriterionRule"
383 childClasses = []
384 isApplicableOperations = {
385 "Includes" : lambda criterion, value: criterion.stateIncludes(value),
386 "Excludes" : lambda criterion, value: not criterion.stateIncludes(value),
387 "Is" : lambda criterion, value: criterion.stateIs(value),
388 "IsNot" : lambda criterion, value: not criterion.stateIs(value)
389 }
390
391 def _initFromDom(self, DomElement):
392 self.selectionCriterion = DomElement.getAttribute("SelectionCriterion")
393 self.matchesWhen = DomElement.getAttribute("MatchesWhen")
394 self.value = DomElement.getAttribute("Value")
395 self.name = "%s %s %s" % (self.selectionCriterion, self.matchesWhen, self.value)
396
397 applicableOperationWithoutValue = self.isApplicableOperations[self.matchesWhen]
398 self.isApplicableOperation = lambda criterion: applicableOperationWithoutValue(criterion, self.value)
399
400 def _isApplicable(self, criteria, childApplicability):
401
402 return criteria.operationOnChild([self.selectionCriterion],
403 self.isApplicableOperation)
404
405
406class CompoundRule(FromDomElement, DomPopulatedElement, Rule):
407 """CompoundRule can be of type ALL or ANY"""
408 tag = "CompoundRule"
409 # Declare childClasses but define it at first class instantiation
410 childClasses = None
411
412 def __init__(self, dom):
413 # Define childClasses at first class instantiation
414 if self.childClasses == None :
415 self.childClasses = [DomElementLocation(CriterionRule),
416 DomElementLocation(CompoundRule)]
417 super().__init__(dom)
418
419 def _initFromDom(self, DomElement):
420
421 type = DomElement.getAttribute("Type")
422 self.ofTypeAll = {"All" : True, "Any" : False}[type]
423 self.name = type
424
425 def _isApplicable(self, criteria, childApplicability):
426 if self.ofTypeAll :
427 applicability = super()._isApplicable(criteria, childApplicability)
428 else:
429 # Forcing evaluation of all child by the list creation
430 applicability = any(list(childApplicability))
431
432 return applicability
433
434class RootRule(DomPopulatedElement, Rule):
435 tag = "RootRule"
436 childClasses = [DomElementLocation(CompoundRule)]
437
438 def populate(self, dom):
439 super().populate(dom)
440 self.debug("Children: %s" % self.children)
441 # A configuration can only have one or no rule
442 assert(len(self.children) <= 1)
443
Kevin Rocard2e404522013-06-14 17:09:32 +0200444 def _getCoverageDependanceElements(self):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100445 return self._getDescendants()
446
447
448class CriteronStates(Element):
449 """Root of configuration application criterion state"""
450 tag = "CriterionStates"
451
452 def parentUsed(self, criteria):
453 """Add criteria to child if not exist, if exist increase it's nbUse"""
454 self._incNbUse()
455
456 matches = [child for child in self.children if child == criteria]
457
458 assert(len(matches) <= 1)
459
460 if matches :
461 self.debug("Criteria state has already been encounter")
462 currentcriteria = matches[0]
463 else :
464 self.debug("Criteria state has never been encounter, saving it")
465 currentcriteria = criteria
466 self.addChild(criteria)
467
468 currentcriteria.parentUsed()
469
Kevin Rocard326e39e2012-12-14 16:11:05 +0100470
471
472class Configuration(FromDomElement, DomPopulatedElement):
473 tag = "Configuration"
474 childClasses = []
475
Kevin Rocard97dbd352013-06-10 15:15:56 +0200476 class IneligibleConfigurationAppliedError(CustomError):
477
478 def __init__(self, configuration, criteria):
479 self.configuration = configuration
480 self.criteria = criteria
481
482 def __str__(self):
483
484 return ("Applying ineligible %s, "
485 "rule:\n%s\n"
486 "Criteria current state:\n%s" % (
487 self.configuration,
488 self.configuration.rootRule.dump(withCoverage=False, withNbUse=False),
489 self.criteria.dump(withCoverage=False, withNbUse=False)
490 ))
491
Kevin Rocard326e39e2012-12-14 16:11:05 +0100492 def __init__(self, DomElement):
493 super().__init__(DomElement)
494
495 self.rootRule = RootRule("RootRule")
496 self.addChild(self.rootRule)
497
498 self.criteronStates = CriteronStates("CriterionStates")
499 self.addChild(self.criteronStates)
500
501 def populate(self, dom):
502 # Delegate to rootRule
503 self.rootRule.populate(dom)
504
505 def _getCoverage(self):
506 # Delegate to rootRule
507 return self.rootRule._getCoverage()
508
509 def used(self, criteria):
510
511 self._incNbUse()
512
513 # Propagate use to parents
514 self._tellParentThatChildUsed()
515
516 # Propagate to criterion coverage
517 self.criteronStates.parentUsed(criteria.export())
518
519 # Propagate to rules
520 if not self.rootRule.usedIfApplicable(criteria) :
521
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100522 self.debug("Applied but rule does not match current "
523 "criteria (parent: %s) " % self.parent.name,
Kevin Rocard925c20b2013-06-10 16:02:28 +0200524 logging.ERROR)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100525
Kevin Rocard97dbd352013-06-10 15:15:56 +0200526 raise self.IneligibleConfigurationAppliedError(self, criteria.export())
Kevin Rocard326e39e2012-12-14 16:11:05 +0100527
528 def _dumpPropagate(self, withCoverage, withNbUse):
529 self.debug("Going to ask %s for description" % self.rootRule)
530 for dumpedDescription in self.rootRule._dumpDescription(
531 withCoverage=withCoverage,
532 withNbUse=withNbUse) :
533 yield dumpedDescription.increasedRank()
534
535 self.debug("Going to ask %s for description" % self.criteronStates)
536 for dumpedDescription in self.criteronStates._dumpDescription(
537 withCoverage=False,
538 withNbUse=withNbUse) :
539 yield dumpedDescription.increasedRank()
540
541
542class Domain(FromDomElement, DomPopulatedElement):
543 tag = "ConfigurableDomain"
544 childClasses = [DomElementLocation(Configuration, ["Configurations"])]
545
546
547class Domains(DomPopulatedElement):
548 tag = "Domains"
549 childClasses = [DomElementLocation(Domain, ["ConfigurableDomains"])]
550
551
552class RankedLine():
553 def __init__(self, string,
554 stringPrefix="|-- ",
555 rankString="| ",
556 linePrefix="",
557 lineSuffix="\n"):
558 self.string = string
559 self.rank = 0
560 self.stringPrefix = stringPrefix
561 self.rankString = rankString
562 self.linePrefix = linePrefix
563 self.lineSuffix = lineSuffix
564
565 def increasedRank(self):
566 self.rank += 1
567 return self
568
569 def __str__(self):
570 return self.linePrefix + \
571 self.rank * self.rankString + \
572 self.stringPrefix + \
573 self.string + \
574 self.lineSuffix
575
576class DebugRankedLine(RankedLine):
577
578 def __init__(self, string, lineSuffix=""):
579 super().__init__(string,
580 stringPrefix="",
581 rankString=" ",
582 linePrefix="",
583 lineSuffix=lineSuffix)
584
585
586class CriterionState(Element):
587 tag = "CriterionState"
588 def used(self):
589 self._incNbUse()
590
591
592class Criterion(Element):
593 tag = "Criterion"
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100594 inclusivenessTranslate = {True: "Inclusive", False: "Exclusive"}
595
Kevin Rocard556538e2013-06-10 15:20:10 +0200596 class ChangeRequestToNonAccessibleState(CustomError):
597 def __init__(self, requestedState, detail):
598 self.requestedState = requestedState
599 self.detail = detail
600
601 def __str__(self):
602 return ("Change request to non accessible state %s. Detail: %s" %
603 (self.requestedState, self.detail))
604
605 def __init__(self, name, isInclusif,
606 stateNamesList, currentStateNamesList,
607 ignoreIntegrity=False):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100608 super().__init__(name)
609 self.isInclusif = isInclusif
610
Kevin Rocard326e39e2012-12-14 16:11:05 +0100611 for state in stateNamesList :
612 self.addChild(CriterionState(state))
613
614 self.currentState = []
Kevin Rocard556538e2013-06-10 15:20:10 +0200615 self.initStateNamesList = list(currentStateNamesList)
616 self.changeState(self.initStateNamesList, ignoreIntegrity)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100617
Kevin Rocard556538e2013-06-10 15:20:10 +0200618 def reset(self):
619 # Set current state as provided at initialisation
620 self.changeState(self.initStateNamesList, ignoreIntegrity=True)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100621
Kevin Rocard556538e2013-06-10 15:20:10 +0200622 def changeState(self, subStateNames, ignoreIntegrity=False):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100623 self.debug("Changing state from: %s to: %s" % (
624 list(self._getElementNames(self.currentState)),
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100625 subStateNames))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100626
Kevin Rocard556538e2013-06-10 15:20:10 +0200627 if not ignoreIntegrity and not self.isIntegre(subStateNames):
628 raise self.ChangeRequestToNonAccessibleState(subStateNames,
629 "An exclusive criterion must have a non empty state")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100630
631 newCurrentState = []
632 for subStateName in subStateNames :
633 subState = self.getChildFromName(subStateName)
634 subState.used()
635 newCurrentState.append(subState)
636
637 self.currentState = newCurrentState
638
639 self._incNbUse()
640 self._tellParentThatChildUsed()
641
Kevin Rocard556538e2013-06-10 15:20:10 +0200642 def isIntegre(self, subStateNames):
643 return self.isInclusif or len(subStateNames) == 1
644
645 def childUsed(self, child):
646 self.currentState = child
647 super().childUsed(child)
648
Kevin Rocard326e39e2012-12-14 16:11:05 +0100649 def export(self):
650 subStateNames = self._getElementNames(self.currentState)
Kevin Rocard556538e2013-06-10 15:20:10 +0200651 return Criterion(self.name, self.isInclusif, subStateNames, subStateNames,
652 ignoreIntegrity=True)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100653
654 def stateIncludes(self, subStateName):
655 subStateCurrentNames = list(self._getElementNames(self.currentState))
656
657 self.debug("Testing if %s is included in %s" % (subStateName, subStateCurrentNames))
658
659 isIncluded = subStateName in subStateCurrentNames
660 self.debug("IsIncluded: %s" % isIncluded)
661
662 return isIncluded
663
664
665 def stateIs(self, subStateNames):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100666 if len(self.currentState) != 1 :
667 return False
668 else :
669 return self.stateIncludes(subStateNames)
670
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100671 def _getXMLAttributes(self):
672 attributes = super()._getXMLAttributes()
673 attributes["Type"] = self.inclusivenessTranslate[self.isInclusif]
674 return attributes
675
Kevin Rocard326e39e2012-12-14 16:11:05 +0100676
677class Criteria(Element):
678 tag = "Criteria"
679
Kevin Rocardd077c552013-06-10 15:31:32 +0200680 class DuplicatedCriterionError(DuplicatedChildError):
681 pass
682
Kevin Rocard326e39e2012-12-14 16:11:05 +0100683 def export(self):
684 self.debug("Exporting criteria")
685 assert(self.children)
686
687 exported = Criteria(self.name)
688 for child in self.children :
689 exported.addChild(child.export())
690 return exported
691
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100692 def addChild(self, child):
693 if child in self.children:
Kevin Rocardd077c552013-06-10 15:31:32 +0200694 raise self.DuplicatedCriterionError(self, child)
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100695 super().addChild(child)
696
697class ConfigAppliedWithoutCriteriaError(CustomError):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100698 def __init__(self, configurationName, domainName):
699 self.configurationName = configurationName
700 self.domainName = domainName
701 def __str__(self):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100702 return ('Applying configuration "%s" from domain "%s" before declaring criteria' %
703 (self.configurationName, self.domainName))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100704
705class ParsePFWlog():
706 MATCH = "match"
707 ACTION = "action"
708
Kevin Rocard9050c812013-06-10 16:01:21 +0200709 def __init__(self, domains, criteria, ErrorsToIgnore=()):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100710
711 self.domains = domains;
712 self.criteria = criteria;
Kevin Rocard9050c812013-06-10 16:01:21 +0200713 self.ErrorsToIgnore = ErrorsToIgnore
Kevin Rocard326e39e2012-12-14 16:11:05 +0100714
715 configApplicationRegext = r""".*Applying configuration "(.*)" from domain "([^"]*)"""
716 matchConfigApplicationLine = re.compile(configApplicationRegext).match
717
718 criterionCreationRegext = ", ".join([
719 r""".*Criterion name: (.*)""",
720 r"""type kind: (.*)""",
721 r"""current state: (.*)""",
722 r"""states: {(.*)}"""
723 ])
724 matchCriterionCreationLine = re.compile(criterionCreationRegext).match
725
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100726 changingCriterionRegext = r""".*Selection criterion changed event: Criterion name: (.*), current state: ([^\n\r]*)"""
Kevin Rocard326e39e2012-12-14 16:11:05 +0100727 matchChangingCriterionLine = re.compile(changingCriterionRegext).match
728
729 self.lineLogTypes = [
730 {
731 self.MATCH: matchConfigApplicationLine,
732 self.ACTION: self._configApplication
733 }, {
734 self.MATCH: matchCriterionCreationLine,
735 self.ACTION: self._criterionCreation
736 }, {
737 self.MATCH: matchChangingCriterionLine,
738 self.ACTION: self._changingCriterion
739 }
740 ]
741
742 @staticmethod
743 def _formatCriterionList(liststring, separator):
744 list = liststring.split(separator)
745 if len(list) == 1 and list[0] == "<none>":
746 list = []
747 return list
748
749 def _criterionCreation(self, matchCriterionCreation):
750 # Unpack
751 criterionName, criterionType, currentCriterionStates, criterionStates = matchCriterionCreation.group(1, 2, 3, 4)
752
753 criterionStateList = self._formatCriterionList(criterionStates, ", ")
754
755 criterionIsInclusif = {"exclusive" : False, "inclusive" : True}[criterionType]
756
757 currentcriterionStateList = self._formatCriterionList(currentCriterionStates, "|")
758
759 logger.info("Creating criterion: " + criterionName +
760 " (" + criterionType + ") " +
761 " with current state: " + str(currentcriterionStateList) +
762 ", possible states:" + str(criterionStateList))
763
Kevin Rocardea874222013-06-10 16:00:08 +0200764 try:
765 self.criteria.addChild(Criterion(
Kevin Rocard326e39e2012-12-14 16:11:05 +0100766 criterionName,
767 criterionIsInclusif,
768 criterionStateList,
769 currentcriterionStateList
770 ))
Kevin Rocardea874222013-06-10 16:00:08 +0200771 except self.criteria.DuplicatedCriterionError as ex:
772 logger.debug(ex)
773 logger.warning("Reseting criterion %s. Did you reset the PFW ?" % criterionName)
774 self.criteria.operationOnChild(
775 [criterionName],
776 lambda criterion: criterion.reset()
777 )
778
779
Kevin Rocard326e39e2012-12-14 16:11:05 +0100780
781 def _changingCriterion(self, matchChangingCriterion):
782 # Unpack
783 criterionName, newCriterionSubStateNames = matchChangingCriterion.group(1, 2)
784
785 newCriterionState = self._formatCriterionList(newCriterionSubStateNames, "|")
786
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100787 logger.info("Changing criterion %s to %s" % (criterionName , newCriterionState))
788
Kevin Rocard326e39e2012-12-14 16:11:05 +0100789 path = [criterionName]
790 changeCriterionOperation = lambda criterion : criterion.changeState(newCriterionState)
791 self.criteria.operationOnChild(path, changeCriterionOperation)
792
793 def _configApplication(self, matchConfig):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100794 # Unpack
795 configurationName, domainName = matchConfig.group(1, 2)
796
Kevin Rocard326e39e2012-12-14 16:11:05 +0100797 # Check that at least one criterion exist
798 if not self.criteria.hasChildren() :
799 logger.error("Applying configuration before declaring criteria")
800 logger.info("Is the log starting at PFW boot ?")
801 raise ConfigAppliedWithoutCriteriaError(configurationName, domainName)
802
Kevin Rocard326e39e2012-12-14 16:11:05 +0100803 # Change criterion state
804 path = [domainName, configurationName]
805 usedOperation = lambda element : element.used(self.criteria)
806
807 logger.info("Applying configuration %s from domain %s" % (
808 configurationName, domainName))
809
810 self.domains.operationOnChild(path, usedOperation)
811
812
813 def _digest(self, lineLogType, lineLog):
Kevin Rocard9050c812013-06-10 16:01:21 +0200814
Kevin Rocard326e39e2012-12-14 16:11:05 +0100815 match = lineLogType[self.MATCH](lineLog)
816 if match :
817 lineLogType[self.ACTION](match)
818 return True
819 return False
820
Kevin Rocard9050c812013-06-10 16:01:21 +0200821
Kevin Rocard326e39e2012-12-14 16:11:05 +0100822 def parsePFWlog(self, lines):
Kevin Rocard9050c812013-06-10 16:01:21 +0200823 for lineNb, lineLog in enumerate(lines):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100824
825 logger.debug("Parsing line :%s" % lineLog.rstrip())
826
827 digested = (self._digest(lineLogType, lineLog)
828 for lineLogType in self.lineLogTypes)
829
Kevin Rocard9050c812013-06-10 16:01:21 +0200830 try:
831 success = any(digested)
832
833 # Catch some exception in order to print the current parsing line,
834 # then raise the exception again if not continue of error
835 except CustomError as ex:
836 logger.error('Error raised while parsing line %s: "%s"' %
837 (lineNb, repr(lineLog)))
838
839 # If exception is a subclass of ErrorsToIgnore, log it and continue
840 # otherwise raise it again.
841 if not issubclass(type(ex), self.ErrorsToIgnore):
842 raise ex
843 else:
844 logger.error('Ignoring exception:"%s", '
845 'can not guarantee database integrity' % ex)
846 else:
847 if not success:
848 logger.debug("Line does not match, dropped")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100849
850
851class Root(Element):
852 tag = "Root"
853 def __init__(self, name, dom):
854 super().__init__(name)
855 # Create domain tree
856 self.domains = Domains("Domains")
857 self.domains.populate(dom)
858 self.addChild(self.domains)
859 # Create criterion list
860 self.criteria = Criteria("CriterionRoot")
861 self.addChild(self.criteria)
862
863 def exportToXML(self):
864 """Export tree to an xml document"""
865 impl = xml.dom.minidom.getDOMImplementation()
866 newdoc = impl.createDocument(None, self.name, None)
867 XMLDocElement = newdoc.documentElement
868
869 for child in self.children:
870 XMLDocElement.appendChild(child.exportToXML())
871
872 return newdoc
873
874# ============================
875# Command line argument parser
876# ============================
877
878
879class ArgumentParser:
880 """class that parse command line arguments with argparse library
881
882 Result of parsing are the class attributes.
883 """
Kevin Rocard925c20b2013-06-10 16:02:28 +0200884 levelTranslate = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100885
886 def __init__(self):
887
888 try:
Kevin Rocard53493b22013-02-04 15:16:10 +0100889 # As argparse is only in the stdlib since python 3.2,
890 # testing its availability
Kevin Rocard326e39e2012-12-14 16:11:05 +0100891 import argparse
892
893 except ImportError:
Kevin Rocard53493b22013-02-04 15:16:10 +0100894 logger.warning("Unable to import argparse "
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100895 "(parser for command-line options and arguments), "
896 "using default argument values:")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100897
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100898 logger.warning(" - InputFile: stdin")
Kevin Rocard53493b22013-02-04 15:16:10 +0100899 self.inputFile = sys.stdin
Kevin Rocard326e39e2012-12-14 16:11:05 +0100900
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100901 logger.warning(" - OutputFile: stdout")
Kevin Rocard53493b22013-02-04 15:16:10 +0100902 self.outputFile = sys.stdout
Kevin Rocard326e39e2012-12-14 16:11:05 +0100903
Kevin Rocard53493b22013-02-04 15:16:10 +0100904 try:
905 self.domainsFile = sys.argv[1]
906 except IndexError as ex:
907 logger.fatal("No domain file provided (first argument)")
908 raise ex
909 else:
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100910 logger.warning(" - Domain file: " + self.domainsFile)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100911
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100912 logger.warning(" - Output format: xml")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100913 self.XMLreport = True
Kevin Rocard326e39e2012-12-14 16:11:05 +0100914
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100915 logger.warning(" - Debug level: error")
Kevin Rocard925c20b2013-06-10 16:02:28 +0200916 self.debugLevel = logging.ERROR
Kevin Rocard326e39e2012-12-14 16:11:05 +0100917 else :
918
919 myArgParser = argparse.ArgumentParser(description='Generate PFW report')
920
921 myArgParser.add_argument(
922 'domainsFile',
923 type=argparse.FileType('r'),
924 help="the PFW domain XML file"
925 )
926 myArgParser.add_argument(
927 'pfwlog', nargs='?',
928 type=argparse.FileType('r'), default=sys.stdin,
929 help="the PFW log file, default stdin"
930 )
931 myArgParser.add_argument(
932 '-o', '--output',
933 dest="outputFile",
934 type=argparse.FileType('w'), default=sys.stdout,
935 help="the coverage report output file, default stdout"
936 )
937 myArgParser.add_argument(
938 '-v', '--verbose',
939 dest="debugLevel", default=0,
940 action='count',
941 help="print debug warnings from warning (default) to debug (-vv)"
942 )
943
944 outputFormatGroupe = myArgParser.add_mutually_exclusive_group(required=False)
945
946 outputFormatGroupe.add_argument(
947 '--xml',
948 dest="xmlFlag",
949 action='store_true',
950 help=" XML coverage output report"
951 )
952 outputFormatGroupe.add_argument(
953 '--raw',
954 dest="rawFlag",
955 action='store_true',
956 help="raw coverage output report"
957 )
958
Kevin Rocard9050c812013-06-10 16:01:21 +0200959 myArgParser.add_argument(
960 '--ignore-incoherent-criterion-state',
961 dest="incoherentCriterionFlag",
962 action='store_true',
963 help="ignore criterion transition to incoherent state"
964 )
965
966 myArgParser.add_argument(
967 '--ignore-ineligible-configuration-application',
968 dest="ineligibleConfigurationApplicationFlag",
969 action='store_true',
970 help="ignore application of configuration with a false rule "
971 "(not applicable configuration)"
972 )
973
Kevin Rocard326e39e2012-12-14 16:11:05 +0100974 # Process command line arguments
975 options = myArgParser.parse_args()
976
977 # Mapping to attributes
978 self.inputFile = options.pfwlog
979 self.outputFile = options.outputFile
980 self.domainsFile = options.domainsFile
981
982 # Output report in xml if flag not set
983 self.XMLreport = not options.rawFlag
984
985 # Setting logger level
986 levelCapped = min(options.debugLevel, len(self.levelTranslate) - 1)
987 self.debugLevel = self.levelTranslate[levelCapped]
988
Kevin Rocard9050c812013-06-10 16:01:21 +0200989 # Setting ignore options
990 errorToIgnore = []
991 if options.ineligibleConfigurationApplicationFlag :
992 errorToIgnore.append(Configuration.IneligibleConfigurationAppliedError)
993
994 if options.incoherentCriterionFlag:
995 errorToIgnore.append(Criterion.ChangeRequestToNonAccessibleState)
996
997 self.errorToIgnore = tuple(errorToIgnore)
998
Kevin Rocard326e39e2012-12-14 16:11:05 +0100999
1000
1001def main():
1002
Kevin Rocard53493b22013-02-04 15:16:10 +01001003 errorDuringLogParsing = -1
1004 errorDuringArgumentParsing = 1
1005
1006 try:
1007 commandLineArguments = ArgumentParser()
1008 except LookupError as ex:
1009 logger.error("Error during argument parsing")
1010 logger.debug(str(ex))
1011 sys.exit(errorDuringArgumentParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001012
1013 # Setting logger level
1014 logger.setLevel(commandLineArguments.debugLevel)
1015 logger.info("Log level set to: %s" %
1016 logging.getLevelName(commandLineArguments.debugLevel))
1017
1018 # Create tree from XML
1019 dom = xml.dom.minidom.parse(commandLineArguments.domainsFile)
1020
Kevin Rocard53493b22013-02-04 15:16:10 +01001021 # Create element tree
Kevin Rocard326e39e2012-12-14 16:11:05 +01001022 root = Root("Coverage", dom)
1023
Kevin Rocard53493b22013-02-04 15:16:10 +01001024 # Parse PFW events
Kevin Rocard9050c812013-06-10 16:01:21 +02001025 parser = ParsePFWlog(root.domains, root.criteria, commandLineArguments.errorToIgnore)
Kevin Rocard53493b22013-02-04 15:16:10 +01001026
Kevin Rocard326e39e2012-12-14 16:11:05 +01001027 try:
1028 parser.parsePFWlog(commandLineArguments.inputFile.readlines())
Kevin Rocard3aa0db42013-02-13 17:15:38 +01001029 except CustomError as ex:
1030 logger.fatal("Error during parsing log file %s: %s" %
Kevin Rocard326e39e2012-12-14 16:11:05 +01001031 (commandLineArguments.inputFile, ex))
Kevin Rocard53493b22013-02-04 15:16:10 +01001032 sys.exit(errorDuringLogParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001033
Kevin Rocard53493b22013-02-04 15:16:10 +01001034 # Output report
Kevin Rocard326e39e2012-12-14 16:11:05 +01001035 outputFile = commandLineArguments.outputFile
1036
1037 if not commandLineArguments.XMLreport :
Kevin Rocard326e39e2012-12-14 16:11:05 +01001038 outputFile.write("%s\n" % root.dump(withCoverage=True, withNbUse=True))
Kevin Rocard326e39e2012-12-14 16:11:05 +01001039 else :
Kevin Rocard326e39e2012-12-14 16:11:05 +01001040 outputFile.write(root.exportToXML().toprettyxml())
1041
1042
Kevin Rocard326e39e2012-12-14 16:11:05 +01001043if __name__ == "__main__" :
Kevin Rocard9050c812013-06-10 16:01:21 +02001044 """ Execute main if the python interpreter is running this module as the main program """
Kevin Rocard326e39e2012-12-14 16:11:05 +01001045 main()
1046