1import re
2import os
3
4class TreeElem:
5    """ Result of the parsing of the test description.
6
7    It is a tree of objects describing the groups, suites and tests
8
9    Attributes:
10        kind (int) : Node kind
11        ident (int) : Indentation level in the test description.
12          It is used to format output of test results
13        parent (TreeElem) : parent of this node
14        id (int) : Node id
15        patterns (list) : List of pairs
16         Each pair is a pattern ID and pattern path
17        outputs (list) : List of pairs
18         Each pair is an output ID and an output path
19
20    """
21
22    TEST = 1
23    SUITE = 2
24    GROUP = 3
25
26    PARAMFILE = 1
27    PARAMGEN = 2
28
29    def __init__(self,ident):
30        self.kind=TreeElem.TEST
31        self.ident = ident
32        self._children = []
33        self.parent = None
34        self._data = None
35        self.id = 1
36        self._path=""
37        self.patterns=[]
38        self.outputs=[]
39        # List of parameters files
40        self.parameters=[]
41        # List of arguments
42        self.params = None
43
44    def __str__(self):
45        """ Convert the TreeElem into a string for debug purpose
46        """
47        if self.kind == TreeElem.TEST:
48            g="Test"
49        if self.kind == TreeElem.SUITE:
50            g="Suite"
51        if self.kind == TreeElem.GROUP:
52            g="Group"
53        a = str("%s -> %s%s(%d)\n" % (g,' ' * self.ident, str(self.data),self.id))
54        if self.params:
55          a = a + str(self.params.full) + "\n" + str(self.params.summary) + "\n" + str(self.params.paramNames) + "\n"
56        for i in self._children:
57            a = a + str(i)
58        return(a)
59
60    def setData(self,data):
61        """ Set the data property of this node
62
63        Args:
64          data (list) : A list of fields for this node
65              The fields are parsed and a data dictionary is created
66          fpga (bool) : false in semihosting mode
67        Raises:
68          Nothing
69        Returns:
70          Nothing
71        """
72        d = {}
73
74        # A node OFF in the list is deprecated. It won't be included
75        # or executed in the final tests
76        # but it will be taken into account for ID generation
77        d["deprecated"] = False
78        # Text message to display to the user zhen displaying test result
79        # This text message is never used in any .txt,.cpp or .h
80        # generated. It is only for better display of the test
81        # results
82        d["message"] = data[0].strip()
83        # CPP class or function name to use
84        if len(data) > 1:
85            d["class"] = data[1].strip()
86        if len(data) == 3:
87            if data[2].strip() == "OFF":
88               d["deprecated"] = True
89            else:
90                self._path = data[2].strip()
91        # New path for this node (when we want a new subfolder
92        # for the patterns or output of a group of suite)
93        if len(data) == 4:
94            self._path = data[3].strip()
95
96        self._data = d
97
98    @property
99    def data(self):
100        return(self._data)
101
102    def writeData(self,d):
103        self._data=d
104
105    def setPath(self,p):
106        self._path=p
107
108    @property
109    def path(self):
110        return(self._path)
111
112    @property
113    def children(self):
114        return(self._children)
115
116    def _fullPath(self):
117      if self.parent:
118         return(os.path.join(self.parent._fullPath() , self.path))
119      else:
120         return("")
121
122    def fullPath(self):
123      return(os.path.normpath(self._fullPath()))
124
125    def categoryDesc(self):
126      if self.parent:
127         p = self.parent.categoryDesc()
128         if p and self.path:
129            return(p + ":" + self.path)
130         if p:
131            return(p)
132         if self.path:
133            return(self.path)
134      else:
135         return("")
136
137    def getSuiteMessage(self):
138      suite = self.parent
139      group = suite.parent
140      p = group.data["message"]
141      return(p)
142
143    def addGroup(self,g):
144        """ Add a group to this node
145
146        Args:
147          g (TreeElem) : group to add
148        Raises:
149          Nothing
150        Returns:
151          Nothing
152        """
153        g.parent = self
154        self._children.append(g)
155
156    def classify(self):
157        """ Compute the node kind recursively
158
159        Node kind is infered from the tree structure and not present
160        in the test description.
161        A suite is basically a leaf of the tree and only contain tests.
162        A group is containing a suite or another group.
163
164        """
165        r = TreeElem.TEST
166        for c in self._children:
167            c.classify()
168
169
170        for c in self._children:
171            if c.kind == TreeElem.TEST and r != TreeElem.GROUP:
172                r = TreeElem.SUITE
173            if c.kind == TreeElem.SUITE:
174                r = TreeElem.GROUP
175            if c.kind == TreeElem.GROUP:
176                r = TreeElem.GROUP
177        self.kind = r
178
179    def computeId(self):
180        """ Compute the node ID and the node param ID
181        """
182        i = 1
183        for c in self._children:
184            c.id = i
185            if not "PARAMID" in c.data and "PARAMID" in self.data:
186              c.data["PARAMID"] = self.data["PARAMID"]
187            c.computeId()
188            i = i + 1
189
190        self.parameterToID={}
191        # PARAM ID is starting at 0
192        paramId=0
193        if self.parameters:
194           for (paramKind,pID,pPath) in self.parameters:
195              self.parameterToID[pID]=paramId
196              paramId = paramId + 1
197
198    def reident(self,current,d=2):
199        """ Recompute identation lebel
200        """
201        self.ident=current
202        for c in self._children:
203            c.reident(current+d)
204
205    def findIdentParent(self,newIdent):
206        """ Find parent of this node based on the new identation level
207
208        Find the node which is the parent of this node with indentation level
209        newIdent.
210
211        Args:
212          newIdent (int) : identation of a new node read in the descriptino file
213
214        """
215        if self.ident < newIdent:
216            return(self)
217        else:
218            return(self.parent.findIdentParent(newIdent))
219
220
221    def __getitem__(self, i):
222        return(self._children[i])
223
224    def __iter__(self):
225      self._currentPos = 0
226      return(self)
227
228    def __next__(self):
229      oldPos = self._currentPos
230      self._currentPos = self._currentPos + 1
231      if (oldPos >= len(self._children)):
232        raise StopIteration
233      return(self._children[oldPos])
234
235    def addPattern(self,theId,thePath):
236        """ Add a new pattern
237
238        Args:
239          theId (int) : pattern ID
240          thePath (str) : pattern path
241
242        """
243        self.patterns.append((theId,thePath))
244        #print(thePath)
245        #print(self.patterns)
246
247    def addParam(self,paramKind,theId,theData):
248        """ Add a new parameter file
249
250        Args:
251          paramKind (int) : parameter kind (path or generator)
252          theId (int) : parameter ID
253          thePath (str or list) : parameter path or generator data
254
255        """
256        self.parameters.append((paramKind,theId,theData))
257        #print(thePath)
258        #print(self.patterns)
259
260    def addOutput(self,theId,thePath):
261        """ Add a new output
262
263        Args:
264          theId (int) : output ID
265          thePath (str) : output path
266
267        """
268        self.outputs.append((theId,thePath))
269
270    def parse(self,filePath):
271       """ Parser the test description file
272
273        Args:
274          filePath (str) : Path to the description file
275       """
276       root = None
277       current = None
278       with open(filePath,"r") as ins:
279          for line in ins:
280              # Compute identation level
281              identLevel = 0
282              if re.match(r'^([ \t]+)[^ \t].*$',line):
283                 leftSpaces=re.sub(r'^([ \t]+)[^ \t].*$',r'\1',line.rstrip())
284                 #print("-%s-" % leftSpaces)
285                 identLevel = len(leftSpaces)
286              # Remove comments
287              line = re.sub(r'^(.*)//.*$',r'\1',line).rstrip()
288              # If line is not just a comment
289              if line:
290                 regPat = r'^[ \t]+Pattern[ \t]+([a-zA-Z0-9_]+)[ \t]*:[ \t]*(.+)$'
291                 regOutput = r'^[ \t]+Output[ \t]+([a-zA-Z0-9_]+)[ \t]*:[ \t]*(.+)$'
292                 # If a pattern line is detected, we record it
293                 if re.match(regPat,line):
294                    m = re.match(regPat,line)
295                    patternID = m.group(1).strip()
296                    patternPath = m.group(2).strip()
297                    #print(patternID)
298                    #print(patternPath)
299                    if identLevel > current.ident:
300                        current.addPattern(patternID,patternPath)
301                 # If an output line is detected, we record it
302                 elif re.match(regOutput,line):
303                    m = re.match(regOutput,line)
304                    outputID = m.group(1).strip()
305                    outputPath = m.group(2).strip()
306                    #print(patternID)
307                    #print(patternPath)
308                    if identLevel > current.ident:
309                        current.addOutput(outputID,outputPath)
310                 else:
311                    #if current is None:
312                    #   print("  -> %d" % (identLevel))
313                    #else:
314                    #   print("%d -> %d" % (current.ident,identLevel))
315                    # Separate line into components
316                    data = line.split(':')
317                    # Remove empty strings
318                    data = [item for item in data if item]
319                    # If it is the first node we detect, it is the root node
320                    if root is None:
321                       root = TreeElem(identLevel)
322                       root.setData(data)
323                       current = root
324                    else:
325                        # We analyse and set the data
326                        newItem = TreeElem(identLevel)
327                        newItem.setData(data)
328                        # New identation then it is a group (or suite)
329                        if identLevel > current.ident:
330                           #print( ">")
331                           current.addGroup(newItem)
332                           current = newItem
333                        # Same identation, we add to parent
334                        elif identLevel == current.ident:
335                           #print( "==")
336                           current.parent.addGroup(newItem)
337                        else:
338                           #print("<")
339                           #print("--")
340                           #print(identLevel)
341                           # Smaller identation we need to find the parent where to
342                           # attach this node.
343                           current = current.findIdentParent(identLevel)
344                           current.addGroup(newItem)
345                           current = newItem
346
347                    #print(identLevel)
348                    #print(data)
349
350       # Identify suites, groups and tests
351       # Above we are just adding TreeElement but we don't yet know their
352       # kind. So we classify them to now if we have group, suite or test
353       root.classify()
354       # We compute ID of all nodes.
355       root.computeId()
356       return(root)
357
358