blob: 4d979c9b3d56f4098dd7e11b39b9a1a429b21eaf [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 Favergeon2942a332020-01-20 14:18:48 +010017
Christophe Favergeon6f8eee92019-10-09 12:21:27 +010018def errorStr(id):
19 if id == 1:
20 return("UNKNOWN_ERROR")
21 if id == 2:
22 return("Equality error")
23 if id == 3:
24 return("Absolute difference error")
25 if id == 4:
26 return("Relative difference error")
27 if id == 5:
28 return("SNR error")
29 if id == 6:
30 return("Different length error")
31 if id == 7:
32 return("Assertion error")
33 if id == 8:
34 return("Memory allocation error")
35 if id == 9:
36 return("Empty pattern error")
37 if id == 10:
38 return("Buffer tail corrupted")
Christophe Favergeonf055bd32019-10-15 12:30:30 +010039 if id == 11:
40 return("Close float error")
Christophe Favergeon6f8eee92019-10-09 12:21:27 +010041
42 return("Unknown error %d" % id)
43
44
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +020045def findItem(root,path):
46 """ Find a node in a tree
47
48 Args:
49 path (list) : A list of node ID
50 This list is describing a path in the tree.
51 By starting from the root and following this path,
52 we can find the node in the tree.
53 Raises:
54 Nothing
55 Returns:
56 TreeItem : A node
57 """
58 # The list is converted into a queue.
59 q = deque(path)
60 q.popleft()
61 c = root
62 while q:
63 n = q.popleft()
64 # We get the children based on its ID and continue
65 c = c[n-1]
66 return(c)
67
68def joinit(iterable, delimiter):
69 # Intersperse a delimiter between element of a list
70 it = iter(iterable)
71 yield next(it)
72 for x in it:
73 yield delimiter
74 yield x
75
76# Return test result as a text tree
77class TextFormatter:
78 def start(self):
79 None
80
81 def printGroup(self,elem,theId):
82 if elem is None:
83 elem = root
84 message=elem.data["message"]
85 if not elem.data["deprecated"]:
86 kind = "Suite"
87 ident = " " * elem.ident
88 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
89 kind = "Group"
90 #print(elem.path)
Christophe Favergeon30c03792019-10-03 12:47:41 +010091 print(Style.BRIGHT + ("%s%s : %s (%d)" % (ident,kind,message,theId)) + Style.RESET_ALL)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +020092
Christophe Favergeon4f462732019-11-13 14:11:14 +010093 def printTest(self,elem, theId, theError,errorDetail,theLine,passed,cycles,params):
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +020094 message=elem.data["message"]
95 if not elem.data["deprecated"]:
96 kind = "Test"
97 ident = " " * elem.ident
Christophe Favergeon30c03792019-10-03 12:47:41 +010098 p=Fore.RED + "FAILED" + Style.RESET_ALL
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +020099 if passed == 1:
Christophe Favergeon30c03792019-10-03 12:47:41 +0100100 p= Fore.GREEN + "PASSED" + Style.RESET_ALL
101 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 +0200102 if params:
103 print("%s %s" % (ident,params))
104 if passed != 1:
Christophe Favergeon6f8eee92019-10-09 12:21:27 +0100105 print(Fore.RED + ("%s %s at line %d" % (ident, errorStr(theError), theLine)) + Style.RESET_ALL)
Christophe Favergeon4f462732019-11-13 14:11:14 +0100106 if (len(errorDetail)>0):
107 print(Fore.RED + ident + " " + errorDetail + Style.RESET_ALL)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200108
109 def pop(self):
110 None
111
112 def end(self):
113 None
114
Christophe Favergeone972cbd2019-11-19 15:54:13 +0100115# Return test result as a text tree
116class HTMLFormatter:
117 def __init__(self):
118 self.nb=1
119 self.suite=False
120
121 def start(self):
122 print("<html><head><title>Test Results</title></head><body>")
123
124 def printGroup(self,elem,theId):
125 if elem is None:
126 elem = root
127 message=elem.data["message"]
128 if not elem.data["deprecated"]:
129 kind = "Suite"
130 ident = " " * elem.ident
131 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
132 kind = "Group"
133 if kind == "Group":
134 print("<h%d> %s (%d) </h%d>" % (self.nb,message,theId,self.nb))
135 else:
136 print("<h%d> %s (%d) </h%d>" % (self.nb,message,theId,self.nb))
137 self.suite=True
138 print("<table style=\"width:100%\">")
139 print("<tr>")
140 print("<td>Name</td>")
141 print("<td>ID</td>")
142 print("<td>Status</td>")
Christophe Favergeon59aeeea2019-11-20 13:39:05 +0100143 print("<td>Params</td>")
Christophe Favergeone972cbd2019-11-19 15:54:13 +0100144 print("<td>Cycles</td>")
145 print("</tr>")
146 self.nb = self.nb + 1
147
148 def printTest(self,elem, theId, theError,errorDetail,theLine,passed,cycles,params):
149 message=elem.data["message"]
150 if not elem.data["deprecated"]:
151 kind = "Test"
152 ident = " " * elem.ident
153 p="<font color=\"red\">FAILED</font>"
154 if passed == 1:
155 p= "<font color=\"green\">PASSED</font>"
156 print("<tr>")
157 print("<td><pre>%s</pre></td>" % message)
158 print("<td>%d</td>" % theId)
159 print("<td>%s</td>" % p)
Christophe Favergeon59aeeea2019-11-20 13:39:05 +0100160 if params:
161 print("<td>%s</td>\n" % (params))
162 else:
163 print("<td></td>\n")
Christophe Favergeone972cbd2019-11-19 15:54:13 +0100164 print("<td>%d</td>" % cycles)
165 print("</tr>")
Christophe Favergeon59aeeea2019-11-20 13:39:05 +0100166
Christophe Favergeone972cbd2019-11-19 15:54:13 +0100167 if passed != 1:
168
169 print("<tr><td colspan=4><font color=\"red\">%s at line %d</font></td></tr>" % (errorStr(theError), theLine))
170 if (len(errorDetail)>0):
171 print("<tr><td colspan=4><font color=\"red\">" + errorDetail + "</font></td></tr>")
172
173 def pop(self):
174 if self.suite:
175 print("</table>")
176 self.nb = self.nb - 1
177 self.suite=False
178
179 def end(self):
180 print("</body></html>")
181
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200182# Return test result as a CSV
183class CSVFormatter:
184
185 def __init__(self):
186 self.name=[]
187 self._start=True
188
189 def start(self):
190 print("CATEGORY,NAME,ID,STATUS,CYCLES,PARAMS")
191
192 def printGroup(self,elem,theId):
193 if elem is None:
194 elem = root
195 # Remove Root from category name in CSV file.
196 if not self._start:
197 self.name.append(elem.data["class"])
198 else:
199 self._start=False
200 message=elem.data["message"]
201 if not elem.data["deprecated"]:
202 kind = "Suite"
203 ident = " " * elem.ident
204 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
205 kind = "Group"
206
Christophe Favergeon4f462732019-11-13 14:11:14 +0100207 def printTest(self,elem, theId, theError, errorDetail,theLine,passed,cycles,params):
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200208 message=elem.data["message"]
209 if not elem.data["deprecated"]:
210 kind = "Test"
211 name=elem.data["class"]
212 category= "".join(list(joinit(self.name,":")))
213 print("%s,%s,%d,%d,%d,\"%s\"" % (category,name,theId,passed,cycles,params))
214
215 def pop(self):
216 if self.name:
217 self.name.pop()
218
219 def end(self):
220 None
221
222class MathematicaFormatter:
223
224 def __init__(self):
225 self._hasContent=[False]
226 self._toPop=[]
227
228 def start(self):
229 None
230
231 def printGroup(self,elem,theId):
232 if self._hasContent[len(self._hasContent)-1]:
233 print(",",end="")
234
235 print("<|")
236 self._hasContent[len(self._hasContent)-1] = True
237 self._hasContent.append(False)
238 if elem is None:
239 elem = root
240 message=elem.data["message"]
241 if not elem.data["deprecated"]:
242
243 kind = "Suite"
244 ident = " " * elem.ident
245 if elem.kind == TestScripts.Parser.TreeElem.GROUP:
246 kind = "Group"
247 print("\"%s\" ->" % (message))
248 #if kind == "Suite":
249 print("{",end="")
250 self._toPop.append("}")
251 #else:
252 # self._toPop.append("")
253
Christophe Favergeon4f462732019-11-13 14:11:14 +0100254 def printTest(self,elem, theId, theError,errorDetail,theLine,passed,cycles,params):
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200255 message=elem.data["message"]
256 if not elem.data["deprecated"]:
257 kind = "Test"
258 ident = " " * elem.ident
259 p="FAILED"
260 if passed == 1:
261 p="PASSED"
262 parameters=""
263 if params:
264 parameters = "%s" % params
265 if self._hasContent[len(self._hasContent)-1]:
266 print(",",end="")
267 print("<|\"NAME\" -> \"%s\",\"ID\" -> %d,\"STATUS\" -> \"%s\",\"CYCLES\" -> %d,\"PARAMS\" -> \"%s\"|>" % (message,theId,p,cycles,parameters))
268 self._hasContent[len(self._hasContent)-1] = True
269 #if passed != 1:
270 # print("%s Error = %d at line %d" % (ident, theError, theLine))
271
272 def pop(self):
273 print(self._toPop.pop(),end="")
274 print("|>")
275 self._hasContent.pop()
276
277 def end(self):
278 None
279
280NORMAL = 1
281INTEST = 2
282TESTPARAM = 3
Christophe Favergeon4f462732019-11-13 14:11:14 +0100283ERRORDESC = 4
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200284
285def createMissingDir(destPath):
286 theDir=os.path.normpath(os.path.dirname(destPath))
287 if not os.path.exists(theDir):
288 os.makedirs(theDir)
289
290def correctPath(path):
291 while (path[0]=="/") or (path[0] == "\\"):
292 path = path[1:]
293 return(path)
294
295def extractDataFiles(results,outputDir):
296 infile = False
297 f = None
298 for l in results:
299 if re.match(r'^.*D:[ ].*$',l):
300 if infile:
301 if re.match(r'^.*D:[ ]END$',l):
302 infile = False
303 if f:
304 f.close()
305 else:
306 if f:
307 m = re.match(r'^.*D:[ ](.*)$',l)
308 data = m.group(1)
309 f.write(data)
310 f.write("\n")
311
312 else:
313 m = re.match(r'^.*D:[ ](.*)$',l)
314 path = str(m.group(1))
315 infile = True
316 destPath = os.path.join(outputDir,correctPath(path))
317 createMissingDir(destPath)
318 f = open(destPath,"w")
319
320
321
322def writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config):
323 if benchFile:
324 name=elem.data["class"]
325 category= elem.categoryDesc()
Christophe Favergeon37b86222019-07-17 11:49:00 +0200326 old=""
327 if "testData" in elem.data:
328 if "oldID" in elem.data["testData"]:
329 old=elem.data["testData"]["oldID"]
330 benchFile.write("\"%s\",\"%s\",%d,\"%s\",%s,%d,%s\n" % (category,name,theId,old,params,cycles,config))
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200331
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100332def getCyclesFromTrace(trace):
333 if not trace:
334 return(0)
335 else:
336 return(TestScripts.ParseTrace.getCycles(trace))
337
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200338def analyseResult(resultPath,root,results,embedded,benchmark,trace,formatter):
Christophe Favergeonbe7efb42019-08-09 10:17:03 +0100339 calibration = 0
340 if trace:
341 # First cycle in the trace is the calibration data
342 # The noramlisation factor must be coherent with the C code one.
343 calibration = int(getCyclesFromTrace(trace) / 20)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200344 formatter.start()
345 path = []
346 state = NORMAL
347 prefix=""
348 elem=None
349 theId=None
350 theError=None
Christophe Favergeon4f462732019-11-13 14:11:14 +0100351 errorDetail=""
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200352 theLine=None
353 passed=0
354 cycles=None
355 benchFile = None
356 config=""
357 if embedded:
358 prefix = ".*S:[ ]"
359
360 # Parse the result file.
361 # NORMAL mode is when we are parsing suite or group.
362 # Otherwise we are parsing a test and we need to analyse the
363 # test result.
364 # TESTPARAM is used to read parameters of the test.
365 # Format of output is:
366 #node ident : s id or g id or t or u
367 #test status : id error linenb status Y or N (Y when passing)
368 #param for this test b x,x,x,x or b alone if not param
369 #node end : p
370 # In FPGA mode:
371 #Prefix S:[ ] before driver dump
372 # D:[ ] before data dump (output patterns)
373
374 for l in results:
375 l = l.strip()
376 if not re.match(r'^.*D:[ ].*$',l):
377 if state == NORMAL:
378 if len(l) > 0:
379 # Line starting with g or s is a suite or group.
380 # In FPGA mode, those line are prefixed with 'S: '
381 # and data file with 'D: '
382 if re.match(r'^%s[gs][ ]+[0-9]+.*$' % prefix,l):
383 # Extract the test id
384 theId=re.sub(r'^%s[gs][ ]+([0-9]+).*$' % prefix,r'\1',l)
385 theId=int(theId)
386 path.append(theId)
387 # From a list of id, find the TreeElem in the Parsed tree
388 # to know what is the node.
389 elem = findItem(root,path)
390 # Display formatted output for this node
391 if elem.params:
392 #print(elem.params.full)
393 benchPath = os.path.join(benchmark,elem.fullPath(),"fullBenchmark.csv")
394 createMissingDir(benchPath)
395 if benchFile:
396 printf("ERROR BENCH FILE %s ALREADY OPEN" % benchPath)
397 benchFile.close()
398 benchFile=None
399 benchFile=open(benchPath,"w")
400 header = "".join(list(joinit(elem.params.full,",")))
401 # A test and a benchmark are different
402 # so we don't dump a status and error
403 # A status and error in a benchmark would
404 # impact the cycles since the test
405 # would be taken into account in the measurement
406 # So benchmark are always passing and contain no test
407 #benchFile.write("ID,%s,PASSED,ERROR,CYCLES\n" % header)
408 csvheaders = ""
409
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200410 with open(os.path.join(resultPath,'currentConfig.csv'), 'r') as f:
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200411 reader = csv.reader(f)
412 csvheaders = next(reader, None)
413 configList = list(reader)
414 #print(configList)
415 config = "".join(list(joinit(configList[0],",")))
416 configHeaders = "".join(list(joinit(csvheaders,",")))
Christophe Favergeon37b86222019-07-17 11:49:00 +0200417 benchFile.write("CATEGORY,NAME,ID,OLDID,%s,CYCLES,%s\n" % (header,configHeaders))
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200418
419 formatter.printGroup(elem,theId)
420
421 # If we have detected a test, we switch to test mode
422 if re.match(r'^%s[t][ ]*$' % prefix,l):
423 state = INTEST
424
425
426 # Pop
427 # End of suite or group
428 if re.match(r'^%sp.*$' % prefix,l):
429 if benchFile:
430 benchFile.close()
431 benchFile=None
432 path.pop()
433 formatter.pop()
434 elif state == INTEST:
435 if len(l) > 0:
436 # In test mode, we are looking for test status.
437 # A line starting with S
438 # (There may be empty lines or line for data files)
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100439 passRe = r'^%s([0-9]+)[ ]+([0-9]+)[ ]+([0-9]+)[ ]+([t0-9]+)[ ]+([YN]).*$' % prefix
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200440 if re.match(passRe,l):
441 # If we have found a test status then we will start again
442 # in normal mode after this.
443
444 m = re.match(passRe,l)
445
446 # Extract test ID, test error code, line number and status
447 theId=m.group(1)
448 theId=int(theId)
449
450 theError=m.group(2)
451 theError=int(theError)
452
453 theLine=m.group(3)
454 theLine=int(theLine)
455
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100456 maybeCycles = m.group(4)
457 if maybeCycles == "t":
Christophe Favergeonbe7efb42019-08-09 10:17:03 +0100458 cycles = getCyclesFromTrace(trace) - calibration
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100459 else:
460 cycles = int(maybeCycles)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200461
462 status=m.group(5)
463 passed=0
464
465 # Convert status to number as used by formatter.
466 if status=="Y":
467 passed = 1
468 if status=="N":
469 passed = 0
470 # Compute path to this node
471 newPath=path.copy()
472 newPath.append(theId)
473 # Find the node in the Tree
474 elem = findItem(root,newPath)
475
476
Christophe Favergeon4f462732019-11-13 14:11:14 +0100477 state = ERRORDESC
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200478 else:
479 if re.match(r'^%sp.*$' % prefix,l):
480 if benchFile:
481 benchFile.close()
482 benchFile=None
483 path.pop()
484 formatter.pop()
485 if re.match(r'^%s[t][ ]*$' % prefix,l):
486 state = INTEST
487 else:
488 state = NORMAL
Christophe Favergeon4f462732019-11-13 14:11:14 +0100489 elif state == ERRORDESC:
490 if len(l) > 0:
491 if re.match(r'^.*E:.*$',l):
492 if re.match(r'^.*E:[ ].*$',l):
493 m = re.match(r'^.*E:[ ](.*)$',l)
494 errorDetail = m.group(1)
495 else:
496 errorDetail = ""
497 state = TESTPARAM
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200498 else:
499 if len(l) > 0:
500 state = INTEST
501 params=""
502 if re.match(r'^.*b[ ]+([0-9,]+)$',l):
503 m=re.match(r'^.*b[ ]+([0-9,]+)$',l)
504 params=m.group(1).strip()
505 # Format the node
506 #print(elem.fullPath())
507 #createMissingDir(destPath)
508 writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config)
509 else:
510 params=""
511 writeBenchmark(elem,benchFile,theId,theError,passed,cycles,params,config)
512 # Format the node
Christophe Favergeon4f462732019-11-13 14:11:14 +0100513 formatter.printTest(elem,theId,theError,errorDetail,theLine,passed,cycles,params)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200514
515
516 formatter.end()
517
518
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100519def analyze(root,results,args,trace):
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200520 # currentConfig.csv should be in the same place
521 resultPath=os.path.dirname(args.r)
522
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100523 if args.c:
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200524 analyseResult(resultPath,root,results,args.e,args.b,trace,CSVFormatter())
Christophe Favergeone972cbd2019-11-19 15:54:13 +0100525 elif args.html:
526 analyseResult(resultPath,root,results,args.e,args.b,trace,HTMLFormatter())
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100527 elif args.m:
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200528 analyseResult(resultPath,root,results,args.e,args.b,trace,MathematicaFormatter())
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100529 else:
Christophe Favergeon2942a332020-01-20 14:18:48 +0100530 print("")
531 print(Fore.RED + "The cycles displayed by this script must not be trusted." + Style.RESET_ALL)
532 print(Fore.RED + "They are just an indication. The timing code has not yet been validated." + Style.RESET_ALL)
533 print("")
534
Christophe Favergeon5cacf9d2019-08-14 10:41:17 +0200535 analyseResult(resultPath,root,results,args.e,args.b,trace,TextFormatter())
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100536
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200537parser = argparse.ArgumentParser(description='Parse test description')
538
Christophe Favergeon6f8eee92019-10-09 12:21:27 +0100539parser.add_argument('-f', nargs='?',type = str, default="Output.pickle", help="Test description file path")
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200540# Where the result file can be found
541parser.add_argument('-r', nargs='?',type = str, default=None, help="Result file path")
542parser.add_argument('-c', action='store_true', help="CSV output")
Christophe Favergeone972cbd2019-11-19 15:54:13 +0100543parser.add_argument('-html', action='store_true', help="HTML output")
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200544parser.add_argument('-e', action='store_true', help="Embedded test")
545# -o needed when -e is true to know where to extract the output files
546parser.add_argument('-o', nargs='?',type = str, default="Output", help="Output dir path")
547
548parser.add_argument('-b', nargs='?',type = str, default="FullBenchmark", help="Full Benchmark dir path")
549parser.add_argument('-m', action='store_true', help="Mathematica output")
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100550parser.add_argument('-t', nargs='?',type = str, default=None, help="External trace file")
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200551
552args = parser.parse_args()
553
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100554
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200555if args.f is not None:
Christophe Favergeon6f8eee92019-10-09 12:21:27 +0100556 #p = parse.Parser()
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200557 # Parse the test description file
Christophe Favergeon6f8eee92019-10-09 12:21:27 +0100558 #root = p.parse(args.f)
559 root=parse.loadRoot(args.f)
Christophe Favergeonf76a8032019-08-09 09:15:50 +0100560 if args.t:
561 with open(args.t,"r") as trace:
562 with open(args.r,"r") as results:
563 analyze(root,results,args,iter(trace))
564 else:
565 with open(args.r,"r") as results:
566 analyze(root,results,args,None)
Christophe Favergeon3b2a0ee2019-06-12 13:29:14 +0200567 if args.e:
568 # In FPGA mode, extract output files from stdout (result file)
569 with open(args.r,"r") as results:
570 extractDataFiles(results,args.o)
571
572else:
573 parser.print_help()