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