blob: 0a23285a168a619f9b8386afce76f29aab4f9e4e [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
David Wagnerace56ce2015-04-15 18:12:34 +0200723 class ChangeRequestOnUnknownCriterion(CustomError):
724 def __init__(self, criterion):
725 self.criterion = criterion
726
727 def __str__(self):
728 return ("Change request on an unknown criterion %s." %
729 self.criterion)
730
Kevin Rocard804e0642013-06-25 12:09:35 +0200731 def __init__(self, domains, criteria, ErrorsToIgnore=()):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100732
Kevin Rocard804e0642013-06-25 12:09:35 +0200733 self.domains = domains;
734 self.criteria = criteria;
735 self.ErrorsToIgnore = ErrorsToIgnore
Kevin Rocard326e39e2012-12-14 16:11:05 +0100736
Kevin Rocard804e0642013-06-25 12:09:35 +0200737 configApplicationRegext = r""".*Applying configuration "(.*)" from domain "([^"]*)"""
738 matchConfigApplicationLine = re.compile(configApplicationRegext).match
Kevin Rocard326e39e2012-12-14 16:11:05 +0100739
Kevin Rocard804e0642013-06-25 12:09:35 +0200740 criterionCreationRegext = ", ".join([
741 r""".*Criterion name: (.*)""",
742 r"""type kind: (.*)""",
743 r"""current state: (.*)""",
744 r"""states: {(.*)}"""
745 ])
746 matchCriterionCreationLine = re.compile(criterionCreationRegext).match
Kevin Rocard326e39e2012-12-14 16:11:05 +0100747
Kevin Rocard804e0642013-06-25 12:09:35 +0200748 changingCriterionRegext = r""".*Selection criterion changed event: Criterion name: (.*), current state: ([^\n\r]*)"""
749 matchChangingCriterionLine = re.compile(changingCriterionRegext).match
Kevin Rocard326e39e2012-12-14 16:11:05 +0100750
Kevin Rocard804e0642013-06-25 12:09:35 +0200751 self.lineLogTypes = [
752 {
753 self.MATCH: matchConfigApplicationLine,
754 self.ACTION: self._configApplication
755 }, {
756 self.MATCH: matchCriterionCreationLine,
757 self.ACTION: self._criterionCreation
758 }, {
759 self.MATCH: matchChangingCriterionLine,
760 self.ACTION: self._changingCriterion
761 }
762 ]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100763
Kevin Rocard804e0642013-06-25 12:09:35 +0200764 @staticmethod
765 def _formatCriterionList(liststring, separator):
766 list = liststring.split(separator)
767 if len(list) == 1 and list[0] == "<none>":
768 list = []
769 return list
Kevin Rocard326e39e2012-12-14 16:11:05 +0100770
Kevin Rocard804e0642013-06-25 12:09:35 +0200771 def _criterionCreation(self, matchCriterionCreation):
772 # Unpack
773 criterionName, criterionType, currentCriterionStates, criterionStates = matchCriterionCreation.group(1, 2, 3, 4)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100774
Kevin Rocard804e0642013-06-25 12:09:35 +0200775 criterionStateList = self._formatCriterionList(criterionStates, ", ")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100776
Kevin Rocard804e0642013-06-25 12:09:35 +0200777 criterionIsInclusif = {"exclusive" : False, "inclusive" : True}[criterionType]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100778
Kevin Rocard804e0642013-06-25 12:09:35 +0200779 currentcriterionStateList = self._formatCriterionList(currentCriterionStates, "|")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100780
Kevin Rocard804e0642013-06-25 12:09:35 +0200781 logger.info("Creating criterion: " + criterionName +
782 " (" + criterionType + ") " +
783 " with current state: " + str(currentcriterionStateList) +
784 ", possible states:" + str(criterionStateList))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100785
Kevin Rocard804e0642013-06-25 12:09:35 +0200786 try:
787 self.criteria.addChild(Criterion(
788 criterionName,
789 criterionIsInclusif,
790 criterionStateList,
791 currentcriterionStateList
792 ))
793 except self.criteria.DuplicatedCriterionError as ex:
794 logger.debug(ex)
795 logger.warning("Reseting criterion %s. Did you reset the PFW ?" % criterionName)
796 self.criteria.operationOnChild(
797 [criterionName],
798 lambda criterion: criterion.reset()
799 )
Kevin Rocardea874222013-06-10 16:00:08 +0200800
801
Kevin Rocard326e39e2012-12-14 16:11:05 +0100802
Kevin Rocard804e0642013-06-25 12:09:35 +0200803 def _changingCriterion(self, matchChangingCriterion):
804 # Unpack
805 criterionName, newCriterionSubStateNames = matchChangingCriterion.group(1, 2)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100806
Kevin Rocard804e0642013-06-25 12:09:35 +0200807 newCriterionState = self._formatCriterionList(newCriterionSubStateNames, "|")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100808
Kevin Rocard804e0642013-06-25 12:09:35 +0200809 logger.info("Changing criterion %s to %s" % (criterionName , newCriterionState))
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100810
Kevin Rocard804e0642013-06-25 12:09:35 +0200811 path = [criterionName]
812 changeCriterionOperation = lambda criterion : criterion.changeState(newCriterionState)
David Wagnerace56ce2015-04-15 18:12:34 +0200813 try:
814 self.criteria.operationOnChild(path, changeCriterionOperation)
815 except ChildNotFoundError:
816 raise self.ChangeRequestOnUnknownCriterion(criterionName)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100817
Kevin Rocard804e0642013-06-25 12:09:35 +0200818 def _configApplication(self, matchConfig):
819 # Unpack
820 configurationName, domainName = matchConfig.group(1, 2)
Kevin Rocard3aa0db42013-02-13 17:15:38 +0100821
Kevin Rocard804e0642013-06-25 12:09:35 +0200822 # Check that at least one criterion exist
823 if not self.criteria.hasChildren() :
824 logger.error("Applying configuration before declaring criteria")
825 logger.info("Is the log starting at PFW boot ?")
826 raise ConfigAppliedWithoutCriteriaError(configurationName, domainName)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100827
Kevin Rocard804e0642013-06-25 12:09:35 +0200828 # Change criterion state
829 path = [domainName, configurationName]
830 usedOperation = lambda element : element.used(self.criteria)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100831
Kevin Rocard804e0642013-06-25 12:09:35 +0200832 logger.info("Applying configuration %s from domain %s" % (
833 configurationName, domainName))
Kevin Rocard326e39e2012-12-14 16:11:05 +0100834
Kevin Rocard804e0642013-06-25 12:09:35 +0200835 self.domains.operationOnChild(path, usedOperation)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100836
837
Kevin Rocard804e0642013-06-25 12:09:35 +0200838 def _digest(self, lineLogType, lineLog):
Kevin Rocard9050c812013-06-10 16:01:21 +0200839
Kevin Rocard804e0642013-06-25 12:09:35 +0200840 match = lineLogType[self.MATCH](lineLog)
841 if match :
842 lineLogType[self.ACTION](match)
843 return True
844 return False
Kevin Rocard326e39e2012-12-14 16:11:05 +0100845
Kevin Rocard9050c812013-06-10 16:01:21 +0200846
Kevin Rocard804e0642013-06-25 12:09:35 +0200847 def parsePFWlog(self, lines):
Kevin Rocard6bd9fcf2015-03-27 20:40:12 +0100848 for lineNb, lineLog in enumerate(lines, 1): # line number starts at 1
Kevin Rocard326e39e2012-12-14 16:11:05 +0100849
Kevin Rocard804e0642013-06-25 12:09:35 +0200850 logger.debug("Parsing line :%s" % lineLog.rstrip())
Kevin Rocard326e39e2012-12-14 16:11:05 +0100851
Kevin Rocard804e0642013-06-25 12:09:35 +0200852 digested = (self._digest(lineLogType, lineLog)
853 for lineLogType in self.lineLogTypes)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100854
Kevin Rocard804e0642013-06-25 12:09:35 +0200855 try:
856 success = any(digested)
Kevin Rocard9050c812013-06-10 16:01:21 +0200857
Kevin Rocard804e0642013-06-25 12:09:35 +0200858 # Catch some exception in order to print the current parsing line,
859 # then raise the exception again if not continue of error
860 except CustomError as ex:
861 logger.error('Error raised while parsing line %s: "%s"' %
862 (lineNb, repr(lineLog)))
Kevin Rocard9050c812013-06-10 16:01:21 +0200863
Kevin Rocard804e0642013-06-25 12:09:35 +0200864 # If exception is a subclass of ErrorsToIgnore, log it and continue
865 # otherwise raise it again.
866 if not issubclass(type(ex), self.ErrorsToIgnore):
867 raise ex
868 else:
869 logger.error('Ignoring exception:"%s", '
870 'can not guarantee database integrity' % ex)
871 else:
872 if not success:
873 logger.debug("Line does not match, dropped")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100874
875
876class Root(Element):
Kevin Rocard804e0642013-06-25 12:09:35 +0200877 tag = "CoverageReport"
878 def __init__(self, name, dom):
879 super().__init__(name)
880 # Create domain tree
881 self.domains = Domains("Domains")
882 self.domains.populate(dom)
883 self.addChild(self.domains)
884 # Create criterion list
885 self.criteria = Criteria("CriterionRoot")
886 self.addChild(self.criteria)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100887
Kevin Rocard804e0642013-06-25 12:09:35 +0200888 def exportToXML(self):
889 """Export tree to an xml document"""
890 impl = xml.dom.minidom.getDOMImplementation()
Kevin Rocard794fea62015-02-10 20:45:35 +0100891 document = impl.createDocument(namespaceURI=None, qualifiedName=self.tag, doctype=None)
892 super().exportToXML(document, document.documentElement)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100893
Kevin Rocard794fea62015-02-10 20:45:35 +0100894 return document
Kevin Rocard326e39e2012-12-14 16:11:05 +0100895
896# ============================
897# Command line argument parser
898# ============================
899
900
901class ArgumentParser:
Kevin Rocard804e0642013-06-25 12:09:35 +0200902 """class that parse command line arguments with argparse library
Kevin Rocard326e39e2012-12-14 16:11:05 +0100903
Kevin Rocard804e0642013-06-25 12:09:35 +0200904 Result of parsing are the class attributes.
905 """
906 levelTranslate = [logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG]
Kevin Rocard326e39e2012-12-14 16:11:05 +0100907
Kevin Rocard804e0642013-06-25 12:09:35 +0200908 def __init__(self):
Kevin Rocard326e39e2012-12-14 16:11:05 +0100909
Kevin Rocard804e0642013-06-25 12:09:35 +0200910 try:
911 # As argparse is only in the stdlib since python 3.2,
912 # testing its availability
913 import argparse
Kevin Rocard326e39e2012-12-14 16:11:05 +0100914
Kevin Rocard804e0642013-06-25 12:09:35 +0200915 except ImportError:
916 logger.warning("Unable to import argparse "
917 "(parser for command-line options and arguments), "
918 "using default argument values:")
Kevin Rocard326e39e2012-12-14 16:11:05 +0100919
Kevin Rocard804e0642013-06-25 12:09:35 +0200920 logger.warning(" - InputFile: stdin")
921 self.inputFile = sys.stdin
Kevin Rocard326e39e2012-12-14 16:11:05 +0100922
Kevin Rocard804e0642013-06-25 12:09:35 +0200923 logger.warning(" - OutputFile: stdout")
924 self.outputFile = sys.stdout
Kevin Rocard326e39e2012-12-14 16:11:05 +0100925
Kevin Rocard804e0642013-06-25 12:09:35 +0200926 try:
927 self.domainsFile = sys.argv[1]
928 except IndexError as ex:
929 logger.fatal("No domain file provided (first argument)")
930 raise ex
931 else:
932 logger.warning(" - Domain file: " + self.domainsFile)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100933
Kevin Rocard804e0642013-06-25 12:09:35 +0200934 logger.warning(" - Output format: xml")
935 self.XMLreport = True
Kevin Rocard326e39e2012-12-14 16:11:05 +0100936
Kevin Rocard804e0642013-06-25 12:09:35 +0200937 logger.warning(" - Debug level: error")
938 self.debugLevel = logging.ERROR
939 else :
Kevin Rocard326e39e2012-12-14 16:11:05 +0100940
Kevin Rocard804e0642013-06-25 12:09:35 +0200941 myArgParser = argparse.ArgumentParser(description='Generate PFW report')
Kevin Rocard326e39e2012-12-14 16:11:05 +0100942
Kevin Rocard804e0642013-06-25 12:09:35 +0200943 myArgParser.add_argument(
944 'domainsFile',
945 type=argparse.FileType('r'),
946 help="the PFW domain XML file"
947 )
948 myArgParser.add_argument(
949 'pfwlog', nargs='?',
950 type=argparse.FileType('r'), default=sys.stdin,
951 help="the PFW log file, default stdin"
952 )
953 myArgParser.add_argument(
954 '-o', '--output',
955 dest="outputFile",
956 type=argparse.FileType('w'), default=sys.stdout,
957 help="the coverage report output file, default stdout"
958 )
959 myArgParser.add_argument(
960 '-v', '--verbose',
961 dest="debugLevel", default=0,
962 action='count',
963 help="print debug warnings from warning (default) to debug (-vv)"
964 )
Kevin Rocard326e39e2012-12-14 16:11:05 +0100965
Kevin Rocard804e0642013-06-25 12:09:35 +0200966 outputFormatGroupe = myArgParser.add_mutually_exclusive_group(required=False)
Kevin Rocard326e39e2012-12-14 16:11:05 +0100967
Kevin Rocard804e0642013-06-25 12:09:35 +0200968 outputFormatGroupe.add_argument(
969 '--xml',
970 dest="xmlFlag",
971 action='store_true',
972 help=" XML coverage output report"
973 )
974 outputFormatGroupe.add_argument(
975 '--raw',
976 dest="rawFlag",
977 action='store_true',
978 help="raw coverage output report"
979 )
Kevin Rocard326e39e2012-12-14 16:11:05 +0100980
Kevin Rocard804e0642013-06-25 12:09:35 +0200981 myArgParser.add_argument(
David Wagnerace56ce2015-04-15 18:12:34 +0200982 '--ignore-unknown-criterion',
983 dest="unknwonCriterionFlag",
984 action='store_true',
985 help="ignore unknown criterion"
986 )
987
988 myArgParser.add_argument(
Kevin Rocard804e0642013-06-25 12:09:35 +0200989 '--ignore-incoherent-criterion-state',
990 dest="incoherentCriterionFlag",
991 action='store_true',
992 help="ignore criterion transition to incoherent state"
993 )
Kevin Rocard9050c812013-06-10 16:01:21 +0200994
Kevin Rocard804e0642013-06-25 12:09:35 +0200995 myArgParser.add_argument(
996 '--ignore-ineligible-configuration-application',
997 dest="ineligibleConfigurationApplicationFlag",
998 action='store_true',
999 help="ignore application of configuration with a false rule "
1000 "(not applicable configuration)"
1001 )
Kevin Rocard9050c812013-06-10 16:01:21 +02001002
Kevin Rocard804e0642013-06-25 12:09:35 +02001003 # Process command line arguments
1004 options = myArgParser.parse_args()
Kevin Rocard326e39e2012-12-14 16:11:05 +01001005
Kevin Rocard804e0642013-06-25 12:09:35 +02001006 # Mapping to attributes
1007 self.inputFile = options.pfwlog
1008 self.outputFile = options.outputFile
1009 self.domainsFile = options.domainsFile
Kevin Rocard326e39e2012-12-14 16:11:05 +01001010
Kevin Rocard804e0642013-06-25 12:09:35 +02001011 # Output report in xml if flag not set
1012 self.XMLreport = not options.rawFlag
Kevin Rocard326e39e2012-12-14 16:11:05 +01001013
Kevin Rocard804e0642013-06-25 12:09:35 +02001014 # Setting logger level
1015 levelCapped = min(options.debugLevel, len(self.levelTranslate) - 1)
1016 self.debugLevel = self.levelTranslate[levelCapped]
Kevin Rocard326e39e2012-12-14 16:11:05 +01001017
Kevin Rocard804e0642013-06-25 12:09:35 +02001018 # Setting ignore options
1019 errorToIgnore = []
1020 if options.ineligibleConfigurationApplicationFlag :
1021 errorToIgnore.append(Configuration.IneligibleConfigurationAppliedError)
Kevin Rocard9050c812013-06-10 16:01:21 +02001022
Kevin Rocard804e0642013-06-25 12:09:35 +02001023 if options.incoherentCriterionFlag:
David Wagner12d9c822015-04-20 10:51:29 +02001024 errorToIgnore.append(Criterion.ChangeRequestToNonAccessibleState)
David Wagnerace56ce2015-04-15 18:12:34 +02001025
1026 if options.unknwonCriterionFlag:
David Wagner12d9c822015-04-20 10:51:29 +02001027 errorToIgnore.append(ParsePFWlog.ChangeRequestOnUnknownCriterion)
Kevin Rocard9050c812013-06-10 16:01:21 +02001028
Kevin Rocard804e0642013-06-25 12:09:35 +02001029 self.errorToIgnore = tuple(errorToIgnore)
Kevin Rocard9050c812013-06-10 16:01:21 +02001030
Kevin Rocard326e39e2012-12-14 16:11:05 +01001031
1032
1033def main():
1034
Kevin Rocard804e0642013-06-25 12:09:35 +02001035 errorDuringLogParsing = -1
1036 errorDuringArgumentParsing = 1
Kevin Rocard53493b22013-02-04 15:16:10 +01001037
Kevin Rocard804e0642013-06-25 12:09:35 +02001038 try:
1039 commandLineArguments = ArgumentParser()
1040 except LookupError as ex:
1041 logger.error("Error during argument parsing")
1042 logger.debug(str(ex))
1043 sys.exit(errorDuringArgumentParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001044
Kevin Rocard804e0642013-06-25 12:09:35 +02001045 # Setting logger level
1046 logger.setLevel(commandLineArguments.debugLevel)
1047 logger.info("Log level set to: %s" %
1048 logging.getLevelName(commandLineArguments.debugLevel))
Kevin Rocard326e39e2012-12-14 16:11:05 +01001049
Kevin Rocard804e0642013-06-25 12:09:35 +02001050 # Create tree from XML
1051 dom = xml.dom.minidom.parse(commandLineArguments.domainsFile)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001052
Kevin Rocard804e0642013-06-25 12:09:35 +02001053 # Create element tree
1054 root = Root("DomainCoverage", dom)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001055
Kevin Rocard804e0642013-06-25 12:09:35 +02001056 # Parse PFW events
1057 parser = ParsePFWlog(root.domains, root.criteria, commandLineArguments.errorToIgnore)
Kevin Rocard53493b22013-02-04 15:16:10 +01001058
Kevin Rocard804e0642013-06-25 12:09:35 +02001059 try:
1060 parser.parsePFWlog(commandLineArguments.inputFile.readlines())
1061 except CustomError as ex:
1062 logger.fatal("Error during parsing log file %s: %s" %
1063 (commandLineArguments.inputFile, ex))
1064 sys.exit(errorDuringLogParsing)
Kevin Rocard326e39e2012-12-14 16:11:05 +01001065
Kevin Rocard804e0642013-06-25 12:09:35 +02001066 # Output report
1067 outputFile = commandLineArguments.outputFile
Kevin Rocard326e39e2012-12-14 16:11:05 +01001068
Kevin Rocard804e0642013-06-25 12:09:35 +02001069 if not commandLineArguments.XMLreport :
1070 outputFile.write("%s\n" % root.dump(withCoverage=True, withNbUse=True))
1071 else :
1072 outputFile.write(root.exportToXML().toprettyxml())
Kevin Rocard326e39e2012-12-14 16:11:05 +01001073
1074
Kevin Rocard326e39e2012-12-14 16:11:05 +01001075if __name__ == "__main__" :
Kevin Rocard804e0642013-06-25 12:09:35 +02001076 """ Execute main if the python interpreter is running this module as the main program """
1077 main()
Kevin Rocard326e39e2012-12-14 16:11:05 +01001078