| # Copyright (c) 2018 Google LLC | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """A number of common spirv result checks coded in mixin classes. | |
| A test case can use these checks by declaring their enclosing mixin classes | |
| as superclass and providing the expected_* variables required by the check_*() | |
| methods in the mixin classes. | |
| """ | |
| import difflib | |
| import functools | |
| import os | |
| import re | |
| import subprocess | |
| import traceback | |
| from spirv_test_framework import SpirvTest | |
| from builtins import bytes | |
| DEFAULT_SPIRV_VERSION = 0x010000 | |
| def convert_to_unix_line_endings(source): | |
| """Converts all line endings in source to be unix line endings.""" | |
| result = source.replace('\r\n', '\n').replace('\r', '\n') | |
| return result | |
| def substitute_file_extension(filename, extension): | |
| """Substitutes file extension, respecting known shader extensions. | |
| foo.vert -> foo.vert.[extension] [similarly for .frag, .comp, etc.] | |
| foo.glsl -> foo.[extension] | |
| foo.unknown -> foo.[extension] | |
| foo -> foo.[extension] | |
| """ | |
| if filename[-5:] not in [ | |
| '.vert', '.frag', '.tesc', '.tese', '.geom', '.comp', '.spvasm' | |
| ]: | |
| return filename.rsplit('.', 1)[0] + '.' + extension | |
| else: | |
| return filename + '.' + extension | |
| def get_object_filename(source_filename): | |
| """Gets the object filename for the given source file.""" | |
| return substitute_file_extension(source_filename, 'spv') | |
| def get_assembly_filename(source_filename): | |
| """Gets the assembly filename for the given source file.""" | |
| return substitute_file_extension(source_filename, 'spvasm') | |
| def verify_file_non_empty(filename): | |
| """Checks that a given file exists and is not empty.""" | |
| if not os.path.isfile(filename): | |
| return False, 'Cannot find file: ' + filename | |
| if not os.path.getsize(filename): | |
| return False, 'Empty file: ' + filename | |
| return True, '' | |
| class ReturnCodeIsZero(SpirvTest): | |
| """Mixin class for checking that the return code is zero.""" | |
| def check_return_code_is_zero(self, status): | |
| if status.returncode: | |
| return False, 'Non-zero return code: {ret}\n'.format( | |
| ret=status.returncode) | |
| return True, '' | |
| class ReturnCodeIsNonZero(SpirvTest): | |
| """Mixin class for checking that the return code is not zero.""" | |
| def check_return_code_is_nonzero(self, status): | |
| if not status.returncode: | |
| return False, 'return code is 0' | |
| return True, '' | |
| class NoOutputOnStdout(SpirvTest): | |
| """Mixin class for checking that there is no output on stdout.""" | |
| def check_no_output_on_stdout(self, status): | |
| if status.stdout: | |
| return False, 'Non empty stdout: {out}\n'.format(out=status.stdout) | |
| return True, '' | |
| class NoOutputOnStderr(SpirvTest): | |
| """Mixin class for checking that there is no output on stderr.""" | |
| def check_no_output_on_stderr(self, status): | |
| if status.stderr: | |
| return False, 'Non empty stderr: {err}\n'.format(err=status.stderr) | |
| return True, '' | |
| class SuccessfulReturn(ReturnCodeIsZero, NoOutputOnStdout, NoOutputOnStderr): | |
| """Mixin class for checking that return code is zero and no output on | |
| stdout and stderr.""" | |
| pass | |
| class NoGeneratedFiles(SpirvTest): | |
| """Mixin class for checking that there is no file generated.""" | |
| def check_no_generated_files(self, status): | |
| all_files = os.listdir(status.directory) | |
| input_files = status.input_filenames | |
| if all([f.startswith(status.directory) for f in input_files]): | |
| all_files = [os.path.join(status.directory, f) for f in all_files] | |
| generated_files = set(all_files) - set(input_files) | |
| if len(generated_files) == 0: | |
| return True, '' | |
| else: | |
| return False, 'Extra files generated: {}'.format(generated_files) | |
| class CorrectBinaryLengthAndPreamble(SpirvTest): | |
| """Provides methods for verifying preamble for a SPIR-V binary.""" | |
| def verify_binary_length_and_header(self, binary, spv_version=0x10000): | |
| """Checks that the given SPIR-V binary has valid length and header. | |
| Returns: | |
| False, error string if anything is invalid | |
| True, '' otherwise | |
| Args: | |
| binary: a bytes object containing the SPIR-V binary | |
| spv_version: target SPIR-V version number, with same encoding | |
| as the version word in a SPIR-V header. | |
| """ | |
| def read_word(binary, index, little_endian): | |
| """Reads the index-th word from the given binary file.""" | |
| word = binary[index * 4:(index + 1) * 4] | |
| if little_endian: | |
| word = reversed(word) | |
| return functools.reduce(lambda w, b: (w << 8) | b, word, 0) | |
| def check_endianness(binary): | |
| """Checks the endianness of the given SPIR-V binary. | |
| Returns: | |
| True if it's little endian, False if it's big endian. | |
| None if magic number is wrong. | |
| """ | |
| first_word = read_word(binary, 0, True) | |
| if first_word == 0x07230203: | |
| return True | |
| first_word = read_word(binary, 0, False) | |
| if first_word == 0x07230203: | |
| return False | |
| return None | |
| num_bytes = len(binary) | |
| if num_bytes % 4 != 0: | |
| return False, ('Incorrect SPV binary: size should be a multiple' | |
| ' of words') | |
| if num_bytes < 20: | |
| return False, 'Incorrect SPV binary: size less than 5 words' | |
| preamble = binary[0:19] | |
| little_endian = check_endianness(preamble) | |
| # SPIR-V module magic number | |
| if little_endian is None: | |
| return False, 'Incorrect SPV binary: wrong magic number' | |
| # SPIR-V version number | |
| version = read_word(preamble, 1, little_endian) | |
| # TODO(dneto): Recent Glslang uses version word 0 for opengl_compat | |
| # profile | |
| if version != spv_version and version != 0: | |
| return False, 'Incorrect SPV binary: wrong version number: ' + hex(version) + ' expected ' + hex(spv_version) | |
| # Shaderc-over-Glslang (0x000d....) or | |
| # SPIRV-Tools (0x0007....) generator number | |
| if read_word(preamble, 2, little_endian) != 0x000d0007 and \ | |
| read_word(preamble, 2, little_endian) != 0x00070000: | |
| return False, ('Incorrect SPV binary: wrong generator magic ' 'number') | |
| # reserved for instruction schema | |
| if read_word(preamble, 4, little_endian) != 0: | |
| return False, 'Incorrect SPV binary: the 5th byte should be 0' | |
| return True, '' | |
| class CorrectObjectFilePreamble(CorrectBinaryLengthAndPreamble): | |
| """Provides methods for verifying preamble for a SPV object file.""" | |
| def verify_object_file_preamble(self, | |
| filename, | |
| spv_version=DEFAULT_SPIRV_VERSION): | |
| """Checks that the given SPIR-V binary file has correct preamble.""" | |
| success, message = verify_file_non_empty(filename) | |
| if not success: | |
| return False, message | |
| with open(filename, 'rb') as object_file: | |
| object_file.seek(0, os.SEEK_END) | |
| num_bytes = object_file.tell() | |
| object_file.seek(0) | |
| binary = bytes(object_file.read()) | |
| return self.verify_binary_length_and_header(binary, spv_version) | |
| return True, '' | |
| class CorrectAssemblyFilePreamble(SpirvTest): | |
| """Provides methods for verifying preamble for a SPV assembly file.""" | |
| def verify_assembly_file_preamble(self, filename): | |
| success, message = verify_file_non_empty(filename) | |
| if not success: | |
| return False, message | |
| with open(filename) as assembly_file: | |
| line1 = assembly_file.readline() | |
| line2 = assembly_file.readline() | |
| line3 = assembly_file.readline() | |
| if (line1 != '; SPIR-V\n' or line2 != '; Version: 1.0\n' or | |
| (not line3.startswith('; Generator: Google Shaderc over Glslang;'))): | |
| return False, 'Incorrect SPV assembly' | |
| return True, '' | |
| class ValidObjectFile(SuccessfulReturn, CorrectObjectFilePreamble): | |
| """Mixin class for checking that every input file generates a valid SPIR-V 1.0 | |
| object file following the object file naming rule, and there is no output on | |
| stdout/stderr.""" | |
| def check_object_file_preamble(self, status): | |
| for input_filename in status.input_filenames: | |
| object_filename = get_object_filename(input_filename) | |
| success, message = self.verify_object_file_preamble( | |
| os.path.join(status.directory, object_filename)) | |
| if not success: | |
| return False, message | |
| return True, '' | |
| class ValidObjectFile1_3(ReturnCodeIsZero, CorrectObjectFilePreamble): | |
| """Mixin class for checking that every input file generates a valid SPIR-V 1.3 | |
| object file following the object file naming rule, and there is no output on | |
| stdout/stderr.""" | |
| def check_object_file_preamble(self, status): | |
| for input_filename in status.input_filenames: | |
| object_filename = get_object_filename(input_filename) | |
| success, message = self.verify_object_file_preamble( | |
| os.path.join(status.directory, object_filename), 0x10300) | |
| if not success: | |
| return False, message | |
| return True, '' | |
| class ValidObjectFile1_4(ReturnCodeIsZero, CorrectObjectFilePreamble): | |
| """Mixin class for checking that every input file generates a valid SPIR-V 1.4 | |
| object file following the object file naming rule, and there is no output on | |
| stdout/stderr.""" | |
| def check_object_file_preamble(self, status): | |
| for input_filename in status.input_filenames: | |
| object_filename = get_object_filename(input_filename) | |
| success, message = self.verify_object_file_preamble( | |
| os.path.join(status.directory, object_filename), 0x10400) | |
| if not success: | |
| return False, message | |
| return True, '' | |
| class ValidObjectFileWithAssemblySubstr(SuccessfulReturn, | |
| CorrectObjectFilePreamble): | |
| """Mixin class for checking that every input file generates a valid object | |
| file following the object file naming rule, there is no output on | |
| stdout/stderr, and the disassmbly contains a specified substring per | |
| input. | |
| """ | |
| def check_object_file_disassembly(self, status): | |
| for an_input in status.inputs: | |
| object_filename = get_object_filename(an_input.filename) | |
| obj_file = str(os.path.join(status.directory, object_filename)) | |
| success, message = self.verify_object_file_preamble(obj_file) | |
| if not success: | |
| return False, message | |
| cmd = [status.test_manager.disassembler_path, '--no-color', obj_file] | |
| process = subprocess.Popen( | |
| args=cmd, | |
| stdin=subprocess.PIPE, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, | |
| cwd=status.directory) | |
| output = process.communicate(None) | |
| disassembly = output[0] | |
| if not isinstance(an_input.assembly_substr, str): | |
| return False, 'Missing assembly_substr member' | |
| if an_input.assembly_substr not in disassembly: | |
| return False, ('Incorrect disassembly output:\n{asm}\n' | |
| 'Expected substring not found:\n{exp}'.format( | |
| asm=disassembly, exp=an_input.assembly_substr)) | |
| return True, '' | |
| class ValidNamedObjectFile(SuccessfulReturn, CorrectObjectFilePreamble): | |
| """Mixin class for checking that a list of object files with the given | |
| names are correctly generated, and there is no output on stdout/stderr. | |
| To mix in this class, subclasses need to provide expected_object_filenames | |
| as the expected object filenames. | |
| """ | |
| def check_object_file_preamble(self, status): | |
| for object_filename in self.expected_object_filenames: | |
| success, message = self.verify_object_file_preamble( | |
| os.path.join(status.directory, object_filename)) | |
| if not success: | |
| return False, message | |
| return True, '' | |
| class ValidFileContents(SpirvTest): | |
| """Mixin class to test that a specific file contains specific text | |
| To mix in this class, subclasses need to provide expected_file_contents as | |
| the contents of the file and target_filename to determine the location.""" | |
| def check_file(self, status): | |
| target_filename = os.path.join(status.directory, self.target_filename) | |
| if not os.path.isfile(target_filename): | |
| return False, 'Cannot find file: ' + target_filename | |
| with open(target_filename, 'r') as target_file: | |
| file_contents = target_file.read() | |
| if isinstance(self.expected_file_contents, str): | |
| if file_contents == self.expected_file_contents: | |
| return True, '' | |
| return False, ('Incorrect file output: \n{act}\n' | |
| 'Expected:\n{exp}' | |
| 'With diff:\n{diff}'.format( | |
| act=file_contents, | |
| exp=self.expected_file_contents, | |
| diff='\n'.join( | |
| list( | |
| difflib.unified_diff( | |
| self.expected_file_contents.split('\n'), | |
| file_contents.split('\n'), | |
| fromfile='expected_output', | |
| tofile='actual_output'))))) | |
| elif isinstance(self.expected_file_contents, type(re.compile(''))): | |
| if self.expected_file_contents.search(file_contents): | |
| return True, '' | |
| return False, ('Incorrect file output: \n{act}\n' | |
| 'Expected matching regex pattern:\n{exp}'.format( | |
| act=file_contents, | |
| exp=self.expected_file_contents.pattern)) | |
| return False, ( | |
| 'Could not open target file ' + target_filename + ' for reading') | |
| class ValidAssemblyFile(SuccessfulReturn, CorrectAssemblyFilePreamble): | |
| """Mixin class for checking that every input file generates a valid assembly | |
| file following the assembly file naming rule, and there is no output on | |
| stdout/stderr.""" | |
| def check_assembly_file_preamble(self, status): | |
| for input_filename in status.input_filenames: | |
| assembly_filename = get_assembly_filename(input_filename) | |
| success, message = self.verify_assembly_file_preamble( | |
| os.path.join(status.directory, assembly_filename)) | |
| if not success: | |
| return False, message | |
| return True, '' | |
| class ValidAssemblyFileWithSubstr(ValidAssemblyFile): | |
| """Mixin class for checking that every input file generates a valid assembly | |
| file following the assembly file naming rule, there is no output on | |
| stdout/stderr, and all assembly files have the given substring specified | |
| by expected_assembly_substr. | |
| To mix in this class, subclasses need to provde expected_assembly_substr | |
| as the expected substring. | |
| """ | |
| def check_assembly_with_substr(self, status): | |
| for input_filename in status.input_filenames: | |
| assembly_filename = get_assembly_filename(input_filename) | |
| success, message = self.verify_assembly_file_preamble( | |
| os.path.join(status.directory, assembly_filename)) | |
| if not success: | |
| return False, message | |
| with open(assembly_filename, 'r') as f: | |
| content = f.read() | |
| if self.expected_assembly_substr not in convert_to_unix_line_endings( | |
| content): | |
| return False, ('Incorrect assembly output:\n{asm}\n' | |
| 'Expected substring not found:\n{exp}'.format( | |
| asm=content, exp=self.expected_assembly_substr)) | |
| return True, '' | |
| class ValidAssemblyFileWithoutSubstr(ValidAssemblyFile): | |
| """Mixin class for checking that every input file generates a valid assembly | |
| file following the assembly file naming rule, there is no output on | |
| stdout/stderr, and no assembly files have the given substring specified | |
| by unexpected_assembly_substr. | |
| To mix in this class, subclasses need to provde unexpected_assembly_substr | |
| as the substring we expect not to see. | |
| """ | |
| def check_assembly_for_substr(self, status): | |
| for input_filename in status.input_filenames: | |
| assembly_filename = get_assembly_filename(input_filename) | |
| success, message = self.verify_assembly_file_preamble( | |
| os.path.join(status.directory, assembly_filename)) | |
| if not success: | |
| return False, message | |
| with open(assembly_filename, 'r') as f: | |
| content = f.read() | |
| if self.unexpected_assembly_substr in convert_to_unix_line_endings( | |
| content): | |
| return False, ('Incorrect assembly output:\n{asm}\n' | |
| 'Unexpected substring found:\n{unexp}'.format( | |
| asm=content, exp=self.unexpected_assembly_substr)) | |
| return True, '' | |
| class ValidNamedAssemblyFile(SuccessfulReturn, CorrectAssemblyFilePreamble): | |
| """Mixin class for checking that a list of assembly files with the given | |
| names are correctly generated, and there is no output on stdout/stderr. | |
| To mix in this class, subclasses need to provide expected_assembly_filenames | |
| as the expected assembly filenames. | |
| """ | |
| def check_object_file_preamble(self, status): | |
| for assembly_filename in self.expected_assembly_filenames: | |
| success, message = self.verify_assembly_file_preamble( | |
| os.path.join(status.directory, assembly_filename)) | |
| if not success: | |
| return False, message | |
| return True, '' | |
| class ErrorMessage(SpirvTest): | |
| """Mixin class for tests that fail with a specific error message. | |
| To mix in this class, subclasses need to provide expected_error as the | |
| expected error message. | |
| The test should fail if the subprocess was terminated by a signal. | |
| """ | |
| def check_has_error_message(self, status): | |
| if not status.returncode: | |
| return False, ('Expected error message, but returned success from ' | |
| 'command execution') | |
| if status.returncode < 0: | |
| # On Unix, a negative value -N for Popen.returncode indicates | |
| # termination by signal N. | |
| # https://docs.python.org/2/library/subprocess.html | |
| return False, ('Expected error message, but command was terminated by ' | |
| 'signal ' + str(status.returncode)) | |
| if not status.stderr: | |
| return False, 'Expected error message, but no output on stderr' | |
| if self.expected_error != convert_to_unix_line_endings(status.stderr): | |
| return False, ('Incorrect stderr output:\n{act}\n' | |
| 'Expected:\n{exp}'.format( | |
| act=status.stderr, exp=self.expected_error)) | |
| return True, '' | |
| class ErrorMessageSubstr(SpirvTest): | |
| """Mixin class for tests that fail with a specific substring in the error | |
| message. | |
| To mix in this class, subclasses need to provide expected_error_substr as | |
| the expected error message substring. | |
| The test should fail if the subprocess was terminated by a signal. | |
| """ | |
| def check_has_error_message_as_substring(self, status): | |
| if not status.returncode: | |
| return False, ('Expected error message, but returned success from ' | |
| 'command execution') | |
| if status.returncode < 0: | |
| # On Unix, a negative value -N for Popen.returncode indicates | |
| # termination by signal N. | |
| # https://docs.python.org/2/library/subprocess.html | |
| return False, ('Expected error message, but command was terminated by ' | |
| 'signal ' + str(status.returncode)) | |
| if not status.stderr: | |
| return False, 'Expected error message, but no output on stderr' | |
| if self.expected_error_substr not in convert_to_unix_line_endings( | |
| status.stderr.decode('utf8')): | |
| return False, ('Incorrect stderr output:\n{act}\n' | |
| 'Expected substring not found in stderr:\n{exp}'.format( | |
| act=status.stderr, exp=self.expected_error_substr)) | |
| return True, '' | |
| class WarningMessage(SpirvTest): | |
| """Mixin class for tests that succeed but have a specific warning message. | |
| To mix in this class, subclasses need to provide expected_warning as the | |
| expected warning message. | |
| """ | |
| def check_has_warning_message(self, status): | |
| if status.returncode: | |
| return False, ('Expected warning message, but returned failure from' | |
| ' command execution') | |
| if not status.stderr: | |
| return False, 'Expected warning message, but no output on stderr' | |
| if self.expected_warning != convert_to_unix_line_endings(status.stderr.decode('utf8')): | |
| return False, ('Incorrect stderr output:\n{act}\n' | |
| 'Expected:\n{exp}'.format( | |
| act=status.stderr, exp=self.expected_warning)) | |
| return True, '' | |
| class ValidObjectFileWithWarning(NoOutputOnStdout, CorrectObjectFilePreamble, | |
| WarningMessage): | |
| """Mixin class for checking that every input file generates a valid object | |
| file following the object file naming rule, with a specific warning message. | |
| """ | |
| def check_object_file_preamble(self, status): | |
| for input_filename in status.input_filenames: | |
| object_filename = get_object_filename(input_filename) | |
| success, message = self.verify_object_file_preamble( | |
| os.path.join(status.directory, object_filename)) | |
| if not success: | |
| return False, message | |
| return True, '' | |
| class ValidAssemblyFileWithWarning(NoOutputOnStdout, | |
| CorrectAssemblyFilePreamble, WarningMessage): | |
| """Mixin class for checking that every input file generates a valid assembly | |
| file following the assembly file naming rule, with a specific warning | |
| message.""" | |
| def check_assembly_file_preamble(self, status): | |
| for input_filename in status.input_filenames: | |
| assembly_filename = get_assembly_filename(input_filename) | |
| success, message = self.verify_assembly_file_preamble( | |
| os.path.join(status.directory, assembly_filename)) | |
| if not success: | |
| return False, message | |
| return True, '' | |
| class StdoutMatch(SpirvTest): | |
| """Mixin class for tests that can expect output on stdout. | |
| To mix in this class, subclasses need to provide expected_stdout as the | |
| expected stdout output. | |
| For expected_stdout, if it's True, then they expect something on stdout but | |
| will not check what it is. If it's a string, expect an exact match. If it's | |
| anything else, it is assumed to be a compiled regular expression which will | |
| be matched against re.search(). It will expect | |
| expected_stdout.search(status.stdout) to be true. | |
| """ | |
| def check_stdout_match(self, status): | |
| # "True" in this case means we expect something on stdout, but we do not | |
| # care what it is, we want to distinguish this from "blah" which means we | |
| # expect exactly the string "blah". | |
| if self.expected_stdout is True: | |
| if not status.stdout: | |
| return False, 'Expected something on stdout' | |
| elif type(self.expected_stdout) == str: | |
| if self.expected_stdout != convert_to_unix_line_endings(status.stdout.decode('utf8')): | |
| return False, ('Incorrect stdout output:\n{ac}\n' | |
| 'Expected:\n{ex}'.format( | |
| ac=status.stdout, ex=self.expected_stdout)) | |
| else: | |
| converted = convert_to_unix_line_endings(status.stdout.decode('utf8')) | |
| if not self.expected_stdout.search(converted): | |
| return False, ('Incorrect stdout output:\n{ac}\n' | |
| 'Expected to match regex:\n{ex}'.format( | |
| ac=status.stdout.decode('utf8'), ex=self.expected_stdout.pattern)) | |
| return True, '' | |
| class StderrMatch(SpirvTest): | |
| """Mixin class for tests that can expect output on stderr. | |
| To mix in this class, subclasses need to provide expected_stderr as the | |
| expected stderr output. | |
| For expected_stderr, if it's True, then they expect something on stderr, | |
| but will not check what it is. If it's a string, expect an exact match. | |
| If it's anything else, it is assumed to be a compiled regular expression | |
| which will be matched against re.search(). It will expect | |
| expected_stderr.search(status.stderr) to be true. | |
| """ | |
| def check_stderr_match(self, status): | |
| # "True" in this case means we expect something on stderr, but we do not | |
| # care what it is, we want to distinguish this from "blah" which means we | |
| # expect exactly the string "blah". | |
| if self.expected_stderr is True: | |
| if not status.stderr: | |
| return False, 'Expected something on stderr' | |
| elif type(self.expected_stderr) == str: | |
| if self.expected_stderr != convert_to_unix_line_endings(status.stderr.decode('utf8')): | |
| return False, ('Incorrect stderr output:\n{ac}\n' | |
| 'Expected:\n{ex}'.format( | |
| ac=status.stderr, ex=self.expected_stderr)) | |
| else: | |
| if not self.expected_stderr.search( | |
| convert_to_unix_line_endings(status.stderr.decode('utf8'))): | |
| return False, ('Incorrect stderr output:\n{ac}\n' | |
| 'Expected to match regex:\n{ex}'.format( | |
| ac=status.stderr, ex=self.expected_stderr.pattern)) | |
| return True, '' | |
| class StdoutNoWiderThan80Columns(SpirvTest): | |
| """Mixin class for tests that require stdout to 80 characters or narrower. | |
| To mix in this class, subclasses need to provide expected_stdout as the | |
| expected stdout output. | |
| """ | |
| def check_stdout_not_too_wide(self, status): | |
| if not status.stdout: | |
| return True, '' | |
| else: | |
| for line in status.stdout.splitlines(): | |
| if len(line) > 80: | |
| return False, ('Stdout line longer than 80 columns: %s' % line) | |
| return True, '' | |
| class NoObjectFile(SpirvTest): | |
| """Mixin class for checking that no input file has a corresponding object | |
| file.""" | |
| def check_no_object_file(self, status): | |
| for input_filename in status.input_filenames: | |
| object_filename = get_object_filename(input_filename) | |
| full_object_file = os.path.join(status.directory, object_filename) | |
| print('checking %s' % full_object_file) | |
| if os.path.isfile(full_object_file): | |
| return False, ( | |
| 'Expected no object file, but found: %s' % full_object_file) | |
| return True, '' | |
| class NoNamedOutputFiles(SpirvTest): | |
| """Mixin class for checking that no specified output files exist. | |
| The expected_output_filenames member should be full pathnames.""" | |
| def check_no_named_output_files(self, status): | |
| for object_filename in self.expected_output_filenames: | |
| if os.path.isfile(object_filename): | |
| return False, ( | |
| 'Expected no output file, but found: %s' % object_filename) | |
| return True, '' | |
| class ExecutedListOfPasses(SpirvTest): | |
| """Mixin class for checking that a list of passes where executed. | |
| It works by analyzing the output of the --print-all flag to spirv-opt. | |
| For this mixin to work, the class member expected_passes should be a sequence | |
| of pass names as returned by Pass::name(). | |
| """ | |
| def check_list_of_executed_passes(self, status): | |
| # Collect all the output lines containing a pass name. | |
| pass_names = [] | |
| pass_name_re = re.compile(r'.*IR before pass (?P<pass_name>[\S]+)') | |
| for line in status.stderr.decode('utf8').splitlines(): | |
| match = pass_name_re.match(line) | |
| if match: | |
| pass_names.append(match.group('pass_name')) | |
| for (expected, actual) in zip(self.expected_passes, pass_names): | |
| if expected != actual: | |
| return False, ( | |
| 'Expected pass "%s" but found pass "%s"\n' % (expected, actual)) | |
| return True, '' |