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