respect-emit-preprocessor-line-directives.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  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 re
  11. sys.path.append("..")
  12. from clr import *
  13. import testfuncs
  14. '''
  15. This test suite validates that azslc respects the preprocessor
  16. #line <number> <filepath>
  17. directives when outputting errors.
  18. The idea is that when reporting errors it should produce meaningful filenames
  19. and line locations from the orignal file where the error is actually coming from.
  20. '''
  21. def validateFilesAppearInLineDirectives(hlslContent, fileList, silent):
  22. """
  23. @fileList List of files to search for in @hlslContent.
  24. It is treated as a stack and we expect the line matching
  25. to occur in the order as they appear in this list
  26. """
  27. regexp = re.compile('#\s*line\s+\d+\s*"(.*)"$')
  28. hlslLines = hlslContent.splitlines()
  29. found0 = False
  30. for hlslLine in hlslLines:
  31. m = regexp.match(hlslLine)
  32. if not m:
  33. continue
  34. f0 = m.group(1).endswith(fileList[0]) # check top of stack
  35. f1 = len(fileList) > 1 and m.group(1).endswith(fileList[1]) # or second position to allow progression in the list
  36. if f0 or f1:
  37. if found0 and f1: del fileList[0] # forget about a file only after its potential repetition is finished
  38. if len(fileList) == 0:
  39. break;
  40. found0 = f0
  41. else: print(fg.RED + f"problem: was expecting to find {fileList[0]} or {fileList[1]} (but got {m.group(1).rsplit('/',1)[-1]})" + fg.RESET)
  42. return len(fileList) <= 1
  43. def testSampleFileCompilationEmitsPreprocessorLineDirectives(theFile, compilerPath, silent):
  44. if not silent:
  45. print (fg.CYAN+ style.BRIGHT+
  46. "testSampleFileCompilationEmitsPreprocessorLineDirectives: "+
  47. "Verifying sample file compiles and #line directives are emitted..."+
  48. style.RESET_ALL)
  49. stdout, ok = testfuncs.buildAndGet(theFile, compilerPath, silent, [])
  50. stdout = stdout.decode('utf-8')
  51. ok = validateFilesAppearInLineDirectives(stdout,
  52. ["srg_semantics.azsli", "level0.azsli", "level1.azsli", "level2.azsli", "main.azsl"],
  53. silent)
  54. return ok
  55. def CreateTmpFileWithSyntaxError(theFile, goodSearchLine, badReplaceLine):
  56. """
  57. Takes a reference file and creates of temporary clone file with a known good line (@goodSearchLine)
  58. that gets replaced with a known bad line (@badReplaceLine)
  59. """
  60. dirName, fileName = os.path.split(theFile)
  61. tmpFilePath = os.path.join(dirName, "{}.tmp".format(fileName))
  62. if os.path.exists(tmpFilePath): os.remove(tmpFilePath)
  63. foundGoodSearchLine = False
  64. tmpFileContent = []
  65. with open(theFile) as fp:
  66. for cnt, line in enumerate(fp):
  67. line = line.rstrip('\r\n')
  68. if line == goodSearchLine:
  69. tmpFileContent.append("{}\n".format(badReplaceLine))
  70. foundGoodSearchLine = True
  71. else:
  72. tmpFileContent.append("{}\n".format(line))
  73. if not foundGoodSearchLine:
  74. print(fg.RED + f"fail: {goodSearchLine} not found in {fileName}" + fg.RESET)
  75. return None
  76. with open(tmpFilePath, 'w') as outFp:
  77. outFp.writelines(tmpFileContent)
  78. return tmpFilePath
  79. def testErrorReportUsesPreprocessorLineDirectives(theFile, compilerPath, silent, goodSearchLine, badReplaceLine, searchFilename, errorType):
  80. """
  81. In this test an error will be injected at a specfic line and expect the stderr
  82. output produced by azslc to mention that the failure comes from one of the included files
  83. instead of the input file.
  84. """
  85. if not silent:
  86. print (fg.CYAN+ style.BRIGHT+
  87. "testSyntaxErrorReportUsesPreprocessorLineDirectives: "+
  88. "Verifying syntax error report..."+ style.RESET_ALL)
  89. filePathOfTmpFile = CreateTmpFileWithSyntaxError(theFile, goodSearchLine, badReplaceLine)
  90. if not filePathOfTmpFile:
  91. return False;
  92. if not silent:
  93. print (fg.CYAN+ style.BRIGHT+
  94. "testSyntaxErrorReportUsesPreprocessorLineDirectives: "+
  95. "Compiling and expecting errors..."+ style.RESET_ALL)
  96. stderr, failed = testfuncs.buildAndGetError(filePathOfTmpFile, compilerPath, silent, [])
  97. stderr = stderr.decode('utf-8')
  98. if not failed:
  99. print(fg.RED + "fail: expected non-buildable didn't report a build error." + fg.RESET)
  100. return False
  101. if not silent:
  102. print (fg.CYAN+ style.BRIGHT+
  103. "testSyntaxErrorReportUsesPreprocessorLineDirectives: "+
  104. "Good, good compiler error, now let's make sure the source file is mentioned..."+ style.RESET_ALL)
  105. if not searchFilename in stderr:
  106. print(fg.RED + f"fail: didn't find {searchFilename} in stderr" + fg.RESET)
  107. return False
  108. if not silent:
  109. print (fg.CYAN+ style.BRIGHT+
  110. "testSyntaxErrorReportUsesPreprocessorLineDirectives: "+
  111. "Good, The search file was mentioned, now let's check the type of error..."+ style.RESET_ALL)
  112. ok = errorType in stderr
  113. if not ok:
  114. print(fg.RED + f"fail: err #{errorType} not in stderr" + fg.RESET)
  115. return ok
  116. result = 0 # to define for sub-tests
  117. resultFailed = 0
  118. def doTests(compiler, silent, azdxcpath):
  119. global result
  120. global resultFailed
  121. # Working directory should have been set to this script's directory by the calling parent
  122. # You can get it once doTests() is called, but not during initialization of the module,
  123. # because at that time it will still be set to the working directory of the calling script
  124. workDir = os.getcwd()
  125. if testSampleFileCompilationEmitsPreprocessorLineDirectives(os.path.join(workDir, "RespectEmitLine/main.azsl.mcpp"),
  126. compiler, silent): result += 1
  127. else:
  128. print(fg.RED + "fail: testSampleFileCompilationEmitsPreprocessorLineDirectives" + fg.RESET)
  129. resultFailed += 1
  130. if not silent: print("\n")
  131. if testErrorReportUsesPreprocessorLineDirectives(os.path.join(workDir, "RespectEmitLine/main.azsl.mcpp"),
  132. compiler, silent,
  133. "ShaderResourceGroup SRG2 : Slot2", "ShaderResour ceGroup SRG2 : Slot2",
  134. "level2.azsli",
  135. "syntax error"): result += 1
  136. else:
  137. print(fg.RED + "fail: testErrorReportUsesPreprocessorLineDirectives" + fg.RESET)
  138. resultFailed += 1
  139. if not silent: print("\n")
  140. if testErrorReportUsesPreprocessorLineDirectives(os.path.join(workDir, "RespectEmitLine/main.azsl.mcpp"),
  141. compiler, silent,
  142. "ShaderResourceGroup SRG1 : Slot1", "ShaderResourceGroup SRG1 : SlotX",
  143. "level1.azsli",
  144. "Semantic error"): result += 1
  145. else: resultFailed += 1
  146. #if testSemanticErrorReportUsesPreprocessorLineDirectives(os.path.join(workDir, "RespectEmitLine/main.azsl.mcpp"),
  147. # compiler, silent): result += 1
  148. #else: resultFailed += 1
  149. if __name__ == "__main__":
  150. print ("please call from testapp.py")