blob: 43b85f4cc83e7724b62cf5f37f83a19950a472eb [file] [log] [blame]
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +02001# Process the test results
2# Test status (like passed, or failed with error code)
3
4import argparse
5import re
6import TestScripts.NewParser as parse
7import TestScripts.CodeGen
8from collections import deque
9import os.path
10import csv
Christophe Favergeonf76a8032019-08-09 09:15:50 +010011import TestScripts.ParseTrace
12
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +020013
14def findItem(root,path):
15 """ Find a node in a tree
16
17 Args:
18 path (list) : A list of node ID
19 This list is describing a path in the tree.
20 By starting from the root and following this path,
21 we can find the node in the tree.
22 Raises:
23 Nothing
24 Returns:
25 TreeItem : A node
26 """
27 # The list is converted into a queue.
28 q = deque(path)
29 q.popleft()
30 c = root
31 while q:
32 n = q.popleft()
33 # We get the children based on its ID and continue
34 c = c[n-1]
35 return(c)
36
37def joinit(iterable, delimiter):
38 # Intersperse a delimiter between element of a list
39 it = iter(iterable)
40 yield next(it)
41 for x in it:
42 yield delimiter
43 yield x
44
45# Return test result as a text tree
46class TextFormatter:
47 def start(self):
48 None
49
50 def printGroup(self,elem,theId):
51 if elem is None:
52 elem = root
53 message=elem.data["message"]
54 if not elem.data["deprecated"]:
55 kind = "Suite"
56 ident = " " * elem.ident
57 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
58 kind = "Group"
59 #print(elem.path)
60 print("%s%s : %s (%d)" % (ident,kind,message,theId))
61
62 def printTest(self,elem, theId, theError,theLine,passed,cycles,params):
63 message=elem.data["message"]
64 if not elem.data["deprecated"]:
65 kind = "Test"
66 ident = " " * elem.ident
67 p="FAILED"
68 if passed == 1:
69 p="PASSED"
70 print("%s%s (%d) : %s (cycles = %d)" % (ident,message,theId,p,cycles))
71 if params:
72 print("%s %s" % (ident,params))
73 if passed != 1:
74 print("%s Error = %d at line %d" % (ident, theError, theLine))
75
76 def pop(self):
77 None
78
79 def end(self):
80 None
81
82# Return test result as a CSV
83class CSVFormatter:
84
85 def __init__(self):
86 self.name=[]
87 self._start=True
88
89 def start(self):
90 print("CATEGORY,NAME,ID,STATUS,CYCLES,PARAMS")
91
92 def printGroup(self,elem,theId):
93 if elem is None:
94 elem = root
95 # Remove Root from category name in CSV file.
96 if not self._start:
97 self.name.append(elem.data["class"])
98 else:
99 self._start=False
100 message=elem.data["message"]
101 if not elem.data["deprecated"]:
102 kind = "Suite"
103 ident = " " * elem.ident
104 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
105 kind = "Group"
106
107 def printTest(self,elem, theId, theError, theLine,passed,cycles,params):
108 message=elem.data["message"]
109 if not elem.data["deprecated"]:
110 kind = "Test"
111 name=elem.data["class"]
112 category= "".join(list(joinit(self.name,":")))
113 print("%s,%s,%d,%d,%d,\"%s\"" % (category,name,theId,passed,cycles,params))
114
115 def pop(self):
116 if self.name:
117 self.name.pop()
118
119 def end(self):
120 None
121
122class MathematicaFormatter:
123
124 def __init__(self):
125 self._hasContent=[False]
126 self._toPop=[]
127
128 def start(self):
129 None
130
131 def printGroup(self,elem,theId):
132 if self._hasContent[len(self._hasContent)-1]:
133 print(",",end="")
134
135 print("<|")
136 self._hasContent[len(self._hasContent)-1] = True
137 self._hasContent.append(False)
138 if elem is None:
139 elem = root
140 message=elem.data["message"]
141 if not elem.data["deprecated"]:
142
143 kind = "Suite"
144 ident = " " * elem.ident
145 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
146 kind = "Group"
147 print("\"%s\" ->" % (message))
148 #if kind == "Suite":
149 print("{",end="")
150 self._toPop.append("}")
151 #else:
152 # self._toPop.append("")
153
154 def printTest(self,elem, theId, theError,theLine,passed,cycles,params):
155 message=elem.data["message"]
156 if not elem.data["deprecated"]:
157 kind = "Test"
158 ident = " " * elem.ident
159 p="FAILED"
160 if passed == 1:
161 p="PASSED"
162 parameters=""
163 if params:
164 parameters = "%s" % params
165 if self._hasContent[len(self._hasContent)-1]:
166 print(",",end="")
167 print("<|\"NAME\" -> \"%s\",\"ID\" -> %d,\"STATUS\" -> \"%s\",\"CYCLES\" -> %d,\"PARAMS\" -> \"%s\"|>" % (message,theId,p,cycles,parameters))
168 self._hasContent[len(self._hasContent)-1] = True
169 #if passed != 1:
170 # print("%s Error = %d at line %d" % (ident, theError, theLine))
171
172 def pop(self):
173 print(self._toPop.pop(),end="")
174 print("|>")
175 self._hasContent.pop()
176
177 def end(self):
178 None
179
180NORMAL = 1
181INTEST = 2
182TESTPARAM = 3
183
184def createMissingDir(destPath):
185 theDir=os.path.normpath(os.path.dirname(destPath))
186 if not os.path.exists(theDir):
187 os.makedirs(theDir)
188
189def correctPath(path):
190 while (path[0]=="/") or (path[0] == "\\"):
191 path = path[1:]
192 return(path)
193
194def extractDataFiles(results,outputDir):
195 infile = False
196 f = None
197 for l in results:
198 if re.match(r'^.*D:[ ].*$',l):
199 if infile:
200 if re.match(r'^.*D:[ ]END$',l):
201 infile = False
202 if f:
203 f.close()
204 else:
205 if f:
206 m = re.match(r'^.*D:[ ](.*)$',l)
207 data = m.group(1)
208 f.write(data)
209 f.write("\n")
210
211 else:
212 m = re.match(r'^.*D:[ ](.*)$',l)
213 path = str(m.group(1))
214 infile = True
215 destPath = os.path.join(outputDir,correctPath(path))
216 createMissingDir(destPath)
217 f = open(destPath,"w")
218
219
220
221def writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config):
222 if benchFile:
223 name=elem.data["class"]
224 category= elem.categoryDesc()
Christophe Favergeon37b86222019-07-17 11:49:00 +0200225 old=""
226 if "testData" in elem.data:
227 if "oldID" in elem.data["testData"]:
228 old=elem.data["testData"]["oldID"]
229 benchFile.write("\"%s\",\"%s\",%d,\"%s\",%s,%d,%s\n" % (category,name,theId,old,params,cycles,config))
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200230
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100231def getCyclesFromTrace(trace):
232 if not trace:
233 return(0)
234 else:
235 return(TestScripts.ParseTrace.getCycles(trace))
236
237def analyseResult(root,results,embedded,benchmark,trace,formatter):
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200238 formatter.start()
239 path = []
240 state = NORMAL
241 prefix=""
242 elem=None
243 theId=None
244 theError=None
245 theLine=None
246 passed=0
247 cycles=None
248 benchFile = None
249 config=""
250 if embedded:
251 prefix = ".*S:[ ]"
252
253 # Parse the result file.
254 # NORMAL mode is when we are parsing suite or group.
255 # Otherwise we are parsing a test and we need to analyse the
256 # test result.
257 # TESTPARAM is used to read parameters of the test.
258 # Format of output is:
259 #node ident : s id or g id or t or u
260 #test status : id error linenb status Y or N (Y when passing)
261 #param for this test b x,x,x,x or b alone if not param
262 #node end : p
263 # In FPGA mode:
264 #Prefix S:[ ] before driver dump
265 # D:[ ] before data dump (output patterns)
266
267 for l in results:
268 l = l.strip()
269 if not re.match(r'^.*D:[ ].*$',l):
270 if state == NORMAL:
271 if len(l) > 0:
272 # Line starting with g or s is a suite or group.
273 # In FPGA mode, those line are prefixed with 'S: '
274 # and data file with 'D: '
275 if re.match(r'^%s[gs][ ]+[0-9]+.*$' % prefix,l):
276 # Extract the test id
277 theId=re.sub(r'^%s[gs][ ]+([0-9]+).*$' % prefix,r'\1',l)
278 theId=int(theId)
279 path.append(theId)
280 # From a list of id, find the TreeElem in the Parsed tree
281 # to know what is the node.
282 elem = findItem(root,path)
283 # Display formatted output for this node
284 if elem.params:
285 #print(elem.params.full)
286 benchPath = os.path.join(benchmark,elem.fullPath(),"fullBenchmark.csv")
287 createMissingDir(benchPath)
288 if benchFile:
289 printf("ERROR BENCH FILE %s ALREADY OPEN" % benchPath)
290 benchFile.close()
291 benchFile=None
292 benchFile=open(benchPath,"w")
293 header = "".join(list(joinit(elem.params.full,",")))
294 # A test and a benchmark are different
295 # so we don't dump a status and error
296 # A status and error in a benchmark would
297 # impact the cycles since the test
298 # would be taken into account in the measurement
299 # So benchmark are always passing and contain no test
300 #benchFile.write("ID,%s,PASSED,ERROR,CYCLES\n" % header)
301 csvheaders = ""
302
303 with open('currentConfig.csv', 'r') as f:
304 reader = csv.reader(f)
305 csvheaders = next(reader, None)
306 configList = list(reader)
307 #print(configList)
308 config = "".join(list(joinit(configList[0],",")))
309 configHeaders = "".join(list(joinit(csvheaders,",")))
Christophe Favergeon37b86222019-07-17 11:49:00 +0200310 benchFile.write("CATEGORY,NAME,ID,OLDID,%s,CYCLES,%s\n" % (header,configHeaders))
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200311
312 formatter.printGroup(elem,theId)
313
314 # If we have detected a test, we switch to test mode
315 if re.match(r'^%s[t][ ]*$' % prefix,l):
316 state = INTEST
317
318
319 # Pop
320 # End of suite or group
321 if re.match(r'^%sp.*$' % prefix,l):
322 if benchFile:
323 benchFile.close()
324 benchFile=None
325 path.pop()
326 formatter.pop()
327 elif state == INTEST:
328 if len(l) > 0:
329 # In test mode, we are looking for test status.
330 # A line starting with S
331 # (There may be empty lines or line for data files)
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100332 passRe = r'^%s([0-9]+)[ ]+([0-9]+)[ ]+([0-9]+)[ ]+([t0-9]+)[ ]+([YN]).*$' % prefix
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200333 if re.match(passRe,l):
334 # If we have found a test status then we will start again
335 # in normal mode after this.
336
337 m = re.match(passRe,l)
338
339 # Extract test ID, test error code, line number and status
340 theId=m.group(1)
341 theId=int(theId)
342
343 theError=m.group(2)
344 theError=int(theError)
345
346 theLine=m.group(3)
347 theLine=int(theLine)
348
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100349 maybeCycles = m.group(4)
350 if maybeCycles == "t":
351 cycles = getCyclesFromTrace(trace)
352 else:
353 cycles = int(maybeCycles)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200354
355 status=m.group(5)
356 passed=0
357
358 # Convert status to number as used by formatter.
359 if status=="Y":
360 passed = 1
361 if status=="N":
362 passed = 0
363 # Compute path to this node
364 newPath=path.copy()
365 newPath.append(theId)
366 # Find the node in the Tree
367 elem = findItem(root,newPath)
368
369
370 state = TESTPARAM
371 else:
372 if re.match(r'^%sp.*$' % prefix,l):
373 if benchFile:
374 benchFile.close()
375 benchFile=None
376 path.pop()
377 formatter.pop()
378 if re.match(r'^%s[t][ ]*$' % prefix,l):
379 state = INTEST
380 else:
381 state = NORMAL
382 else:
383 if len(l) > 0:
384 state = INTEST
385 params=""
386 if re.match(r'^.*b[ ]+([0-9,]+)$',l):
387 m=re.match(r'^.*b[ ]+([0-9,]+)$',l)
388 params=m.group(1).strip()
389 # Format the node
390 #print(elem.fullPath())
391 #createMissingDir(destPath)
392 writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config)
393 else:
394 params=""
395 writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config)
396 # Format the node
397 formatter.printTest(elem,theId,theError,theLine,passed,cycles,params)
398
399
400 formatter.end()
401
402
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100403def analyze(root,results,args,trace):
404 if args.c:
405 analyseResult(root,results,args.e,args.b,trace,CSVFormatter())
406 elif args.m:
407 analyseResult(root,results,args.e,args.b,trace,MathematicaFormatter())
408 else:
409 analyseResult(root,results,args.e,args.b,trace,TextFormatter())
410
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200411parser = argparse.ArgumentParser(description='Parse test description')
412
413parser.add_argument('-f', nargs='?',type = str, default=None, help="Test description file path")
414# Where the result file can be found
415parser.add_argument('-r', nargs='?',type = str, default=None, help="Result file path")
416parser.add_argument('-c', action='store_true', help="CSV output")
417parser.add_argument('-e', action='store_true', help="Embedded test")
418# -o needed when -e is true to know where to extract the output files
419parser.add_argument('-o', nargs='?',type = str, default="Output", help="Output dir path")
420
421parser.add_argument('-b', nargs='?',type = str, default="FullBenchmark", help="Full Benchmark dir path")
422parser.add_argument('-m', action='store_true', help="Mathematica output")
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100423parser.add_argument('-t', nargs='?',type = str, default=None, help="External trace file")
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200424
425args = parser.parse_args()
426
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100427
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200428if args.f is not None:
429 p = parse.Parser()
430 # Parse the test description file
431 root = p.parse(args.f)
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100432 if args.t:
433 with open(args.t,"r") as trace:
434 with open(args.r,"r") as results:
435 analyze(root,results,args,iter(trace))
436 else:
437 with open(args.r,"r") as results:
438 analyze(root,results,args,None)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200439 if args.e:
440 # In FPGA mode, extract output files from stdout (result file)
441 with open(args.r,"r") as results:
442 extractDataFiles(results,args.o)
443
444else:
445 parser.print_help()