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