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