blob: f2b26bab6d0ddb64e6ce97bfe95de453fd147060 [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 Favergeonbe7efb42019-08-09 10:17:03 +0100238 calibration = 0
239 if trace:
240 # First cycle in the trace is the calibration data
241 # The noramlisation factor must be coherent with the C code one.
242 calibration = int(getCyclesFromTrace(trace) / 20)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200243 formatter.start()
244 path = []
245 state = NORMAL
246 prefix=""
247 elem=None
248 theId=None
249 theError=None
250 theLine=None
251 passed=0
252 cycles=None
253 benchFile = None
254 config=""
255 if embedded:
256 prefix = ".*S:[ ]"
257
258 # Parse the result file.
259 # NORMAL mode is when we are parsing suite or group.
260 # Otherwise we are parsing a test and we need to analyse the
261 # test result.
262 # TESTPARAM is used to read parameters of the test.
263 # Format of output is:
264 #node ident : s id or g id or t or u
265 #test status : id error linenb status Y or N (Y when passing)
266 #param for this test b x,x,x,x or b alone if not param
267 #node end : p
268 # In FPGA mode:
269 #Prefix S:[ ] before driver dump
270 # D:[ ] before data dump (output patterns)
271
272 for l in results:
273 l = l.strip()
274 if not re.match(r'^.*D:[ ].*$',l):
275 if state == NORMAL:
276 if len(l) > 0:
277 # Line starting with g or s is a suite or group.
278 # In FPGA mode, those line are prefixed with 'S: '
279 # and data file with 'D: '
280 if re.match(r'^%s[gs][ ]+[0-9]+.*$' % prefix,l):
281 # Extract the test id
282 theId=re.sub(r'^%s[gs][ ]+([0-9]+).*$' % prefix,r'\1',l)
283 theId=int(theId)
284 path.append(theId)
285 # From a list of id, find the TreeElem in the Parsed tree
286 # to know what is the node.
287 elem = findItem(root,path)
288 # Display formatted output for this node
289 if elem.params:
290 #print(elem.params.full)
291 benchPath = os.path.join(benchmark,elem.fullPath(),"fullBenchmark.csv")
292 createMissingDir(benchPath)
293 if benchFile:
294 printf("ERROR BENCH FILE %s ALREADY OPEN" % benchPath)
295 benchFile.close()
296 benchFile=None
297 benchFile=open(benchPath,"w")
298 header = "".join(list(joinit(elem.params.full,",")))
299 # A test and a benchmark are different
300 # so we don't dump a status and error
301 # A status and error in a benchmark would
302 # impact the cycles since the test
303 # would be taken into account in the measurement
304 # So benchmark are always passing and contain no test
305 #benchFile.write("ID,%s,PASSED,ERROR,CYCLES\n" % header)
306 csvheaders = ""
307
308 with open('currentConfig.csv', 'r') as f:
309 reader = csv.reader(f)
310 csvheaders = next(reader, None)
311 configList = list(reader)
312 #print(configList)
313 config = "".join(list(joinit(configList[0],",")))
314 configHeaders = "".join(list(joinit(csvheaders,",")))
Christophe Favergeon37b86222019-07-17 11:49:00 +0200315 benchFile.write("CATEGORY,NAME,ID,OLDID,%s,CYCLES,%s\n" % (header,configHeaders))
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200316
317 formatter.printGroup(elem,theId)
318
319 # If we have detected a test, we switch to test mode
320 if re.match(r'^%s[t][ ]*$' % prefix,l):
321 state = INTEST
322
323
324 # Pop
325 # End of suite or group
326 if re.match(r'^%sp.*$' % prefix,l):
327 if benchFile:
328 benchFile.close()
329 benchFile=None
330 path.pop()
331 formatter.pop()
332 elif state == INTEST:
333 if len(l) > 0:
334 # In test mode, we are looking for test status.
335 # A line starting with S
336 # (There may be empty lines or line for data files)
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100337 passRe = r'^%s([0-9]+)[ ]+([0-9]+)[ ]+([0-9]+)[ ]+([t0-9]+)[ ]+([YN]).*$' % prefix
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200338 if re.match(passRe,l):
339 # If we have found a test status then we will start again
340 # in normal mode after this.
341
342 m = re.match(passRe,l)
343
344 # Extract test ID, test error code, line number and status
345 theId=m.group(1)
346 theId=int(theId)
347
348 theError=m.group(2)
349 theError=int(theError)
350
351 theLine=m.group(3)
352 theLine=int(theLine)
353
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100354 maybeCycles = m.group(4)
355 if maybeCycles == "t":
Christophe Favergeonbe7efb42019-08-09 10:17:03 +0100356 cycles = getCyclesFromTrace(trace) - calibration
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100357 else:
358 cycles = int(maybeCycles)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200359
360 status=m.group(5)
361 passed=0
362
363 # Convert status to number as used by formatter.
364 if status=="Y":
365 passed = 1
366 if status=="N":
367 passed = 0
368 # Compute path to this node
369 newPath=path.copy()
370 newPath.append(theId)
371 # Find the node in the Tree
372 elem = findItem(root,newPath)
373
374
375 state = TESTPARAM
376 else:
377 if re.match(r'^%sp.*$' % prefix,l):
378 if benchFile:
379 benchFile.close()
380 benchFile=None
381 path.pop()
382 formatter.pop()
383 if re.match(r'^%s[t][ ]*$' % prefix,l):
384 state = INTEST
385 else:
386 state = NORMAL
387 else:
388 if len(l) > 0:
389 state = INTEST
390 params=""
391 if re.match(r'^.*b[ ]+([0-9,]+)$',l):
392 m=re.match(r'^.*b[ ]+([0-9,]+)$',l)
393 params=m.group(1).strip()
394 # Format the node
395 #print(elem.fullPath())
396 #createMissingDir(destPath)
397 writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config)
398 else:
399 params=""
400 writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config)
401 # Format the node
402 formatter.printTest(elem,theId,theError,theLine,passed,cycles,params)
403
404
405 formatter.end()
406
407
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100408def analyze(root,results,args,trace):
409 if args.c:
410 analyseResult(root,results,args.e,args.b,trace,CSVFormatter())
411 elif args.m:
412 analyseResult(root,results,args.e,args.b,trace,MathematicaFormatter())
413 else:
414 analyseResult(root,results,args.e,args.b,trace,TextFormatter())
415
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200416parser = argparse.ArgumentParser(description='Parse test description')
417
418parser.add_argument('-f', nargs='?',type = str, default=None, help="Test description file path")
419# Where the result file can be found
420parser.add_argument('-r', nargs='?',type = str, default=None, help="Result file path")
421parser.add_argument('-c', action='store_true', help="CSV output")
422parser.add_argument('-e', action='store_true', help="Embedded test")
423# -o needed when -e is true to know where to extract the output files
424parser.add_argument('-o', nargs='?',type = str, default="Output", help="Output dir path")
425
426parser.add_argument('-b', nargs='?',type = str, default="FullBenchmark", help="Full Benchmark dir path")
427parser.add_argument('-m', action='store_true', help="Mathematica output")
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100428parser.add_argument('-t', nargs='?',type = str, default=None, help="External trace file")
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200429
430args = parser.parse_args()
431
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100432
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200433if args.f is not None:
434 p = parse.Parser()
435 # Parse the test description file
436 root = p.parse(args.f)
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100437 if args.t:
438 with open(args.t,"r") as trace:
439 with open(args.r,"r") as results:
440 analyze(root,results,args,iter(trace))
441 else:
442 with open(args.r,"r") as results:
443 analyze(root,results,args,None)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200444 if args.e:
445 # In FPGA mode, extract output files from stdout (result file)
446 with open(args.r,"r") as results:
447 extractDataFiles(results,args.o)
448
449else:
450 parser.print_help()