blob: c74dfe8c8905e44c75375ba190f411ef3027a635 [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
114# Return test result as a CSV
115class CSVFormatter:
116
117 def __init__(self):
118 self.name=[]
119 self._start=True
120
121 def start(self):
122 print("CATEGORY,NAME,ID,STATUS,CYCLES,PARAMS")
123
124 def printGroup(self,elem,theId):
125 if elem is None:
126 elem = root
127 # Remove Root from category name in CSV file.
128 if not self._start:
129 self.name.append(elem.data["class"])
130 else:
131 self._start=False
132 message=elem.data["message"]
133 if not elem.data["deprecated"]:
134 kind = "Suite"
135 ident = " " * elem.ident
136 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
137 kind = "Group"
138
Christophe Favergeon4f462732019-11-13 14:11:14 +0100139 def printTest(self,elem, theId, theError, errorDetail,theLine,passed,cycles,params):
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200140 message=elem.data["message"]
141 if not elem.data["deprecated"]:
142 kind = "Test"
143 name=elem.data["class"]
144 category= "".join(list(joinit(self.name,":")))
145 print("%s,%s,%d,%d,%d,\"%s\"" % (category,name,theId,passed,cycles,params))
146
147 def pop(self):
148 if self.name:
149 self.name.pop()
150
151 def end(self):
152 None
153
154class MathematicaFormatter:
155
156 def __init__(self):
157 self._hasContent=[False]
158 self._toPop=[]
159
160 def start(self):
161 None
162
163 def printGroup(self,elem,theId):
164 if self._hasContent[len(self._hasContent)-1]:
165 print(",",end="")
166
167 print("<|")
168 self._hasContent[len(self._hasContent)-1] = True
169 self._hasContent.append(False)
170 if elem is None:
171 elem = root
172 message=elem.data["message"]
173 if not elem.data["deprecated"]:
174
175 kind = "Suite"
176 ident = " " * elem.ident
177 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
178 kind = "Group"
179 print("\"%s\" ->" % (message))
180 #if kind == "Suite":
181 print("{",end="")
182 self._toPop.append("}")
183 #else:
184 # self._toPop.append("")
185
Christophe Favergeon4f462732019-11-13 14:11:14 +0100186 def printTest(self,elem, theId, theError,errorDetail,theLine,passed,cycles,params):
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200187 message=elem.data["message"]
188 if not elem.data["deprecated"]:
189 kind = "Test"
190 ident = " " * elem.ident
191 p="FAILED"
192 if passed == 1:
193 p="PASSED"
194 parameters=""
195 if params:
196 parameters = "%s" % params
197 if self._hasContent[len(self._hasContent)-1]:
198 print(",",end="")
199 print("<|\"NAME\" -> \"%s\",\"ID\" -> %d,\"STATUS\" -> \"%s\",\"CYCLES\" -> %d,\"PARAMS\" -> \"%s\"|>" % (message,theId,p,cycles,parameters))
200 self._hasContent[len(self._hasContent)-1] = True
201 #if passed != 1:
202 # print("%s Error = %d at line %d" % (ident, theError, theLine))
203
204 def pop(self):
205 print(self._toPop.pop(),end="")
206 print("|>")
207 self._hasContent.pop()
208
209 def end(self):
210 None
211
212NORMAL = 1
213INTEST = 2
214TESTPARAM = 3
Christophe Favergeon4f462732019-11-13 14:11:14 +0100215ERRORDESC = 4
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200216
217def createMissingDir(destPath):
218 theDir=os.path.normpath(os.path.dirname(destPath))
219 if not os.path.exists(theDir):
220 os.makedirs(theDir)
221
222def correctPath(path):
223 while (path[0]=="/") or (path[0] == "\\"):
224 path = path[1:]
225 return(path)
226
227def extractDataFiles(results,outputDir):
228 infile = False
229 f = None
230 for l in results:
231 if re.match(r'^.*D:[ ].*$',l):
232 if infile:
233 if re.match(r'^.*D:[ ]END$',l):
234 infile = False
235 if f:
236 f.close()
237 else:
238 if f:
239 m = re.match(r'^.*D:[ ](.*)$',l)
240 data = m.group(1)
241 f.write(data)
242 f.write("\n")
243
244 else:
245 m = re.match(r'^.*D:[ ](.*)$',l)
246 path = str(m.group(1))
247 infile = True
248 destPath = os.path.join(outputDir,correctPath(path))
249 createMissingDir(destPath)
250 f = open(destPath,"w")
251
252
253
254def writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config):
255 if benchFile:
256 name=elem.data["class"]
257 category= elem.categoryDesc()
Christophe Favergeon37b86222019-07-17 11:49:00 +0200258 old=""
259 if "testData" in elem.data:
260 if "oldID" in elem.data["testData"]:
261 old=elem.data["testData"]["oldID"]
262 benchFile.write("\"%s\",\"%s\",%d,\"%s\",%s,%d,%s\n" % (category,name,theId,old,params,cycles,config))
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200263
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100264def getCyclesFromTrace(trace):
265 if not trace:
266 return(0)
267 else:
268 return(TestScripts.ParseTrace.getCycles(trace))
269
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200270def analyseResult(resultPath,root,results,embedded,benchmark,trace,formatter):
Christophe Favergeonbe7efb42019-08-09 10:17:03 +0100271 calibration = 0
272 if trace:
273 # First cycle in the trace is the calibration data
274 # The noramlisation factor must be coherent with the C code one.
275 calibration = int(getCyclesFromTrace(trace) / 20)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200276 formatter.start()
277 path = []
278 state = NORMAL
279 prefix=""
280 elem=None
281 theId=None
282 theError=None
Christophe Favergeon4f462732019-11-13 14:11:14 +0100283 errorDetail=""
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200284 theLine=None
285 passed=0
286 cycles=None
287 benchFile = None
288 config=""
289 if embedded:
290 prefix = ".*S:[ ]"
291
292 # Parse the result file.
293 # NORMAL mode is when we are parsing suite or group.
294 # Otherwise we are parsing a test and we need to analyse the
295 # test result.
296 # TESTPARAM is used to read parameters of the test.
297 # Format of output is:
298 #node ident : s id or g id or t or u
299 #test status : id error linenb status Y or N (Y when passing)
300 #param for this test b x,x,x,x or b alone if not param
301 #node end : p
302 # In FPGA mode:
303 #Prefix S:[ ] before driver dump
304 # D:[ ] before data dump (output patterns)
305
306 for l in results:
307 l = l.strip()
308 if not re.match(r'^.*D:[ ].*$',l):
309 if state == NORMAL:
310 if len(l) > 0:
311 # Line starting with g or s is a suite or group.
312 # In FPGA mode, those line are prefixed with 'S: '
313 # and data file with 'D: '
314 if re.match(r'^%s[gs][ ]+[0-9]+.*$' % prefix,l):
315 # Extract the test id
316 theId=re.sub(r'^%s[gs][ ]+([0-9]+).*$' % prefix,r'\1',l)
317 theId=int(theId)
318 path.append(theId)
319 # From a list of id, find the TreeElem in the Parsed tree
320 # to know what is the node.
321 elem = findItem(root,path)
322 # Display formatted output for this node
323 if elem.params:
324 #print(elem.params.full)
325 benchPath = os.path.join(benchmark,elem.fullPath(),"fullBenchmark.csv")
326 createMissingDir(benchPath)
327 if benchFile:
328 printf("ERROR BENCH FILE %s ALREADY OPEN" % benchPath)
329 benchFile.close()
330 benchFile=None
331 benchFile=open(benchPath,"w")
332 header = "".join(list(joinit(elem.params.full,",")))
333 # A test and a benchmark are different
334 # so we don't dump a status and error
335 # A status and error in a benchmark would
336 # impact the cycles since the test
337 # would be taken into account in the measurement
338 # So benchmark are always passing and contain no test
339 #benchFile.write("ID,%s,PASSED,ERROR,CYCLES\n" % header)
340 csvheaders = ""
341
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200342 with open(os.path.join(resultPath,'currentConfig.csv'), 'r') as f:
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200343 reader = csv.reader(f)
344 csvheaders = next(reader, None)
345 configList = list(reader)
346 #print(configList)
347 config = "".join(list(joinit(configList[0],",")))
348 configHeaders = "".join(list(joinit(csvheaders,",")))
Christophe Favergeon37b86222019-07-17 11:49:00 +0200349 benchFile.write("CATEGORY,NAME,ID,OLDID,%s,CYCLES,%s\n" % (header,configHeaders))
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200350
351 formatter.printGroup(elem,theId)
352
353 # If we have detected a test, we switch to test mode
354 if re.match(r'^%s[t][ ]*$' % prefix,l):
355 state = INTEST
356
357
358 # Pop
359 # End of suite or group
360 if re.match(r'^%sp.*$' % prefix,l):
361 if benchFile:
362 benchFile.close()
363 benchFile=None
364 path.pop()
365 formatter.pop()
366 elif state == INTEST:
367 if len(l) > 0:
368 # In test mode, we are looking for test status.
369 # A line starting with S
370 # (There may be empty lines or line for data files)
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100371 passRe = r'^%s([0-9]+)[ ]+([0-9]+)[ ]+([0-9]+)[ ]+([t0-9]+)[ ]+([YN]).*$' % prefix
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200372 if re.match(passRe,l):
373 # If we have found a test status then we will start again
374 # in normal mode after this.
375
376 m = re.match(passRe,l)
377
378 # Extract test ID, test error code, line number and status
379 theId=m.group(1)
380 theId=int(theId)
381
382 theError=m.group(2)
383 theError=int(theError)
384
385 theLine=m.group(3)
386 theLine=int(theLine)
387
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100388 maybeCycles = m.group(4)
389 if maybeCycles == "t":
Christophe Favergeonbe7efb42019-08-09 10:17:03 +0100390 cycles = getCyclesFromTrace(trace) - calibration
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100391 else:
392 cycles = int(maybeCycles)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200393
394 status=m.group(5)
395 passed=0
396
397 # Convert status to number as used by formatter.
398 if status=="Y":
399 passed = 1
400 if status=="N":
401 passed = 0
402 # Compute path to this node
403 newPath=path.copy()
404 newPath.append(theId)
405 # Find the node in the Tree
406 elem = findItem(root,newPath)
407
408
Christophe Favergeon4f462732019-11-13 14:11:14 +0100409 state = ERRORDESC
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200410 else:
411 if re.match(r'^%sp.*$' % prefix,l):
412 if benchFile:
413 benchFile.close()
414 benchFile=None
415 path.pop()
416 formatter.pop()
417 if re.match(r'^%s[t][ ]*$' % prefix,l):
418 state = INTEST
419 else:
420 state = NORMAL
Christophe Favergeon4f462732019-11-13 14:11:14 +0100421 elif state == ERRORDESC:
422 if len(l) > 0:
423 if re.match(r'^.*E:.*$',l):
424 if re.match(r'^.*E:[ ].*$',l):
425 m = re.match(r'^.*E:[ ](.*)$',l)
426 errorDetail = m.group(1)
427 else:
428 errorDetail = ""
429 state = TESTPARAM
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200430 else:
431 if len(l) > 0:
432 state = INTEST
433 params=""
434 if re.match(r'^.*b[ ]+([0-9,]+)$',l):
435 m=re.match(r'^.*b[ ]+([0-9,]+)$',l)
436 params=m.group(1).strip()
437 # Format the node
438 #print(elem.fullPath())
439 #createMissingDir(destPath)
440 writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config)
441 else:
442 params=""
443 writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config)
444 # Format the node
Christophe Favergeon4f462732019-11-13 14:11:14 +0100445 formatter.printTest(elem,theId,theError,errorDetail,theLine,passed,cycles,params)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200446
447
448 formatter.end()
449
450
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100451def analyze(root,results,args,trace):
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200452 # currentConfig.csv should be in the same place
453 resultPath=os.path.dirname(args.r)
454
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100455 if args.c:
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200456 analyseResult(resultPath,root,results,args.e,args.b,trace,CSVFormatter())
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100457 elif args.m:
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200458 analyseResult(resultPath,root,results,args.e,args.b,trace,MathematicaFormatter())
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100459 else:
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200460 analyseResult(resultPath,root,results,args.e,args.b,trace,TextFormatter())
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100461
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200462parser = argparse.ArgumentParser(description='Parse test description')
463
Christophe Favergeon6f8eee92019-10-09 12:21:27 +0100464parser.add_argument('-f', nargs='?',type = str, default="Output.pickle", help="Test description file path")
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200465# Where the result file can be found
466parser.add_argument('-r', nargs='?',type = str, default=None, help="Result file path")
467parser.add_argument('-c', action='store_true', help="CSV output")
468parser.add_argument('-e', action='store_true', help="Embedded test")
469# -o needed when -e is true to know where to extract the output files
470parser.add_argument('-o', nargs='?',type = str, default="Output", help="Output dir path")
471
472parser.add_argument('-b', nargs='?',type = str, default="FullBenchmark", help="Full Benchmark dir path")
473parser.add_argument('-m', action='store_true', help="Mathematica output")
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100474parser.add_argument('-t', nargs='?',type = str, default=None, help="External trace file")
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200475
476args = parser.parse_args()
477
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100478
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200479if args.f is not None:
Christophe Favergeon6f8eee92019-10-09 12:21:27 +0100480 #p = parse.Parser()
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200481 # Parse the test description file
Christophe Favergeon6f8eee92019-10-09 12:21:27 +0100482 #root = p.parse(args.f)
483 root=parse.loadRoot(args.f)
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100484 if args.t:
485 with open(args.t,"r") as trace:
486 with open(args.r,"r") as results:
487 analyze(root,results,args,iter(trace))
488 else:
489 with open(args.r,"r") as results:
490 analyze(root,results,args,None)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200491 if args.e:
492 # In FPGA mode, extract output files from stdout (result file)
493 with open(args.r,"r") as results:
494 extractDataFiles(results,args.o)
495
496else:
497 parser.print_help()