blob: 65e75f5423e65892a09aaf8712e85f3d5f75e31b [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
Christophe Favergeon30c03792019-10-03 12:47:41 +010012import colorama
13from colorama import init,Fore, Back, Style
Christophe Favergeonf76a8032019-08-09 09:15:50 +010014
Christophe Favergeon30c03792019-10-03 12:47:41 +010015init()
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +020016
Christophe Favergeon6f8eee92019-10-09 12:21:27 +010017def errorStr(id):
18 if id == 1:
19 return("UNKNOWN_ERROR")
20 if id == 2:
21 return("Equality error")
22 if id == 3:
23 return("Absolute difference error")
24 if id == 4:
25 return("Relative difference error")
26 if id == 5:
27 return("SNR error")
28 if id == 6:
29 return("Different length error")
30 if id == 7:
31 return("Assertion error")
32 if id == 8:
33 return("Memory allocation error")
34 if id == 9:
35 return("Empty pattern error")
36 if id == 10:
37 return("Buffer tail corrupted")
Christophe Favergeonf055bd32019-10-15 12:30:30 +010038 if id == 11:
39 return("Close float error")
Christophe Favergeon6f8eee92019-10-09 12:21:27 +010040
41 return("Unknown error %d" % id)
42
43
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +020044def findItem(root,path):
45 """ Find a node in a tree
46
47 Args:
48 path (list) : A list of node ID
49 This list is describing a path in the tree.
50 By starting from the root and following this path,
51 we can find the node in the tree.
52 Raises:
53 Nothing
54 Returns:
55 TreeItem : A node
56 """
57 # The list is converted into a queue.
58 q = deque(path)
59 q.popleft()
60 c = root
61 while q:
62 n = q.popleft()
63 # We get the children based on its ID and continue
64 c = c[n-1]
65 return(c)
66
67def joinit(iterable, delimiter):
68 # Intersperse a delimiter between element of a list
69 it = iter(iterable)
70 yield next(it)
71 for x in it:
72 yield delimiter
73 yield x
74
75# Return test result as a text tree
76class TextFormatter:
77 def start(self):
78 None
79
80 def printGroup(self,elem,theId):
81 if elem is None:
82 elem = root
83 message=elem.data["message"]
84 if not elem.data["deprecated"]:
85 kind = "Suite"
86 ident = " " * elem.ident
87 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
88 kind = "Group"
89 #print(elem.path)
Christophe Favergeon30c03792019-10-03 12:47:41 +010090 print(Style.BRIGHT + ("%s%s : %s (%d)" % (ident,kind,message,theId)) + Style.RESET_ALL)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +020091
Christophe Favergeon4f462732019-11-13 14:11:14 +010092 def printTest(self,elem, theId, theError,errorDetail,theLine,passed,cycles,params):
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +020093 message=elem.data["message"]
94 if not elem.data["deprecated"]:
95 kind = "Test"
96 ident = " " * elem.ident
Christophe Favergeon30c03792019-10-03 12:47:41 +010097 p=Fore.RED + "FAILED" + Style.RESET_ALL
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +020098 if passed == 1:
Christophe Favergeon30c03792019-10-03 12:47:41 +010099 p= Fore.GREEN + "PASSED" + Style.RESET_ALL
100 print("%s%s %s(%d)%s : %s (cycles = %d)" % (ident,message,Style.BRIGHT,theId,Style.RESET_ALL,p,cycles))
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200101 if params:
102 print("%s %s" % (ident,params))
103 if passed != 1:
Christophe Favergeon6f8eee92019-10-09 12:21:27 +0100104 print(Fore.RED + ("%s %s at line %d" % (ident, errorStr(theError), theLine)) + Style.RESET_ALL)
Christophe Favergeon4f462732019-11-13 14:11:14 +0100105 if (len(errorDetail)>0):
106 print(Fore.RED + ident + " " + errorDetail + Style.RESET_ALL)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200107
108 def pop(self):
109 None
110
111 def end(self):
112 None
113
Christophe Favergeone972cbd2019-11-19 15:54:13 +0100114# Return test result as a text tree
115class HTMLFormatter:
116 def __init__(self):
117 self.nb=1
118 self.suite=False
119
120 def start(self):
121 print("<html><head><title>Test Results</title></head><body>")
122
123 def printGroup(self,elem,theId):
124 if elem is None:
125 elem = root
126 message=elem.data["message"]
127 if not elem.data["deprecated"]:
128 kind = "Suite"
129 ident = " " * elem.ident
130 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
131 kind = "Group"
132 if kind == "Group":
133 print("<h%d> %s (%d) </h%d>" % (self.nb,message,theId,self.nb))
134 else:
135 print("<h%d> %s (%d) </h%d>" % (self.nb,message,theId,self.nb))
136 self.suite=True
137 print("<table style=\"width:100%\">")
138 print("<tr>")
139 print("<td>Name</td>")
140 print("<td>ID</td>")
141 print("<td>Status</td>")
Christophe Favergeon59aeeea2019-11-20 13:39:05 +0100142 print("<td>Params</td>")
Christophe Favergeone972cbd2019-11-19 15:54:13 +0100143 print("<td>Cycles</td>")
144 print("</tr>")
145 self.nb = self.nb + 1
146
147 def printTest(self,elem, theId, theError,errorDetail,theLine,passed,cycles,params):
148 message=elem.data["message"]
149 if not elem.data["deprecated"]:
150 kind = "Test"
151 ident = " " * elem.ident
152 p="<font color=\"red\">FAILED</font>"
153 if passed == 1:
154 p= "<font color=\"green\">PASSED</font>"
155 print("<tr>")
156 print("<td><pre>%s</pre></td>" % message)
157 print("<td>%d</td>" % theId)
158 print("<td>%s</td>" % p)
Christophe Favergeon59aeeea2019-11-20 13:39:05 +0100159 if params:
160 print("<td>%s</td>\n" % (params))
161 else:
162 print("<td></td>\n")
Christophe Favergeone972cbd2019-11-19 15:54:13 +0100163 print("<td>%d</td>" % cycles)
164 print("</tr>")
Christophe Favergeon59aeeea2019-11-20 13:39:05 +0100165
Christophe Favergeone972cbd2019-11-19 15:54:13 +0100166 if passed != 1:
167
168 print("<tr><td colspan=4><font color=\"red\">%s at line %d</font></td></tr>" % (errorStr(theError), theLine))
169 if (len(errorDetail)>0):
170 print("<tr><td colspan=4><font color=\"red\">" + errorDetail + "</font></td></tr>")
171
172 def pop(self):
173 if self.suite:
174 print("</table>")
175 self.nb = self.nb - 1
176 self.suite=False
177
178 def end(self):
179 print("</body></html>")
180
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200181# Return test result as a CSV
182class CSVFormatter:
183
184 def __init__(self):
185 self.name=[]
186 self._start=True
187
188 def start(self):
189 print("CATEGORY,NAME,ID,STATUS,CYCLES,PARAMS")
190
191 def printGroup(self,elem,theId):
192 if elem is None:
193 elem = root
194 # Remove Root from category name in CSV file.
195 if not self._start:
196 self.name.append(elem.data["class"])
197 else:
198 self._start=False
199 message=elem.data["message"]
200 if not elem.data["deprecated"]:
201 kind = "Suite"
202 ident = " " * elem.ident
203 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
204 kind = "Group"
205
Christophe Favergeon4f462732019-11-13 14:11:14 +0100206 def printTest(self,elem, theId, theError, errorDetail,theLine,passed,cycles,params):
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200207 message=elem.data["message"]
208 if not elem.data["deprecated"]:
209 kind = "Test"
210 name=elem.data["class"]
211 category= "".join(list(joinit(self.name,":")))
212 print("%s,%s,%d,%d,%d,\"%s\"" % (category,name,theId,passed,cycles,params))
213
214 def pop(self):
215 if self.name:
216 self.name.pop()
217
218 def end(self):
219 None
220
221class MathematicaFormatter:
222
223 def __init__(self):
224 self._hasContent=[False]
225 self._toPop=[]
226
227 def start(self):
228 None
229
230 def printGroup(self,elem,theId):
231 if self._hasContent[len(self._hasContent)-1]:
232 print(",",end="")
233
234 print("<|")
235 self._hasContent[len(self._hasContent)-1] = True
236 self._hasContent.append(False)
237 if elem is None:
238 elem = root
239 message=elem.data["message"]
240 if not elem.data["deprecated"]:
241
242 kind = "Suite"
243 ident = " " * elem.ident
244 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
245 kind = "Group"
246 print("\"%s\" ->" % (message))
247 #if kind == "Suite":
248 print("{",end="")
249 self._toPop.append("}")
250 #else:
251 # self._toPop.append("")
252
Christophe Favergeon4f462732019-11-13 14:11:14 +0100253 def printTest(self,elem, theId, theError,errorDetail,theLine,passed,cycles,params):
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200254 message=elem.data["message"]
255 if not elem.data["deprecated"]:
256 kind = "Test"
257 ident = " " * elem.ident
258 p="FAILED"
259 if passed == 1:
260 p="PASSED"
261 parameters=""
262 if params:
263 parameters = "%s" % params
264 if self._hasContent[len(self._hasContent)-1]:
265 print(",",end="")
266 print("<|\"NAME\" -> \"%s\",\"ID\" -> %d,\"STATUS\" -> \"%s\",\"CYCLES\" -> %d,\"PARAMS\" -> \"%s\"|>" % (message,theId,p,cycles,parameters))
267 self._hasContent[len(self._hasContent)-1] = True
268 #if passed != 1:
269 # print("%s Error = %d at line %d" % (ident, theError, theLine))
270
271 def pop(self):
272 print(self._toPop.pop(),end="")
273 print("|>")
274 self._hasContent.pop()
275
276 def end(self):
277 None
278
279NORMAL = 1
280INTEST = 2
281TESTPARAM = 3
Christophe Favergeon4f462732019-11-13 14:11:14 +0100282ERRORDESC = 4
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200283
284def createMissingDir(destPath):
285 theDir=os.path.normpath(os.path.dirname(destPath))
286 if not os.path.exists(theDir):
287 os.makedirs(theDir)
288
289def correctPath(path):
290 while (path[0]=="/") or (path[0] == "\\"):
291 path = path[1:]
292 return(path)
293
294def extractDataFiles(results,outputDir):
295 infile = False
296 f = None
297 for l in results:
298 if re.match(r'^.*D:[ ].*$',l):
299 if infile:
300 if re.match(r'^.*D:[ ]END$',l):
301 infile = False
302 if f:
303 f.close()
304 else:
305 if f:
306 m = re.match(r'^.*D:[ ](.*)$',l)
307 data = m.group(1)
308 f.write(data)
309 f.write("\n")
310
311 else:
312 m = re.match(r'^.*D:[ ](.*)$',l)
313 path = str(m.group(1))
314 infile = True
315 destPath = os.path.join(outputDir,correctPath(path))
316 createMissingDir(destPath)
317 f = open(destPath,"w")
318
319
320
321def writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config):
322 if benchFile:
323 name=elem.data["class"]
324 category= elem.categoryDesc()
Christophe Favergeon37b86222019-07-17 11:49:00 +0200325 old=""
326 if "testData" in elem.data:
327 if "oldID" in elem.data["testData"]:
328 old=elem.data["testData"]["oldID"]
329 benchFile.write("\"%s\",\"%s\",%d,\"%s\",%s,%d,%s\n" % (category,name,theId,old,params,cycles,config))
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200330
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100331def getCyclesFromTrace(trace):
332 if not trace:
333 return(0)
334 else:
335 return(TestScripts.ParseTrace.getCycles(trace))
336
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200337def analyseResult(resultPath,root,results,embedded,benchmark,trace,formatter):
Christophe Favergeonbe7efb42019-08-09 10:17:03 +0100338 calibration = 0
339 if trace:
340 # First cycle in the trace is the calibration data
341 # The noramlisation factor must be coherent with the C code one.
342 calibration = int(getCyclesFromTrace(trace) / 20)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200343 formatter.start()
344 path = []
345 state = NORMAL
346 prefix=""
347 elem=None
348 theId=None
349 theError=None
Christophe Favergeon4f462732019-11-13 14:11:14 +0100350 errorDetail=""
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200351 theLine=None
352 passed=0
353 cycles=None
354 benchFile = None
355 config=""
356 if embedded:
357 prefix = ".*S:[ ]"
358
359 # Parse the result file.
360 # NORMAL mode is when we are parsing suite or group.
361 # Otherwise we are parsing a test and we need to analyse the
362 # test result.
363 # TESTPARAM is used to read parameters of the test.
364 # Format of output is:
365 #node ident : s id or g id or t or u
366 #test status : id error linenb status Y or N (Y when passing)
367 #param for this test b x,x,x,x or b alone if not param
368 #node end : p
369 # In FPGA mode:
370 #Prefix S:[ ] before driver dump
371 # D:[ ] before data dump (output patterns)
372
373 for l in results:
374 l = l.strip()
375 if not re.match(r'^.*D:[ ].*$',l):
376 if state == NORMAL:
377 if len(l) > 0:
378 # Line starting with g or s is a suite or group.
379 # In FPGA mode, those line are prefixed with 'S: '
380 # and data file with 'D: '
381 if re.match(r'^%s[gs][ ]+[0-9]+.*$' % prefix,l):
382 # Extract the test id
383 theId=re.sub(r'^%s[gs][ ]+([0-9]+).*$' % prefix,r'\1',l)
384 theId=int(theId)
385 path.append(theId)
386 # From a list of id, find the TreeElem in the Parsed tree
387 # to know what is the node.
388 elem = findItem(root,path)
389 # Display formatted output for this node
390 if elem.params:
391 #print(elem.params.full)
392 benchPath = os.path.join(benchmark,elem.fullPath(),"fullBenchmark.csv")
393 createMissingDir(benchPath)
394 if benchFile:
395 printf("ERROR BENCH FILE %s ALREADY OPEN" % benchPath)
396 benchFile.close()
397 benchFile=None
398 benchFile=open(benchPath,"w")
399 header = "".join(list(joinit(elem.params.full,",")))
400 # A test and a benchmark are different
401 # so we don't dump a status and error
402 # A status and error in a benchmark would
403 # impact the cycles since the test
404 # would be taken into account in the measurement
405 # So benchmark are always passing and contain no test
406 #benchFile.write("ID,%s,PASSED,ERROR,CYCLES\n" % header)
407 csvheaders = ""
408
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200409 with open(os.path.join(resultPath,'currentConfig.csv'), 'r') as f:
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200410 reader = csv.reader(f)
411 csvheaders = next(reader, None)
412 configList = list(reader)
413 #print(configList)
414 config = "".join(list(joinit(configList[0],",")))
415 configHeaders = "".join(list(joinit(csvheaders,",")))
Christophe Favergeon37b86222019-07-17 11:49:00 +0200416 benchFile.write("CATEGORY,NAME,ID,OLDID,%s,CYCLES,%s\n" % (header,configHeaders))
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200417
418 formatter.printGroup(elem,theId)
419
420 # If we have detected a test, we switch to test mode
421 if re.match(r'^%s[t][ ]*$' % prefix,l):
422 state = INTEST
423
424
425 # Pop
426 # End of suite or group
427 if re.match(r'^%sp.*$' % prefix,l):
428 if benchFile:
429 benchFile.close()
430 benchFile=None
431 path.pop()
432 formatter.pop()
433 elif state == INTEST:
434 if len(l) > 0:
435 # In test mode, we are looking for test status.
436 # A line starting with S
437 # (There may be empty lines or line for data files)
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100438 passRe = r'^%s([0-9]+)[ ]+([0-9]+)[ ]+([0-9]+)[ ]+([t0-9]+)[ ]+([YN]).*$' % prefix
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200439 if re.match(passRe,l):
440 # If we have found a test status then we will start again
441 # in normal mode after this.
442
443 m = re.match(passRe,l)
444
445 # Extract test ID, test error code, line number and status
446 theId=m.group(1)
447 theId=int(theId)
448
449 theError=m.group(2)
450 theError=int(theError)
451
452 theLine=m.group(3)
453 theLine=int(theLine)
454
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100455 maybeCycles = m.group(4)
456 if maybeCycles == "t":
Christophe Favergeonbe7efb42019-08-09 10:17:03 +0100457 cycles = getCyclesFromTrace(trace) - calibration
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100458 else:
459 cycles = int(maybeCycles)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200460
461 status=m.group(5)
462 passed=0
463
464 # Convert status to number as used by formatter.
465 if status=="Y":
466 passed = 1
467 if status=="N":
468 passed = 0
469 # Compute path to this node
470 newPath=path.copy()
471 newPath.append(theId)
472 # Find the node in the Tree
473 elem = findItem(root,newPath)
474
475
Christophe Favergeon4f462732019-11-13 14:11:14 +0100476 state = ERRORDESC
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200477 else:
478 if re.match(r'^%sp.*$' % prefix,l):
479 if benchFile:
480 benchFile.close()
481 benchFile=None
482 path.pop()
483 formatter.pop()
484 if re.match(r'^%s[t][ ]*$' % prefix,l):
485 state = INTEST
486 else:
487 state = NORMAL
Christophe Favergeon4f462732019-11-13 14:11:14 +0100488 elif state == ERRORDESC:
489 if len(l) > 0:
490 if re.match(r'^.*E:.*$',l):
491 if re.match(r'^.*E:[ ].*$',l):
492 m = re.match(r'^.*E:[ ](.*)$',l)
493 errorDetail = m.group(1)
494 else:
495 errorDetail = ""
496 state = TESTPARAM
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200497 else:
498 if len(l) > 0:
499 state = INTEST
500 params=""
501 if re.match(r'^.*b[ ]+([0-9,]+)$',l):
502 m=re.match(r'^.*b[ ]+([0-9,]+)$',l)
503 params=m.group(1).strip()
504 # Format the node
505 #print(elem.fullPath())
506 #createMissingDir(destPath)
507 writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config)
508 else:
509 params=""
510 writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config)
511 # Format the node
Christophe Favergeon4f462732019-11-13 14:11:14 +0100512 formatter.printTest(elem,theId,theError,errorDetail,theLine,passed,cycles,params)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200513
514
515 formatter.end()
516
517
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100518def analyze(root,results,args,trace):
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200519 # currentConfig.csv should be in the same place
520 resultPath=os.path.dirname(args.r)
521
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100522 if args.c:
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200523 analyseResult(resultPath,root,results,args.e,args.b,trace,CSVFormatter())
Christophe Favergeone972cbd2019-11-19 15:54:13 +0100524 elif args.html:
525 analyseResult(resultPath,root,results,args.e,args.b,trace,HTMLFormatter())
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100526 elif args.m:
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200527 analyseResult(resultPath,root,results,args.e,args.b,trace,MathematicaFormatter())
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100528 else:
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200529 analyseResult(resultPath,root,results,args.e,args.b,trace,TextFormatter())
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100530
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200531parser = argparse.ArgumentParser(description='Parse test description')
532
Christophe Favergeon6f8eee92019-10-09 12:21:27 +0100533parser.add_argument('-f', nargs='?',type = str, default="Output.pickle", help="Test description file path")
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200534# Where the result file can be found
535parser.add_argument('-r', nargs='?',type = str, default=None, help="Result file path")
536parser.add_argument('-c', action='store_true', help="CSV output")
Christophe Favergeone972cbd2019-11-19 15:54:13 +0100537parser.add_argument('-html', action='store_true', help="HTML output")
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200538parser.add_argument('-e', action='store_true', help="Embedded test")
539# -o needed when -e is true to know where to extract the output files
540parser.add_argument('-o', nargs='?',type = str, default="Output", help="Output dir path")
541
542parser.add_argument('-b', nargs='?',type = str, default="FullBenchmark", help="Full Benchmark dir path")
543parser.add_argument('-m', action='store_true', help="Mathematica output")
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100544parser.add_argument('-t', nargs='?',type = str, default=None, help="External trace file")
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200545
546args = parser.parse_args()
547
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100548
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200549if args.f is not None:
Christophe Favergeon6f8eee92019-10-09 12:21:27 +0100550 #p = parse.Parser()
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200551 # Parse the test description file
Christophe Favergeon6f8eee92019-10-09 12:21:27 +0100552 #root = p.parse(args.f)
553 root=parse.loadRoot(args.f)
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100554 if args.t:
555 with open(args.t,"r") as trace:
556 with open(args.r,"r") as results:
557 analyze(root,results,args,iter(trace))
558 else:
559 with open(args.r,"r") as results:
560 analyze(root,results,args,None)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200561 if args.e:
562 # In FPGA mode, extract output files from stdout (result file)
563 with open(args.r,"r") as results:
564 extractDataFiles(results,args.o)
565
566else:
567 parser.print_help()