processResult.py 16 KB

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