blob: df79b5be4586419db0936f8ef778ff55bd70c019 [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
Kevin Rocard02726ec2013-06-20 10:28:54 +0200198 def exportToXML(self, domElement=None):
199 if domElement == None:
200 domElement = xml.dom.minidom.Element(self.tag)
201
Kevin Rocard326e39e2012-12-14 16:11:05 +0100202 self._XMLaddAttributes(domElement)
203
204 for child in self.children :
205 domElement.appendChild(child.exportToXML())
206
207 return domElement
208
209 def _XMLaddAttributes(self, domElement):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100210 attributes = self._getXMLAttributes()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100211
212 coverage = self._getCoverage()
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100213 if coverage != None :
214 attributes["Coverage"] = self._number2percent(coverage)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100215
216 for key, value in attributes.items():
217 domElement.setAttribute(key, value)
218
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100219 def _getXMLAttributes(self):
220 return {
221 "Name": self.name,
222 "NbUse": str(self.nbUse)
223 }
Kevin Rocard326e39e2012-12-14 16:11:05 +0100224
225 def _incNbUse(self):
226 self.nbUse += 1
227
228 def childUsed(self, child):
229 self._incNbUse()
230 # Propagate to parent
231 self._tellParentThatChildUsed()
232
233 def _tellParentThatChildUsed(self):
234 if self.parent :
235 self.parent.childUsed(self)
236
237
238 def parentUsed(self):
239 self._incNbUse()
240 # Propagate to children
241 for child in self.children :
242 child.parentUsed()
243
244 def hasBeenUsed(self):
245 return self.nbUse > 0
246
247 def operationOnChild(self, path, operation):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100248
249 if path:
250 return self._operationPropagate(path, operation)
251 else :
252 self.debug("operating on self")
253 return operation(self)
254
255 def _operationPropagate(self, path, operation):
256
257 childName = path.pop(0)
258 child = self.getChildFromName(childName)
259
260 return child.operationOnChild(path, operation)
261
262
263
264 def debug(self, stringOrFunction, level=logging.DEBUG):
265 """Print a debug line on stderr in tree form
266
267 If the debug line is expensive to generate, provide callable
268 object, it will be called if log is enable for this level.
269 This callable object should return the logline string.
270 """
271 if logger.isEnabledFor(level):
272
273 # TODO: use buildin callable if python >= 3.2
274 if hasattr(stringOrFunction, "__call__"):
275 string = stringOrFunction()
276 else:
277 string = stringOrFunction
278
279 rankedLine = DebugRankedLine("%s: %s" % (self, string))
280 self._logDebug(rankedLine, level)
281
282 def _logDebug(self, rankedLine, level):
283
284 if self.parent:
285 self.parent._logDebug(rankedLine.increasedRank(), level)
286 else :
287 logger.log(level, str(rankedLine))
288
289
290
291
292class FromDomElement(Element):
293 def __init__(self, DomElement):
294 self._initFromDom(DomElement)
295 super().__init__(self.name)
296
297
298 def _initFromDom(self, DomElement):
299 self.name = DomElement.getAttribute("Name")
300
301
302
303class DomElementLocation():
304 def __init__(self, classConstructor, path=None):
305 self.classConstructor = classConstructor
306 if path :
307 self.path = path
308 else :
309 self.path = []
310
311 self.path.append(classConstructor.tag)
312
313
314class DomPopulatedElement(Element):
315 """Default child populate
316
317 Look for each dom element with tag specified in self.tag
318 and instantiate it with the dom element
319 """
320 childClasses = []
321
322 def populate(self, dom):
323
324 for childDomElementLocation in self.childClasses :
325
326 self.debug("Looking for child %s in path %s" % (
327 childDomElementLocation.path[-1], childDomElementLocation.path))
328
329 for childDomElement in self._findChildFromTagPath(dom, childDomElementLocation.path) :
330
331 childElement = childDomElementLocation.classConstructor(childDomElement)
332 self.addChild(childElement)
333
334 childElement.populate(childDomElement)
335
336 def _findChildFromTagPath(self, dom, path):
337 if not path :
338 yield dom
339 else :
340 # Copy list
341 path = list(path)
342
343 tag = path.pop(0)
344
345 # Find element with tag
346 self.debug("Going to find elements with tag %s in %s" % (tag, dom))
347 self.debug(lambda: "Nb of solutions: %s" % len(dom.getElementsByTagName(tag)))
348
349 for elementByTag in dom.getElementsByTagName(tag) :
350
351 self.debug("Found element: %s" % elementByTag)
352
353 # If the same tag is found
354 if elementByTag in dom.childNodes :
355
356 # Yield next level
357 for element in self._findChildFromTagPath(elementByTag, path) :
358 yield element
359
360
361class Rule(Element):
362
363 def usedIfApplicable(self, criteria):
364 childApplicability = (child.usedIfApplicable(criteria)
365 for child in self.children)
366
367 isApplicable = self._isApplicable(criteria, childApplicability)
368
369 if isApplicable :
370 self._incNbUse()
371
372 self.debug("Rule applicability: %s" % isApplicable)
373 assert(isApplicable == True or isApplicable == False)
374
375 return isApplicable
376
377
378 def _isApplicable(self, criteria, childApplicability):
Kevin Rocardcf031992013-06-14 18:09:54 +0200379 """Return the rule applicability depending on children applicability.
380
381 If at least one child is applicable, return true"""
382 # Lazy evaluation as in the PFW
383 return all(childApplicability)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100384
385
386class CriterionRule(FromDomElement, DomPopulatedElement, Rule):
387 tag = "SelectionCriterionRule"
388 childClasses = []
389 isApplicableOperations = {
390 "Includes" : lambda criterion, value: criterion.stateIncludes(value),
391 "Excludes" : lambda criterion, value: not criterion.stateIncludes(value),
392 "Is" : lambda criterion, value: criterion.stateIs(value),
393 "IsNot" : lambda criterion, value: not criterion.stateIs(value)
394 }
395
396 def _initFromDom(self, DomElement):
397 self.selectionCriterion = DomElement.getAttribute("SelectionCriterion")
398 self.matchesWhen = DomElement.getAttribute("MatchesWhen")
399 self.value = DomElement.getAttribute("Value")
400 self.name = "%s %s %s" % (self.selectionCriterion, self.matchesWhen, self.value)
401
402 applicableOperationWithoutValue = self.isApplicableOperations[self.matchesWhen]
403 self.isApplicableOperation = lambda criterion: applicableOperationWithoutValue(criterion, self.value)
404
405 def _isApplicable(self, criteria, childApplicability):
406
407 return criteria.operationOnChild([self.selectionCriterion],
408 self.isApplicableOperation)
409
410
411class CompoundRule(FromDomElement, DomPopulatedElement, Rule):
412 """CompoundRule can be of type ALL or ANY"""
413 tag = "CompoundRule"
414 # Declare childClasses but define it at first class instantiation
415 childClasses = None
416
417 def __init__(self, dom):
418 # Define childClasses at first class instantiation
419 if self.childClasses == None :
420 self.childClasses = [DomElementLocation(CriterionRule),
421 DomElementLocation(CompoundRule)]
422 super().__init__(dom)
423
424 def _initFromDom(self, DomElement):
425
426 type = DomElement.getAttribute("Type")
427 self.ofTypeAll = {"All" : True, "Any" : False}[type]
428 self.name = type
429
430 def _isApplicable(self, criteria, childApplicability):
431 if self.ofTypeAll :
432 applicability = super()._isApplicable(criteria, childApplicability)
433 else:
Kevin Rocardcf031992013-06-14 18:09:54 +0200434 # Lazy evaluation as in the PFW
435 applicability = any(childApplicability)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100436
437 return applicability
438
439class RootRule(DomPopulatedElement, Rule):
440 tag = "RootRule"
441 childClasses = [DomElementLocation(CompoundRule)]
442
443 def populate(self, dom):
444 super().populate(dom)
445 self.debug("Children: %s" % self.children)
446 # A configuration can only have one or no rule
447 assert(len(self.children) <= 1)
448
Kevin Rocard2e404522013-06-14 17:09:32 +0200449 def _getCoverageDependanceElements(self):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100450 return self._getDescendants()
451
452
453class CriteronStates(Element):
454 """Root of configuration application criterion state"""
455 tag = "CriterionStates"
456
457 def parentUsed(self, criteria):
458 """Add criteria to child if not exist, if exist increase it's nbUse"""
459 self._incNbUse()
460
461 matches = [child for child in self.children if child == criteria]
462
463 assert(len(matches) <= 1)
464
465 if matches :
466 self.debug("Criteria state has already been encounter")
467 currentcriteria = matches[0]
468 else :
469 self.debug("Criteria state has never been encounter, saving it")
470 currentcriteria = criteria
471 self.addChild(criteria)
472
473 currentcriteria.parentUsed()
474
Kevin Rocard326e39e2012-12-14 16:11:05 +0100475
476
477class Configuration(FromDomElement, DomPopulatedElement):
478 tag = "Configuration"
479 childClasses = []
480
Kevin Rocard97dbd352013-06-10 15:15:56 +0200481 class IneligibleConfigurationAppliedError(CustomError):
482
483 def __init__(self, configuration, criteria):
484 self.configuration = configuration
485 self.criteria = criteria
486
487 def __str__(self):
488
489 return ("Applying ineligible %s, "
490 "rule:\n%s\n"
491 "Criteria current state:\n%s" % (
492 self.configuration,
493 self.configuration.rootRule.dump(withCoverage=False, withNbUse=False),
494 self.criteria.dump(withCoverage=False, withNbUse=False)
495 ))
496
Kevin Rocard326e39e2012-12-14 16:11:05 +0100497 def __init__(self, DomElement):
498 super().__init__(DomElement)
499
500 self.rootRule = RootRule("RootRule")
501 self.addChild(self.rootRule)
502
503 self.criteronStates = CriteronStates("CriterionStates")
504 self.addChild(self.criteronStates)
505
506 def populate(self, dom):
507 # Delegate to rootRule
508 self.rootRule.populate(dom)
509
510 def _getCoverage(self):
511 # Delegate to rootRule
512 return self.rootRule._getCoverage()
513
514 def used(self, criteria):
515
516 self._incNbUse()
517
518 # Propagate use to parents
519 self._tellParentThatChildUsed()
520
521 # Propagate to criterion coverage
522 self.criteronStates.parentUsed(criteria.export())
523
524 # Propagate to rules
525 if not self.rootRule.usedIfApplicable(criteria) :
526
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100527 self.debug("Applied but rule does not match current "
528 "criteria (parent: %s) " % self.parent.name,
Kevin Rocard925c20b2013-06-10 16:02:28 +0200529 logging.ERROR)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100530
Kevin Rocard97dbd352013-06-10 15:15:56 +0200531 raise self.IneligibleConfigurationAppliedError(self, criteria.export())
Kevin Rocard326e39e2012-12-14 16:11:05 +0100532
533 def _dumpPropagate(self, withCoverage, withNbUse):
534 self.debug("Going to ask %s for description" % self.rootRule)
535 for dumpedDescription in self.rootRule._dumpDescription(
536 withCoverage=withCoverage,
537 withNbUse=withNbUse) :
538 yield dumpedDescription.increasedRank()
539
540 self.debug("Going to ask %s for description" % self.criteronStates)
541 for dumpedDescription in self.criteronStates._dumpDescription(
542 withCoverage=False,
543 withNbUse=withNbUse) :
544 yield dumpedDescription.increasedRank()
545
546
547class Domain(FromDomElement, DomPopulatedElement):
548 tag = "ConfigurableDomain"
549 childClasses = [DomElementLocation(Configuration, ["Configurations"])]
550
551
552class Domains(DomPopulatedElement):
553 tag = "Domains"
554 childClasses = [DomElementLocation(Domain, ["ConfigurableDomains"])]
555
556
557class RankedLine():
558 def __init__(self, string,
559 stringPrefix="|-- ",
560 rankString="| ",
561 linePrefix="",
562 lineSuffix="\n"):
563 self.string = string
564 self.rank = 0
565 self.stringPrefix = stringPrefix
566 self.rankString = rankString
567 self.linePrefix = linePrefix
568 self.lineSuffix = lineSuffix
569
570 def increasedRank(self):
571 self.rank += 1
572 return self
573
574 def __str__(self):
575 return self.linePrefix + \
576 self.rank * self.rankString + \
577 self.stringPrefix + \
578 self.string + \
579 self.lineSuffix
580
581class DebugRankedLine(RankedLine):
582
583 def __init__(self, string, lineSuffix=""):
584 super().__init__(string,
585 stringPrefix="",
586 rankString=" ",
587 linePrefix="",
588 lineSuffix=lineSuffix)
589
590
591class CriterionState(Element):
592 tag = "CriterionState"
593 def used(self):
594 self._incNbUse()
595
596
597class Criterion(Element):
598 tag = "Criterion"
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100599 inclusivenessTranslate = {True: "Inclusive", False: "Exclusive"}
600
Kevin Rocard556538e2013-06-10 15:20:10 +0200601 class ChangeRequestToNonAccessibleState(CustomError):
602 def __init__(self, requestedState, detail):
603 self.requestedState = requestedState
604 self.detail = detail
605
606 def __str__(self):
607 return ("Change request to non accessible state %s. Detail: %s" %
608 (self.requestedState, self.detail))
609
610 def __init__(self, name, isInclusif,
611 stateNamesList, currentStateNamesList,
612 ignoreIntegrity=False):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100613 super().__init__(name)
614 self.isInclusif = isInclusif
615
Kevin Rocard326e39e2012-12-14 16:11:05 +0100616 for state in stateNamesList :
617 self.addChild(CriterionState(state))
618
619 self.currentState = []
Kevin Rocard556538e2013-06-10 15:20:10 +0200620 self.initStateNamesList = list(currentStateNamesList)
621 self.changeState(self.initStateNamesList, ignoreIntegrity)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100622
Kevin Rocard556538e2013-06-10 15:20:10 +0200623 def reset(self):
624 # Set current state as provided at initialisation
625 self.changeState(self.initStateNamesList, ignoreIntegrity=True)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100626
Kevin Rocard556538e2013-06-10 15:20:10 +0200627 def changeState(self, subStateNames, ignoreIntegrity=False):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100628 self.debug("Changing state from: %s to: %s" % (
629 list(self._getElementNames(self.currentState)),
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100630 subStateNames))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100631
Kevin Rocard556538e2013-06-10 15:20:10 +0200632 if not ignoreIntegrity and not self.isIntegre(subStateNames):
633 raise self.ChangeRequestToNonAccessibleState(subStateNames,
634 "An exclusive criterion must have a non empty state")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100635
636 newCurrentState = []
637 for subStateName in subStateNames :
638 subState = self.getChildFromName(subStateName)
639 subState.used()
640 newCurrentState.append(subState)
641
642 self.currentState = newCurrentState
643
644 self._incNbUse()
645 self._tellParentThatChildUsed()
646
Kevin Rocard556538e2013-06-10 15:20:10 +0200647 def isIntegre(self, subStateNames):
648 return self.isInclusif or len(subStateNames) == 1
649
650 def childUsed(self, child):
651 self.currentState = child
652 super().childUsed(child)
653
Kevin Rocard326e39e2012-12-14 16:11:05 +0100654 def export(self):
655 subStateNames = self._getElementNames(self.currentState)
Kevin Rocard556538e2013-06-10 15:20:10 +0200656 return Criterion(self.name, self.isInclusif, subStateNames, subStateNames,
657 ignoreIntegrity=True)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100658
659 def stateIncludes(self, subStateName):
660 subStateCurrentNames = list(self._getElementNames(self.currentState))
661
662 self.debug("Testing if %s is included in %s" % (subStateName, subStateCurrentNames))
663
664 isIncluded = subStateName in subStateCurrentNames
665 self.debug("IsIncluded: %s" % isIncluded)
666
667 return isIncluded
668
669
670 def stateIs(self, subStateNames):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100671 if len(self.currentState) != 1 :
672 return False
673 else :
674 return self.stateIncludes(subStateNames)
675
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100676 def _getXMLAttributes(self):
677 attributes = super()._getXMLAttributes()
678 attributes["Type"] = self.inclusivenessTranslate[self.isInclusif]
679 return attributes
680
Kevin Rocard326e39e2012-12-14 16:11:05 +0100681
682class Criteria(Element):
683 tag = "Criteria"
684
Kevin Rocardd077c552013-06-10 15:31:32 +0200685 class DuplicatedCriterionError(DuplicatedChildError):
686 pass
687
Kevin Rocard326e39e2012-12-14 16:11:05 +0100688 def export(self):
689 self.debug("Exporting criteria")
690 assert(self.children)
691
692 exported = Criteria(self.name)
693 for child in self.children :
694 exported.addChild(child.export())
695 return exported
696
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100697 def addChild(self, child):
698 if child in self.children:
Kevin Rocardd077c552013-06-10 15:31:32 +0200699 raise self.DuplicatedCriterionError(self, child)
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100700 super().addChild(child)
701
702class ConfigAppliedWithoutCriteriaError(CustomError):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100703 def __init__(self, configurationName, domainName):
704 self.configurationName = configurationName
705 self.domainName = domainName
706 def __str__(self):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100707 return ('Applying configuration "%s" from domain "%s" before declaring criteria' %
708 (self.configurationName, self.domainName))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100709
710class ParsePFWlog():
711 MATCH = "match"
712 ACTION = "action"
713
Kevin Rocard9050c812013-06-10 16:01:21 +0200714 def __init__(self, domains, criteria, ErrorsToIgnore=()):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100715
716 self.domains = domains;
717 self.criteria = criteria;
Kevin Rocard9050c812013-06-10 16:01:21 +0200718 self.ErrorsToIgnore = ErrorsToIgnore
Kevin Rocard326e39e2012-12-14 16:11:05 +0100719
720 configApplicationRegext = r""".*Applying configuration "(.*)" from domain "([^"]*)"""
721 matchConfigApplicationLine = re.compile(configApplicationRegext).match
722
723 criterionCreationRegext = ", ".join([
724 r""".*Criterion name: (.*)""",
725 r"""type kind: (.*)""",
726 r"""current state: (.*)""",
727 r"""states: {(.*)}"""
728 ])
729 matchCriterionCreationLine = re.compile(criterionCreationRegext).match
730
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100731 changingCriterionRegext = r""".*Selection criterion changed event: Criterion name: (.*), current state: ([^\n\r]*)"""
Kevin Rocard326e39e2012-12-14 16:11:05 +0100732 matchChangingCriterionLine = re.compile(changingCriterionRegext).match
733
734 self.lineLogTypes = [
735 {
736 self.MATCH: matchConfigApplicationLine,
737 self.ACTION: self._configApplication
738 }, {
739 self.MATCH: matchCriterionCreationLine,
740 self.ACTION: self._criterionCreation
741 }, {
742 self.MATCH: matchChangingCriterionLine,
743 self.ACTION: self._changingCriterion
744 }
745 ]
746
747 @staticmethod
748 def _formatCriterionList(liststring, separator):
749 list = liststring.split(separator)
750 if len(list) == 1 and list[0] == "<none>":
751 list = []
752 return list
753
754 def _criterionCreation(self, matchCriterionCreation):
755 # Unpack
756 criterionName, criterionType, currentCriterionStates, criterionStates = matchCriterionCreation.group(1, 2, 3, 4)
757
758 criterionStateList = self._formatCriterionList(criterionStates, ", ")
759
760 criterionIsInclusif = {"exclusive" : False, "inclusive" : True}[criterionType]
761
762 currentcriterionStateList = self._formatCriterionList(currentCriterionStates, "|")
763
764 logger.info("Creating criterion: " + criterionName +
765 " (" + criterionType + ") " +
766 " with current state: " + str(currentcriterionStateList) +
767 ", possible states:" + str(criterionStateList))
768
Kevin Rocardea874222013-06-10 16:00:08 +0200769 try:
770 self.criteria.addChild(Criterion(
Kevin Rocard326e39e2012-12-14 16:11:05 +0100771 criterionName,
772 criterionIsInclusif,
773 criterionStateList,
774 currentcriterionStateList
775 ))
Kevin Rocardea874222013-06-10 16:00:08 +0200776 except self.criteria.DuplicatedCriterionError as ex:
777 logger.debug(ex)
778 logger.warning("Reseting criterion %s. Did you reset the PFW ?" % criterionName)
779 self.criteria.operationOnChild(
780 [criterionName],
781 lambda criterion: criterion.reset()
782 )
783
784
Kevin Rocard326e39e2012-12-14 16:11:05 +0100785
786 def _changingCriterion(self, matchChangingCriterion):
787 # Unpack
788 criterionName, newCriterionSubStateNames = matchChangingCriterion.group(1, 2)
789
790 newCriterionState = self._formatCriterionList(newCriterionSubStateNames, "|")
791
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100792 logger.info("Changing criterion %s to %s" % (criterionName , newCriterionState))
793
Kevin Rocard326e39e2012-12-14 16:11:05 +0100794 path = [criterionName]
795 changeCriterionOperation = lambda criterion : criterion.changeState(newCriterionState)
796 self.criteria.operationOnChild(path, changeCriterionOperation)
797
798 def _configApplication(self, matchConfig):
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100799 # Unpack
800 configurationName, domainName = matchConfig.group(1, 2)
801
Kevin Rocard326e39e2012-12-14 16:11:05 +0100802 # Check that at least one criterion exist
803 if not self.criteria.hasChildren() :
804 logger.error("Applying configuration before declaring criteria")
805 logger.info("Is the log starting at PFW boot ?")
806 raise ConfigAppliedWithoutCriteriaError(configurationName, domainName)
807
Kevin Rocard326e39e2012-12-14 16:11:05 +0100808 # Change criterion state
809 path = [domainName, configurationName]
810 usedOperation = lambda element : element.used(self.criteria)
811
812 logger.info("Applying configuration %s from domain %s" % (
813 configurationName, domainName))
814
815 self.domains.operationOnChild(path, usedOperation)
816
817
818 def _digest(self, lineLogType, lineLog):
Kevin Rocard9050c812013-06-10 16:01:21 +0200819
Kevin Rocard326e39e2012-12-14 16:11:05 +0100820 match = lineLogType[self.MATCH](lineLog)
821 if match :
822 lineLogType[self.ACTION](match)
823 return True
824 return False
825
Kevin Rocard9050c812013-06-10 16:01:21 +0200826
Kevin Rocard326e39e2012-12-14 16:11:05 +0100827 def parsePFWlog(self, lines):
Kevin Rocard9050c812013-06-10 16:01:21 +0200828 for lineNb, lineLog in enumerate(lines):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100829
830 logger.debug("Parsing line :%s" % lineLog.rstrip())
831
832 digested = (self._digest(lineLogType, lineLog)
833 for lineLogType in self.lineLogTypes)
834
Kevin Rocard9050c812013-06-10 16:01:21 +0200835 try:
836 success = any(digested)
837
838 # Catch some exception in order to print the current parsing line,
839 # then raise the exception again if not continue of error
840 except CustomError as ex:
841 logger.error('Error raised while parsing line %s: "%s"' %
842 (lineNb, repr(lineLog)))
843
844 # If exception is a subclass of ErrorsToIgnore, log it and continue
845 # otherwise raise it again.
846 if not issubclass(type(ex), self.ErrorsToIgnore):
847 raise ex
848 else:
849 logger.error('Ignoring exception:"%s", '
850 'can not guarantee database integrity' % ex)
851 else:
852 if not success:
853 logger.debug("Line does not match, dropped")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100854
855
856class Root(Element):
Kevin Rocard02726ec2013-06-20 10:28:54 +0200857 tag = "CoverageReport"
Kevin Rocard326e39e2012-12-14 16:11:05 +0100858 def __init__(self, name, dom):
859 super().__init__(name)
860 # Create domain tree
861 self.domains = Domains("Domains")
862 self.domains.populate(dom)
863 self.addChild(self.domains)
864 # Create criterion list
865 self.criteria = Criteria("CriterionRoot")
866 self.addChild(self.criteria)
867
868 def exportToXML(self):
869 """Export tree to an xml document"""
870 impl = xml.dom.minidom.getDOMImplementation()
Kevin Rocard02726ec2013-06-20 10:28:54 +0200871 newdoc = impl.createDocument(namespaceURI=None, qualifiedName=self.tag, doctype=None)
872 super().exportToXML(newdoc.documentElement)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100873
874 return newdoc
875
876# ============================
877# Command line argument parser
878# ============================
879
880
881class ArgumentParser:
882 """class that parse command line arguments with argparse library
883
884 Result of parsing are the class attributes.
885 """
Kevin Rocard925c20b2013-06-10 16:02:28 +0200886 levelTranslate = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100887
888 def __init__(self):
889
890 try:
Kevin Rocard53493b22013-02-04 15:16:10 +0100891 # As argparse is only in the stdlib since python 3.2,
892 # testing its availability
Kevin Rocard326e39e2012-12-14 16:11:05 +0100893 import argparse
894
895 except ImportError:
Kevin Rocard53493b22013-02-04 15:16:10 +0100896 logger.warning("Unable to import argparse "
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100897 "(parser for command-line options and arguments), "
898 "using default argument values:")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100899
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100900 logger.warning(" - InputFile: stdin")
Kevin Rocard53493b22013-02-04 15:16:10 +0100901 self.inputFile = sys.stdin
Kevin Rocard326e39e2012-12-14 16:11:05 +0100902
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100903 logger.warning(" - OutputFile: stdout")
Kevin Rocard53493b22013-02-04 15:16:10 +0100904 self.outputFile = sys.stdout
Kevin Rocard326e39e2012-12-14 16:11:05 +0100905
Kevin Rocard53493b22013-02-04 15:16:10 +0100906 try:
907 self.domainsFile = sys.argv[1]
908 except IndexError as ex:
909 logger.fatal("No domain file provided (first argument)")
910 raise ex
911 else:
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100912 logger.warning(" - Domain file: " + self.domainsFile)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100913
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100914 logger.warning(" - Output format: xml")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100915 self.XMLreport = True
Kevin Rocard326e39e2012-12-14 16:11:05 +0100916
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100917 logger.warning(" - Debug level: error")
Kevin Rocard925c20b2013-06-10 16:02:28 +0200918 self.debugLevel = logging.ERROR
Kevin Rocard326e39e2012-12-14 16:11:05 +0100919 else :
920
921 myArgParser = argparse.ArgumentParser(description='Generate PFW report')
922
923 myArgParser.add_argument(
924 'domainsFile',
925 type=argparse.FileType('r'),
926 help="the PFW domain XML file"
927 )
928 myArgParser.add_argument(
929 'pfwlog', nargs='?',
930 type=argparse.FileType('r'), default=sys.stdin,
931 help="the PFW log file, default stdin"
932 )
933 myArgParser.add_argument(
934 '-o', '--output',
935 dest="outputFile",
936 type=argparse.FileType('w'), default=sys.stdout,
937 help="the coverage report output file, default stdout"
938 )
939 myArgParser.add_argument(
940 '-v', '--verbose',
941 dest="debugLevel", default=0,
942 action='count',
943 help="print debug warnings from warning (default) to debug (-vv)"
944 )
945
946 outputFormatGroupe = myArgParser.add_mutually_exclusive_group(required=False)
947
948 outputFormatGroupe.add_argument(
949 '--xml',
950 dest="xmlFlag",
951 action='store_true',
952 help=" XML coverage output report"
953 )
954 outputFormatGroupe.add_argument(
955 '--raw',
956 dest="rawFlag",
957 action='store_true',
958 help="raw coverage output report"
959 )
960
Kevin Rocard9050c812013-06-10 16:01:21 +0200961 myArgParser.add_argument(
962 '--ignore-incoherent-criterion-state',
963 dest="incoherentCriterionFlag",
964 action='store_true',
965 help="ignore criterion transition to incoherent state"
966 )
967
968 myArgParser.add_argument(
969 '--ignore-ineligible-configuration-application',
970 dest="ineligibleConfigurationApplicationFlag",
971 action='store_true',
972 help="ignore application of configuration with a false rule "
973 "(not applicable configuration)"
974 )
975
Kevin Rocard326e39e2012-12-14 16:11:05 +0100976 # Process command line arguments
977 options = myArgParser.parse_args()
978
979 # Mapping to attributes
980 self.inputFile = options.pfwlog
981 self.outputFile = options.outputFile
982 self.domainsFile = options.domainsFile
983
984 # Output report in xml if flag not set
985 self.XMLreport = not options.rawFlag
986
987 # Setting logger level
988 levelCapped = min(options.debugLevel, len(self.levelTranslate) - 1)
989 self.debugLevel = self.levelTranslate[levelCapped]
990
Kevin Rocard9050c812013-06-10 16:01:21 +0200991 # Setting ignore options
992 errorToIgnore = []
993 if options.ineligibleConfigurationApplicationFlag :
994 errorToIgnore.append(Configuration.IneligibleConfigurationAppliedError)
995
996 if options.incoherentCriterionFlag:
997 errorToIgnore.append(Criterion.ChangeRequestToNonAccessibleState)
998
999 self.errorToIgnore = tuple(errorToIgnore)
1000
Kevin Rocard326e39e2012-12-14 16:11:05 +01001001
1002
1003def main():
1004
Kevin Rocard53493b22013-02-04 15:16:10 +01001005 errorDuringLogParsing = -1
1006 errorDuringArgumentParsing = 1
1007
1008 try:
1009 commandLineArguments = ArgumentParser()
1010 except LookupError as ex:
1011 logger.error("Error during argument parsing")
1012 logger.debug(str(ex))
1013 sys.exit(errorDuringArgumentParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001014
1015 # Setting logger level
1016 logger.setLevel(commandLineArguments.debugLevel)
1017 logger.info("Log level set to: %s" %
1018 logging.getLevelName(commandLineArguments.debugLevel))
1019
1020 # Create tree from XML
1021 dom = xml.dom.minidom.parse(commandLineArguments.domainsFile)
1022
Kevin Rocard53493b22013-02-04 15:16:10 +01001023 # Create element tree
Kevin Rocard02726ec2013-06-20 10:28:54 +02001024 root = Root("DomainCoverage", dom)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001025
Kevin Rocard53493b22013-02-04 15:16:10 +01001026 # Parse PFW events
Kevin Rocard9050c812013-06-10 16:01:21 +02001027 parser = ParsePFWlog(root.domains, root.criteria, commandLineArguments.errorToIgnore)
Kevin Rocard53493b22013-02-04 15:16:10 +01001028
Kevin Rocard326e39e2012-12-14 16:11:05 +01001029 try:
1030 parser.parsePFWlog(commandLineArguments.inputFile.readlines())
Kevin Rocard3aa0db42013-02-13 17:15:38 +01001031 except CustomError as ex:
1032 logger.fatal("Error during parsing log file %s: %s" %
Kevin Rocard326e39e2012-12-14 16:11:05 +01001033 (commandLineArguments.inputFile, ex))
Kevin Rocard53493b22013-02-04 15:16:10 +01001034 sys.exit(errorDuringLogParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001035
Kevin Rocard53493b22013-02-04 15:16:10 +01001036 # Output report
Kevin Rocard326e39e2012-12-14 16:11:05 +01001037 outputFile = commandLineArguments.outputFile
1038
1039 if not commandLineArguments.XMLreport :
Kevin Rocard326e39e2012-12-14 16:11:05 +01001040 outputFile.write("%s\n" % root.dump(withCoverage=True, withNbUse=True))
Kevin Rocard326e39e2012-12-14 16:11:05 +01001041 else :
Kevin Rocard326e39e2012-12-14 16:11:05 +01001042 outputFile.write(root.exportToXML().toprettyxml())
1043
1044
Kevin Rocard326e39e2012-12-14 16:11:05 +01001045if __name__ == "__main__" :
Kevin Rocard9050c812013-06-10 16:01:21 +02001046 """ Execute main if the python interpreter is running this module as the main program """
Kevin Rocard326e39e2012-12-14 16:11:05 +01001047 main()
1048