blob: 8c18bc7bcaea0ba764256f3bf37d6b96adb63c67 [file] [log] [blame]
Kevin Rocard326e39e2012-12-14 16:11:05 +01001#!/usr/bin/env python3
2
David Wagnerb76c9d62014-02-05 18:30:24 +01003# Copyright (c) 2011-2014, Intel Corporation
4# All rights reserved.
Kevin Rocardfe753d42013-02-08 11:37:39 +01005#
David Wagnerb76c9d62014-02-05 18:30:24 +01006# Redistribution and use in source and binary forms, with or without modification,
7# are permitted provided that the following conditions are met:
Kevin Rocardfe753d42013-02-08 11:37:39 +01008#
David Wagnerb76c9d62014-02-05 18:30:24 +01009# 1. Redistributions of source code must retain the above copyright notice, this
10# list of conditions and the following disclaimer.
11#
12# 2. Redistributions in binary form must reproduce the above copyright notice,
13# this list of conditions and the following disclaimer in the documentation and/or
14# other materials provided with the distribution.
15#
16# 3. Neither the name of the copyright holder nor the names of its contributors
17# may be used to endorse or promote products derived from this software without
18# specific prior written permission.
19#
20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
21# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
24# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
27# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
29# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
Kevin Rocardfe753d42013-02-08 11:37:39 +010031
Kevin Rocard326e39e2012-12-14 16:11:05 +010032"""
33Generate a coverage report by parsing parameter framework log.
34
35The coverage report contains the:
36 - domain
37 - configuration
38 - rule
39 - criterion
40basic coverage statistics.
41"""
42
43import xml.dom.minidom
44import sys
45import re
46import logging
47
48FORMAT = '%(levelname)s: %(message)s'
49logging.basicConfig(stream=sys.stderr, level=logging.WARNING, format=FORMAT)
50logger = logging.getLogger("Coverage")
51
Kevin Rocard3aa0db42013-02-13 17:15:38 +010052class CustomError(Exception):
Kevin Rocard804e0642013-06-25 12:09:35 +020053 pass
Kevin Rocard3aa0db42013-02-13 17:15:38 +010054
55class ChildError(CustomError):
Kevin Rocard804e0642013-06-25 12:09:35 +020056 def __init__(self, parent, child):
57 self.parent = parent
58 self.child = child
Kevin Rocard3aa0db42013-02-13 17:15:38 +010059
60class ChildNotFoundError(ChildError):
Kevin Rocard804e0642013-06-25 12:09:35 +020061 def __str__(self):
Kevin Rocard0bb508d2015-03-27 20:37:46 +010062 return 'Unable to find the child "%s" in "%s"' % (self.child, self.parent)
Kevin Rocard3aa0db42013-02-13 17:15:38 +010063
64class DuplicatedChildError(ChildError):
Kevin Rocard804e0642013-06-25 12:09:35 +020065 def __str__(self):
Kevin Rocard0bb508d2015-03-27 20:37:46 +010066 return 'Add existing child "%s" in "%s".' % (self.child, self.parent)
Kevin Rocard326e39e2012-12-14 16:11:05 +010067
68class Element():
Kevin Rocard804e0642013-06-25 12:09:35 +020069 """Root class for all coverage elements"""
70 tag = "element"
Kevin Rocard326e39e2012-12-14 16:11:05 +010071
Kevin Rocard804e0642013-06-25 12:09:35 +020072 def __init__(self, name):
Kevin Rocard326e39e2012-12-14 16:11:05 +010073
Kevin Rocard804e0642013-06-25 12:09:35 +020074 self.parent = None
75 self.children = []
Kevin Rocard326e39e2012-12-14 16:11:05 +010076
Kevin Rocard804e0642013-06-25 12:09:35 +020077 self.nbUse = 0
Kevin Rocard326e39e2012-12-14 16:11:05 +010078
Kevin Rocard804e0642013-06-25 12:09:35 +020079 self.name = name
Kevin Rocard326e39e2012-12-14 16:11:05 +010080
Kevin Rocard804e0642013-06-25 12:09:35 +020081 self.debug("New element")
Kevin Rocard326e39e2012-12-14 16:11:05 +010082
83
Kevin Rocard804e0642013-06-25 12:09:35 +020084 def __str__(self):
85 return "%s (%s)" % (self.name, self.tag)
Kevin Rocard326e39e2012-12-14 16:11:05 +010086
Kevin Rocard804e0642013-06-25 12:09:35 +020087 def __eq__(self, compared):
88 return (self.name == compared.name) and (self.children == compared.children)
Kevin Rocard326e39e2012-12-14 16:11:05 +010089
Kevin Rocard804e0642013-06-25 12:09:35 +020090 def getName(self, default=""):
91 return self.name or default
Kevin Rocard326e39e2012-12-14 16:11:05 +010092
Kevin Rocard804e0642013-06-25 12:09:35 +020093 def hasChildren(self):
94 return bool(self.children)
Kevin Rocard326e39e2012-12-14 16:11:05 +010095
Kevin Rocard804e0642013-06-25 12:09:35 +020096 def getChildren(self):
97 return self.children
Kevin Rocard326e39e2012-12-14 16:11:05 +010098
Kevin Rocard804e0642013-06-25 12:09:35 +020099 def _getDescendants(self):
100 for child in self.children:
101 yield child
102 for descendant in child._getDescendants() :
103 yield descendant
Kevin Rocard326e39e2012-12-14 16:11:05 +0100104
Kevin Rocard804e0642013-06-25 12:09:35 +0200105 def getChildFromName(self, childName):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100106
Kevin Rocard804e0642013-06-25 12:09:35 +0200107 for child in self.children :
Kevin Rocard326e39e2012-12-14 16:11:05 +0100108
Kevin Rocard804e0642013-06-25 12:09:35 +0200109 if child.getName() == childName :
110 return child
Kevin Rocard326e39e2012-12-14 16:11:05 +0100111
Kevin Rocard0bb508d2015-03-27 20:37:46 +0100112 self.debug('Child "%s" not found' % childName, logging.ERROR)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100113
Kevin Rocard804e0642013-06-25 12:09:35 +0200114 self.debug("Child list :")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100115
Kevin Rocard804e0642013-06-25 12:09:35 +0200116 for child in self.children :
117 self.debug(" - %s" % child)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100118
Kevin Rocard804e0642013-06-25 12:09:35 +0200119 raise ChildNotFoundError(self, childName)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100120
121
Kevin Rocard804e0642013-06-25 12:09:35 +0200122 def addChild(self, child):
123 self.debug("new child: " + child.name)
124 self.children.append(child)
125 child._adoptedBy(self)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100126
Kevin Rocard804e0642013-06-25 12:09:35 +0200127 def _adoptedBy(self, parent):
128 assert(not self.parent)
129 self.parent = parent
Kevin Rocard326e39e2012-12-14 16:11:05 +0100130
Kevin Rocard804e0642013-06-25 12:09:35 +0200131 def _getElementNames(self, elementList):
132 return (substate.name for substate in elementList)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100133
Kevin Rocard804e0642013-06-25 12:09:35 +0200134 def _description(self, withCoverage, withNbUse):
135 description = self.name
Kevin Rocard326e39e2012-12-14 16:11:05 +0100136
Kevin Rocard804e0642013-06-25 12:09:35 +0200137 if withNbUse or withCoverage :
138 description += " has been used " + str(self.nbUse) + " time"
Kevin Rocard326e39e2012-12-14 16:11:05 +0100139
Kevin Rocard804e0642013-06-25 12:09:35 +0200140 if withCoverage :
141 description += self._coverageFormating(self._getCoverage())
Kevin Rocard326e39e2012-12-14 16:11:05 +0100142
Kevin Rocard804e0642013-06-25 12:09:35 +0200143 return description
Kevin Rocard326e39e2012-12-14 16:11:05 +0100144
145
Kevin Rocard804e0642013-06-25 12:09:35 +0200146 def _getCoverage(self):
147 """Return the coverage of the element between 0 and 1
Kevin Rocard326e39e2012-12-14 16:11:05 +0100148
Kevin Rocard804e0642013-06-25 12:09:35 +0200149 If the element has no coverage dependency (usually child) return 0 or 1.
150 otherwise the element coverage is the dependency coverage average"""
151 coverageDependanceElements = list(self._getCoverageDependanceElements())
Kevin Rocard326e39e2012-12-14 16:11:05 +0100152
Kevin Rocard804e0642013-06-25 12:09:35 +0200153 nbcoverageDependence = len(coverageDependanceElements)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100154
Kevin Rocard804e0642013-06-25 12:09:35 +0200155 if nbcoverageDependence == 0:
156 if self.nbUse == 0:
157 return 0
158 else:
159 return 1
Kevin Rocard326e39e2012-12-14 16:11:05 +0100160
Kevin Rocard804e0642013-06-25 12:09:35 +0200161 coverageDependenceValues = (depElement._getCoverage()
162 for depElement in coverageDependanceElements)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100163
Kevin Rocard804e0642013-06-25 12:09:35 +0200164 return sum(coverageDependenceValues) / nbcoverageDependence
Kevin Rocard326e39e2012-12-14 16:11:05 +0100165
Kevin Rocard804e0642013-06-25 12:09:35 +0200166 def _getCoverageDependanceElements(self):
167 return self.children
Kevin Rocard326e39e2012-12-14 16:11:05 +0100168
Kevin Rocard804e0642013-06-25 12:09:35 +0200169 def _coverageFormating(self, coverage):
170 # If no coverage provided
171 if not coverage :
172 return ""
Kevin Rocard326e39e2012-12-14 16:11:05 +0100173
Kevin Rocard804e0642013-06-25 12:09:35 +0200174 # Calculate coverage
175 return " (%s coverage)" % self._number2percent(coverage)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100176
Kevin Rocard804e0642013-06-25 12:09:35 +0200177 @staticmethod
178 def _number2percent(number):
179 """Format a number to a integer % string
Kevin Rocard326e39e2012-12-14 16:11:05 +0100180
Kevin Rocard804e0642013-06-25 12:09:35 +0200181 example: _number2percent(0.6666) -> "67%"
182 """
183 return "{0:.0f}%".format(100 * number)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100184
185
Kevin Rocard804e0642013-06-25 12:09:35 +0200186 def _dumpDescription(self, withCoverage, withNbUse):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100187
Kevin Rocard804e0642013-06-25 12:09:35 +0200188 self.debug("yelding description")
189 yield RankedLine(self._description(withCoverage, withNbUse), lineSuffix="")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100190
Kevin Rocard804e0642013-06-25 12:09:35 +0200191 for dumped in self._dumpPropagate(withCoverage, withNbUse) :
192 yield dumped
Kevin Rocard326e39e2012-12-14 16:11:05 +0100193
Kevin Rocard804e0642013-06-25 12:09:35 +0200194 def _dumpPropagate(self, withCoverage, withNbUse):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100195
Kevin Rocard804e0642013-06-25 12:09:35 +0200196 for child in self.children :
197 for dumpedDescription in child._dumpDescription(withCoverage, withNbUse) :
198 yield dumpedDescription.increasedRank()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100199
200
Kevin Rocard804e0642013-06-25 12:09:35 +0200201 def dump(self, withCoverage=False, withNbUse=True):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100202
Kevin Rocard804e0642013-06-25 12:09:35 +0200203 return "\n".join(
204 str(dumpedDescription) for dumpedDescription in
205 self._dumpDescription(withCoverage, withNbUse))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100206
Kevin Rocard794fea62015-02-10 20:45:35 +0100207 def exportToXML(self, document, domElement=None):
Kevin Rocard804e0642013-06-25 12:09:35 +0200208 if domElement == None:
Kevin Rocard794fea62015-02-10 20:45:35 +0100209 domElement = document.createElement(self.tag)
Kevin Rocard02726ec2013-06-20 10:28:54 +0200210
Kevin Rocard804e0642013-06-25 12:09:35 +0200211 self._XMLaddAttributes(domElement)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100212
Kevin Rocard804e0642013-06-25 12:09:35 +0200213 for child in self.children :
Kevin Rocard794fea62015-02-10 20:45:35 +0100214 domElement.appendChild(child.exportToXML(document))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100215
Kevin Rocard804e0642013-06-25 12:09:35 +0200216 return domElement
Kevin Rocard326e39e2012-12-14 16:11:05 +0100217
Kevin Rocard804e0642013-06-25 12:09:35 +0200218 def _XMLaddAttributes(self, domElement):
219 attributes = self._getXMLAttributes()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100220
Kevin Rocard804e0642013-06-25 12:09:35 +0200221 coverage = self._getCoverage()
222 if coverage != None :
223 attributes["Coverage"] = self._number2percent(coverage)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100224
Kevin Rocard804e0642013-06-25 12:09:35 +0200225 for key, value in attributes.items():
226 domElement.setAttribute(key, value)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100227
Kevin Rocard804e0642013-06-25 12:09:35 +0200228 def _getXMLAttributes(self):
229 return {
230 "Name": self.name,
231 "NbUse": str(self.nbUse)
232 }
Kevin Rocard326e39e2012-12-14 16:11:05 +0100233
Kevin Rocard804e0642013-06-25 12:09:35 +0200234 def _incNbUse(self):
235 self.nbUse += 1
Kevin Rocard326e39e2012-12-14 16:11:05 +0100236
Kevin Rocard804e0642013-06-25 12:09:35 +0200237 def childUsed(self, child):
238 self._incNbUse()
239 # Propagate to parent
240 self._tellParentThatChildUsed()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100241
Kevin Rocard804e0642013-06-25 12:09:35 +0200242 def _tellParentThatChildUsed(self):
243 if self.parent :
244 self.parent.childUsed(self)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100245
246
Kevin Rocard804e0642013-06-25 12:09:35 +0200247 def parentUsed(self):
248 self._incNbUse()
249 # Propagate to children
250 for child in self.children :
251 child.parentUsed()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100252
Kevin Rocard804e0642013-06-25 12:09:35 +0200253 def hasBeenUsed(self):
254 return self.nbUse > 0
Kevin Rocard326e39e2012-12-14 16:11:05 +0100255
Kevin Rocard804e0642013-06-25 12:09:35 +0200256 def operationOnChild(self, path, operation):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100257
Kevin Rocard804e0642013-06-25 12:09:35 +0200258 if path:
259 return self._operationPropagate(path, operation)
260 else :
261 self.debug("operating on self")
262 return operation(self)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100263
Kevin Rocard804e0642013-06-25 12:09:35 +0200264 def _operationPropagate(self, path, operation):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100265
Kevin Rocard804e0642013-06-25 12:09:35 +0200266 childName = path.pop(0)
267 child = self.getChildFromName(childName)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100268
Kevin Rocard804e0642013-06-25 12:09:35 +0200269 return child.operationOnChild(path, operation)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100270
271
272
Kevin Rocard804e0642013-06-25 12:09:35 +0200273 def debug(self, stringOrFunction, level=logging.DEBUG):
274 """Print a debug line on stderr in tree form
Kevin Rocard326e39e2012-12-14 16:11:05 +0100275
Kevin Rocard804e0642013-06-25 12:09:35 +0200276 If the debug line is expensive to generate, provide callable
277 object, it will be called if log is enable for this level.
278 This callable object should return the logline string.
279 """
280 if logger.isEnabledFor(level):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100281
Kevin Rocard804e0642013-06-25 12:09:35 +0200282 # TODO: use buildin callable if python >= 3.2
283 if hasattr(stringOrFunction, "__call__"):
284 string = stringOrFunction()
285 else:
286 string = stringOrFunction
Kevin Rocard326e39e2012-12-14 16:11:05 +0100287
Kevin Rocard804e0642013-06-25 12:09:35 +0200288 rankedLine = DebugRankedLine("%s: %s" % (self, string))
289 self._logDebug(rankedLine, level)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100290
Kevin Rocard804e0642013-06-25 12:09:35 +0200291 def _logDebug(self, rankedLine, level):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100292
Kevin Rocard804e0642013-06-25 12:09:35 +0200293 if self.parent:
294 self.parent._logDebug(rankedLine.increasedRank(), level)
295 else :
296 logger.log(level, str(rankedLine))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100297
298
299
300
301class FromDomElement(Element):
Kevin Rocard804e0642013-06-25 12:09:35 +0200302 def __init__(self, DomElement):
303 self._initFromDom(DomElement)
304 super().__init__(self.name)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100305
306
Kevin Rocard804e0642013-06-25 12:09:35 +0200307 def _initFromDom(self, DomElement):
308 self.name = DomElement.getAttribute("Name")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100309
310
311
312class DomElementLocation():
Kevin Rocard804e0642013-06-25 12:09:35 +0200313 def __init__(self, classConstructor, path=None):
314 self.classConstructor = classConstructor
315 if path :
316 self.path = path
317 else :
318 self.path = []
Kevin Rocard326e39e2012-12-14 16:11:05 +0100319
Kevin Rocard804e0642013-06-25 12:09:35 +0200320 self.path.append(classConstructor.tag)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100321
322
323class DomPopulatedElement(Element):
Kevin Rocard804e0642013-06-25 12:09:35 +0200324 """Default child populate
Kevin Rocard326e39e2012-12-14 16:11:05 +0100325
Kevin Rocard804e0642013-06-25 12:09:35 +0200326 Look for each dom element with tag specified in self.tag
327 and instantiate it with the dom element
328 """
329 childClasses = []
Kevin Rocard326e39e2012-12-14 16:11:05 +0100330
Kevin Rocard804e0642013-06-25 12:09:35 +0200331 def populate(self, dom):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100332
Kevin Rocard804e0642013-06-25 12:09:35 +0200333 for childDomElementLocation in self.childClasses :
Kevin Rocard326e39e2012-12-14 16:11:05 +0100334
Kevin Rocard804e0642013-06-25 12:09:35 +0200335 self.debug("Looking for child %s in path %s" % (
336 childDomElementLocation.path[-1], childDomElementLocation.path))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100337
Kevin Rocard804e0642013-06-25 12:09:35 +0200338 for childDomElement in self._findChildFromTagPath(dom, childDomElementLocation.path) :
Kevin Rocard326e39e2012-12-14 16:11:05 +0100339
Kevin Rocard804e0642013-06-25 12:09:35 +0200340 childElement = childDomElementLocation.classConstructor(childDomElement)
341 self.addChild(childElement)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100342
Kevin Rocard804e0642013-06-25 12:09:35 +0200343 childElement.populate(childDomElement)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100344
Kevin Rocard804e0642013-06-25 12:09:35 +0200345 def _findChildFromTagPath(self, dom, path):
346 if not path :
347 yield dom
348 else :
349 # Copy list
350 path = list(path)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100351
Kevin Rocard804e0642013-06-25 12:09:35 +0200352 tag = path.pop(0)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100353
Kevin Rocard804e0642013-06-25 12:09:35 +0200354 # Find element with tag
355 self.debug("Going to find elements with tag %s in %s" % (tag, dom))
356 self.debug(lambda: "Nb of solutions: %s" % len(dom.getElementsByTagName(tag)))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100357
Kevin Rocard804e0642013-06-25 12:09:35 +0200358 for elementByTag in dom.getElementsByTagName(tag) :
Kevin Rocard326e39e2012-12-14 16:11:05 +0100359
Kevin Rocard804e0642013-06-25 12:09:35 +0200360 self.debug("Found element: %s" % elementByTag)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100361
Kevin Rocard804e0642013-06-25 12:09:35 +0200362 # If the same tag is found
363 if elementByTag in dom.childNodes :
Kevin Rocard326e39e2012-12-14 16:11:05 +0100364
Kevin Rocard804e0642013-06-25 12:09:35 +0200365 # Yield next level
366 for element in self._findChildFromTagPath(elementByTag, path) :
367 yield element
Kevin Rocard326e39e2012-12-14 16:11:05 +0100368
369
370class Rule(Element):
371
Kevin Rocard804e0642013-06-25 12:09:35 +0200372 def usedIfApplicable(self, criteria):
373 childApplicability = (child.usedIfApplicable(criteria)
374 for child in self.children)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100375
Kevin Rocard804e0642013-06-25 12:09:35 +0200376 isApplicable = self._isApplicable(criteria, childApplicability)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100377
Kevin Rocard804e0642013-06-25 12:09:35 +0200378 if isApplicable :
379 self._incNbUse()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100380
Kevin Rocard804e0642013-06-25 12:09:35 +0200381 self.debug("Rule applicability: %s" % isApplicable)
382 assert(isApplicable == True or isApplicable == False)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100383
Kevin Rocard804e0642013-06-25 12:09:35 +0200384 return isApplicable
Kevin Rocard326e39e2012-12-14 16:11:05 +0100385
386
Kevin Rocard804e0642013-06-25 12:09:35 +0200387 def _isApplicable(self, criteria, childApplicability):
388 """Return the rule applicability depending on children applicability.
Kevin Rocardcf031992013-06-14 18:09:54 +0200389
Kevin Rocard804e0642013-06-25 12:09:35 +0200390 If at least one child is applicable, return true"""
391 # Lazy evaluation as in the PFW
392 return all(childApplicability)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100393
394
395class CriterionRule(FromDomElement, DomPopulatedElement, Rule):
Kevin Rocard804e0642013-06-25 12:09:35 +0200396 tag = "SelectionCriterionRule"
397 childClasses = []
398 isApplicableOperations = {
399 "Includes" : lambda criterion, value: criterion.stateIncludes(value),
400 "Excludes" : lambda criterion, value: not criterion.stateIncludes(value),
401 "Is" : lambda criterion, value: criterion.stateIs(value),
402 "IsNot" : lambda criterion, value: not criterion.stateIs(value)
403 }
Kevin Rocard326e39e2012-12-14 16:11:05 +0100404
Kevin Rocard804e0642013-06-25 12:09:35 +0200405 def _initFromDom(self, DomElement):
406 self.selectionCriterion = DomElement.getAttribute("SelectionCriterion")
407 self.matchesWhen = DomElement.getAttribute("MatchesWhen")
408 self.value = DomElement.getAttribute("Value")
409 self.name = "%s %s %s" % (self.selectionCriterion, self.matchesWhen, self.value)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100410
Kevin Rocard804e0642013-06-25 12:09:35 +0200411 applicableOperationWithoutValue = self.isApplicableOperations[self.matchesWhen]
412 self.isApplicableOperation = lambda criterion: applicableOperationWithoutValue(criterion, self.value)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100413
Kevin Rocard804e0642013-06-25 12:09:35 +0200414 def _isApplicable(self, criteria, childApplicability):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100415
Kevin Rocard804e0642013-06-25 12:09:35 +0200416 return criteria.operationOnChild([self.selectionCriterion],
417 self.isApplicableOperation)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100418
419
420class CompoundRule(FromDomElement, DomPopulatedElement, Rule):
Kevin Rocard804e0642013-06-25 12:09:35 +0200421 """CompoundRule can be of type ALL or ANY"""
422 tag = "CompoundRule"
423 # Declare childClasses but define it at first class instantiation
424 childClasses = None
Kevin Rocard326e39e2012-12-14 16:11:05 +0100425
Kevin Rocard804e0642013-06-25 12:09:35 +0200426 def __init__(self, dom):
427 # Define childClasses at first class instantiation
428 if self.childClasses == None :
429 self.childClasses = [DomElementLocation(CriterionRule),
430 DomElementLocation(CompoundRule)]
431 super().__init__(dom)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100432
Kevin Rocard804e0642013-06-25 12:09:35 +0200433 def _initFromDom(self, DomElement):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100434
Kevin Rocard804e0642013-06-25 12:09:35 +0200435 type = DomElement.getAttribute("Type")
436 self.ofTypeAll = {"All" : True, "Any" : False}[type]
437 self.name = type
Kevin Rocard326e39e2012-12-14 16:11:05 +0100438
Kevin Rocard804e0642013-06-25 12:09:35 +0200439 def _isApplicable(self, criteria, childApplicability):
440 if self.ofTypeAll :
441 applicability = super()._isApplicable(criteria, childApplicability)
442 else:
443 # Lazy evaluation as in the PFW
444 applicability = any(childApplicability)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100445
Kevin Rocard804e0642013-06-25 12:09:35 +0200446 return applicability
Kevin Rocard326e39e2012-12-14 16:11:05 +0100447
448class RootRule(DomPopulatedElement, Rule):
Kevin Rocard804e0642013-06-25 12:09:35 +0200449 tag = "RootRule"
450 childClasses = [DomElementLocation(CompoundRule)]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100451
Kevin Rocard804e0642013-06-25 12:09:35 +0200452 def populate(self, dom):
453 super().populate(dom)
454 self.debug("Children: %s" % self.children)
455 # A configuration can only have one or no rule
456 assert(len(self.children) <= 1)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100457
Kevin Rocard804e0642013-06-25 12:09:35 +0200458 def _getCoverageDependanceElements(self):
459 return self._getDescendants()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100460
461
462class CriteronStates(Element):
Kevin Rocard804e0642013-06-25 12:09:35 +0200463 """Root of configuration application criterion state"""
464 tag = "CriterionStates"
Kevin Rocard326e39e2012-12-14 16:11:05 +0100465
Kevin Rocard804e0642013-06-25 12:09:35 +0200466 def parentUsed(self, criteria):
467 """Add criteria to child if not exist, if exist increase it's nbUse"""
468 self._incNbUse()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100469
Kevin Rocard804e0642013-06-25 12:09:35 +0200470 matches = [child for child in self.children if child == criteria]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100471
Kevin Rocard804e0642013-06-25 12:09:35 +0200472 assert(len(matches) <= 1)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100473
Kevin Rocard804e0642013-06-25 12:09:35 +0200474 if matches :
475 self.debug("Criteria state has already been encounter")
476 currentcriteria = matches[0]
477 else :
478 self.debug("Criteria state has never been encounter, saving it")
479 currentcriteria = criteria
480 self.addChild(criteria)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100481
Kevin Rocard804e0642013-06-25 12:09:35 +0200482 currentcriteria.parentUsed()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100483
Kevin Rocard326e39e2012-12-14 16:11:05 +0100484
485
486class Configuration(FromDomElement, DomPopulatedElement):
Kevin Rocard804e0642013-06-25 12:09:35 +0200487 tag = "Configuration"
488 childClasses = []
Kevin Rocard326e39e2012-12-14 16:11:05 +0100489
Kevin Rocard804e0642013-06-25 12:09:35 +0200490 class IneligibleConfigurationAppliedError(CustomError):
Kevin Rocard97dbd352013-06-10 15:15:56 +0200491
Kevin Rocard804e0642013-06-25 12:09:35 +0200492 def __init__(self, configuration, criteria):
493 self.configuration = configuration
494 self.criteria = criteria
Kevin Rocard97dbd352013-06-10 15:15:56 +0200495
Kevin Rocard804e0642013-06-25 12:09:35 +0200496 def __str__(self):
Kevin Rocard97dbd352013-06-10 15:15:56 +0200497
Kevin Rocard804e0642013-06-25 12:09:35 +0200498 return ("Applying ineligible %s, "
499 "rule:\n%s\n"
500 "Criteria current state:\n%s" % (
501 self.configuration,
502 self.configuration.rootRule.dump(withCoverage=False, withNbUse=False),
503 self.criteria.dump(withCoverage=False, withNbUse=False)
504 ))
Kevin Rocard97dbd352013-06-10 15:15:56 +0200505
Kevin Rocard804e0642013-06-25 12:09:35 +0200506 def __init__(self, DomElement):
507 super().__init__(DomElement)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100508
Kevin Rocard804e0642013-06-25 12:09:35 +0200509 self.rootRule = RootRule("RootRule")
510 self.addChild(self.rootRule)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100511
Kevin Rocard804e0642013-06-25 12:09:35 +0200512 self.criteronStates = CriteronStates("CriterionStates")
513 self.addChild(self.criteronStates)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100514
Kevin Rocard804e0642013-06-25 12:09:35 +0200515 def populate(self, dom):
516 # Delegate to rootRule
517 self.rootRule.populate(dom)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100518
Kevin Rocard804e0642013-06-25 12:09:35 +0200519 def _getCoverage(self):
520 # Delegate to rootRule
521 return self.rootRule._getCoverage()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100522
Kevin Rocard804e0642013-06-25 12:09:35 +0200523 def used(self, criteria):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100524
Kevin Rocard804e0642013-06-25 12:09:35 +0200525 self._incNbUse()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100526
Kevin Rocard804e0642013-06-25 12:09:35 +0200527 # Propagate use to parents
528 self._tellParentThatChildUsed()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100529
Kevin Rocard804e0642013-06-25 12:09:35 +0200530 # Propagate to criterion coverage
531 self.criteronStates.parentUsed(criteria.export())
Kevin Rocard326e39e2012-12-14 16:11:05 +0100532
Kevin Rocard804e0642013-06-25 12:09:35 +0200533 # Propagate to rules
534 if not self.rootRule.usedIfApplicable(criteria) :
Kevin Rocard326e39e2012-12-14 16:11:05 +0100535
Kevin Rocard804e0642013-06-25 12:09:35 +0200536 self.debug("Applied but rule does not match current "
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100537 "criteria (parent: %s) " % self.parent.name,
Kevin Rocard804e0642013-06-25 12:09:35 +0200538 logging.ERROR)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100539
Kevin Rocard804e0642013-06-25 12:09:35 +0200540 raise self.IneligibleConfigurationAppliedError(self, criteria.export())
Kevin Rocard326e39e2012-12-14 16:11:05 +0100541
Kevin Rocard804e0642013-06-25 12:09:35 +0200542 def _dumpPropagate(self, withCoverage, withNbUse):
543 self.debug("Going to ask %s for description" % self.rootRule)
544 for dumpedDescription in self.rootRule._dumpDescription(
545 withCoverage=withCoverage,
546 withNbUse=withNbUse) :
547 yield dumpedDescription.increasedRank()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100548
Kevin Rocard804e0642013-06-25 12:09:35 +0200549 self.debug("Going to ask %s for description" % self.criteronStates)
550 for dumpedDescription in self.criteronStates._dumpDescription(
551 withCoverage=False,
552 withNbUse=withNbUse) :
553 yield dumpedDescription.increasedRank()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100554
555
556class Domain(FromDomElement, DomPopulatedElement):
Kevin Rocard804e0642013-06-25 12:09:35 +0200557 tag = "ConfigurableDomain"
558 childClasses = [DomElementLocation(Configuration, ["Configurations"])]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100559
560
561class Domains(DomPopulatedElement):
Kevin Rocard804e0642013-06-25 12:09:35 +0200562 tag = "Domains"
563 childClasses = [DomElementLocation(Domain, ["ConfigurableDomains"])]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100564
565
566class RankedLine():
Kevin Rocard804e0642013-06-25 12:09:35 +0200567 def __init__(self, string,
568 stringPrefix="|-- ",
569 rankString="| ",
570 linePrefix="",
571 lineSuffix="\n"):
572 self.string = string
573 self.rank = 0
574 self.stringPrefix = stringPrefix
575 self.rankString = rankString
576 self.linePrefix = linePrefix
577 self.lineSuffix = lineSuffix
Kevin Rocard326e39e2012-12-14 16:11:05 +0100578
Kevin Rocard804e0642013-06-25 12:09:35 +0200579 def increasedRank(self):
580 self.rank += 1
581 return self
Kevin Rocard326e39e2012-12-14 16:11:05 +0100582
Kevin Rocard804e0642013-06-25 12:09:35 +0200583 def __str__(self):
584 return self.linePrefix + \
585 self.rank * self.rankString + \
586 self.stringPrefix + \
587 self.string + \
588 self.lineSuffix
Kevin Rocard326e39e2012-12-14 16:11:05 +0100589
590class DebugRankedLine(RankedLine):
591
Kevin Rocard804e0642013-06-25 12:09:35 +0200592 def __init__(self, string, lineSuffix=""):
593 super().__init__(string,
594 stringPrefix="",
595 rankString=" ",
596 linePrefix="",
597 lineSuffix=lineSuffix)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100598
599
600class CriterionState(Element):
Kevin Rocard804e0642013-06-25 12:09:35 +0200601 tag = "CriterionState"
602 def used(self):
603 self._incNbUse()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100604
605
606class Criterion(Element):
Kevin Rocard804e0642013-06-25 12:09:35 +0200607 tag = "Criterion"
608 inclusivenessTranslate = {True: "Inclusive", False: "Exclusive"}
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100609
Kevin Rocard804e0642013-06-25 12:09:35 +0200610 class ChangeRequestToNonAccessibleState(CustomError):
611 def __init__(self, requestedState, detail):
612 self.requestedState = requestedState
613 self.detail = detail
Kevin Rocard556538e2013-06-10 15:20:10 +0200614
Kevin Rocard804e0642013-06-25 12:09:35 +0200615 def __str__(self):
616 return ("Change request to non accessible state %s. Detail: %s" %
617 (self.requestedState, self.detail))
Kevin Rocard556538e2013-06-10 15:20:10 +0200618
Kevin Rocard804e0642013-06-25 12:09:35 +0200619 def __init__(self, name, isInclusif,
620 stateNamesList, currentStateNamesList,
621 ignoreIntegrity=False):
622 super().__init__(name)
623 self.isInclusif = isInclusif
Kevin Rocard326e39e2012-12-14 16:11:05 +0100624
Kevin Rocard804e0642013-06-25 12:09:35 +0200625 for state in stateNamesList :
626 self.addChild(CriterionState(state))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100627
Kevin Rocard804e0642013-06-25 12:09:35 +0200628 self.currentState = []
629 self.initStateNamesList = list(currentStateNamesList)
630 self.changeState(self.initStateNamesList, ignoreIntegrity)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100631
Kevin Rocard804e0642013-06-25 12:09:35 +0200632 def reset(self):
633 # Set current state as provided at initialisation
634 self.changeState(self.initStateNamesList, ignoreIntegrity=True)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100635
Kevin Rocard804e0642013-06-25 12:09:35 +0200636 def changeState(self, subStateNames, ignoreIntegrity=False):
637 self.debug("Changing state from: %s to: %s" % (
638 list(self._getElementNames(self.currentState)),
639 subStateNames))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100640
Kevin Rocard804e0642013-06-25 12:09:35 +0200641 if not ignoreIntegrity and not self.isIntegre(subStateNames):
642 raise self.ChangeRequestToNonAccessibleState(subStateNames,
643 "An exclusive criterion must have a non empty state")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100644
Kevin Rocard804e0642013-06-25 12:09:35 +0200645 newCurrentState = []
646 for subStateName in subStateNames :
647 subState = self.getChildFromName(subStateName)
648 subState.used()
649 newCurrentState.append(subState)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100650
Kevin Rocard804e0642013-06-25 12:09:35 +0200651 self.currentState = newCurrentState
Kevin Rocard326e39e2012-12-14 16:11:05 +0100652
Kevin Rocard804e0642013-06-25 12:09:35 +0200653 self._incNbUse()
654 self._tellParentThatChildUsed()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100655
Kevin Rocard804e0642013-06-25 12:09:35 +0200656 def isIntegre(self, subStateNames):
657 return self.isInclusif or len(subStateNames) == 1
Kevin Rocard556538e2013-06-10 15:20:10 +0200658
Kevin Rocard804e0642013-06-25 12:09:35 +0200659 def childUsed(self, child):
660 self.currentState = child
661 super().childUsed(child)
Kevin Rocard556538e2013-06-10 15:20:10 +0200662
Kevin Rocard804e0642013-06-25 12:09:35 +0200663 def export(self):
664 subStateNames = self._getElementNames(self.currentState)
665 return Criterion(self.name, self.isInclusif, subStateNames, subStateNames,
666 ignoreIntegrity=True)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100667
Kevin Rocard804e0642013-06-25 12:09:35 +0200668 def stateIncludes(self, subStateName):
669 subStateCurrentNames = list(self._getElementNames(self.currentState))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100670
Kevin Rocard804e0642013-06-25 12:09:35 +0200671 self.debug("Testing if %s is included in %s" % (subStateName, subStateCurrentNames))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100672
Kevin Rocard804e0642013-06-25 12:09:35 +0200673 isIncluded = subStateName in subStateCurrentNames
674 self.debug("IsIncluded: %s" % isIncluded)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100675
Kevin Rocard804e0642013-06-25 12:09:35 +0200676 return isIncluded
Kevin Rocard326e39e2012-12-14 16:11:05 +0100677
678
Kevin Rocard804e0642013-06-25 12:09:35 +0200679 def stateIs(self, subStateNames):
680 if len(self.currentState) != 1 :
681 return False
682 else :
683 return self.stateIncludes(subStateNames)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100684
Kevin Rocard804e0642013-06-25 12:09:35 +0200685 def _getXMLAttributes(self):
686 attributes = super()._getXMLAttributes()
687 attributes["Type"] = self.inclusivenessTranslate[self.isInclusif]
688 return attributes
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100689
Kevin Rocard326e39e2012-12-14 16:11:05 +0100690
691class Criteria(Element):
Kevin Rocard804e0642013-06-25 12:09:35 +0200692 tag = "Criteria"
Kevin Rocard326e39e2012-12-14 16:11:05 +0100693
Kevin Rocard804e0642013-06-25 12:09:35 +0200694 class DuplicatedCriterionError(DuplicatedChildError):
695 pass
Kevin Rocardd077c552013-06-10 15:31:32 +0200696
Kevin Rocard804e0642013-06-25 12:09:35 +0200697 def export(self):
698 self.debug("Exporting criteria")
699 assert(self.children)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100700
Kevin Rocard804e0642013-06-25 12:09:35 +0200701 exported = Criteria(self.name)
702 for child in self.children :
703 exported.addChild(child.export())
704 return exported
Kevin Rocard326e39e2012-12-14 16:11:05 +0100705
Kevin Rocard804e0642013-06-25 12:09:35 +0200706 def addChild(self, child):
707 if child in self.children:
708 raise self.DuplicatedCriterionError(self, child)
709 super().addChild(child)
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100710
711class ConfigAppliedWithoutCriteriaError(CustomError):
Kevin Rocard804e0642013-06-25 12:09:35 +0200712 def __init__(self, configurationName, domainName):
713 self.configurationName = configurationName
714 self.domainName = domainName
715 def __str__(self):
716 return ('Applying configuration "%s" from domain "%s" before declaring criteria' %
717 (self.configurationName, self.domainName))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100718
719class ParsePFWlog():
Kevin Rocard804e0642013-06-25 12:09:35 +0200720 MATCH = "match"
721 ACTION = "action"
Kevin Rocard326e39e2012-12-14 16:11:05 +0100722
Kevin Rocard804e0642013-06-25 12:09:35 +0200723 def __init__(self, domains, criteria, ErrorsToIgnore=()):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100724
Kevin Rocard804e0642013-06-25 12:09:35 +0200725 self.domains = domains;
726 self.criteria = criteria;
727 self.ErrorsToIgnore = ErrorsToIgnore
Kevin Rocard326e39e2012-12-14 16:11:05 +0100728
Kevin Rocard804e0642013-06-25 12:09:35 +0200729 configApplicationRegext = r""".*Applying configuration "(.*)" from domain "([^"]*)"""
730 matchConfigApplicationLine = re.compile(configApplicationRegext).match
Kevin Rocard326e39e2012-12-14 16:11:05 +0100731
Kevin Rocard804e0642013-06-25 12:09:35 +0200732 criterionCreationRegext = ", ".join([
733 r""".*Criterion name: (.*)""",
734 r"""type kind: (.*)""",
735 r"""current state: (.*)""",
736 r"""states: {(.*)}"""
737 ])
738 matchCriterionCreationLine = re.compile(criterionCreationRegext).match
Kevin Rocard326e39e2012-12-14 16:11:05 +0100739
Kevin Rocard804e0642013-06-25 12:09:35 +0200740 changingCriterionRegext = r""".*Selection criterion changed event: Criterion name: (.*), current state: ([^\n\r]*)"""
741 matchChangingCriterionLine = re.compile(changingCriterionRegext).match
Kevin Rocard326e39e2012-12-14 16:11:05 +0100742
Kevin Rocard804e0642013-06-25 12:09:35 +0200743 self.lineLogTypes = [
744 {
745 self.MATCH: matchConfigApplicationLine,
746 self.ACTION: self._configApplication
747 }, {
748 self.MATCH: matchCriterionCreationLine,
749 self.ACTION: self._criterionCreation
750 }, {
751 self.MATCH: matchChangingCriterionLine,
752 self.ACTION: self._changingCriterion
753 }
754 ]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100755
Kevin Rocard804e0642013-06-25 12:09:35 +0200756 @staticmethod
757 def _formatCriterionList(liststring, separator):
758 list = liststring.split(separator)
759 if len(list) == 1 and list[0] == "<none>":
760 list = []
761 return list
Kevin Rocard326e39e2012-12-14 16:11:05 +0100762
Kevin Rocard804e0642013-06-25 12:09:35 +0200763 def _criterionCreation(self, matchCriterionCreation):
764 # Unpack
765 criterionName, criterionType, currentCriterionStates, criterionStates = matchCriterionCreation.group(1, 2, 3, 4)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100766
Kevin Rocard804e0642013-06-25 12:09:35 +0200767 criterionStateList = self._formatCriterionList(criterionStates, ", ")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100768
Kevin Rocard804e0642013-06-25 12:09:35 +0200769 criterionIsInclusif = {"exclusive" : False, "inclusive" : True}[criterionType]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100770
Kevin Rocard804e0642013-06-25 12:09:35 +0200771 currentcriterionStateList = self._formatCriterionList(currentCriterionStates, "|")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100772
Kevin Rocard804e0642013-06-25 12:09:35 +0200773 logger.info("Creating criterion: " + criterionName +
774 " (" + criterionType + ") " +
775 " with current state: " + str(currentcriterionStateList) +
776 ", possible states:" + str(criterionStateList))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100777
Kevin Rocard804e0642013-06-25 12:09:35 +0200778 try:
779 self.criteria.addChild(Criterion(
780 criterionName,
781 criterionIsInclusif,
782 criterionStateList,
783 currentcriterionStateList
784 ))
785 except self.criteria.DuplicatedCriterionError as ex:
786 logger.debug(ex)
787 logger.warning("Reseting criterion %s. Did you reset the PFW ?" % criterionName)
788 self.criteria.operationOnChild(
789 [criterionName],
790 lambda criterion: criterion.reset()
791 )
Kevin Rocardea874222013-06-10 16:00:08 +0200792
793
Kevin Rocard326e39e2012-12-14 16:11:05 +0100794
Kevin Rocard804e0642013-06-25 12:09:35 +0200795 def _changingCriterion(self, matchChangingCriterion):
796 # Unpack
797 criterionName, newCriterionSubStateNames = matchChangingCriterion.group(1, 2)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100798
Kevin Rocard804e0642013-06-25 12:09:35 +0200799 newCriterionState = self._formatCriterionList(newCriterionSubStateNames, "|")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100800
Kevin Rocard804e0642013-06-25 12:09:35 +0200801 logger.info("Changing criterion %s to %s" % (criterionName , newCriterionState))
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100802
Kevin Rocard804e0642013-06-25 12:09:35 +0200803 path = [criterionName]
804 changeCriterionOperation = lambda criterion : criterion.changeState(newCriterionState)
805 self.criteria.operationOnChild(path, changeCriterionOperation)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100806
Kevin Rocard804e0642013-06-25 12:09:35 +0200807 def _configApplication(self, matchConfig):
808 # Unpack
809 configurationName, domainName = matchConfig.group(1, 2)
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100810
Kevin Rocard804e0642013-06-25 12:09:35 +0200811 # Check that at least one criterion exist
812 if not self.criteria.hasChildren() :
813 logger.error("Applying configuration before declaring criteria")
814 logger.info("Is the log starting at PFW boot ?")
815 raise ConfigAppliedWithoutCriteriaError(configurationName, domainName)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100816
Kevin Rocard804e0642013-06-25 12:09:35 +0200817 # Change criterion state
818 path = [domainName, configurationName]
819 usedOperation = lambda element : element.used(self.criteria)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100820
Kevin Rocard804e0642013-06-25 12:09:35 +0200821 logger.info("Applying configuration %s from domain %s" % (
822 configurationName, domainName))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100823
Kevin Rocard804e0642013-06-25 12:09:35 +0200824 self.domains.operationOnChild(path, usedOperation)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100825
826
Kevin Rocard804e0642013-06-25 12:09:35 +0200827 def _digest(self, lineLogType, lineLog):
Kevin Rocard9050c812013-06-10 16:01:21 +0200828
Kevin Rocard804e0642013-06-25 12:09:35 +0200829 match = lineLogType[self.MATCH](lineLog)
830 if match :
831 lineLogType[self.ACTION](match)
832 return True
833 return False
Kevin Rocard326e39e2012-12-14 16:11:05 +0100834
Kevin Rocard9050c812013-06-10 16:01:21 +0200835
Kevin Rocard804e0642013-06-25 12:09:35 +0200836 def parsePFWlog(self, lines):
Kevin Rocard6bd9fcf2015-03-27 20:40:12 +0100837 for lineNb, lineLog in enumerate(lines, 1): # line number starts at 1
Kevin Rocard326e39e2012-12-14 16:11:05 +0100838
Kevin Rocard804e0642013-06-25 12:09:35 +0200839 logger.debug("Parsing line :%s" % lineLog.rstrip())
Kevin Rocard326e39e2012-12-14 16:11:05 +0100840
Kevin Rocard804e0642013-06-25 12:09:35 +0200841 digested = (self._digest(lineLogType, lineLog)
842 for lineLogType in self.lineLogTypes)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100843
Kevin Rocard804e0642013-06-25 12:09:35 +0200844 try:
845 success = any(digested)
Kevin Rocard9050c812013-06-10 16:01:21 +0200846
Kevin Rocard804e0642013-06-25 12:09:35 +0200847 # Catch some exception in order to print the current parsing line,
848 # then raise the exception again if not continue of error
849 except CustomError as ex:
850 logger.error('Error raised while parsing line %s: "%s"' %
851 (lineNb, repr(lineLog)))
Kevin Rocard9050c812013-06-10 16:01:21 +0200852
Kevin Rocard804e0642013-06-25 12:09:35 +0200853 # If exception is a subclass of ErrorsToIgnore, log it and continue
854 # otherwise raise it again.
855 if not issubclass(type(ex), self.ErrorsToIgnore):
856 raise ex
857 else:
858 logger.error('Ignoring exception:"%s", '
859 'can not guarantee database integrity' % ex)
860 else:
861 if not success:
862 logger.debug("Line does not match, dropped")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100863
864
865class Root(Element):
Kevin Rocard804e0642013-06-25 12:09:35 +0200866 tag = "CoverageReport"
867 def __init__(self, name, dom):
868 super().__init__(name)
869 # Create domain tree
870 self.domains = Domains("Domains")
871 self.domains.populate(dom)
872 self.addChild(self.domains)
873 # Create criterion list
874 self.criteria = Criteria("CriterionRoot")
875 self.addChild(self.criteria)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100876
Kevin Rocard804e0642013-06-25 12:09:35 +0200877 def exportToXML(self):
878 """Export tree to an xml document"""
879 impl = xml.dom.minidom.getDOMImplementation()
Kevin Rocard794fea62015-02-10 20:45:35 +0100880 document = impl.createDocument(namespaceURI=None, qualifiedName=self.tag, doctype=None)
881 super().exportToXML(document, document.documentElement)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100882
Kevin Rocard794fea62015-02-10 20:45:35 +0100883 return document
Kevin Rocard326e39e2012-12-14 16:11:05 +0100884
885# ============================
886# Command line argument parser
887# ============================
888
889
890class ArgumentParser:
Kevin Rocard804e0642013-06-25 12:09:35 +0200891 """class that parse command line arguments with argparse library
Kevin Rocard326e39e2012-12-14 16:11:05 +0100892
Kevin Rocard804e0642013-06-25 12:09:35 +0200893 Result of parsing are the class attributes.
894 """
895 levelTranslate = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100896
Kevin Rocard804e0642013-06-25 12:09:35 +0200897 def __init__(self):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100898
Kevin Rocard804e0642013-06-25 12:09:35 +0200899 try:
900 # As argparse is only in the stdlib since python 3.2,
901 # testing its availability
902 import argparse
Kevin Rocard326e39e2012-12-14 16:11:05 +0100903
Kevin Rocard804e0642013-06-25 12:09:35 +0200904 except ImportError:
905 logger.warning("Unable to import argparse "
906 "(parser for command-line options and arguments), "
907 "using default argument values:")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100908
Kevin Rocard804e0642013-06-25 12:09:35 +0200909 logger.warning(" - InputFile: stdin")
910 self.inputFile = sys.stdin
Kevin Rocard326e39e2012-12-14 16:11:05 +0100911
Kevin Rocard804e0642013-06-25 12:09:35 +0200912 logger.warning(" - OutputFile: stdout")
913 self.outputFile = sys.stdout
Kevin Rocard326e39e2012-12-14 16:11:05 +0100914
Kevin Rocard804e0642013-06-25 12:09:35 +0200915 try:
916 self.domainsFile = sys.argv[1]
917 except IndexError as ex:
918 logger.fatal("No domain file provided (first argument)")
919 raise ex
920 else:
921 logger.warning(" - Domain file: " + self.domainsFile)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100922
Kevin Rocard804e0642013-06-25 12:09:35 +0200923 logger.warning(" - Output format: xml")
924 self.XMLreport = True
Kevin Rocard326e39e2012-12-14 16:11:05 +0100925
Kevin Rocard804e0642013-06-25 12:09:35 +0200926 logger.warning(" - Debug level: error")
927 self.debugLevel = logging.ERROR
928 else :
Kevin Rocard326e39e2012-12-14 16:11:05 +0100929
Kevin Rocard804e0642013-06-25 12:09:35 +0200930 myArgParser = argparse.ArgumentParser(description='Generate PFW report')
Kevin Rocard326e39e2012-12-14 16:11:05 +0100931
Kevin Rocard804e0642013-06-25 12:09:35 +0200932 myArgParser.add_argument(
933 'domainsFile',
934 type=argparse.FileType('r'),
935 help="the PFW domain XML file"
936 )
937 myArgParser.add_argument(
938 'pfwlog', nargs='?',
939 type=argparse.FileType('r'), default=sys.stdin,
940 help="the PFW log file, default stdin"
941 )
942 myArgParser.add_argument(
943 '-o', '--output',
944 dest="outputFile",
945 type=argparse.FileType('w'), default=sys.stdout,
946 help="the coverage report output file, default stdout"
947 )
948 myArgParser.add_argument(
949 '-v', '--verbose',
950 dest="debugLevel", default=0,
951 action='count',
952 help="print debug warnings from warning (default) to debug (-vv)"
953 )
Kevin Rocard326e39e2012-12-14 16:11:05 +0100954
Kevin Rocard804e0642013-06-25 12:09:35 +0200955 outputFormatGroupe = myArgParser.add_mutually_exclusive_group(required=False)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100956
Kevin Rocard804e0642013-06-25 12:09:35 +0200957 outputFormatGroupe.add_argument(
958 '--xml',
959 dest="xmlFlag",
960 action='store_true',
961 help=" XML coverage output report"
962 )
963 outputFormatGroupe.add_argument(
964 '--raw',
965 dest="rawFlag",
966 action='store_true',
967 help="raw coverage output report"
968 )
Kevin Rocard326e39e2012-12-14 16:11:05 +0100969
Kevin Rocard804e0642013-06-25 12:09:35 +0200970 myArgParser.add_argument(
971 '--ignore-incoherent-criterion-state',
972 dest="incoherentCriterionFlag",
973 action='store_true',
974 help="ignore criterion transition to incoherent state"
975 )
Kevin Rocard9050c812013-06-10 16:01:21 +0200976
Kevin Rocard804e0642013-06-25 12:09:35 +0200977 myArgParser.add_argument(
978 '--ignore-ineligible-configuration-application',
979 dest="ineligibleConfigurationApplicationFlag",
980 action='store_true',
981 help="ignore application of configuration with a false rule "
982 "(not applicable configuration)"
983 )
Kevin Rocard9050c812013-06-10 16:01:21 +0200984
Kevin Rocard804e0642013-06-25 12:09:35 +0200985 # Process command line arguments
986 options = myArgParser.parse_args()
Kevin Rocard326e39e2012-12-14 16:11:05 +0100987
Kevin Rocard804e0642013-06-25 12:09:35 +0200988 # Mapping to attributes
989 self.inputFile = options.pfwlog
990 self.outputFile = options.outputFile
991 self.domainsFile = options.domainsFile
Kevin Rocard326e39e2012-12-14 16:11:05 +0100992
Kevin Rocard804e0642013-06-25 12:09:35 +0200993 # Output report in xml if flag not set
994 self.XMLreport = not options.rawFlag
Kevin Rocard326e39e2012-12-14 16:11:05 +0100995
Kevin Rocard804e0642013-06-25 12:09:35 +0200996 # Setting logger level
997 levelCapped = min(options.debugLevel, len(self.levelTranslate) - 1)
998 self.debugLevel = self.levelTranslate[levelCapped]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100999
Kevin Rocard804e0642013-06-25 12:09:35 +02001000 # Setting ignore options
1001 errorToIgnore = []
1002 if options.ineligibleConfigurationApplicationFlag :
1003 errorToIgnore.append(Configuration.IneligibleConfigurationAppliedError)
Kevin Rocard9050c812013-06-10 16:01:21 +02001004
Kevin Rocard804e0642013-06-25 12:09:35 +02001005 if options.incoherentCriterionFlag:
1006 errorToIgnore.append(Criterion.ChangeRequestToNonAccessibleState)
Kevin Rocard9050c812013-06-10 16:01:21 +02001007
Kevin Rocard804e0642013-06-25 12:09:35 +02001008 self.errorToIgnore = tuple(errorToIgnore)
Kevin Rocard9050c812013-06-10 16:01:21 +02001009
Kevin Rocard326e39e2012-12-14 16:11:05 +01001010
1011
1012def main():
1013
Kevin Rocard804e0642013-06-25 12:09:35 +02001014 errorDuringLogParsing = -1
1015 errorDuringArgumentParsing = 1
Kevin Rocard53493b22013-02-04 15:16:10 +01001016
Kevin Rocard804e0642013-06-25 12:09:35 +02001017 try:
1018 commandLineArguments = ArgumentParser()
1019 except LookupError as ex:
1020 logger.error("Error during argument parsing")
1021 logger.debug(str(ex))
1022 sys.exit(errorDuringArgumentParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001023
Kevin Rocard804e0642013-06-25 12:09:35 +02001024 # Setting logger level
1025 logger.setLevel(commandLineArguments.debugLevel)
1026 logger.info("Log level set to: %s" %
1027 logging.getLevelName(commandLineArguments.debugLevel))
Kevin Rocard326e39e2012-12-14 16:11:05 +01001028
Kevin Rocard804e0642013-06-25 12:09:35 +02001029 # Create tree from XML
1030 dom = xml.dom.minidom.parse(commandLineArguments.domainsFile)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001031
Kevin Rocard804e0642013-06-25 12:09:35 +02001032 # Create element tree
1033 root = Root("DomainCoverage", dom)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001034
Kevin Rocard804e0642013-06-25 12:09:35 +02001035 # Parse PFW events
1036 parser = ParsePFWlog(root.domains, root.criteria, commandLineArguments.errorToIgnore)
Kevin Rocard53493b22013-02-04 15:16:10 +01001037
Kevin Rocard804e0642013-06-25 12:09:35 +02001038 try:
1039 parser.parsePFWlog(commandLineArguments.inputFile.readlines())
1040 except CustomError as ex:
1041 logger.fatal("Error during parsing log file %s: %s" %
1042 (commandLineArguments.inputFile, ex))
1043 sys.exit(errorDuringLogParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001044
Kevin Rocard804e0642013-06-25 12:09:35 +02001045 # Output report
1046 outputFile = commandLineArguments.outputFile
Kevin Rocard326e39e2012-12-14 16:11:05 +01001047
Kevin Rocard804e0642013-06-25 12:09:35 +02001048 if not commandLineArguments.XMLreport :
1049 outputFile.write("%s\n" % root.dump(withCoverage=True, withNbUse=True))
1050 else :
1051 outputFile.write(root.exportToXML().toprettyxml())
Kevin Rocard326e39e2012-12-14 16:11:05 +01001052
1053
Kevin Rocard326e39e2012-12-14 16:11:05 +01001054if __name__ == "__main__" :
Kevin Rocard804e0642013-06-25 12:09:35 +02001055 """ Execute main if the python interpreter is running this module as the main program """
1056 main()
Kevin Rocard326e39e2012-12-14 16:11:05 +01001057