processResult.py 20 KB

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