1
0

callvalidator.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. #!/usr/bin/env python
  2. # usage:callvalidator.py source=<src_input_dir> [file=<filename> | list=<listfilename>] [folder=<folder>]
  3. # tries to determine whether calls like gt 'location', 'event' are valid or not.
  4. from os import listdir
  5. from os.path import isfile, join
  6. import sys
  7. import re
  8. import io
  9. import time
  10. import xml.etree.ElementTree as ET
  11. idir = ''
  12. callList = []
  13. locationCallList = []
  14. neverCalledLocations = []
  15. callFileList = []
  16. calledFileList = []
  17. validate = 'all'
  18. def loadValidationList(validationListFile):
  19. result = []
  20. try:
  21. tree = ET.parse(validationListFile)
  22. root = tree.getroot()
  23. for file in root.iter('File'):
  24. result.append(join(file.attrib['folder'], file.attrib['name']))
  25. return result
  26. except:
  27. pass
  28. #endtry
  29. try:
  30. with io.open(validationListFile, 'r', encoding='utf-8') as ifile:
  31. lines = ifile.readlines()
  32. index = 1
  33. for line in lines:
  34. temp = line.strip(" \r\n\t").split(";")
  35. file = temp[0].strip(" \t")
  36. folder = idir
  37. if ".qsrc" not in file:
  38. raise SyntaxError("Incorrect file content: '<filename>[; <folder>] expected, found %s." % (index, line),(validationListFile, index, 1, 'if ".qsrc" not in temp[0].strip(" \t"):', index, len(line)))
  39. if len(temp) == 2:
  40. folder = temp[1].strip(" \t")
  41. result.append(join(folder,file))
  42. index += 1
  43. return result
  44. except SyntaxError as e:
  45. raise SystemExit("Invalid list filed: %s" % e.msg)
  46. #endtry
  47. #enddef
  48. def functionEmptyOrNamed(callArray):
  49. if len(callArray) < 3:
  50. return ''
  51. else:
  52. return callArray[2].strip(" \t\n")
  53. #endif
  54. #enddef
  55. def processLines(lines, fileName):
  56. findPattern = "(gt|gs|xgt|xgs)\s*('|\")\w+('|\")(,\s*('|\")\w+('|\"))?"
  57. blockEndPattern = "[^}]*}$"
  58. commentBlock = False
  59. lineNo = 0
  60. locationCalls = {"calllocation": lines[0].strip(' #\n'), "fileName": fileName, "calls": []}
  61. for line in lines:
  62. line = line.strip(" \t\n")
  63. if (not commentBlock) and line.strip("!").startswith("{"):
  64. commentBlock = True
  65. if commentBlock and (line == '}' or re.search(blockEndPattern, line) != None):
  66. commentBlock = False
  67. if not commentBlock and not line.startswith("!"):
  68. match = re.search(findPattern, line)
  69. if match != None:
  70. temp = match.group().replace('\t', '').replace(' ', '').replace("','","|").replace('","','|').strip(" '\n").replace("'","|").replace('"','|').split('|')
  71. temp = [t.strip(" '\"") for t in temp]
  72. if temp[1].lower() not in ['boystat', 'exp_gain']:
  73. calltype = temp[0]
  74. loc = temp[1]
  75. fun = functionEmptyOrNamed(temp)
  76. if ("%s.qsrc" % loc) not in calledFileList:
  77. calledFileList.append("%s.qsrc" % loc)
  78. calledLocation = {"location": loc, "function": fun, "valid": 0, "reason": "%s.qsrc doesn't exist." % loc}
  79. callLine = {"callId": 0, "calltype": calltype, "call": "%s '%s', '%s'" % (calltype, loc, fun), "lineNo": lineNo}
  80. if calledLocation not in callList:
  81. callList.append(calledLocation)
  82. callLine['callId'] = callList.index(calledLocation)
  83. locationCalls['calls'].append(callLine)
  84. #endif
  85. #endif
  86. #endif
  87. lineNo += 1
  88. #endfor
  89. locationCallList.append(locationCalls)
  90. #enddedf
  91. def validateCalls(lines):
  92. calls = [c for c in callList if c['location'].lower() == lines[0].strip(' #\n').lower()]
  93. if validate == 'all' and (calls == None or len(calls) == 0):
  94. neverCalledLocations.append(lines[0].strip(' #\n').lower())
  95. for call in calls:
  96. if call['function'] == 'start' or call['function'] == 'Start' or call['function'] == '':
  97. call['valid'] = 1
  98. call['reason'] = ""
  99. else:
  100. call['reason'] = "No $ARGS[0] or ARGS[i] expecting '%s'" % call['function']
  101. findPattern = "\$*(args|ARGS)\[(0|i)\]\s*=\s*('|\")%s('|\")" % call['function']
  102. for line in lines:
  103. match = re.search(findPattern, line)
  104. if match != None:
  105. call['valid'] = 1
  106. call['reason'] = ''
  107. break
  108. #endfor
  109. #endif
  110. #endfor
  111. #enddef
  112. runtime = [0,1,2,3,4,5,6,7,8,9,10,11,12]
  113. runtime[0] = time.perf_counter_ns()
  114. runtime_text = ["Setup","Build callfile","Build call list","Index call list","Validate calls","Validation finished","Build report [invalid calls]","Sort invalid calls","Build report [location list]","Sort location list","Generate report file","Build report file","Report finished, all done"]
  115. assert (len(sys.argv) == 2 or len(sys.argv) == 3 or len(sys.argv) == 4), "usage:\ncallvalidator.py source=<src_input_dir> [file=<filename> | list=<listfilename>] [folder=<folder>]"
  116. idir = str(sys.argv[1].replace("source=", ""))
  117. if len(sys.argv) == 4:
  118. vdir = str(sys.argv[3].replace("folder=", ""))
  119. else:
  120. vdir = idir
  121. validationTarget = ''
  122. runtime[1] = time.perf_counter_ns()
  123. if len(sys.argv) > 2 and "file=" in str(sys.argv[2]):
  124. validationTarget = sys.argv[2].replace("file=", "")
  125. callFileList = [join(vdir, sys.argv[2].replace("file=", ""))]
  126. validate = 'file'
  127. if len(sys.argv) > 2 and "list=" in str(sys.argv[2]):
  128. validationTarget = sys.argv[2].replace("list=", "")
  129. callFileList = loadValidationList(join(sys.argv[3].replace("folder=", ""), sys.argv[2].replace("list=", "")))
  130. validate = 'list'
  131. if validate == 'all':
  132. validationTarget = idir
  133. callFileList = [join(idir,f) for f in listdir(idir) if ".qsrc" in f]
  134. # build a list of all the calls happening
  135. runtime[2] = time.perf_counter_ns()
  136. for file in callFileList:
  137. with io.open(file, 'rt', encoding='utf-8') as ifile:
  138. lines = ifile.readlines()
  139. processLines(lines, ifile.name)
  140. #endwith
  141. #endfor
  142. runtime[3] = time.perf_counter_ns()
  143. for call in callList:
  144. call['callId'] = callList.index(call)
  145. runtime[4] = time.perf_counter_ns()
  146. # validating that all the calls are for valid locations
  147. for file in calledFileList:
  148. if file not in ['boyStat.qsrc', 'exp_gain.qsrc']:
  149. try:
  150. with io.open(join(idir, file), 'rt', encoding='utf-8') as ifile:
  151. lines = ifile.readlines()
  152. validateCalls(lines)
  153. except IOError:
  154. pass
  155. #endwith
  156. #endif
  157. #endfor
  158. runtime[5] = time.perf_counter_ns()
  159. # create the call validity file and a list of files that call invalid locations
  160. timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
  161. txtname = 'call_validity [%s] - %s.txt' % (validationTarget, time.strftime('%Y%m%d%H%M%S', time.localtime()))
  162. htmlname = 'call_validity [%s] - %s.html' % (validationTarget, time.strftime('%Y%m%d%H%M%S', time.localtime()))
  163. callheads = []
  164. callIds = []
  165. invalidCalls = []
  166. noInvLoc = 0
  167. noNonExistingLoc = 0
  168. noInvFun = 0
  169. noInvCall = 0
  170. noLocNeverCalled = len(neverCalledLocations)
  171. runtime[6] = time.perf_counter_ns()
  172. currLoc = ''
  173. for call in callList:
  174. if call['valid'] == 0:
  175. if currLoc != call['location']:
  176. noInvLoc += 1
  177. if ".qsrc doesn't exist" in call['reason']:
  178. noNonExistingLoc += 1
  179. callIds.append(call['callId'])
  180. invalidCalls.append(call)
  181. #enfif
  182. #endfor
  183. runtime[7] = time.perf_counter_ns()
  184. invalidCalls = sorted(invalidCalls, key=lambda k: (k['location'].lower(), k['function'].lower()))
  185. noInvFun = len(invalidCalls)
  186. runtime[8] = time.perf_counter_ns()
  187. for locationCall in locationCallList:
  188. calls = locationCall['calls']
  189. Ids = [call['callId'] for call in calls]
  190. if any(x in Ids for x in callIds):
  191. callheads.append(locationCall)
  192. #endfor
  193. #endfor
  194. runtime[9] = time.perf_counter_ns()
  195. callheads = sorted(callheads, key=lambda k: (k['calllocation'].lower()))
  196. noInvCall = len(callheads)
  197. runtime[10] = time.perf_counter_ns()
  198. location = ''
  199. txtInvalidLines = []
  200. for call in invalidCalls:
  201. if location != '' and location.lower() != call['location'].lower():
  202. txtInvalidLines.append('\n')
  203. txtInvalidLines.append(" '%s', '%s' : %s\n" % (call['location'], call['function'], call['reason']))
  204. location = call['location']
  205. #endfor
  206. txtLocationLines = []
  207. for head in callheads:
  208. txtLocationLines.append(" ---- %s [%s]:\n" % (head["calllocation"], head["fileName"]))
  209. for call in head['calls']:
  210. if call['callId'] in callIds:
  211. target = callList[call['callId']]
  212. txtLocationLines.append(" invalid call on line %04d: %s '%s', '%s'\t: %s\n" % (call['lineNo'], call['calltype'], target['location'], target['function'], target['reason']))
  213. #endif
  214. #endfor
  215. txtLocationLines.append('\n')
  216. #endfor
  217. runtime[11] = time.perf_counter_ns()
  218. try:
  219. with io.open(txtname, 'w', encoding='utf-8') as ofile:
  220. ofile.write("----- Summary -----\n")
  221. ofile.write(" Locations called incorrectly : %03d\n" % noInvLoc)
  222. ofile.write(" Non-existing locations called : %03d\n" % noNonExistingLoc)
  223. ofile.write(" Number of incorrect calls : %03d\n" % noInvFun)
  224. ofile.write(" Locations making incorrect calls: %03d\n" % noInvCall)
  225. if validate == 'all':
  226. ofile.write(" Locations never called : %03d\n" % noLocNeverCalled)
  227. ofile.write("\n")
  228. if validate == 'list':
  229. ofile.write("----- Validated files -----\n")
  230. if len(callFileList) > 10:
  231. ofile.write(" The list contains %d files. Please see the list in %s.\n" % (len(callFileList), validationTarget))
  232. else:
  233. for file in callFileList:
  234. ofile.write(" %s\n" % file)
  235. #endif
  236. ofile.write('\n')
  237. #endif
  238. ofile.write("----- List of Invalid calls -----\n")
  239. ofile.write("\n")
  240. for line in txtInvalidLines:
  241. ofile.write(line)
  242. ofile.write('\n')
  243. ofile.write("----- List of Locations and invalid calls they make -----\n")
  244. ofile.write('\n')
  245. for line in txtLocationLines:
  246. ofile.write(line)
  247. if validate == 'all':
  248. ofile.write("----- List of Locations that are never called -----\n")
  249. for line in neverCalledLocations:
  250. ofile.write("%s\n" % line)
  251. #endwith
  252. except IOError as e:
  253. raise SystemExit("ERROR: call validity file was not created! REASON: %s" % e.strerror)
  254. #endtry_except
  255. runtime[12] = time.perf_counter_ns()
  256. max = len(runtime)
  257. index = 0
  258. while index < max:
  259. print("%s: %s" % (runtime_text[index], runtime[index]))
  260. index += 1