testbuilder.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. #!/usr/bin/env python
  2. # usage: test_txtmerge.py <-help>|<-h>|<?> or ntest_txtmerge.py <src_input_dir> <testsuite file>
  3. # builds a test version of the QSP game
  4. import os
  5. import sys
  6. import re
  7. import io
  8. import xml.etree.ElementTree as ET
  9. from datetime import datetime
  10. from os.path import join, isfile
  11. assert len(sys.argv) in [3,4], "usage:\ntest_txtmerge.py <-help>|<-h>|<?> or ntest_txtmerge.py <src_input_dir> <testsuite file> <save_enabled>"
  12. if str(sys.argv[1]) in ['-help', '-h', '?']:
  13. print("testbuilder.py requires two parameters to run properly:")
  14. print("\t<src_input_dir>: the folder where the files of the tested application are relative to the root folder, e.g. 'location'")
  15. print("\t<testsuite file>: the testsuite config file inlcuding the folder where it can be found relative to the root folder, e.g. 'test\test-suite.qproj'")
  16. print("Other parameters that can be used:")
  17. print("\t-help|-h|? : prints this help text")
  18. print("")
  19. exit
  20. else:
  21. idir = str(sys.argv[1])
  22. suitefile = str(sys.argv[2])
  23. save_enabled = str(sys.argv[3]).lower() == 'true'.lower()
  24. # read the project xml file first
  25. # let's do this later in order to implement directory structure
  26. tree = ET.parse(suitefile)
  27. root = tree.getroot()
  28. project = root.attrib['project']
  29. testsuite = root.attrib["name"]
  30. testdir = root.attrib["folder"]
  31. ################################################################################################
  32. #
  33. # Validate that the 'location', 'function' called in the tests actually exist in the code.
  34. #
  35. #####
  36. # Iterate through the test locations and create a list of `$LOCATIONNAME`, `$FUNCTIONNAME` pairs called
  37. testCalls = []
  38. test_locations = []
  39. test_run_list = []
  40. tested_locations = []
  41. valid_locations = []
  42. dependency_locations = []
  43. for location in root.iter('TestLocation'):
  44. tname = location.attrib['name']
  45. tname = tname.replace("$","_")
  46. try:
  47. testdir = location.attrib['folder']
  48. except:
  49. pass
  50. locationPattern = "(?:\$LOCATIONNAME\s*=\s*(?:'|\"))(\w*)(?:'|\")"
  51. functionPattern = "(?:\$FUNCTIONNAME = (?:'|\"))(\w*)(?:'|\")"
  52. testFunctionPattern = "(?:@run\s*(?:'|\")?)(\w+)(?:'|\")?"
  53. try:
  54. with io.open(join(testdir,tname + '.qsrc'), 'rt', encoding='utf-8') as ifile:
  55. text = ifile.read()
  56. location = re.findall(locationPattern, text)[0]
  57. for testFunction in re.findall(testFunctionPattern, text):
  58. start_calls = "gs '%s', '%s'" % (tname, testFunction)
  59. if start_calls not in test_run_list: test_run_list.append(start_calls)
  60. for function in re.findall(functionPattern, text):
  61. call = {"location" : location, "function" : function, "validcall": 0}
  62. if call not in testCalls: testCalls.append(call)
  63. if tname not in test_locations: test_locations.append(tname)
  64. if location not in tested_locations: tested_locations.append(location)
  65. except IOError:
  66. print("WARNING: missing location %s" % tname)
  67. pass
  68. #endtry
  69. #endfor <- TestLocations
  70. tname = ''
  71. # load the valid calls from the 'validcallsfortesting.txt' file and compare the test calls.
  72. try:
  73. with io.open(join('validcallsfortesting.txt'), 'rt', encoding='utf-8') as ifile:
  74. text = ifile.read()
  75. validCalls = [{"location": call[0], "function": call[1], "validcall": 0} for call in re.findall("(\$?\w+)(?:\:)(\w*)", text)]
  76. for call in validCalls:
  77. if call['location'] not in valid_locations: valid_locations.append(call['location'])
  78. for call in (call for call in testCalls if not call['validcall']):
  79. if call['location'] not in valid_locations:
  80. for update in (update for update in testCalls if update['location'] == call['location']):
  81. call['validcall'] = -2
  82. tested_locations.remove(call['location'])
  83. elif call['function'] == '' or call in validCalls:
  84. call['validcall'] = 1
  85. else:
  86. call['validcall'] = -1
  87. except FileNotFoundError as e:
  88. raise SystemExit("ERROR: validcallsfortesting.txt doesn't exist, can't validate if tests are calling valid location. Please run callvalidator.py then run testbuilder.py again. REASON: %s" % e.strerror)
  89. except Exception as e:
  90. raise SystemExit(e)
  91. iname = ''
  92. # Create the `testvalidity_info.qsrc` file with the validity check results:
  93. infoqsrc = join(testdir,"testvalidity_info.qsrc")
  94. try:
  95. if isfile(infoqsrc):
  96. os.remove(infoqsrc)
  97. with io.open(infoqsrc, 'w', encoding='utf-8') as ofile:
  98. ofile.write("#testvalidity_info\n")
  99. ofile.write("\n")
  100. ofile.write("_ISTEST = 1\n")
  101. ofile.write("\n")
  102. for entry in testCalls:
  103. ofile.write("_ISVALIDCALL['%s,%s']=%d\n" % (entry['location'], entry['function'], entry['validcall']))
  104. ofile.write("\n")
  105. ofile.write("--- testvalidity_info --------------------------------\n")
  106. except IOError as e:
  107. raise SystemExit("ERROR: testvalidity_info.qsrc not created! REASON: %s" % e.strerror)
  108. oname = ''
  109. # Create a list of locations that are needed by the valid tested locations
  110. checklist = tested_locations.copy()
  111. for location in checklist:
  112. iname = location.replace("$","_")
  113. with io.open(join(idir,iname+'.qsrc'), 'rt', encoding='utf-8') as ifile:
  114. text = ifile.read()
  115. # We are interested only in gs and xgs calls, and only the location part.
  116. callLinePattern = "(?:gs|xgs)(?:\s*(?:'|\"))(\w+)(?:'|\")(?:(?:,\s*)(?:'|\")(?:\w*)(?:'|\"))?"
  117. dependencies = re.findall(callLinePattern, text)
  118. for dependency in dependencies:
  119. if dependency not in dependency_locations and dependency not in checklist:
  120. #checklist.append(dependency)
  121. dependency_locations.append(dependency)
  122. #endfor dependencies
  123. #endfor <- tested_locations
  124. # Add every `$LOCATIONNAME`, `$FUNCTIONAME`, `ISVALID` as an `_ISVALIDCALL['$LOCATIONNAME,$FUNCTIONNAME'] = ISVALID`
  125. # line where `ISVALID` is `0` for `false` and `1` for `true`.
  126. ################################################################################################
  127. #
  128. # Create the `start.qsrc` file tests for the test suite.
  129. #
  130. #####
  131. starqsrc = join(testdir,"start.qsrc")
  132. try:
  133. if isfile(starqsrc): os.remove(starqsrc)
  134. with io.open(starqsrc, 'w', encoding='utf-8') as ofile:
  135. ofile.write("# start\n")
  136. ofile.write("\n")
  137. ofile.write("killall\n")
  138. ofile.write("close all\n")
  139. ofile.write("usehtml = 1\n")
  140. ofile.write("debug = 1\n")
  141. ofile.write("showstat 0\n")
  142. ofile.write("showobjs 0\n")
  143. ofile.write("showinput 0\n")
  144. ofile.write("showacts 0\n")
  145. ofile.write("disablescroll = 1\n")
  146. ofile.write("$fname = 'Tahoma'\n")
  147. ofile.write("fsize = 12\n")
  148. #ofile.write("$onnewloc = 'testlocationlimiter'\n")
  149. ofile.write(" \n")
  150. ofile.write("!! ------------------------------------------------------------------------------------\n")
  151. ofile.write("!! Test Setup\n")
  152. ofile.write("!! ------------------------------------------------------------------------------------\n")
  153. ofile.write("\n")
  154. ofile.write("_ISTEST = 1\n")
  155. ofile.write("$_TESTSUITE = '%s'\n" % testsuite)
  156. ofile.write("$_TESTBUILDTIME = '%s'\n" % datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
  157. ofile.write("$_TESTFILETIME = '%s'\n" % datetime.now().strftime('%Y%m%d-%H%M%S'))
  158. ofile.write("_STAT_BLOCKED = 1\n")
  159. ofile.write("\n")
  160. ofile.write("gs 'testvalidity_info'\n")
  161. ofile.write("\n")
  162. ofile.write("!! ------------------------------------------------------------------------------------\n")
  163. ofile.write("!! Call the tests to run\n")
  164. ofile.write("!! ------------------------------------------------------------------------------------\n")
  165. ofile.write("\n")
  166. for test in test_run_list: ofile.write("\t%s\n" % test)
  167. ofile.write("\n")
  168. ofile.write("!! ------------------------------------------------------------------------------------\n")
  169. ofile.write("!! Display the test results\n")
  170. ofile.write("!! ------------------------------------------------------------------------------------\n")
  171. ofile.write("gs 'testframework', 'displayTestResults'\n")
  172. ofile.write("\n")
  173. if save_enabled:
  174. ofile.write("!! ------------------------------------------------------------------------------------\n")
  175. ofile.write("!! Save the test results\n")
  176. ofile.write("!! ------------------------------------------------------------------------------------\n")
  177. ofile.write("\n")
  178. ofile.write("if _TESTSAVED = 0: gs 'testframework', 'saveTestResults'\n")
  179. ofile.write("\n")
  180. #endif <- saveallowed
  181. ofile.write("--- start --------------------------------\n")
  182. except IOError as e:
  183. raise SystemExit("ERROR: start.qsrc could not be created! REASON: %s" % e.strerror)
  184. #endtry
  185. ################################################################################################
  186. #
  187. # Create the `test_x.qproj` file to include all the test locations and the required game locations
  188. #
  189. #####
  190. filename = "%s-%s-testsuite" % (project, testsuite)
  191. qprojfile = "%s.qproj" % filename
  192. try:
  193. if isfile(qprojfile):
  194. os.remove(qprojfile)
  195. with io.open(qprojfile, 'w', encoding='utf-8') as ofile:
  196. ofile.write('<?xml version="1.0" encoding="utf-8"?>\n')
  197. ofile.write('<QGen-project version="4.0.0 beta 1">\n')
  198. ofile.write('\t<Structure>\n')
  199. ofile.write('\t\t<Folder name="framework" folder="%s">\n' % testdir)
  200. ofile.write('\t\t\t<Location name="start"/>\n')
  201. ofile.write('\t\t\t<Location name="testframework"/>\n')
  202. ofile.write('\t\t\t<Location name="testvalidity_info"/>\n')
  203. ofile.write('\t\t\t<Location name="testlocationlimiter"/>\n')
  204. ofile.write('\t\t</Folder>\n')
  205. ofile.write('\t\t<Folder name="test-suite" folder="%s">\n' % testdir)
  206. for location in test_locations:
  207. ofile.write('\t\t\t<Location name="%s"/>\n' % location)
  208. ofile.write('\t\t</Folder>\n')
  209. ofile.write('\t\t<Folder name="code-to-test" folder="%s">\n' % idir)
  210. for entry in tested_locations:
  211. ofile.write('\t\t\t<Location name="%s"/>\n' % entry)
  212. ofile.write('\t\t</Folder>\n')
  213. ofile.write('\t\t<Folder name="dependency-locations" folder="%s">\n' % idir)
  214. for entry in dependency_locations:
  215. ofile.write('\t\t\t<Location name="%s"/>\n' % entry)
  216. ofile.write('\t\t</Folder>\n')
  217. ofile.write('\t</Structure>\n')
  218. ofile.write('</QGen-project>\n')
  219. except IOError as e:
  220. raise SystemExit("ERROR: testvalidity_info.qsrc not created! REASON: %s" % e.strerror)
  221. oname = ''
  222. # This will need to use the the locations used in the test files and other locations based on the
  223. # dependency graph.
  224. # Also can define some mandatory locations - `stat`, for example.
  225. ################################################################################################
  226. #
  227. # Create the *.txt file from *.qsrc for txt2game
  228. #
  229. ####
  230. tree = ET.parse(qprojfile)
  231. root = tree.getroot()
  232. txtfile = "%s.txt" % filename
  233. with io.open(txtfile, 'w', encoding='utf-16', newline='\r\n') as ofile:
  234. try:
  235. for folder in root.iter('Folder'):
  236. dir = folder.attrib["folder"]
  237. for location in folder.iter('Location'):
  238. iname = location.attrib['name']
  239. iname = iname.replace("$","_")
  240. try:
  241. with io.open(join(dir,iname + '.qsrc'), 'rt', encoding='utf-8') as ifile:
  242. text = ifile.read()
  243. # make sure there's a line at the end of file
  244. # (why wouldn't there be one? WINDOWS!
  245. if text[-1] != u'\n':
  246. text += u'\n\n'
  247. ofile.write(text)
  248. except IOError:
  249. print("WARNING: missing location %s" % iname)
  250. #endfor <- locations
  251. #endfor <- folders
  252. # Writing a little file so the batch file knows what the .txt and .qsp file names are
  253. with io.open("_temp-filename.txt", 'w', encoding='utf-8') as ofile:
  254. ofile.write(filename)
  255. if isfile(starqsrc): os.remove(starqsrc)
  256. if isfile(infoqsrc): os.remove(infoqsrc)
  257. if isfile(qprojfile): os.remove(qprojfile)
  258. except IOError as e:
  259. raise SystemExit("WARNING: %s was not created. Error: %s" % (txtfile, e.strerror))