testhelper.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  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. import platform
  12. from clr import *
  13. import testfuncs
  14. import subprocess
  15. import os.path
  16. from os.path import join, normpath, basename
  17. import re
  18. import glob
  19. # for reporting in detailed mode
  20. failList = []
  21. # remember the last regexp patch position to avoid repeating scan from zero
  22. lastEnd = 0
  23. def reset():
  24. global lastEnd
  25. lastEnd = 0
  26. def found(needle, haystack):
  27. '''search a regex in a haystack'''
  28. global lastEnd # ugly state retention to avoid headaches with the lambdas calling this.
  29. words = re.split("\s", needle)
  30. words = filter(None, words)
  31. words = [re.escape(w) for w in words] # need to interpret each token as a string needle, not a regex pattern.
  32. pttrn = "\s+".join(words) # free number of spaces between each needle.
  33. p = re.compile(pttrn)
  34. consumed = haystack[lastEnd:] # we search on the rest only. not the whole string at each time.
  35. matchObject = p.search(consumed)
  36. if matchObject:
  37. lastEnd = matchObject.end()
  38. return True
  39. return False
  40. # parse the argument mentioned in the shader source file Ex : Cmdargs: --namespace=vk or Cmdargs: ['--unique-idx', '--root-sig', '--root-const', '0']
  41. def parse_strlist(sl):
  42. clean = re.sub("[\[\],\s]","",sl)
  43. splitted = re.split("[\'\"]",clean)
  44. values_only = [s for s in splitted if s != '']
  45. return values_only
  46. def verifyEmissionPattern(azslFile, patternsFileName, compilerPath, silent, argList):
  47. """
  48. Compiles @azslFile and validates all the predicates in @patternsFileName.
  49. """
  50. if not os.path.exists(patternsFileName):
  51. print ("Pattern file not found: " + patternsFileName)
  52. return False
  53. arg = []
  54. with io.open(patternsFileName, "r", encoding="utf-8") as f:
  55. for line in f:
  56. if line.find("Cmdargs") >= 0:
  57. line = line[line.rfind(':')+1:]
  58. arg = parse_strlist(line)
  59. if arg and not silent: print ("Adding extra command line arguments: " + str(arg))
  60. # now arg has additional arguments required for the test
  61. shaderCode, ok = testfuncs.buildAndGet(azslFile, compilerPath, silent, argList + arg)
  62. if ok:
  63. if not silent: print (style.BRIGHT + fg.CYAN + "Now to check emission patterns for " + patternsFileName + style.RESET_ALL)
  64. # normalizes the shader code by inserting spaces around identifiers stuck to other things.
  65. # eg : 'func()' will become 'func ( )'
  66. allidents = re.split("([a-zA-Z_]+[a-zA-Z_0-9]*)|(\.)|(,)|(::)|(;)|(\()|(\))|(<)|(>)|( )", shaderCode.decode('utf-8'))
  67. allidents = filter(lambda s: s is not None and s!="" and s!=" ", allidents) # remove empties from ['', 'code', '', 'piece']...
  68. shaderCode = " ".join(allidents)
  69. predicates = []
  70. # load the pattern file:
  71. with io.open(patternsFileName, encoding="utf-8") as f:
  72. i = 0
  73. for line in f:
  74. if line.startswith("\""):
  75. line = line.strip().strip('"') #remove quotes at start&end
  76. if not silent: print (fg.CYAN + "Verify (" + str(i) + "): " + line + style.RESET_ALL)
  77. predicates.append(lambda line=line: found(line, shaderCode))
  78. i = i+1
  79. reset()
  80. ok = testfuncs.verifyAllPredicates(predicates, shaderCode, silent)
  81. return ok
  82. # read the output of the compiler and tries to match some regex on it
  83. # to verify that the output looks like what we expect.
  84. def verifyEmissionPatterns(thefile, compilerPath, silent, argList):
  85. global failList
  86. localFailList = []
  87. result = 0
  88. base = os.path.basename(thefile)
  89. filePrefixName = os.path.dirname(thefile) + '/' + os.path.splitext(base)[0]
  90. # loop over the .txt file for the same .azsl file by matching pattern filename.txt or filename-[0-9].txt
  91. # extract the Cmdargs (command arguments) specified in the .txt file to compile the .azsl shader with those arguments
  92. for file in glob.glob('%s*[0-9].txt' % filePrefixName) or glob.glob('%s.txt' % filePrefixName):
  93. patterFileName = file
  94. if not verifyEmissionPattern(thefile, patterFileName, compilerPath, silent, argList):
  95. localFailList.append(patterFileName)
  96. else:
  97. result = result + 1
  98. if len(localFailList) > 0:
  99. failList.extend(localFailList)
  100. return 0
  101. else:
  102. return result
  103. def printFailedTestList(silent):
  104. global failList
  105. if not silent and len(failList) > 0:
  106. print(style.BRIGHT + fg.RED + "failed files: " + fg.WHITE + str(failList) + style.RESET_ALL)
  107. failList = [] # since the module is imported for other platforms too, reset the list
  108. def compileAndExpectError(thefile, compilerPath, silent, argList):
  109. """
  110. This function will compile @thefile, with the given @argList.
  111. Returns 1 (success) if the compilation failed AND the error code of the runtime exception
  112. produced by AZSLc matches the "#EC <code number>" expression found inside the comments of @thefile.
  113. Otherwise returns 0 (failure).
  114. """
  115. global failList
  116. result = 0
  117. options = [thefile]
  118. options.extend(argList)
  119. out, err, code = testfuncs.launchCompiler(compilerPath, options, silent)
  120. if code == 0:
  121. if not silent:
  122. print (fg.RED + style.BRIGHT + f"FAIL. Expected {thefile} to report compilation errors" + style.RESET_ALL)
  123. failList.append(thefile)
  124. return 0 # Failure
  125. # Read the expected error code from the source file.
  126. f = io.open(thefile, 'r', encoding="utf-8")
  127. azslCode = f.read()
  128. f.close()
  129. expectedErrorCode = testfuncs.findTokenToInt(azslCode, r"#EC\s\d*")
  130. outputEC = testfuncs.findTokenToInt(err.decode('utf-8'), r"error\s#\d*:")
  131. if outputEC == expectedErrorCode:
  132. return 1 # Success
  133. if not silent:
  134. print (fg.RED + style.BRIGHT + f"FAIL. Expected error code {expectedErrorCode} from {thefile}, instead got error code {outputEC}" + style.RESET_ALL)
  135. failList.append(thefile)
  136. return 0 # Success