expect.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. # Copyright (c) 2018 Google LLC
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """A number of common spirv result checks coded in mixin classes.
  15. A test case can use these checks by declaring their enclosing mixin classes
  16. as superclass and providing the expected_* variables required by the check_*()
  17. methods in the mixin classes.
  18. """
  19. import difflib
  20. import functools
  21. import os
  22. import re
  23. import subprocess
  24. import traceback
  25. from spirv_test_framework import SpirvTest
  26. from builtins import bytes
  27. DEFAULT_SPIRV_VERSION = 0x010000
  28. def convert_to_unix_line_endings(source):
  29. """Converts all line endings in source to be unix line endings."""
  30. result = source.replace('\r\n', '\n').replace('\r', '\n')
  31. return result
  32. def substitute_file_extension(filename, extension):
  33. """Substitutes file extension, respecting known shader extensions.
  34. foo.vert -> foo.vert.[extension] [similarly for .frag, .comp, etc.]
  35. foo.glsl -> foo.[extension]
  36. foo.unknown -> foo.[extension]
  37. foo -> foo.[extension]
  38. """
  39. if filename[-5:] not in [
  40. '.vert', '.frag', '.tesc', '.tese', '.geom', '.comp', '.spvasm'
  41. ]:
  42. return filename.rsplit('.', 1)[0] + '.' + extension
  43. else:
  44. return filename + '.' + extension
  45. def get_object_filename(source_filename):
  46. """Gets the object filename for the given source file."""
  47. return substitute_file_extension(source_filename, 'spv')
  48. def get_assembly_filename(source_filename):
  49. """Gets the assembly filename for the given source file."""
  50. return substitute_file_extension(source_filename, 'spvasm')
  51. def verify_file_non_empty(filename):
  52. """Checks that a given file exists and is not empty."""
  53. if not os.path.isfile(filename):
  54. return False, 'Cannot find file: ' + filename
  55. if not os.path.getsize(filename):
  56. return False, 'Empty file: ' + filename
  57. return True, ''
  58. class ReturnCodeIsZero(SpirvTest):
  59. """Mixin class for checking that the return code is zero."""
  60. def check_return_code_is_zero(self, status):
  61. if status.returncode:
  62. return False, 'Non-zero return code: {ret}\n'.format(
  63. ret=status.returncode)
  64. return True, ''
  65. class ReturnCodeIsNonZero(SpirvTest):
  66. """Mixin class for checking that the return code is not zero."""
  67. def check_return_code_is_nonzero(self, status):
  68. if not status.returncode:
  69. return False, 'return code is 0'
  70. return True, ''
  71. class NoOutputOnStdout(SpirvTest):
  72. """Mixin class for checking that there is no output on stdout."""
  73. def check_no_output_on_stdout(self, status):
  74. if status.stdout:
  75. return False, 'Non empty stdout: {out}\n'.format(out=status.stdout)
  76. return True, ''
  77. class NoOutputOnStderr(SpirvTest):
  78. """Mixin class for checking that there is no output on stderr."""
  79. def check_no_output_on_stderr(self, status):
  80. if status.stderr:
  81. return False, 'Non empty stderr: {err}\n'.format(err=status.stderr)
  82. return True, ''
  83. class SuccessfulReturn(ReturnCodeIsZero, NoOutputOnStdout, NoOutputOnStderr):
  84. """Mixin class for checking that return code is zero and no output on
  85. stdout and stderr."""
  86. pass
  87. class NoGeneratedFiles(SpirvTest):
  88. """Mixin class for checking that there is no file generated."""
  89. def check_no_generated_files(self, status):
  90. all_files = os.listdir(status.directory)
  91. input_files = status.input_filenames
  92. if all([f.startswith(status.directory) for f in input_files]):
  93. all_files = [os.path.join(status.directory, f) for f in all_files]
  94. generated_files = set(all_files) - set(input_files)
  95. if len(generated_files) == 0:
  96. return True, ''
  97. else:
  98. return False, 'Extra files generated: {}'.format(generated_files)
  99. class CorrectBinaryLengthAndPreamble(SpirvTest):
  100. """Provides methods for verifying preamble for a SPIR-V binary."""
  101. def verify_binary_length_and_header(self, binary, spv_version=0x10000):
  102. """Checks that the given SPIR-V binary has valid length and header.
  103. Returns:
  104. False, error string if anything is invalid
  105. True, '' otherwise
  106. Args:
  107. binary: a bytes object containing the SPIR-V binary
  108. spv_version: target SPIR-V version number, with same encoding
  109. as the version word in a SPIR-V header.
  110. """
  111. def read_word(binary, index, little_endian):
  112. """Reads the index-th word from the given binary file."""
  113. word = binary[index * 4:(index + 1) * 4]
  114. if little_endian:
  115. word = reversed(word)
  116. return functools.reduce(lambda w, b: (w << 8) | b, word, 0)
  117. def check_endianness(binary):
  118. """Checks the endianness of the given SPIR-V binary.
  119. Returns:
  120. True if it's little endian, False if it's big endian.
  121. None if magic number is wrong.
  122. """
  123. first_word = read_word(binary, 0, True)
  124. if first_word == 0x07230203:
  125. return True
  126. first_word = read_word(binary, 0, False)
  127. if first_word == 0x07230203:
  128. return False
  129. return None
  130. num_bytes = len(binary)
  131. if num_bytes % 4 != 0:
  132. return False, ('Incorrect SPV binary: size should be a multiple'
  133. ' of words')
  134. if num_bytes < 20:
  135. return False, 'Incorrect SPV binary: size less than 5 words'
  136. preamble = binary[0:19]
  137. little_endian = check_endianness(preamble)
  138. # SPIR-V module magic number
  139. if little_endian is None:
  140. return False, 'Incorrect SPV binary: wrong magic number'
  141. # SPIR-V version number
  142. version = read_word(preamble, 1, little_endian)
  143. # TODO(dneto): Recent Glslang uses version word 0 for opengl_compat
  144. # profile
  145. if version != spv_version and version != 0:
  146. return False, 'Incorrect SPV binary: wrong version number: ' + hex(version) + ' expected ' + hex(spv_version)
  147. # Shaderc-over-Glslang (0x000d....) or
  148. # SPIRV-Tools (0x0007....) generator number
  149. if read_word(preamble, 2, little_endian) != 0x000d0007 and \
  150. read_word(preamble, 2, little_endian) != 0x00070000:
  151. return False, ('Incorrect SPV binary: wrong generator magic ' 'number')
  152. # reserved for instruction schema
  153. if read_word(preamble, 4, little_endian) != 0:
  154. return False, 'Incorrect SPV binary: the 5th byte should be 0'
  155. return True, ''
  156. class CorrectObjectFilePreamble(CorrectBinaryLengthAndPreamble):
  157. """Provides methods for verifying preamble for a SPV object file."""
  158. def verify_object_file_preamble(self,
  159. filename,
  160. spv_version=DEFAULT_SPIRV_VERSION):
  161. """Checks that the given SPIR-V binary file has correct preamble."""
  162. success, message = verify_file_non_empty(filename)
  163. if not success:
  164. return False, message
  165. with open(filename, 'rb') as object_file:
  166. object_file.seek(0, os.SEEK_END)
  167. num_bytes = object_file.tell()
  168. object_file.seek(0)
  169. binary = bytes(object_file.read())
  170. return self.verify_binary_length_and_header(binary, spv_version)
  171. return True, ''
  172. class CorrectAssemblyFilePreamble(SpirvTest):
  173. """Provides methods for verifying preamble for a SPV assembly file."""
  174. def verify_assembly_file_preamble(self, filename):
  175. success, message = verify_file_non_empty(filename)
  176. if not success:
  177. return False, message
  178. with open(filename) as assembly_file:
  179. line1 = assembly_file.readline()
  180. line2 = assembly_file.readline()
  181. line3 = assembly_file.readline()
  182. if (line1 != '; SPIR-V\n' or line2 != '; Version: 1.0\n' or
  183. (not line3.startswith('; Generator: Google Shaderc over Glslang;'))):
  184. return False, 'Incorrect SPV assembly'
  185. return True, ''
  186. class ValidObjectFile(SuccessfulReturn, CorrectObjectFilePreamble):
  187. """Mixin class for checking that every input file generates a valid SPIR-V 1.0
  188. object file following the object file naming rule, and there is no output on
  189. stdout/stderr."""
  190. def check_object_file_preamble(self, status):
  191. for input_filename in status.input_filenames:
  192. object_filename = get_object_filename(input_filename)
  193. success, message = self.verify_object_file_preamble(
  194. os.path.join(status.directory, object_filename))
  195. if not success:
  196. return False, message
  197. return True, ''
  198. class ValidObjectFile1_3(ReturnCodeIsZero, CorrectObjectFilePreamble):
  199. """Mixin class for checking that every input file generates a valid SPIR-V 1.3
  200. object file following the object file naming rule, and there is no output on
  201. stdout/stderr."""
  202. def check_object_file_preamble(self, status):
  203. for input_filename in status.input_filenames:
  204. object_filename = get_object_filename(input_filename)
  205. success, message = self.verify_object_file_preamble(
  206. os.path.join(status.directory, object_filename), 0x10300)
  207. if not success:
  208. return False, message
  209. return True, ''
  210. class ValidObjectFile1_5(ReturnCodeIsZero, CorrectObjectFilePreamble):
  211. """Mixin class for checking that every input file generates a valid SPIR-V 1.5
  212. object file following the object file naming rule, and there is no output on
  213. stdout/stderr."""
  214. def check_object_file_preamble(self, status):
  215. for input_filename in status.input_filenames:
  216. object_filename = get_object_filename(input_filename)
  217. success, message = self.verify_object_file_preamble(
  218. os.path.join(status.directory, object_filename), 0x10500)
  219. if not success:
  220. return False, message
  221. return True, ''
  222. class ValidObjectFile1_6(ReturnCodeIsZero, CorrectObjectFilePreamble):
  223. """Mixin class for checking that every input file generates a valid SPIR-V 1.6
  224. object file following the object file naming rule, and there is no output on
  225. stdout/stderr."""
  226. def check_object_file_preamble(self, status):
  227. for input_filename in status.input_filenames:
  228. object_filename = get_object_filename(input_filename)
  229. success, message = self.verify_object_file_preamble(
  230. os.path.join(status.directory, object_filename), 0x10600)
  231. if not success:
  232. return False, message
  233. return True, ''
  234. class ValidObjectFileWithAssemblySubstr(SuccessfulReturn,
  235. CorrectObjectFilePreamble):
  236. """Mixin class for checking that every input file generates a valid object
  237. file following the object file naming rule, there is no output on
  238. stdout/stderr, and the disassmbly contains a specified substring per
  239. input.
  240. """
  241. def check_object_file_disassembly(self, status):
  242. for an_input in status.inputs:
  243. object_filename = get_object_filename(an_input.filename)
  244. obj_file = str(os.path.join(status.directory, object_filename))
  245. success, message = self.verify_object_file_preamble(obj_file)
  246. if not success:
  247. return False, message
  248. cmd = [status.test_manager.disassembler_path, '--no-color', obj_file]
  249. process = subprocess.Popen(
  250. args=cmd,
  251. stdin=subprocess.PIPE,
  252. stdout=subprocess.PIPE,
  253. stderr=subprocess.PIPE,
  254. cwd=status.directory)
  255. output = process.communicate(None)
  256. disassembly = output[0]
  257. if not isinstance(an_input.assembly_substr, str):
  258. return False, 'Missing assembly_substr member'
  259. if an_input.assembly_substr not in disassembly:
  260. return False, ('Incorrect disassembly output:\n{asm}\n'
  261. 'Expected substring not found:\n{exp}'.format(
  262. asm=disassembly, exp=an_input.assembly_substr))
  263. return True, ''
  264. class ValidNamedObjectFile(SuccessfulReturn, CorrectObjectFilePreamble):
  265. """Mixin class for checking that a list of object files with the given
  266. names are correctly generated, and there is no output on stdout/stderr.
  267. To mix in this class, subclasses need to provide expected_object_filenames
  268. as the expected object filenames.
  269. """
  270. def check_object_file_preamble(self, status):
  271. for object_filename in self.expected_object_filenames:
  272. success, message = self.verify_object_file_preamble(
  273. os.path.join(status.directory, object_filename))
  274. if not success:
  275. return False, message
  276. return True, ''
  277. class ValidFileContents(SpirvTest):
  278. """Mixin class to test that a specific file contains specific text
  279. To mix in this class, subclasses need to provide expected_file_contents as
  280. the contents of the file and target_filename to determine the location."""
  281. def check_file(self, status):
  282. target_filename = os.path.join(status.directory, self.target_filename)
  283. if not os.path.isfile(target_filename):
  284. return False, 'Cannot find file: ' + target_filename
  285. with open(target_filename, 'r') as target_file:
  286. file_contents = target_file.read()
  287. if isinstance(self.expected_file_contents, str):
  288. if file_contents == self.expected_file_contents:
  289. return True, ''
  290. return False, ('Incorrect file output: \n{act}\n'
  291. 'Expected:\n{exp}'
  292. 'With diff:\n{diff}'.format(
  293. act=file_contents,
  294. exp=self.expected_file_contents,
  295. diff='\n'.join(
  296. list(
  297. difflib.unified_diff(
  298. self.expected_file_contents.split('\n'),
  299. file_contents.split('\n'),
  300. fromfile='expected_output',
  301. tofile='actual_output')))))
  302. elif isinstance(self.expected_file_contents, type(re.compile(''))):
  303. if self.expected_file_contents.search(file_contents):
  304. return True, ''
  305. return False, ('Incorrect file output: \n{act}\n'
  306. 'Expected matching regex pattern:\n{exp}'.format(
  307. act=file_contents,
  308. exp=self.expected_file_contents.pattern))
  309. return False, (
  310. 'Could not open target file ' + target_filename + ' for reading')
  311. class ValidAssemblyFile(SuccessfulReturn, CorrectAssemblyFilePreamble):
  312. """Mixin class for checking that every input file generates a valid assembly
  313. file following the assembly file naming rule, and there is no output on
  314. stdout/stderr."""
  315. def check_assembly_file_preamble(self, status):
  316. for input_filename in status.input_filenames:
  317. assembly_filename = get_assembly_filename(input_filename)
  318. success, message = self.verify_assembly_file_preamble(
  319. os.path.join(status.directory, assembly_filename))
  320. if not success:
  321. return False, message
  322. return True, ''
  323. class ValidAssemblyFileWithSubstr(ValidAssemblyFile):
  324. """Mixin class for checking that every input file generates a valid assembly
  325. file following the assembly file naming rule, there is no output on
  326. stdout/stderr, and all assembly files have the given substring specified
  327. by expected_assembly_substr.
  328. To mix in this class, subclasses need to provde expected_assembly_substr
  329. as the expected substring.
  330. """
  331. def check_assembly_with_substr(self, status):
  332. for input_filename in status.input_filenames:
  333. assembly_filename = get_assembly_filename(input_filename)
  334. success, message = self.verify_assembly_file_preamble(
  335. os.path.join(status.directory, assembly_filename))
  336. if not success:
  337. return False, message
  338. with open(assembly_filename, 'r') as f:
  339. content = f.read()
  340. if self.expected_assembly_substr not in convert_to_unix_line_endings(
  341. content):
  342. return False, ('Incorrect assembly output:\n{asm}\n'
  343. 'Expected substring not found:\n{exp}'.format(
  344. asm=content, exp=self.expected_assembly_substr))
  345. return True, ''
  346. class ValidAssemblyFileWithoutSubstr(ValidAssemblyFile):
  347. """Mixin class for checking that every input file generates a valid assembly
  348. file following the assembly file naming rule, there is no output on
  349. stdout/stderr, and no assembly files have the given substring specified
  350. by unexpected_assembly_substr.
  351. To mix in this class, subclasses need to provde unexpected_assembly_substr
  352. as the substring we expect not to see.
  353. """
  354. def check_assembly_for_substr(self, status):
  355. for input_filename in status.input_filenames:
  356. assembly_filename = get_assembly_filename(input_filename)
  357. success, message = self.verify_assembly_file_preamble(
  358. os.path.join(status.directory, assembly_filename))
  359. if not success:
  360. return False, message
  361. with open(assembly_filename, 'r') as f:
  362. content = f.read()
  363. if self.unexpected_assembly_substr in convert_to_unix_line_endings(
  364. content):
  365. return False, ('Incorrect assembly output:\n{asm}\n'
  366. 'Unexpected substring found:\n{unexp}'.format(
  367. asm=content, exp=self.unexpected_assembly_substr))
  368. return True, ''
  369. class ValidNamedAssemblyFile(SuccessfulReturn, CorrectAssemblyFilePreamble):
  370. """Mixin class for checking that a list of assembly files with the given
  371. names are correctly generated, and there is no output on stdout/stderr.
  372. To mix in this class, subclasses need to provide expected_assembly_filenames
  373. as the expected assembly filenames.
  374. """
  375. def check_object_file_preamble(self, status):
  376. for assembly_filename in self.expected_assembly_filenames:
  377. success, message = self.verify_assembly_file_preamble(
  378. os.path.join(status.directory, assembly_filename))
  379. if not success:
  380. return False, message
  381. return True, ''
  382. class ErrorMessage(SpirvTest):
  383. """Mixin class for tests that fail with a specific error message.
  384. To mix in this class, subclasses need to provide expected_error as the
  385. expected error message.
  386. The test should fail if the subprocess was terminated by a signal.
  387. """
  388. def check_has_error_message(self, status):
  389. if not status.returncode:
  390. return False, ('Expected error message, but returned success from '
  391. 'command execution')
  392. if status.returncode < 0:
  393. # On Unix, a negative value -N for Popen.returncode indicates
  394. # termination by signal N.
  395. # https://docs.python.org/2/library/subprocess.html
  396. return False, ('Expected error message, but command was terminated by '
  397. 'signal ' + str(status.returncode))
  398. if not status.stderr:
  399. return False, 'Expected error message, but no output on stderr'
  400. if self.expected_error != convert_to_unix_line_endings(status.stderr):
  401. return False, ('Incorrect stderr output:\n{act}\n'
  402. 'Expected:\n{exp}'.format(
  403. act=status.stderr, exp=self.expected_error))
  404. return True, ''
  405. class ErrorMessageSubstr(SpirvTest):
  406. """Mixin class for tests that fail with a specific substring in the error
  407. message.
  408. To mix in this class, subclasses need to provide expected_error_substr as
  409. the expected error message substring.
  410. The test should fail if the subprocess was terminated by a signal.
  411. """
  412. def check_has_error_message_as_substring(self, status):
  413. if not status.returncode:
  414. return False, ('Expected error message, but returned success from '
  415. 'command execution')
  416. if status.returncode < 0:
  417. # On Unix, a negative value -N for Popen.returncode indicates
  418. # termination by signal N.
  419. # https://docs.python.org/2/library/subprocess.html
  420. return False, ('Expected error message, but command was terminated by '
  421. 'signal ' + str(status.returncode))
  422. if not status.stderr:
  423. return False, 'Expected error message, but no output on stderr'
  424. if self.expected_error_substr not in convert_to_unix_line_endings(
  425. status.stderr):
  426. return False, ('Incorrect stderr output:\n{act}\n'
  427. 'Expected substring not found in stderr:\n{exp}'.format(
  428. act=status.stderr, exp=self.expected_error_substr))
  429. return True, ''
  430. class WarningMessage(SpirvTest):
  431. """Mixin class for tests that succeed but have a specific warning message.
  432. To mix in this class, subclasses need to provide expected_warning as the
  433. expected warning message.
  434. """
  435. def check_has_warning_message(self, status):
  436. if status.returncode:
  437. return False, ('Expected warning message, but returned failure from'
  438. ' command execution')
  439. if not status.stderr:
  440. return False, 'Expected warning message, but no output on stderr'
  441. if self.expected_warning != convert_to_unix_line_endings(status.stderr):
  442. return False, ('Incorrect stderr output:\n{act}\n'
  443. 'Expected:\n{exp}'.format(
  444. act=status.stderr, exp=self.expected_warning))
  445. return True, ''
  446. class ValidObjectFileWithWarning(NoOutputOnStdout, CorrectObjectFilePreamble,
  447. WarningMessage):
  448. """Mixin class for checking that every input file generates a valid object
  449. file following the object file naming rule, with a specific warning message.
  450. """
  451. def check_object_file_preamble(self, status):
  452. for input_filename in status.input_filenames:
  453. object_filename = get_object_filename(input_filename)
  454. success, message = self.verify_object_file_preamble(
  455. os.path.join(status.directory, object_filename))
  456. if not success:
  457. return False, message
  458. return True, ''
  459. class ValidAssemblyFileWithWarning(NoOutputOnStdout,
  460. CorrectAssemblyFilePreamble, WarningMessage):
  461. """Mixin class for checking that every input file generates a valid assembly
  462. file following the assembly file naming rule, with a specific warning
  463. message."""
  464. def check_assembly_file_preamble(self, status):
  465. for input_filename in status.input_filenames:
  466. assembly_filename = get_assembly_filename(input_filename)
  467. success, message = self.verify_assembly_file_preamble(
  468. os.path.join(status.directory, assembly_filename))
  469. if not success:
  470. return False, message
  471. return True, ''
  472. class StdoutMatch(SpirvTest):
  473. """Mixin class for tests that can expect output on stdout.
  474. To mix in this class, subclasses need to provide expected_stdout as the
  475. expected stdout output.
  476. For expected_stdout, if it's True, then they expect something on stdout but
  477. will not check what it is. If it's a string, expect an exact match. If it's
  478. anything else, it is assumed to be a compiled regular expression which will
  479. be matched against re.search(). It will expect
  480. expected_stdout.search(status.stdout) to be true.
  481. """
  482. def check_stdout_match(self, status):
  483. # "True" in this case means we expect something on stdout, but we do not
  484. # care what it is, we want to distinguish this from "blah" which means we
  485. # expect exactly the string "blah".
  486. if self.expected_stdout is True:
  487. if not status.stdout:
  488. return False, 'Expected something on stdout'
  489. elif type(self.expected_stdout) == str:
  490. if self.expected_stdout != convert_to_unix_line_endings(status.stdout):
  491. return False, ('Incorrect stdout output:\n{ac}\n'
  492. 'Expected:\n{ex}'.format(
  493. ac=status.stdout, ex=self.expected_stdout))
  494. else:
  495. converted = convert_to_unix_line_endings(status.stdout)
  496. if not self.expected_stdout.search(converted):
  497. return False, ('Incorrect stdout output:\n{ac}\n'
  498. 'Expected to match regex:\n{ex}'.format(
  499. ac=status.stdout, ex=self.expected_stdout.pattern))
  500. return True, ''
  501. class StderrMatch(SpirvTest):
  502. """Mixin class for tests that can expect output on stderr.
  503. To mix in this class, subclasses need to provide expected_stderr as the
  504. expected stderr output.
  505. For expected_stderr, if it's True, then they expect something on stderr,
  506. but will not check what it is. If it's a string, expect an exact match.
  507. If it's anything else, it is assumed to be a compiled regular expression
  508. which will be matched against re.search(). It will expect
  509. expected_stderr.search(status.stderr) to be true.
  510. """
  511. def check_stderr_match(self, status):
  512. # "True" in this case means we expect something on stderr, but we do not
  513. # care what it is, we want to distinguish this from "blah" which means we
  514. # expect exactly the string "blah".
  515. if self.expected_stderr is True:
  516. if not status.stderr:
  517. return False, 'Expected something on stderr'
  518. elif type(self.expected_stderr) == str:
  519. if self.expected_stderr != convert_to_unix_line_endings(status.stderr):
  520. return False, ('Incorrect stderr output:\n{ac}\n'
  521. 'Expected:\n{ex}'.format(
  522. ac=status.stderr, ex=self.expected_stderr))
  523. else:
  524. if not self.expected_stderr.search(
  525. convert_to_unix_line_endings(status.stderr)):
  526. return False, ('Incorrect stderr output:\n{ac}\n'
  527. 'Expected to match regex:\n{ex}'.format(
  528. ac=status.stderr, ex=self.expected_stderr.pattern))
  529. return True, ''
  530. class StdoutNoWiderThan80Columns(SpirvTest):
  531. """Mixin class for tests that require stdout to 80 characters or narrower.
  532. To mix in this class, subclasses need to provide expected_stdout as the
  533. expected stdout output.
  534. """
  535. def check_stdout_not_too_wide(self, status):
  536. if not status.stdout:
  537. return True, ''
  538. else:
  539. for line in status.stdout.splitlines():
  540. if len(line) > 80:
  541. return False, ('Stdout line longer than 80 columns: %s' % line)
  542. return True, ''
  543. class NoObjectFile(SpirvTest):
  544. """Mixin class for checking that no input file has a corresponding object
  545. file."""
  546. def check_no_object_file(self, status):
  547. for input_filename in status.input_filenames:
  548. object_filename = get_object_filename(input_filename)
  549. full_object_file = os.path.join(status.directory, object_filename)
  550. print('checking %s' % full_object_file)
  551. if os.path.isfile(full_object_file):
  552. return False, (
  553. 'Expected no object file, but found: %s' % full_object_file)
  554. return True, ''
  555. class NoNamedOutputFiles(SpirvTest):
  556. """Mixin class for checking that no specified output files exist.
  557. The expected_output_filenames member should be full pathnames."""
  558. def check_no_named_output_files(self, status):
  559. for object_filename in self.expected_output_filenames:
  560. if os.path.isfile(object_filename):
  561. return False, (
  562. 'Expected no output file, but found: %s' % object_filename)
  563. return True, ''
  564. class ExecutedListOfPasses(SpirvTest):
  565. """Mixin class for checking that a list of passes where executed.
  566. It works by analyzing the output of the --print-all flag to spirv-opt.
  567. For this mixin to work, the class member expected_passes should be a sequence
  568. of pass names as returned by Pass::name().
  569. """
  570. def check_list_of_executed_passes(self, status):
  571. # Collect all the output lines containing a pass name.
  572. pass_names = []
  573. pass_name_re = re.compile(r'.*IR before pass (?P<pass_name>[\S]+)')
  574. for line in status.stderr.splitlines():
  575. match = pass_name_re.match(line)
  576. if match:
  577. pass_names.append(match.group('pass_name'))
  578. for (expected, actual) in zip(self.expected_passes, pass_names):
  579. if expected != actual:
  580. return False, (
  581. 'Expected pass "%s" but found pass "%s"\n' % (expected, actual))
  582. return True, ''