blob: 8aa941f04e638c2c1bbc9efe157e3c3094808b68 [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):
Kevin Rocardcf031992013-06-14 18:09:54 +0200377 """Return the rule applicability depending on children applicability.
378
379 If at least one child is applicable, return true"""
380 # Lazy evaluation as in the PFW
381 return all(childApplicability)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100382
383
384class CriterionRule(FromDomElement, DomPopulatedElement, Rule):
385 tag = "SelectionCriterionRule"
386 childClasses = []
387 isApplicableOperations = {
388 "Includes" : lambda criterion, value: criterion.stateIncludes(value),
389 "Excludes" : lambda criterion, value: not criterion.stateIncludes(value),
390 "Is" : lambda criterion, value: criterion.stateIs(value),
391 "IsNot" : lambda criterion, value: not criterion.stateIs(value)
392 }
393
394 def _initFromDom(self, DomElement):
395 self.selectionCriterion = DomElement.getAttribute("SelectionCriterion")
396 self.matchesWhen = DomElement.getAttribute("MatchesWhen")
397 self.value = DomElement.getAttribute("Value")
398 self.name = "%s %s %s" % (self.selectionCriterion, self.matchesWhen, self.value)
399
400 applicableOperationWithoutValue = self.isApplicableOperations[self.matchesWhen]
401 self.isApplicableOperation = lambda criterion: applicableOperationWithoutValue(criterion, self.value)
402
403 def _isApplicable(self, criteria, childApplicability):
404
405 return criteria.operationOnChild([self.selectionCriterion],
406 self.isApplicableOperation)
407
408
409class CompoundRule(FromDomElement, DomPopulatedElement, Rule):
410 """CompoundRule can be of type ALL or ANY"""
411 tag = "CompoundRule"
412 # Declare childClasses but define it at first class instantiation
413 childClasses = None
414
415 def __init__(self, dom):
416 # Define childClasses at first class instantiation
417 if self.childClasses == None :
418 self.childClasses = [DomElementLocation(CriterionRule),
419 DomElementLocation(CompoundRule)]
420 super().__init__(dom)
421
422 def _initFromDom(self, DomElement):
423
424 type = DomElement.getAttribute("Type")
425 self.ofTypeAll = {"All" : True, "Any" : False}[type]
426 self.name = type
427
428 def _isApplicable(self, criteria, childApplicability):
429 if self.ofTypeAll :
430 applicability = super()._isApplicable(criteria, childApplicability)
431 else:
Kevin Rocardcf031992013-06-14 18:09:54 +0200432 # Lazy evaluation as in the PFW
433 applicability = any(childApplicability)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100434
435 return applicability
436
437class RootRule(DomPopulatedElement, Rule):
438 tag = "RootRule"
439 childClasses = [DomElementLocation(CompoundRule)]
440
441 def populate(self, dom):
442 super().populate(dom)
443 self.debug("Children: %s" % self.children)
444 # A configuration can only have one or no rule
445 assert(len(self.children) <= 1)
446
Kevin Rocard2e404522013-06-14 17:09:32 +0200447 def _getCoverageDependanceElements(self):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100448 return self._getDescendants()
449
450
451class CriteronStates(Element):
452 """Root of configuration application criterion state"""
453 tag = "CriterionStates"
454
455 def parentUsed(self, criteria):
456 """Add criteria to child if not exist, if exist increase it's nbUse"""
457 self._incNbUse()
458
459 matches = [child for child in self.children if child == criteria]
460
461 assert(len(matches) <= 1)
462
463 if matches :
464 self.debug("Criteria state has already been encounter")
465 currentcriteria = matches[0]
466 else :
467 self.debug("Criteria state has never been encounter, saving it")
468 currentcriteria = criteria
469 self.addChild(criteria)
470
471 currentcriteria.parentUsed()
472
Kevin Rocard326e39e2012-12-14 16:11:05 +0100473
474
475class Configuration(FromDomElement, DomPopulatedElement):
476 tag = "Configuration"
477 childClasses = []
478
Kevin Rocard97dbd352013-06-10 15:15:56 +0200479 class IneligibleConfigurationAppliedError(CustomError):
480
481 def __init__(self, configuration, criteria):
482 self.configuration = configuration
483 self.criteria = criteria
484
485 def __str__(self):
486
487 return ("Applying ineligible %s, "
488 "rule:\n%s\n"
489 "Criteria current state:\n%s" % (
490 self.configuration,
491 self.configuration.rootRule.dump(withCoverage=False, withNbUse=False),
492 self.criteria.dump(withCoverage=False, withNbUse=False)
493 ))
494
Kevin Rocard326e39e2012-12-14 16:11:05 +0100495 def __init__(self, DomElement):
496 super().__init__(DomElement)
497
498 self.rootRule = RootRule("RootRule")
499 self.addChild(self.rootRule)
500
501 self.criteronStates = CriteronStates("CriterionStates")
502 self.addChild(self.criteronStates)
503
504 def populate(self, dom):
505 # Delegate to rootRule
506 self.rootRule.populate(dom)
507
508 def _getCoverage(self):
509 # Delegate to rootRule
510 return self.rootRule._getCoverage()
511
512 def used(self, criteria):
513
514 self._incNbUse()
515
516 # Propagate use to parents
517 self._tellParentThatChildUsed()
518
519 # Propagate to criterion coverage
520 self.criteronStates.parentUsed(criteria.export())
521
522 # Propagate to rules
523 if not self.rootRule.usedIfApplicable(criteria) :
524
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100525 self.debug("Applied but rule does not match current "
526 "criteria (parent: %s) " % self.parent.name,
Kevin Rocard925c20b2013-06-10 16:02:28 +0200527 logging.ERROR)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100528
Kevin Rocard97dbd352013-06-10 15:15:56 +0200529 raise self.IneligibleConfigurationAppliedError(self, criteria.export())
Kevin Rocard326e39e2012-12-14 16:11:05 +0100530
531 def _dumpPropagate(self, withCoverage, withNbUse):
532 self.debug("Going to ask %s for description" % self.rootRule)
533 for dumpedDescription in self.rootRule._dumpDescription(
534 withCoverage=withCoverage,
535 withNbUse=withNbUse) :
536 yield dumpedDescription.increasedRank()
537
538 self.debug("Going to ask %s for description" % self.criteronStates)
539 for dumpedDescription in self.criteronStates._dumpDescription(
540 withCoverage=False,
541 withNbUse=withNbUse) :
542 yield dumpedDescription.increasedRank()
543
544
545class Domain(FromDomElement, DomPopulatedElement):
546 tag = "ConfigurableDomain"
547 childClasses = [DomElementLocation(Configuration, ["Configurations"])]
548
549
550class Domains(DomPopulatedElement):
551 tag = "Domains"
552 childClasses = [DomElementLocation(Domain, ["ConfigurableDomains"])]
553
554
555class RankedLine():
556 def __init__(self, string,
557 stringPrefix="|-- ",
558 rankString="| ",
559 linePrefix="",
560 lineSuffix="\n"):
561 self.string = string
562 self.rank = 0
563 self.stringPrefix = stringPrefix
564 self.rankString = rankString
565 self.linePrefix = linePrefix
566 self.lineSuffix = lineSuffix
567
568 def increasedRank(self):
569 self.rank += 1
570 return self
571
572 def __str__(self):
573 return self.linePrefix + \
574 self.rank * self.rankString + \
575 self.stringPrefix + \
576 self.string + \
577 self.lineSuffix
578
579class DebugRankedLine(RankedLine):
580
581 def __init__(self, string, lineSuffix=""):
582 super().__init__(string,
583 stringPrefix="",
584 rankString=" ",
585 linePrefix="",
586 lineSuffix=lineSuffix)
587
588
589class CriterionState(Element):
590 tag = "CriterionState"
591 def used(self):
592 self._incNbUse()
593
594
595class Criterion(Element):
596 tag = "Criterion"
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100597 inclusivenessTranslate = {True: "Inclusive", False: "Exclusive"}
598
Kevin Rocard556538e2013-06-10 15:20:10 +0200599 class ChangeRequestToNonAccessibleState(CustomError):
600 def __init__(self, requestedState, detail):
601 self.requestedState = requestedState
602 self.detail = detail
603
604 def __str__(self):
605 return ("Change request to non accessible state %s. Detail: %s" %
606 (self.requestedState, self.detail))
607
608 def __init__(self, name, isInclusif,
609 stateNamesList, currentStateNamesList,
610 ignoreIntegrity=False):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100611 super().__init__(name)
612 self.isInclusif = isInclusif
613
Kevin Rocard326e39e2012-12-14 16:11:05 +0100614 for state in stateNamesList :
615 self.addChild(CriterionState(state))
616
617 self.currentState = []
Kevin Rocard556538e2013-06-10 15:20:10 +0200618 self.initStateNamesList = list(currentStateNamesList)
619 self.changeState(self.initStateNamesList, ignoreIntegrity)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100620
Kevin Rocard556538e2013-06-10 15:20:10 +0200621 def reset(self):
622 # Set current state as provided at initialisation
623 self.changeState(self.initStateNamesList, ignoreIntegrity=True)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100624
Kevin Rocard556538e2013-06-10 15:20:10 +0200625 def changeState(self, subStateNames, ignoreIntegrity=False):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100626 self.debug("Changing state from: %s to: %s" % (
627 list(self._getElementNames(self.currentState)),
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100628 subStateNames))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100629
Kevin Rocard556538e2013-06-10 15:20:10 +0200630 if not ignoreIntegrity and not self.isIntegre(subStateNames):
631 raise self.ChangeRequestToNonAccessibleState(subStateNames,
632 "An exclusive criterion must have a non empty state")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100633
634 newCurrentState = []
635 for subStateName in subStateNames :
636 subState = self.getChildFromName(subStateName)
637 subState.used()
638 newCurrentState.append(subState)
639
640 self.currentState = newCurrentState
641
642 self._incNbUse()
643 self._tellParentThatChildUsed()
644
Kevin Rocard556538e2013-06-10 15:20:10 +0200645 def isIntegre(self, subStateNames):
646 return self.isInclusif or len(subStateNames) == 1
647
648 def childUsed(self, child):
649 self.currentState = child
650 super().childUsed(child)
651
Kevin Rocard326e39e2012-12-14 16:11:05 +0100652 def export(self):
653 subStateNames = self._getElementNames(self.currentState)
Kevin Rocard556538e2013-06-10 15:20:10 +0200654 return Criterion(self.name, self.isInclusif, subStateNames, subStateNames,
655 ignoreIntegrity=True)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100656
657 def stateIncludes(self, subStateName):
658 subStateCurrentNames = list(self._getElementNames(self.currentState))
659
660 self.debug("Testing if %s is included in %s" % (subStateName, subStateCurrentNames))
661
662 isIncluded = subStateName in subStateCurrentNames
663 self.debug("IsIncluded: %s" % isIncluded)
664
665 return isIncluded
666
667
668 def stateIs(self, subStateNames):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100669 if len(self.currentState) != 1 :
670 return False
671 else :
672 return self.stateIncludes(subStateNames)
673
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100674 def _getXMLAttributes(self):
675 attributes = super()._getXMLAttributes()
676 attributes["Type"] = self.inclusivenessTranslate[self.isInclusif]
677 return attributes
678
Kevin Rocard326e39e2012-12-14 16:11:05 +0100679
680class Criteria(Element):
681 tag = "Criteria"
682
Kevin Rocardd077c552013-06-10 15:31:32 +0200683 class DuplicatedCriterionError(DuplicatedChildError):
684 pass
685
Kevin Rocard326e39e2012-12-14 16:11:05 +0100686 def export(self):
687 self.debug("Exporting criteria")
688 assert(self.children)
689
690 exported = Criteria(self.name)
691 for child in self.children :
692 exported.addChild(child.export())
693 return exported
694
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100695 def addChild(self, child):
696 if child in self.children:
Kevin Rocardd077c552013-06-10 15:31:32 +0200697 raise self.DuplicatedCriterionError(self, child)
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100698 super().addChild(child)
699
700class ConfigAppliedWithoutCriteriaError(CustomError):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100701 def __init__(self, configurationName, domainName):
702 self.configurationName = configurationName
703 self.domainName = domainName
704 def __str__(self):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100705 return ('Applying configuration "%s" from domain "%s" before declaring criteria' %
706 (self.configurationName, self.domainName))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100707
708class ParsePFWlog():
709 MATCH = "match"
710 ACTION = "action"
711
Kevin Rocard9050c812013-06-10 16:01:21 +0200712 def __init__(self, domains, criteria, ErrorsToIgnore=()):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100713
714 self.domains = domains;
715 self.criteria = criteria;
Kevin Rocard9050c812013-06-10 16:01:21 +0200716 self.ErrorsToIgnore = ErrorsToIgnore
Kevin Rocard326e39e2012-12-14 16:11:05 +0100717
718 configApplicationRegext = r""".*Applying configuration "(.*)" from domain "([^"]*)"""
719 matchConfigApplicationLine = re.compile(configApplicationRegext).match
720
721 criterionCreationRegext = ", ".join([
722 r""".*Criterion name: (.*)""",
723 r"""type kind: (.*)""",
724 r"""current state: (.*)""",
725 r"""states: {(.*)}"""
726 ])
727 matchCriterionCreationLine = re.compile(criterionCreationRegext).match
728
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100729 changingCriterionRegext = r""".*Selection criterion changed event: Criterion name: (.*), current state: ([^\n\r]*)"""
Kevin Rocard326e39e2012-12-14 16:11:05 +0100730 matchChangingCriterionLine = re.compile(changingCriterionRegext).match
731
732 self.lineLogTypes = [
733 {
734 self.MATCH: matchConfigApplicationLine,
735 self.ACTION: self._configApplication
736 }, {
737 self.MATCH: matchCriterionCreationLine,
738 self.ACTION: self._criterionCreation
739 }, {
740 self.MATCH: matchChangingCriterionLine,
741 self.ACTION: self._changingCriterion
742 }
743 ]
744
745 @staticmethod
746 def _formatCriterionList(liststring, separator):
747 list = liststring.split(separator)
748 if len(list) == 1 and list[0] == "<none>":
749 list = []
750 return list
751
752 def _criterionCreation(self, matchCriterionCreation):
753 # Unpack
754 criterionName, criterionType, currentCriterionStates, criterionStates = matchCriterionCreation.group(1, 2, 3, 4)
755
756 criterionStateList = self._formatCriterionList(criterionStates, ", ")
757
758 criterionIsInclusif = {"exclusive" : False, "inclusive" : True}[criterionType]
759
760 currentcriterionStateList = self._formatCriterionList(currentCriterionStates, "|")
761
762 logger.info("Creating criterion: " + criterionName +
763 " (" + criterionType + ") " +
764 " with current state: " + str(currentcriterionStateList) +
765 ", possible states:" + str(criterionStateList))
766
Kevin Rocardea874222013-06-10 16:00:08 +0200767 try:
768 self.criteria.addChild(Criterion(
Kevin Rocard326e39e2012-12-14 16:11:05 +0100769 criterionName,
770 criterionIsInclusif,
771 criterionStateList,
772 currentcriterionStateList
773 ))
Kevin Rocardea874222013-06-10 16:00:08 +0200774 except self.criteria.DuplicatedCriterionError as ex:
775 logger.debug(ex)
776 logger.warning("Reseting criterion %s. Did you reset the PFW ?" % criterionName)
777 self.criteria.operationOnChild(
778 [criterionName],
779 lambda criterion: criterion.reset()
780 )
781
782
Kevin Rocard326e39e2012-12-14 16:11:05 +0100783
784 def _changingCriterion(self, matchChangingCriterion):
785 # Unpack
786 criterionName, newCriterionSubStateNames = matchChangingCriterion.group(1, 2)
787
788 newCriterionState = self._formatCriterionList(newCriterionSubStateNames, "|")
789
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100790 logger.info("Changing criterion %s to %s" % (criterionName , newCriterionState))
791
Kevin Rocard326e39e2012-12-14 16:11:05 +0100792 path = [criterionName]
793 changeCriterionOperation = lambda criterion : criterion.changeState(newCriterionState)
794 self.criteria.operationOnChild(path, changeCriterionOperation)
795
796 def _configApplication(self, matchConfig):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100797 # Unpack
798 configurationName, domainName = matchConfig.group(1, 2)
799
Kevin Rocard326e39e2012-12-14 16:11:05 +0100800 # Check that at least one criterion exist
801 if not self.criteria.hasChildren() :
802 logger.error("Applying configuration before declaring criteria")
803 logger.info("Is the log starting at PFW boot ?")
804 raise ConfigAppliedWithoutCriteriaError(configurationName, domainName)
805
Kevin Rocard326e39e2012-12-14 16:11:05 +0100806 # Change criterion state
807 path = [domainName, configurationName]
808 usedOperation = lambda element : element.used(self.criteria)
809
810 logger.info("Applying configuration %s from domain %s" % (
811 configurationName, domainName))
812
813 self.domains.operationOnChild(path, usedOperation)
814
815
816 def _digest(self, lineLogType, lineLog):
Kevin Rocard9050c812013-06-10 16:01:21 +0200817
Kevin Rocard326e39e2012-12-14 16:11:05 +0100818 match = lineLogType[self.MATCH](lineLog)
819 if match :
820 lineLogType[self.ACTION](match)
821 return True
822 return False
823
Kevin Rocard9050c812013-06-10 16:01:21 +0200824
Kevin Rocard326e39e2012-12-14 16:11:05 +0100825 def parsePFWlog(self, lines):
Kevin Rocard9050c812013-06-10 16:01:21 +0200826 for lineNb, lineLog in enumerate(lines):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100827
828 logger.debug("Parsing line :%s" % lineLog.rstrip())
829
830 digested = (self._digest(lineLogType, lineLog)
831 for lineLogType in self.lineLogTypes)
832
Kevin Rocard9050c812013-06-10 16:01:21 +0200833 try:
834 success = any(digested)
835
836 # Catch some exception in order to print the current parsing line,
837 # then raise the exception again if not continue of error
838 except CustomError as ex:
839 logger.error('Error raised while parsing line %s: "%s"' %
840 (lineNb, repr(lineLog)))
841
842 # If exception is a subclass of ErrorsToIgnore, log it and continue
843 # otherwise raise it again.
844 if not issubclass(type(ex), self.ErrorsToIgnore):
845 raise ex
846 else:
847 logger.error('Ignoring exception:"%s", '
848 'can not guarantee database integrity' % ex)
849 else:
850 if not success:
851 logger.debug("Line does not match, dropped")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100852
853
854class Root(Element):
855 tag = "Root"
856 def __init__(self, name, dom):
857 super().__init__(name)
858 # Create domain tree
859 self.domains = Domains("Domains")
860 self.domains.populate(dom)
861 self.addChild(self.domains)
862 # Create criterion list
863 self.criteria = Criteria("CriterionRoot")
864 self.addChild(self.criteria)
865
866 def exportToXML(self):
867 """Export tree to an xml document"""
868 impl = xml.dom.minidom.getDOMImplementation()
869 newdoc = impl.createDocument(None, self.name, None)
870 XMLDocElement = newdoc.documentElement
871
872 for child in self.children:
873 XMLDocElement.appendChild(child.exportToXML())
874
875 return newdoc
876
877# ============================
878# Command line argument parser
879# ============================
880
881
882class ArgumentParser:
883 """class that parse command line arguments with argparse library
884
885 Result of parsing are the class attributes.
886 """
Kevin Rocard925c20b2013-06-10 16:02:28 +0200887 levelTranslate = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100888
889 def __init__(self):
890
891 try:
Kevin Rocard53493b22013-02-04 15:16:10 +0100892 # As argparse is only in the stdlib since python 3.2,
893 # testing its availability
Kevin Rocard326e39e2012-12-14 16:11:05 +0100894 import argparse
895
896 except ImportError:
Kevin Rocard53493b22013-02-04 15:16:10 +0100897 logger.warning("Unable to import argparse "
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100898 "(parser for command-line options and arguments), "
899 "using default argument values:")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100900
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100901 logger.warning(" - InputFile: stdin")
Kevin Rocard53493b22013-02-04 15:16:10 +0100902 self.inputFile = sys.stdin
Kevin Rocard326e39e2012-12-14 16:11:05 +0100903
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100904 logger.warning(" - OutputFile: stdout")
Kevin Rocard53493b22013-02-04 15:16:10 +0100905 self.outputFile = sys.stdout
Kevin Rocard326e39e2012-12-14 16:11:05 +0100906
Kevin Rocard53493b22013-02-04 15:16:10 +0100907 try:
908 self.domainsFile = sys.argv[1]
909 except IndexError as ex:
910 logger.fatal("No domain file provided (first argument)")
911 raise ex
912 else:
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100913 logger.warning(" - Domain file: " + self.domainsFile)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100914
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100915 logger.warning(" - Output format: xml")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100916 self.XMLreport = True
Kevin Rocard326e39e2012-12-14 16:11:05 +0100917
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100918 logger.warning(" - Debug level: error")
Kevin Rocard925c20b2013-06-10 16:02:28 +0200919 self.debugLevel = logging.ERROR
Kevin Rocard326e39e2012-12-14 16:11:05 +0100920 else :
921
922 myArgParser = argparse.ArgumentParser(description='Generate PFW report')
923
924 myArgParser.add_argument(
925 'domainsFile',
926 type=argparse.FileType('r'),
927 help="the PFW domain XML file"
928 )
929 myArgParser.add_argument(
930 'pfwlog', nargs='?',
931 type=argparse.FileType('r'), default=sys.stdin,
932 help="the PFW log file, default stdin"
933 )
934 myArgParser.add_argument(
935 '-o', '--output',
936 dest="outputFile",
937 type=argparse.FileType('w'), default=sys.stdout,
938 help="the coverage report output file, default stdout"
939 )
940 myArgParser.add_argument(
941 '-v', '--verbose',
942 dest="debugLevel", default=0,
943 action='count',
944 help="print debug warnings from warning (default) to debug (-vv)"
945 )
946
947 outputFormatGroupe = myArgParser.add_mutually_exclusive_group(required=False)
948
949 outputFormatGroupe.add_argument(
950 '--xml',
951 dest="xmlFlag",
952 action='store_true',
953 help=" XML coverage output report"
954 )
955 outputFormatGroupe.add_argument(
956 '--raw',
957 dest="rawFlag",
958 action='store_true',
959 help="raw coverage output report"
960 )
961
Kevin Rocard9050c812013-06-10 16:01:21 +0200962 myArgParser.add_argument(
963 '--ignore-incoherent-criterion-state',
964 dest="incoherentCriterionFlag",
965 action='store_true',
966 help="ignore criterion transition to incoherent state"
967 )
968
969 myArgParser.add_argument(
970 '--ignore-ineligible-configuration-application',
971 dest="ineligibleConfigurationApplicationFlag",
972 action='store_true',
973 help="ignore application of configuration with a false rule "
974 "(not applicable configuration)"
975 )
976
Kevin Rocard326e39e2012-12-14 16:11:05 +0100977 # Process command line arguments
978 options = myArgParser.parse_args()
979
980 # Mapping to attributes
981 self.inputFile = options.pfwlog
982 self.outputFile = options.outputFile
983 self.domainsFile = options.domainsFile
984
985 # Output report in xml if flag not set
986 self.XMLreport = not options.rawFlag
987
988 # Setting logger level
989 levelCapped = min(options.debugLevel, len(self.levelTranslate) - 1)
990 self.debugLevel = self.levelTranslate[levelCapped]
991
Kevin Rocard9050c812013-06-10 16:01:21 +0200992 # Setting ignore options
993 errorToIgnore = []
994 if options.ineligibleConfigurationApplicationFlag :
995 errorToIgnore.append(Configuration.IneligibleConfigurationAppliedError)
996
997 if options.incoherentCriterionFlag:
998 errorToIgnore.append(Criterion.ChangeRequestToNonAccessibleState)
999
1000 self.errorToIgnore = tuple(errorToIgnore)
1001
Kevin Rocard326e39e2012-12-14 16:11:05 +01001002
1003
1004def main():
1005
Kevin Rocard53493b22013-02-04 15:16:10 +01001006 errorDuringLogParsing = -1
1007 errorDuringArgumentParsing = 1
1008
1009 try:
1010 commandLineArguments = ArgumentParser()
1011 except LookupError as ex:
1012 logger.error("Error during argument parsing")
1013 logger.debug(str(ex))
1014 sys.exit(errorDuringArgumentParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001015
1016 # Setting logger level
1017 logger.setLevel(commandLineArguments.debugLevel)
1018 logger.info("Log level set to: %s" %
1019 logging.getLevelName(commandLineArguments.debugLevel))
1020
1021 # Create tree from XML
1022 dom = xml.dom.minidom.parse(commandLineArguments.domainsFile)
1023
Kevin Rocard53493b22013-02-04 15:16:10 +01001024 # Create element tree
Kevin Rocard326e39e2012-12-14 16:11:05 +01001025 root = Root("Coverage", dom)
1026
Kevin Rocard53493b22013-02-04 15:16:10 +01001027 # Parse PFW events
Kevin Rocard9050c812013-06-10 16:01:21 +02001028 parser = ParsePFWlog(root.domains, root.criteria, commandLineArguments.errorToIgnore)
Kevin Rocard53493b22013-02-04 15:16:10 +01001029
Kevin Rocard326e39e2012-12-14 16:11:05 +01001030 try:
1031 parser.parsePFWlog(commandLineArguments.inputFile.readlines())
Kevin Rocard3aa0db42013-02-13 17:15:38 +01001032 except CustomError as ex:
1033 logger.fatal("Error during parsing log file %s: %s" %
Kevin Rocard326e39e2012-12-14 16:11:05 +01001034 (commandLineArguments.inputFile, ex))
Kevin Rocard53493b22013-02-04 15:16:10 +01001035 sys.exit(errorDuringLogParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001036
Kevin Rocard53493b22013-02-04 15:16:10 +01001037 # Output report
Kevin Rocard326e39e2012-12-14 16:11:05 +01001038 outputFile = commandLineArguments.outputFile
1039
1040 if not commandLineArguments.XMLreport :
Kevin Rocard326e39e2012-12-14 16:11:05 +01001041 outputFile.write("%s\n" % root.dump(withCoverage=True, withNbUse=True))
Kevin Rocard326e39e2012-12-14 16:11:05 +01001042 else :
Kevin Rocard326e39e2012-12-14 16:11:05 +01001043 outputFile.write(root.exportToXML().toprettyxml())
1044
1045
Kevin Rocard326e39e2012-12-14 16:11:05 +01001046if __name__ == "__main__" :
Kevin Rocard9050c812013-06-10 16:01:21 +02001047 """ Execute main if the python interpreter is running this module as the main program """
Kevin Rocard326e39e2012-12-14 16:11:05 +01001048 main()
1049