expect.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  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 ValidObjectFileWithAssemblySubstr(SuccessfulReturn,
  223. CorrectObjectFilePreamble):
  224. """Mixin class for checking that every input file generates a valid object
  225. file following the object file naming rule, there is no output on
  226. stdout/stderr, and the disassmbly contains a specified substring per
  227. input.
  228. """
  229. def check_object_file_disassembly(self, status):
  230. for an_input in status.inputs:
  231. object_filename = get_object_filename(an_input.filename)
  232. obj_file = str(os.path.join(status.directory, object_filename))
  233. success, message = self.verify_object_file_preamble(obj_file)
  234. if not success:
  235. return False, message
  236. cmd = [status.test_manager.disassembler_path, '--no-color', obj_file]
  237. process = subprocess.Popen(
  238. args=cmd,
  239. stdin=subprocess.PIPE,
  240. stdout=subprocess.PIPE,
  241. stderr=subprocess.PIPE,
  242. cwd=status.directory)
  243. output = process.communicate(None)
  244. disassembly = output[0]
  245. if not isinstance(an_input.assembly_substr, str):
  246. return False, 'Missing assembly_substr member'
  247. if an_input.assembly_substr not in disassembly:
  248. return False, ('Incorrect disassembly output:\n{asm}\n'
  249. 'Expected substring not found:\n{exp}'.format(
  250. asm=disassembly, exp=an_input.assembly_substr))
  251. return True, ''
  252. class ValidNamedObjectFile(SuccessfulReturn, CorrectObjectFilePreamble):
  253. """Mixin class for checking that a list of object files with the given
  254. names are correctly generated, and there is no output on stdout/stderr.
  255. To mix in this class, subclasses need to provide expected_object_filenames
  256. as the expected object filenames.
  257. """
  258. def check_object_file_preamble(self, status):
  259. for object_filename in self.expected_object_filenames:
  260. success, message = self.verify_object_file_preamble(
  261. os.path.join(status.directory, object_filename))
  262. if not success:
  263. return False, message
  264. return True, ''
  265. class ValidFileContents(SpirvTest):
  266. """Mixin class to test that a specific file contains specific text
  267. To mix in this class, subclasses need to provide expected_file_contents as
  268. the contents of the file and target_filename to determine the location."""
  269. def check_file(self, status):
  270. target_filename = os.path.join(status.directory, self.target_filename)
  271. if not os.path.isfile(target_filename):
  272. return False, 'Cannot find file: ' + target_filename
  273. with open(target_filename, 'r') as target_file:
  274. file_contents = target_file.read()
  275. if isinstance(self.expected_file_contents, str):
  276. if file_contents == self.expected_file_contents:
  277. return True, ''
  278. return False, ('Incorrect file output: \n{act}\n'
  279. 'Expected:\n{exp}'
  280. 'With diff:\n{diff}'.format(
  281. act=file_contents,
  282. exp=self.expected_file_contents,
  283. diff='\n'.join(
  284. list(
  285. difflib.unified_diff(
  286. self.expected_file_contents.split('\n'),
  287. file_contents.split('\n'),
  288. fromfile='expected_output',
  289. tofile='actual_output')))))
  290. elif isinstance(self.expected_file_contents, type(re.compile(''))):
  291. if self.expected_file_contents.search(file_contents):
  292. return True, ''
  293. return False, ('Incorrect file output: \n{act}\n'
  294. 'Expected matching regex pattern:\n{exp}'.format(
  295. act=file_contents,
  296. exp=self.expected_file_contents.pattern))
  297. return False, (
  298. 'Could not open target file ' + target_filename + ' for reading')
  299. class ValidAssemblyFile(SuccessfulReturn, CorrectAssemblyFilePreamble):
  300. """Mixin class for checking that every input file generates a valid assembly
  301. file following the assembly file naming rule, and there is no output on
  302. stdout/stderr."""
  303. def check_assembly_file_preamble(self, status):
  304. for input_filename in status.input_filenames:
  305. assembly_filename = get_assembly_filename(input_filename)
  306. success, message = self.verify_assembly_file_preamble(
  307. os.path.join(status.directory, assembly_filename))
  308. if not success:
  309. return False, message
  310. return True, ''
  311. class ValidAssemblyFileWithSubstr(ValidAssemblyFile):
  312. """Mixin class for checking that every input file generates a valid assembly
  313. file following the assembly file naming rule, there is no output on
  314. stdout/stderr, and all assembly files have the given substring specified
  315. by expected_assembly_substr.
  316. To mix in this class, subclasses need to provde expected_assembly_substr
  317. as the expected substring.
  318. """
  319. def check_assembly_with_substr(self, status):
  320. for input_filename in status.input_filenames:
  321. assembly_filename = get_assembly_filename(input_filename)
  322. success, message = self.verify_assembly_file_preamble(
  323. os.path.join(status.directory, assembly_filename))
  324. if not success:
  325. return False, message
  326. with open(assembly_filename, 'r') as f:
  327. content = f.read()
  328. if self.expected_assembly_substr not in convert_to_unix_line_endings(
  329. content):
  330. return False, ('Incorrect assembly output:\n{asm}\n'
  331. 'Expected substring not found:\n{exp}'.format(
  332. asm=content, exp=self.expected_assembly_substr))
  333. return True, ''
  334. class ValidAssemblyFileWithoutSubstr(ValidAssemblyFile):
  335. """Mixin class for checking that every input file generates a valid assembly
  336. file following the assembly file naming rule, there is no output on
  337. stdout/stderr, and no assembly files have the given substring specified
  338. by unexpected_assembly_substr.
  339. To mix in this class, subclasses need to provde unexpected_assembly_substr
  340. as the substring we expect not to see.
  341. """
  342. def check_assembly_for_substr(self, status):
  343. for input_filename in status.input_filenames:
  344. assembly_filename = get_assembly_filename(input_filename)
  345. success, message = self.verify_assembly_file_preamble(
  346. os.path.join(status.directory, assembly_filename))
  347. if not success:
  348. return False, message
  349. with open(assembly_filename, 'r') as f:
  350. content = f.read()
  351. if self.unexpected_assembly_substr in convert_to_unix_line_endings(
  352. content):
  353. return False, ('Incorrect assembly output:\n{asm}\n'
  354. 'Unexpected substring found:\n{unexp}'.format(
  355. asm=content, exp=self.unexpected_assembly_substr))
  356. return True, ''
  357. class ValidNamedAssemblyFile(SuccessfulReturn, CorrectAssemblyFilePreamble):
  358. """Mixin class for checking that a list of assembly files with the given
  359. names are correctly generated, and there is no output on stdout/stderr.
  360. To mix in this class, subclasses need to provide expected_assembly_filenames
  361. as the expected assembly filenames.
  362. """
  363. def check_object_file_preamble(self, status):
  364. for assembly_filename in self.expected_assembly_filenames:
  365. success, message = self.verify_assembly_file_preamble(
  366. os.path.join(status.directory, assembly_filename))
  367. if not success:
  368. return False, message
  369. return True, ''
  370. class ErrorMessage(SpirvTest):
  371. """Mixin class for tests that fail with a specific error message.
  372. To mix in this class, subclasses need to provide expected_error as the
  373. expected error message.
  374. The test should fail if the subprocess was terminated by a signal.
  375. """
  376. def check_has_error_message(self, status):
  377. if not status.returncode:
  378. return False, ('Expected error message, but returned success from '
  379. 'command execution')
  380. if status.returncode < 0:
  381. # On Unix, a negative value -N for Popen.returncode indicates
  382. # termination by signal N.
  383. # https://docs.python.org/2/library/subprocess.html
  384. return False, ('Expected error message, but command was terminated by '
  385. 'signal ' + str(status.returncode))
  386. if not status.stderr:
  387. return False, 'Expected error message, but no output on stderr'
  388. if self.expected_error != convert_to_unix_line_endings(status.stderr):
  389. return False, ('Incorrect stderr output:\n{act}\n'
  390. 'Expected:\n{exp}'.format(
  391. act=status.stderr, exp=self.expected_error))
  392. return True, ''
  393. class ErrorMessageSubstr(SpirvTest):
  394. """Mixin class for tests that fail with a specific substring in the error
  395. message.
  396. To mix in this class, subclasses need to provide expected_error_substr as
  397. the expected error message substring.
  398. The test should fail if the subprocess was terminated by a signal.
  399. """
  400. def check_has_error_message_as_substring(self, status):
  401. if not status.returncode:
  402. return False, ('Expected error message, but returned success from '
  403. 'command execution')
  404. if status.returncode < 0:
  405. # On Unix, a negative value -N for Popen.returncode indicates
  406. # termination by signal N.
  407. # https://docs.python.org/2/library/subprocess.html
  408. return False, ('Expected error message, but command was terminated by '
  409. 'signal ' + str(status.returncode))
  410. if not status.stderr:
  411. return False, 'Expected error message, but no output on stderr'
  412. if self.expected_error_substr not in convert_to_unix_line_endings(
  413. status.stderr.decode('utf8')):
  414. return False, ('Incorrect stderr output:\n{act}\n'
  415. 'Expected substring not found in stderr:\n{exp}'.format(
  416. act=status.stderr, exp=self.expected_error_substr))
  417. return True, ''
  418. class WarningMessage(SpirvTest):
  419. """Mixin class for tests that succeed but have a specific warning message.
  420. To mix in this class, subclasses need to provide expected_warning as the
  421. expected warning message.
  422. """
  423. def check_has_warning_message(self, status):
  424. if status.returncode:
  425. return False, ('Expected warning message, but returned failure from'
  426. ' command execution')
  427. if not status.stderr:
  428. return False, 'Expected warning message, but no output on stderr'
  429. if self.expected_warning != convert_to_unix_line_endings(status.stderr.decode('utf8')):
  430. return False, ('Incorrect stderr output:\n{act}\n'
  431. 'Expected:\n{exp}'.format(
  432. act=status.stderr, exp=self.expected_warning))
  433. return True, ''
  434. class ValidObjectFileWithWarning(NoOutputOnStdout, CorrectObjectFilePreamble,
  435. WarningMessage):
  436. """Mixin class for checking that every input file generates a valid object
  437. file following the object file naming rule, with a specific warning message.
  438. """
  439. def check_object_file_preamble(self, status):
  440. for input_filename in status.input_filenames:
  441. object_filename = get_object_filename(input_filename)
  442. success, message = self.verify_object_file_preamble(
  443. os.path.join(status.directory, object_filename))
  444. if not success:
  445. return False, message
  446. return True, ''
  447. class ValidAssemblyFileWithWarning(NoOutputOnStdout,
  448. CorrectAssemblyFilePreamble, WarningMessage):
  449. """Mixin class for checking that every input file generates a valid assembly
  450. file following the assembly file naming rule, with a specific warning
  451. message."""
  452. def check_assembly_file_preamble(self, status):
  453. for input_filename in status.input_filenames:
  454. assembly_filename = get_assembly_filename(input_filename)
  455. success, message = self.verify_assembly_file_preamble(
  456. os.path.join(status.directory, assembly_filename))
  457. if not success:
  458. return False, message
  459. return True, ''
  460. class StdoutMatch(SpirvTest):
  461. """Mixin class for tests that can expect output on stdout.
  462. To mix in this class, subclasses need to provide expected_stdout as the
  463. expected stdout output.
  464. For expected_stdout, if it's True, then they expect something on stdout but
  465. will not check what it is. If it's a string, expect an exact match. If it's
  466. anything else, it is assumed to be a compiled regular expression which will
  467. be matched against re.search(). It will expect
  468. expected_stdout.search(status.stdout) to be true.
  469. """
  470. def check_stdout_match(self, status):
  471. # "True" in this case means we expect something on stdout, but we do not
  472. # care what it is, we want to distinguish this from "blah" which means we
  473. # expect exactly the string "blah".
  474. if self.expected_stdout is True:
  475. if not status.stdout:
  476. return False, 'Expected something on stdout'
  477. elif type(self.expected_stdout) == str:
  478. if self.expected_stdout != convert_to_unix_line_endings(status.stdout.decode('utf8')):
  479. return False, ('Incorrect stdout output:\n{ac}\n'
  480. 'Expected:\n{ex}'.format(
  481. ac=status.stdout, ex=self.expected_stdout))
  482. else:
  483. converted = convert_to_unix_line_endings(status.stdout.decode('utf8'))
  484. if not self.expected_stdout.search(converted):
  485. return False, ('Incorrect stdout output:\n{ac}\n'
  486. 'Expected to match regex:\n{ex}'.format(
  487. ac=status.stdout.decode('utf8'), ex=self.expected_stdout.pattern))
  488. return True, ''
  489. class StderrMatch(SpirvTest):
  490. """Mixin class for tests that can expect output on stderr.
  491. To mix in this class, subclasses need to provide expected_stderr as the
  492. expected stderr output.
  493. For expected_stderr, if it's True, then they expect something on stderr,
  494. but will not check what it is. If it's a string, expect an exact match.
  495. If it's anything else, it is assumed to be a compiled regular expression
  496. which will be matched against re.search(). It will expect
  497. expected_stderr.search(status.stderr) to be true.
  498. """
  499. def check_stderr_match(self, status):
  500. # "True" in this case means we expect something on stderr, but we do not
  501. # care what it is, we want to distinguish this from "blah" which means we
  502. # expect exactly the string "blah".
  503. if self.expected_stderr is True:
  504. if not status.stderr:
  505. return False, 'Expected something on stderr'
  506. elif type(self.expected_stderr) == str:
  507. if self.expected_stderr != convert_to_unix_line_endings(status.stderr.decode('utf8')):
  508. return False, ('Incorrect stderr output:\n{ac}\n'
  509. 'Expected:\n{ex}'.format(
  510. ac=status.stderr, ex=self.expected_stderr))
  511. else:
  512. if not self.expected_stderr.search(
  513. convert_to_unix_line_endings(status.stderr.decode('utf8'))):
  514. return False, ('Incorrect stderr output:\n{ac}\n'
  515. 'Expected to match regex:\n{ex}'.format(
  516. ac=status.stderr, ex=self.expected_stderr.pattern))
  517. return True, ''
  518. class StdoutNoWiderThan80Columns(SpirvTest):
  519. """Mixin class for tests that require stdout to 80 characters or narrower.
  520. To mix in this class, subclasses need to provide expected_stdout as the
  521. expected stdout output.
  522. """
  523. def check_stdout_not_too_wide(self, status):
  524. if not status.stdout:
  525. return True, ''
  526. else:
  527. for line in status.stdout.splitlines():
  528. if len(line) > 80:
  529. return False, ('Stdout line longer than 80 columns: %s' % line)
  530. return True, ''
  531. class NoObjectFile(SpirvTest):
  532. """Mixin class for checking that no input file has a corresponding object
  533. file."""
  534. def check_no_object_file(self, status):
  535. for input_filename in status.input_filenames:
  536. object_filename = get_object_filename(input_filename)
  537. full_object_file = os.path.join(status.directory, object_filename)
  538. print('checking %s' % full_object_file)
  539. if os.path.isfile(full_object_file):
  540. return False, (
  541. 'Expected no object file, but found: %s' % full_object_file)
  542. return True, ''
  543. class NoNamedOutputFiles(SpirvTest):
  544. """Mixin class for checking that no specified output files exist.
  545. The expected_output_filenames member should be full pathnames."""
  546. def check_no_named_output_files(self, status):
  547. for object_filename in self.expected_output_filenames:
  548. if os.path.isfile(object_filename):
  549. return False, (
  550. 'Expected no output file, but found: %s' % object_filename)
  551. return True, ''
  552. class ExecutedListOfPasses(SpirvTest):
  553. """Mixin class for checking that a list of passes where executed.
  554. It works by analyzing the output of the --print-all flag to spirv-opt.
  555. For this mixin to work, the class member expected_passes should be a sequence
  556. of pass names as returned by Pass::name().
  557. """
  558. def check_list_of_executed_passes(self, status):
  559. # Collect all the output lines containing a pass name.
  560. pass_names = []
  561. pass_name_re = re.compile(r'.*IR before pass (?P<pass_name>[\S]+)')
  562. for line in status.stderr.decode('utf8').splitlines():
  563. match = pass_name_re.match(line)
  564. if match:
  565. pass_names.append(match.group('pass_name'))
  566. for (expected, actual) in zip(self.expected_passes, pass_names):
  567. if expected != actual:
  568. return False, (
  569. 'Expected pass "%s" but found pass "%s"\n' % (expected, actual))
  570. return True, ''