testapp.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. """
  4. Copyright (c) Contributors to the Open 3D Engine Project.
  5. For complete copyright and license terms please see the LICENSE at the root of this distribution.
  6. SPDX-License-Identifier: Apache-2.0 OR MIT
  7. """
  8. import sys
  9. import os
  10. import io
  11. from argparse import ArgumentParser
  12. from os.path import join, normpath, basename
  13. import inspect
  14. clrpath = os.path.join(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))), "clr.py")
  15. print(f"clr path is {clrpath}")
  16. import importlib.util
  17. spec = importlib.util.spec_from_file_location("clr", clrpath)
  18. clrmodule = importlib.util.module_from_spec(spec)
  19. sys.modules["clr"] = clrmodule
  20. spec.loader.exec_module(clrmodule)
  21. globals().update({v: vars(clrmodule)[v] for v in ["fg", "bg", "style"]})
  22. import re
  23. import testfuncs
  24. import importlib
  25. from timeit import default_timer as timer
  26. from datetime import timedelta
  27. class testResult:
  28. def __init__(self, p, t, f, m):
  29. self.numPass = p
  30. self.numTodo = t
  31. self.numFail = f
  32. self.numEC = m #EC = error code
  33. def add(self, other):
  34. self.numPass += other.numPass
  35. self.numTodo += other.numTodo
  36. self.numFail += other.numFail
  37. self.numEC += other.numEC
  38. '''Returns a testResult. Prints a color-coded status before returning. Total number of tests is (numPass + numTodo + numFail).'''
  39. def GetStatusVerbose(numPass, numFail, inputsource, suffixMessage, extraColor, extraMessage):
  40. if numFail == 0:
  41. print(fg.GREEN + style.BRIGHT+ "\\\\[ OK ]// : {}".format(inputsource)+ \
  42. suffixMessage+ extraColor+ extraMessage+ style.RESET_ALL)
  43. return testResult(numPass, 0, numFail, 0)
  44. if inputsource.find("wip-") >= 0:
  45. print (fg.YELLOW+ style.BRIGHT+ "\\\\[ TODO ]// : {}".format(inputsource)+\
  46. suffixMessage+ extraColor+ extraMessage+ style.RESET_ALL)
  47. return testResult(numPass, numFail, 0, 0)
  48. if extraMessage.find("Missing ErrorCode") >= 0:
  49. print (fg.YELLOW+ style.BRIGHT+ "\\\\[ EC ]// : {}".format(inputsource)+\
  50. suffixMessage+ extraColor+ extraMessage+ style.RESET_ALL)
  51. return testResult(numPass, 0, 0, numFail)
  52. print (fg.RED+ style.BRIGHT+ "\\\\[ FAILED ]// : {}".format(inputsource)+\
  53. suffixMessage+ extraColor+ extraMessage+ style.RESET_ALL)
  54. return testResult(numPass, 0, numFail, 0)
  55. '''Returns a testResult. Total number of tests is (numPass + numTodo + numFail)'''
  56. def testAdvancedScript(advancedFolder, inputsource, whatToTest, compilerPath, silent, az3rdParty):
  57. pass
  58. '''Returns a testResult. Total number of tests is (numPass + numTodo + numFail)'''
  59. def testFile(advancedFolder, inputsource, expectPass, whatToTest, compilerPath, silent, az3rdParty):
  60. '''returned integer is the number of ok-tests passed. You can use it to increment your global counter'''
  61. if inputsource.endswith(".py"):
  62. # Import the new module:
  63. # - make sure it's discoverable
  64. # - load the module
  65. # - change the current working directory
  66. sys.path.append(advancedFolder)
  67. moduleName = os.path.splitext(os.path.basename(inputsource))[0]
  68. testScriptModule = importlib.import_module(moduleName)
  69. oldCwd = os.getcwd()
  70. os.chdir(advancedFolder)
  71. # Run the test
  72. testScriptModule.doTests(compilerPath, silent, az3rdParty)
  73. # Unload the module:
  74. # - change the current working directory back
  75. # - make sure it's *not* discoverable
  76. # - delete the module
  77. os.chdir(oldCwd)
  78. sys.path.remove(advancedFolder)
  79. runResult = GetStatusVerbose(testScriptModule.result, testScriptModule.resultFailed, inputsource, "", fg.WHITE, "")
  80. del sys.modules[moduleName]
  81. return runResult
  82. options = [inputsource]
  83. if whatToTest == "[syntax]":
  84. options.append("--syntax")
  85. if whatToTest == "[semantic]":
  86. options.append("--semantic")
  87. out, err, code = testfuncs.launchCompiler(compilerPath, options, silent)
  88. (okpreds, numpreds) = testfuncs.executePredicateChecks(out)
  89. outputEC = testfuncs.findTokenToInt(err.decode('utf-8'), r"error\s#\d*:")
  90. fine = code == 0 and okpreds
  91. if expectPass == fine:
  92. if not whatToTest == "[syntax]" and not expectPass:
  93. # further verify in the case of Semantic+error check. do a second --syntax pass to verify it passes.
  94. # because semantic tests should not have syntax errors.
  95. if whatToTest == "[semantic]": options.pop()
  96. options.append("--syntax")
  97. out, err, syntaxCode = testfuncs.launchCompiler(compilerPath, options, silent)
  98. if syntaxCode != 0:
  99. return GetStatusVerbose(0, 1, inputsource, "", fg.RED,"Invalid syntax in a 'semantic error' check. These tests must have valid syntax.")
  100. predsOkMsg = " (" + str(numpreds) + " predicates ok)" if numpreds else ""
  101. # check if error code matches from the azlsc and azsl file
  102. f = io.open(inputsource, 'r', encoding="latin-1")
  103. azslCode = f.read()
  104. f.close()
  105. errorCode = testfuncs.findTokenToInt(azslCode, r"#EC\s\d*")
  106. if errorCode == -2:
  107. return GetStatusVerbose(0, 1, inputsource, "", fg.RED, ' Error code does not contain an integer')
  108. if errorCode != -1 and outputEC != errorCode:
  109. return GetStatusVerbose(0, 1, inputsource, "", fg.RED, ' Error code returned form the compiler(\"{0}\") does not match the one expected in the azsl file(\"{1}\")'.format(outputEC, errorCode))
  110. # errorcodes is only supported for Semantic related errors.
  111. # Log the files that don't have a specific errorcode assigned to their exceptions
  112. if outputEC == 1 and not whatToTest == "[syntax]":
  113. return GetStatusVerbose(1, 1, inputsource, "", fg.YELLOW, ' Missing ErrorCode')
  114. return GetStatusVerbose(1, 0, inputsource, predsOkMsg, fg.WHITE, "")
  115. else:
  116. expectedCode = 0 if expectPass else 1
  117. gotExpected = expectedCode == code
  118. codeMessage = fg.WHITE + " got expected code " + str(code)
  119. errorColor = (fg.WHITE if okpreds else fg.RED)
  120. errorMessage = " predicates: " + ("None" if numpreds==0 else str(okpreds))
  121. if not gotExpected:
  122. codeMessage = " expected code " + str(expectedCode) + " (but got " + str(code) + ")"
  123. return GetStatusVerbose(0, 1, inputsource, codeMessage, errorColor, errorMessage)
  124. '''Returns a testResult. Total number of tests is (numPass + numTodo + numFail + numEC)'''
  125. def runTests(testsRoot, paths, compiler, verboseLevel, az3rdParty):
  126. compiler = os.path.abspath(compiler) # TODO Does this work on Jenkins? Or do we need another solution?
  127. numTotal = testResult(0, 0, 0, 0)
  128. argsAreOnlyFiles = all([os.path.isfile(join(testsRoot, p)) for p in paths])
  129. if argsAreOnlyFiles:
  130. for f in paths:
  131. f = join(testsRoot, f)
  132. whatToTest = "[everything]"
  133. if "syntax" in f.lower(): whatToTest = "[syntax]"
  134. if "semantic" in f.lower(): whatToTest = "[semantic]"
  135. if "samples" in f.lower(): whatToTest = "[samples]"
  136. if verboseLevel > 0: print (fg.MAGENTA+ style.BRIGHT+ "== individual check "+ f+ " =="+ style.RESET_ALL)
  137. numTotal.add( testFile("Advanced", f, "error" not in f.lower(), whatToTest, compiler, True if verboseLevel < 2 else False, az3rdParty) )
  138. else:
  139. for dir in paths:
  140. joinDir = join(testsRoot, dir)
  141. for root, dirs, files in os.walk(joinDir):
  142. for f in files:
  143. expectPass = "error" not in root.lower()
  144. whatToTest = "[everything]"
  145. if "syntax" in root.lower(): whatToTest = "[syntax]"
  146. if "semantic" in root.lower(): whatToTest = "[semantic]"
  147. if "samples" in root.lower(): whatToTest = "[samples]"
  148. advancedTest = "advanced" in root.lower() # these tests have their own python routines
  149. if advancedTest:
  150. if f.endswith(".py"):
  151. if verboseLevel > 0: print (fg.MAGENTA+ style.BRIGHT+ "== advanced script "+ join(root,f)+ style.RESET_ALL)
  152. numTotal.add( testFile(joinDir, join(root, f), expectPass, whatToTest, compiler, True if verboseLevel < 2 else False, az3rdParty) )
  153. elif f.endswith(".azsl"):
  154. whatmust = "must [pass]" if expectPass else "must [fail]"
  155. if verboseLevel > 0: print (fg.MAGENTA + style.BRIGHT + "== start to build " + join(root,f) + " == "+ whatmust + whatToTest + style.RESET_ALL)
  156. numTotal.add( testFile(joinDir, join(root, f), expectPass, whatToTest, compiler, True if verboseLevel < 2 else False, az3rdParty) )
  157. return (numTotal, argsAreOnlyFiles)
  158. '''Returns a testResult. Total number of tests is (numPass + numTodo + numFail + numEC)'''
  159. def runAll(testsRoot, paths, compiler, verboseLevel, az3rdParty):
  160. numAllTests, argsWereOnlyFiles = runTests(testsRoot, paths, compiler, verboseLevel, az3rdParty)
  161. if argsWereOnlyFiles:
  162. return numAllTests
  163. # do PAL only for non-specific test runs.
  164. platformsDir = join(testsRoot, "../Platform/")
  165. if os.path.exists(platformsDir):
  166. subDirs = [join(platformsDir, dir) for dir in os.listdir(platformsDir) if os.path.isdir(join(platformsDir, dir))]
  167. for d in subDirs:
  168. print (fg.WHITE + style.BRIGHT+ "Per platform testing (" + d + ")" + style.RESET_ALL)
  169. platformTests = join(d, "tests")
  170. if os.path.isdir(platformTests):
  171. numAllTests.add( runTests(platformTests, paths, compiler, verboseLevel, az3rdParty)[0] )
  172. else: print (fg.GREEN + style.BRIGHT + " ... no extra tests found." + style.RESET_ALL)
  173. return numAllTests
  174. if __name__ == "__main__":
  175. os.system('') # activate VT100 mode for windows console
  176. try:
  177. import yaml
  178. except ImportError as err:
  179. print ( fg.YELLOW + style.BRIGHT + "It seems your python environment lacks pyyaml. Run first through project-root's \"test.and.py\" (or pip install it)" + style.RESET_ALL )
  180. if input("Continue (may result in false failures)? y/n:").lower() != "y":
  181. exit(0)
  182. parser = ArgumentParser()
  183. parser.add_argument(
  184. '--path', dest='path',
  185. type=str, nargs='*',
  186. help='the directories with test files (takes . if not provided)',
  187. )
  188. parser.add_argument(
  189. '--compiler', dest='compiler',
  190. type=str,
  191. help='the path to the compiler exe',
  192. )
  193. parser.add_argument(
  194. '--silent', dest='silent',
  195. action='store_true', default=False,
  196. help="not show compiler's stdout",
  197. )
  198. parser.add_argument(
  199. '--az3rdParty', dest='az3rdParty',
  200. type=str,
  201. help="The path to amazon DXC)",
  202. )
  203. args = parser.parse_args()
  204. paths = args.path
  205. if paths is None: paths = ["."]
  206. # if all paths are files (i.e. do not contain even one directory)
  207. verboseLevel = 1 if args.silent else 2
  208. # chrono
  209. startTime = timer()
  210. # go
  211. numAllTests = runAll(".", paths, args.compiler, verboseLevel, args.az3rdParty)
  212. numTotal = numAllTests.numPass + numAllTests.numTodo + numAllTests.numFail + numAllTests.numEC
  213. # measure
  214. endTime = timer()
  215. # print stats
  216. print (fg.WHITE + style.BRIGHT + "FINISHED. Total = {}".format(numTotal) + style.RESET_ALL)
  217. print (fg.GREEN + style.BRIGHT + "PASS = {}".format(numAllTests.numPass) + fg.WHITE+ " /{}".format(numTotal) + style.RESET_ALL)
  218. print (fg.YELLOW + style.BRIGHT + "TODO = {}".format(numAllTests.numTodo) + fg.WHITE+ " /{}".format(numTotal) + style.RESET_ALL)
  219. print (fg.YELLOW + style.BRIGHT + "Missing EC = {}".format(numAllTests.numEC) + fg.WHITE+ " /{}".format(numTotal) + style.RESET_ALL)
  220. print (fg.RED + style.BRIGHT + "FAIL = {}".format(numAllTests.numFail) + fg.WHITE+ " /{}".format(numTotal) + style.RESET_ALL)
  221. td = timedelta(seconds=(endTime - startTime))
  222. print (fg.CYAN + style.BRIGHT + "Time taken: " + fg.WHITE + str(td) + style.RESET_ALL)