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