blob: c9c3d224c85d5459bca47f469cb9104735183e56 [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")
38
39 return("Unknown error %d" % id)
40
41
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +020042def findItem(root,path):
43 """ Find a node in a tree
44
45 Args:
46 path (list) : A list of node ID
47 This list is describing a path in the tree.
48 By starting from the root and following this path,
49 we can find the node in the tree.
50 Raises:
51 Nothing
52 Returns:
53 TreeItem : A node
54 """
55 # The list is converted into a queue.
56 q = deque(path)
57 q.popleft()
58 c = root
59 while q:
60 n = q.popleft()
61 # We get the children based on its ID and continue
62 c = c[n-1]
63 return(c)
64
65def joinit(iterable, delimiter):
66 # Intersperse a delimiter between element of a list
67 it = iter(iterable)
68 yield next(it)
69 for x in it:
70 yield delimiter
71 yield x
72
73# Return test result as a text tree
74class TextFormatter:
75 def start(self):
76 None
77
78 def printGroup(self,elem,theId):
79 if elem is None:
80 elem = root
81 message=elem.data["message"]
82 if not elem.data["deprecated"]:
83 kind = "Suite"
84 ident = " " * elem.ident
85 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
86 kind = "Group"
87 #print(elem.path)
Christophe Favergeon30c03792019-10-03 12:47:41 +010088 print(Style.BRIGHT + ("%s%s : %s (%d)" % (ident,kind,message,theId)) + Style.RESET_ALL)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +020089
90 def printTest(self,elem, theId, theError,theLine,passed,cycles,params):
91 message=elem.data["message"]
92 if not elem.data["deprecated"]:
93 kind = "Test"
94 ident = " " * elem.ident
Christophe Favergeon30c03792019-10-03 12:47:41 +010095 p=Fore.RED + "FAILED" + Style.RESET_ALL
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +020096 if passed == 1:
Christophe Favergeon30c03792019-10-03 12:47:41 +010097 p= Fore.GREEN + "PASSED" + Style.RESET_ALL
98 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 +020099 if params:
100 print("%s %s" % (ident,params))
101 if passed != 1:
Christophe Favergeon6f8eee92019-10-09 12:21:27 +0100102 print(Fore.RED + ("%s %s at line %d" % (ident, errorStr(theError), theLine)) + Style.RESET_ALL)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200103
104 def pop(self):
105 None
106
107 def end(self):
108 None
109
110# Return test result as a CSV
111class CSVFormatter:
112
113 def __init__(self):
114 self.name=[]
115 self._start=True
116
117 def start(self):
118 print("CATEGORY,NAME,ID,STATUS,CYCLES,PARAMS")
119
120 def printGroup(self,elem,theId):
121 if elem is None:
122 elem = root
123 # Remove Root from category name in CSV file.
124 if not self._start:
125 self.name.append(elem.data["class"])
126 else:
127 self._start=False
128 message=elem.data["message"]
129 if not elem.data["deprecated"]:
130 kind = "Suite"
131 ident = " " * elem.ident
132 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
133 kind = "Group"
134
135 def printTest(self,elem, theId, theError, theLine,passed,cycles,params):
136 message=elem.data["message"]
137 if not elem.data["deprecated"]:
138 kind = "Test"
139 name=elem.data["class"]
140 category= "".join(list(joinit(self.name,":")))
141 print("%s,%s,%d,%d,%d,\"%s\"" % (category,name,theId,passed,cycles,params))
142
143 def pop(self):
144 if self.name:
145 self.name.pop()
146
147 def end(self):
148 None
149
150class MathematicaFormatter:
151
152 def __init__(self):
153 self._hasContent=[False]
154 self._toPop=[]
155
156 def start(self):
157 None
158
159 def printGroup(self,elem,theId):
160 if self._hasContent[len(self._hasContent)-1]:
161 print(",",end="")
162
163 print("<|")
164 self._hasContent[len(self._hasContent)-1] = True
165 self._hasContent.append(False)
166 if elem is None:
167 elem = root
168 message=elem.data["message"]
169 if not elem.data["deprecated"]:
170
171 kind = "Suite"
172 ident = " " * elem.ident
173 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
174 kind = "Group"
175 print("\"%s\" ->" % (message))
176 #if kind == "Suite":
177 print("{",end="")
178 self._toPop.append("}")
179 #else:
180 # self._toPop.append("")
181
182 def printTest(self,elem, theId, theError,theLine,passed,cycles,params):
183 message=elem.data["message"]
184 if not elem.data["deprecated"]:
185 kind = "Test"
186 ident = " " * elem.ident
187 p="FAILED"
188 if passed == 1:
189 p="PASSED"
190 parameters=""
191 if params:
192 parameters = "%s" % params
193 if self._hasContent[len(self._hasContent)-1]:
194 print(",",end="")
195 print("<|\"NAME\" -> \"%s\",\"ID\" -> %d,\"STATUS\" -> \"%s\",\"CYCLES\" -> %d,\"PARAMS\" -> \"%s\"|>" % (message,theId,p,cycles,parameters))
196 self._hasContent[len(self._hasContent)-1] = True
197 #if passed != 1:
198 # print("%s Error = %d at line %d" % (ident, theError, theLine))
199
200 def pop(self):
201 print(self._toPop.pop(),end="")
202 print("|>")
203 self._hasContent.pop()
204
205 def end(self):
206 None
207
208NORMAL = 1
209INTEST = 2
210TESTPARAM = 3
211
212def createMissingDir(destPath):
213 theDir=os.path.normpath(os.path.dirname(destPath))
214 if not os.path.exists(theDir):
215 os.makedirs(theDir)
216
217def correctPath(path):
218 while (path[0]=="/") or (path[0] == "\\"):
219 path = path[1:]
220 return(path)
221
222def extractDataFiles(results,outputDir):
223 infile = False
224 f = None
225 for l in results:
226 if re.match(r'^.*D:[ ].*$',l):
227 if infile:
228 if re.match(r'^.*D:[ ]END$',l):
229 infile = False
230 if f:
231 f.close()
232 else:
233 if f:
234 m = re.match(r'^.*D:[ ](.*)$',l)
235 data = m.group(1)
236 f.write(data)
237 f.write("\n")
238
239 else:
240 m = re.match(r'^.*D:[ ](.*)$',l)
241 path = str(m.group(1))
242 infile = True
243 destPath = os.path.join(outputDir,correctPath(path))
244 createMissingDir(destPath)
245 f = open(destPath,"w")
246
247
248
249def writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config):
250 if benchFile:
251 name=elem.data["class"]
252 category= elem.categoryDesc()
Christophe Favergeon37b86222019-07-17 11:49:00 +0200253 old=""
254 if "testData" in elem.data:
255 if "oldID" in elem.data["testData"]:
256 old=elem.data["testData"]["oldID"]
257 benchFile.write("\"%s\",\"%s\",%d,\"%s\",%s,%d,%s\n" % (category,name,theId,old,params,cycles,config))
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200258
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100259def getCyclesFromTrace(trace):
260 if not trace:
261 return(0)
262 else:
263 return(TestScripts.ParseTrace.getCycles(trace))
264
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200265def analyseResult(resultPath,root,results,embedded,benchmark,trace,formatter):
Christophe Favergeonbe7efb42019-08-09 10:17:03 +0100266 calibration = 0
267 if trace:
268 # First cycle in the trace is the calibration data
269 # The noramlisation factor must be coherent with the C code one.
270 calibration = int(getCyclesFromTrace(trace) / 20)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200271 formatter.start()
272 path = []
273 state = NORMAL
274 prefix=""
275 elem=None
276 theId=None
277 theError=None
278 theLine=None
279 passed=0
280 cycles=None
281 benchFile = None
282 config=""
283 if embedded:
284 prefix = ".*S:[ ]"
285
286 # Parse the result file.
287 # NORMAL mode is when we are parsing suite or group.
288 # Otherwise we are parsing a test and we need to analyse the
289 # test result.
290 # TESTPARAM is used to read parameters of the test.
291 # Format of output is:
292 #node ident : s id or g id or t or u
293 #test status : id error linenb status Y or N (Y when passing)
294 #param for this test b x,x,x,x or b alone if not param
295 #node end : p
296 # In FPGA mode:
297 #Prefix S:[ ] before driver dump
298 # D:[ ] before data dump (output patterns)
299
300 for l in results:
301 l = l.strip()
302 if not re.match(r'^.*D:[ ].*$',l):
303 if state == NORMAL:
304 if len(l) > 0:
305 # Line starting with g or s is a suite or group.
306 # In FPGA mode, those line are prefixed with 'S: '
307 # and data file with 'D: '
308 if re.match(r'^%s[gs][ ]+[0-9]+.*$' % prefix,l):
309 # Extract the test id
310 theId=re.sub(r'^%s[gs][ ]+([0-9]+).*$' % prefix,r'\1',l)
311 theId=int(theId)
312 path.append(theId)
313 # From a list of id, find the TreeElem in the Parsed tree
314 # to know what is the node.
315 elem = findItem(root,path)
316 # Display formatted output for this node
317 if elem.params:
318 #print(elem.params.full)
319 benchPath = os.path.join(benchmark,elem.fullPath(),"fullBenchmark.csv")
320 createMissingDir(benchPath)
321 if benchFile:
322 printf("ERROR BENCH FILE %s ALREADY OPEN" % benchPath)
323 benchFile.close()
324 benchFile=None
325 benchFile=open(benchPath,"w")
326 header = "".join(list(joinit(elem.params.full,",")))
327 # A test and a benchmark are different
328 # so we don't dump a status and error
329 # A status and error in a benchmark would
330 # impact the cycles since the test
331 # would be taken into account in the measurement
332 # So benchmark are always passing and contain no test
333 #benchFile.write("ID,%s,PASSED,ERROR,CYCLES\n" % header)
334 csvheaders = ""
335
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200336 with open(os.path.join(resultPath,'currentConfig.csv'), 'r') as f:
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200337 reader = csv.reader(f)
338 csvheaders = next(reader, None)
339 configList = list(reader)
340 #print(configList)
341 config = "".join(list(joinit(configList[0],",")))
342 configHeaders = "".join(list(joinit(csvheaders,",")))
Christophe Favergeon37b86222019-07-17 11:49:00 +0200343 benchFile.write("CATEGORY,NAME,ID,OLDID,%s,CYCLES,%s\n" % (header,configHeaders))
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200344
345 formatter.printGroup(elem,theId)
346
347 # If we have detected a test, we switch to test mode
348 if re.match(r'^%s[t][ ]*$' % prefix,l):
349 state = INTEST
350
351
352 # Pop
353 # End of suite or group
354 if re.match(r'^%sp.*$' % prefix,l):
355 if benchFile:
356 benchFile.close()
357 benchFile=None
358 path.pop()
359 formatter.pop()
360 elif state == INTEST:
361 if len(l) > 0:
362 # In test mode, we are looking for test status.
363 # A line starting with S
364 # (There may be empty lines or line for data files)
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100365 passRe = r'^%s([0-9]+)[ ]+([0-9]+)[ ]+([0-9]+)[ ]+([t0-9]+)[ ]+([YN]).*$' % prefix
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200366 if re.match(passRe,l):
367 # If we have found a test status then we will start again
368 # in normal mode after this.
369
370 m = re.match(passRe,l)
371
372 # Extract test ID, test error code, line number and status
373 theId=m.group(1)
374 theId=int(theId)
375
376 theError=m.group(2)
377 theError=int(theError)
378
379 theLine=m.group(3)
380 theLine=int(theLine)
381
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100382 maybeCycles = m.group(4)
383 if maybeCycles == "t":
Christophe Favergeonbe7efb42019-08-09 10:17:03 +0100384 cycles = getCyclesFromTrace(trace) - calibration
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100385 else:
386 cycles = int(maybeCycles)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200387
388 status=m.group(5)
389 passed=0
390
391 # Convert status to number as used by formatter.
392 if status=="Y":
393 passed = 1
394 if status=="N":
395 passed = 0
396 # Compute path to this node
397 newPath=path.copy()
398 newPath.append(theId)
399 # Find the node in the Tree
400 elem = findItem(root,newPath)
401
402
403 state = TESTPARAM
404 else:
405 if re.match(r'^%sp.*$' % prefix,l):
406 if benchFile:
407 benchFile.close()
408 benchFile=None
409 path.pop()
410 formatter.pop()
411 if re.match(r'^%s[t][ ]*$' % prefix,l):
412 state = INTEST
413 else:
414 state = NORMAL
415 else:
416 if len(l) > 0:
417 state = INTEST
418 params=""
419 if re.match(r'^.*b[ ]+([0-9,]+)$',l):
420 m=re.match(r'^.*b[ ]+([0-9,]+)$',l)
421 params=m.group(1).strip()
422 # Format the node
423 #print(elem.fullPath())
424 #createMissingDir(destPath)
425 writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config)
426 else:
427 params=""
428 writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config)
429 # Format the node
430 formatter.printTest(elem,theId,theError,theLine,passed,cycles,params)
431
432
433 formatter.end()
434
435
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100436def analyze(root,results,args,trace):
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200437 # currentConfig.csv should be in the same place
438 resultPath=os.path.dirname(args.r)
439
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100440 if args.c:
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200441 analyseResult(resultPath,root,results,args.e,args.b,trace,CSVFormatter())
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100442 elif args.m:
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200443 analyseResult(resultPath,root,results,args.e,args.b,trace,MathematicaFormatter())
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100444 else:
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200445 analyseResult(resultPath,root,results,args.e,args.b,trace,TextFormatter())
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100446
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200447parser = argparse.ArgumentParser(description='Parse test description')
448
Christophe Favergeon6f8eee92019-10-09 12:21:27 +0100449parser.add_argument('-f', nargs='?',type = str, default="Output.pickle", help="Test description file path")
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200450# Where the result file can be found
451parser.add_argument('-r', nargs='?',type = str, default=None, help="Result file path")
452parser.add_argument('-c', action='store_true', help="CSV output")
453parser.add_argument('-e', action='store_true', help="Embedded test")
454# -o needed when -e is true to know where to extract the output files
455parser.add_argument('-o', nargs='?',type = str, default="Output", help="Output dir path")
456
457parser.add_argument('-b', nargs='?',type = str, default="FullBenchmark", help="Full Benchmark dir path")
458parser.add_argument('-m', action='store_true', help="Mathematica output")
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100459parser.add_argument('-t', nargs='?',type = str, default=None, help="External trace file")
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200460
461args = parser.parse_args()
462
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100463
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200464if args.f is not None:
Christophe Favergeon6f8eee92019-10-09 12:21:27 +0100465 #p = parse.Parser()
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200466 # Parse the test description file
Christophe Favergeon6f8eee92019-10-09 12:21:27 +0100467 #root = p.parse(args.f)
468 root=parse.loadRoot(args.f)
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100469 if args.t:
470 with open(args.t,"r") as trace:
471 with open(args.r,"r") as results:
472 analyze(root,results,args,iter(trace))
473 else:
474 with open(args.r,"r") as results:
475 analyze(root,results,args,None)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200476 if args.e:
477 # In FPGA mode, extract output files from stdout (result file)
478 with open(args.r,"r") as results:
479 extractDataFiles(results,args.o)
480
481else:
482 parser.print_help()