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