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