processResult.py 20 KB

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