mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-25 13:00:04 +00:00
57b4cb40b2
This avoids Python2 vs Python3 issues related to how we decode bytes later on in the tests. Switching over to using unittest instead of nosetest
709 lines
27 KiB
Python
Executable File
709 lines
27 KiB
Python
Executable File
# Copyright (c) 2018 Google LLC
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""A number of common spirv result checks coded in mixin classes.
|
|
|
|
A test case can use these checks by declaring their enclosing mixin classes
|
|
as superclass and providing the expected_* variables required by the check_*()
|
|
methods in the mixin classes.
|
|
"""
|
|
import difflib
|
|
import functools
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import traceback
|
|
from spirv_test_framework import SpirvTest
|
|
from builtins import bytes
|
|
|
|
DEFAULT_SPIRV_VERSION = 0x010000
|
|
|
|
def convert_to_unix_line_endings(source):
|
|
"""Converts all line endings in source to be unix line endings."""
|
|
result = source.replace('\r\n', '\n').replace('\r', '\n')
|
|
return result
|
|
|
|
|
|
def substitute_file_extension(filename, extension):
|
|
"""Substitutes file extension, respecting known shader extensions.
|
|
|
|
foo.vert -> foo.vert.[extension] [similarly for .frag, .comp, etc.]
|
|
foo.glsl -> foo.[extension]
|
|
foo.unknown -> foo.[extension]
|
|
foo -> foo.[extension]
|
|
"""
|
|
if filename[-5:] not in [
|
|
'.vert', '.frag', '.tesc', '.tese', '.geom', '.comp', '.spvasm'
|
|
]:
|
|
return filename.rsplit('.', 1)[0] + '.' + extension
|
|
else:
|
|
return filename + '.' + extension
|
|
|
|
|
|
def get_object_filename(source_filename):
|
|
"""Gets the object filename for the given source file."""
|
|
return substitute_file_extension(source_filename, 'spv')
|
|
|
|
|
|
def get_assembly_filename(source_filename):
|
|
"""Gets the assembly filename for the given source file."""
|
|
return substitute_file_extension(source_filename, 'spvasm')
|
|
|
|
|
|
def verify_file_non_empty(filename):
|
|
"""Checks that a given file exists and is not empty."""
|
|
if not os.path.isfile(filename):
|
|
return False, 'Cannot find file: ' + filename
|
|
if not os.path.getsize(filename):
|
|
return False, 'Empty file: ' + filename
|
|
return True, ''
|
|
|
|
|
|
class ReturnCodeIsZero(SpirvTest):
|
|
"""Mixin class for checking that the return code is zero."""
|
|
|
|
def check_return_code_is_zero(self, status):
|
|
if status.returncode:
|
|
return False, 'Non-zero return code: {ret}\n'.format(
|
|
ret=status.returncode)
|
|
return True, ''
|
|
|
|
|
|
class ReturnCodeIsNonZero(SpirvTest):
|
|
"""Mixin class for checking that the return code is not zero."""
|
|
|
|
def check_return_code_is_nonzero(self, status):
|
|
if not status.returncode:
|
|
return False, 'return code is 0'
|
|
return True, ''
|
|
|
|
|
|
class NoOutputOnStdout(SpirvTest):
|
|
"""Mixin class for checking that there is no output on stdout."""
|
|
|
|
def check_no_output_on_stdout(self, status):
|
|
if status.stdout:
|
|
return False, 'Non empty stdout: {out}\n'.format(out=status.stdout)
|
|
return True, ''
|
|
|
|
|
|
class NoOutputOnStderr(SpirvTest):
|
|
"""Mixin class for checking that there is no output on stderr."""
|
|
|
|
def check_no_output_on_stderr(self, status):
|
|
if status.stderr:
|
|
return False, 'Non empty stderr: {err}\n'.format(err=status.stderr)
|
|
return True, ''
|
|
|
|
|
|
class SuccessfulReturn(ReturnCodeIsZero, NoOutputOnStdout, NoOutputOnStderr):
|
|
"""Mixin class for checking that return code is zero and no output on
|
|
stdout and stderr."""
|
|
pass
|
|
|
|
|
|
class NoGeneratedFiles(SpirvTest):
|
|
"""Mixin class for checking that there is no file generated."""
|
|
|
|
def check_no_generated_files(self, status):
|
|
all_files = os.listdir(status.directory)
|
|
input_files = status.input_filenames
|
|
if all([f.startswith(status.directory) for f in input_files]):
|
|
all_files = [os.path.join(status.directory, f) for f in all_files]
|
|
generated_files = set(all_files) - set(input_files)
|
|
if len(generated_files) == 0:
|
|
return True, ''
|
|
else:
|
|
return False, 'Extra files generated: {}'.format(generated_files)
|
|
|
|
|
|
class CorrectBinaryLengthAndPreamble(SpirvTest):
|
|
"""Provides methods for verifying preamble for a SPIR-V binary."""
|
|
|
|
def verify_binary_length_and_header(self, binary, spv_version=0x10000):
|
|
"""Checks that the given SPIR-V binary has valid length and header.
|
|
|
|
Returns:
|
|
False, error string if anything is invalid
|
|
True, '' otherwise
|
|
Args:
|
|
binary: a bytes object containing the SPIR-V binary
|
|
spv_version: target SPIR-V version number, with same encoding
|
|
as the version word in a SPIR-V header.
|
|
"""
|
|
|
|
def read_word(binary, index, little_endian):
|
|
"""Reads the index-th word from the given binary file."""
|
|
word = binary[index * 4:(index + 1) * 4]
|
|
if little_endian:
|
|
word = reversed(word)
|
|
return functools.reduce(lambda w, b: (w << 8) | b, word, 0)
|
|
|
|
def check_endianness(binary):
|
|
"""Checks the endianness of the given SPIR-V binary.
|
|
|
|
Returns:
|
|
True if it's little endian, False if it's big endian.
|
|
None if magic number is wrong.
|
|
"""
|
|
first_word = read_word(binary, 0, True)
|
|
if first_word == 0x07230203:
|
|
return True
|
|
first_word = read_word(binary, 0, False)
|
|
if first_word == 0x07230203:
|
|
return False
|
|
return None
|
|
|
|
num_bytes = len(binary)
|
|
if num_bytes % 4 != 0:
|
|
return False, ('Incorrect SPV binary: size should be a multiple'
|
|
' of words')
|
|
if num_bytes < 20:
|
|
return False, 'Incorrect SPV binary: size less than 5 words'
|
|
|
|
preamble = binary[0:19]
|
|
little_endian = check_endianness(preamble)
|
|
# SPIR-V module magic number
|
|
if little_endian is None:
|
|
return False, 'Incorrect SPV binary: wrong magic number'
|
|
|
|
# SPIR-V version number
|
|
version = read_word(preamble, 1, little_endian)
|
|
# TODO(dneto): Recent Glslang uses version word 0 for opengl_compat
|
|
# profile
|
|
|
|
if version != spv_version and version != 0:
|
|
return False, 'Incorrect SPV binary: wrong version number: ' + hex(version) + ' expected ' + hex(spv_version)
|
|
# Shaderc-over-Glslang (0x000d....) or
|
|
# SPIRV-Tools (0x0007....) generator number
|
|
if read_word(preamble, 2, little_endian) != 0x000d0007 and \
|
|
read_word(preamble, 2, little_endian) != 0x00070000:
|
|
return False, ('Incorrect SPV binary: wrong generator magic ' 'number')
|
|
# reserved for instruction schema
|
|
if read_word(preamble, 4, little_endian) != 0:
|
|
return False, 'Incorrect SPV binary: the 5th byte should be 0'
|
|
|
|
return True, ''
|
|
|
|
|
|
class CorrectObjectFilePreamble(CorrectBinaryLengthAndPreamble):
|
|
"""Provides methods for verifying preamble for a SPV object file."""
|
|
|
|
def verify_object_file_preamble(self,
|
|
filename,
|
|
spv_version=DEFAULT_SPIRV_VERSION):
|
|
"""Checks that the given SPIR-V binary file has correct preamble."""
|
|
|
|
success, message = verify_file_non_empty(filename)
|
|
if not success:
|
|
return False, message
|
|
|
|
with open(filename, 'rb') as object_file:
|
|
object_file.seek(0, os.SEEK_END)
|
|
num_bytes = object_file.tell()
|
|
|
|
object_file.seek(0)
|
|
|
|
binary = bytes(object_file.read())
|
|
return self.verify_binary_length_and_header(binary, spv_version)
|
|
|
|
return True, ''
|
|
|
|
|
|
class CorrectAssemblyFilePreamble(SpirvTest):
|
|
"""Provides methods for verifying preamble for a SPV assembly file."""
|
|
|
|
def verify_assembly_file_preamble(self, filename):
|
|
success, message = verify_file_non_empty(filename)
|
|
if not success:
|
|
return False, message
|
|
|
|
with open(filename) as assembly_file:
|
|
line1 = assembly_file.readline()
|
|
line2 = assembly_file.readline()
|
|
line3 = assembly_file.readline()
|
|
|
|
if (line1 != '; SPIR-V\n' or line2 != '; Version: 1.0\n' or
|
|
(not line3.startswith('; Generator: Google Shaderc over Glslang;'))):
|
|
return False, 'Incorrect SPV assembly'
|
|
|
|
return True, ''
|
|
|
|
|
|
class ValidObjectFile(SuccessfulReturn, CorrectObjectFilePreamble):
|
|
"""Mixin class for checking that every input file generates a valid SPIR-V 1.0
|
|
object file following the object file naming rule, and there is no output on
|
|
stdout/stderr."""
|
|
|
|
def check_object_file_preamble(self, status):
|
|
for input_filename in status.input_filenames:
|
|
object_filename = get_object_filename(input_filename)
|
|
success, message = self.verify_object_file_preamble(
|
|
os.path.join(status.directory, object_filename))
|
|
if not success:
|
|
return False, message
|
|
return True, ''
|
|
|
|
|
|
class ValidObjectFile1_3(ReturnCodeIsZero, CorrectObjectFilePreamble):
|
|
"""Mixin class for checking that every input file generates a valid SPIR-V 1.3
|
|
object file following the object file naming rule, and there is no output on
|
|
stdout/stderr."""
|
|
|
|
def check_object_file_preamble(self, status):
|
|
for input_filename in status.input_filenames:
|
|
object_filename = get_object_filename(input_filename)
|
|
success, message = self.verify_object_file_preamble(
|
|
os.path.join(status.directory, object_filename), 0x10300)
|
|
if not success:
|
|
return False, message
|
|
return True, ''
|
|
|
|
|
|
class ValidObjectFile1_5(ReturnCodeIsZero, CorrectObjectFilePreamble):
|
|
"""Mixin class for checking that every input file generates a valid SPIR-V 1.5
|
|
object file following the object file naming rule, and there is no output on
|
|
stdout/stderr."""
|
|
|
|
def check_object_file_preamble(self, status):
|
|
for input_filename in status.input_filenames:
|
|
object_filename = get_object_filename(input_filename)
|
|
success, message = self.verify_object_file_preamble(
|
|
os.path.join(status.directory, object_filename), 0x10500)
|
|
if not success:
|
|
return False, message
|
|
return True, ''
|
|
|
|
|
|
class ValidObjectFileWithAssemblySubstr(SuccessfulReturn,
|
|
CorrectObjectFilePreamble):
|
|
"""Mixin class for checking that every input file generates a valid object
|
|
|
|
file following the object file naming rule, there is no output on
|
|
stdout/stderr, and the disassmbly contains a specified substring per
|
|
input.
|
|
"""
|
|
|
|
def check_object_file_disassembly(self, status):
|
|
for an_input in status.inputs:
|
|
object_filename = get_object_filename(an_input.filename)
|
|
obj_file = str(os.path.join(status.directory, object_filename))
|
|
success, message = self.verify_object_file_preamble(obj_file)
|
|
if not success:
|
|
return False, message
|
|
cmd = [status.test_manager.disassembler_path, '--no-color', obj_file]
|
|
process = subprocess.Popen(
|
|
args=cmd,
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
cwd=status.directory)
|
|
output = process.communicate(None)
|
|
disassembly = output[0]
|
|
if not isinstance(an_input.assembly_substr, str):
|
|
return False, 'Missing assembly_substr member'
|
|
if an_input.assembly_substr not in disassembly:
|
|
return False, ('Incorrect disassembly output:\n{asm}\n'
|
|
'Expected substring not found:\n{exp}'.format(
|
|
asm=disassembly, exp=an_input.assembly_substr))
|
|
return True, ''
|
|
|
|
|
|
class ValidNamedObjectFile(SuccessfulReturn, CorrectObjectFilePreamble):
|
|
"""Mixin class for checking that a list of object files with the given
|
|
names are correctly generated, and there is no output on stdout/stderr.
|
|
|
|
To mix in this class, subclasses need to provide expected_object_filenames
|
|
as the expected object filenames.
|
|
"""
|
|
|
|
def check_object_file_preamble(self, status):
|
|
for object_filename in self.expected_object_filenames:
|
|
success, message = self.verify_object_file_preamble(
|
|
os.path.join(status.directory, object_filename))
|
|
if not success:
|
|
return False, message
|
|
return True, ''
|
|
|
|
|
|
class ValidFileContents(SpirvTest):
|
|
"""Mixin class to test that a specific file contains specific text
|
|
To mix in this class, subclasses need to provide expected_file_contents as
|
|
the contents of the file and target_filename to determine the location."""
|
|
|
|
def check_file(self, status):
|
|
target_filename = os.path.join(status.directory, self.target_filename)
|
|
if not os.path.isfile(target_filename):
|
|
return False, 'Cannot find file: ' + target_filename
|
|
with open(target_filename, 'r') as target_file:
|
|
file_contents = target_file.read()
|
|
if isinstance(self.expected_file_contents, str):
|
|
if file_contents == self.expected_file_contents:
|
|
return True, ''
|
|
return False, ('Incorrect file output: \n{act}\n'
|
|
'Expected:\n{exp}'
|
|
'With diff:\n{diff}'.format(
|
|
act=file_contents,
|
|
exp=self.expected_file_contents,
|
|
diff='\n'.join(
|
|
list(
|
|
difflib.unified_diff(
|
|
self.expected_file_contents.split('\n'),
|
|
file_contents.split('\n'),
|
|
fromfile='expected_output',
|
|
tofile='actual_output')))))
|
|
elif isinstance(self.expected_file_contents, type(re.compile(''))):
|
|
if self.expected_file_contents.search(file_contents):
|
|
return True, ''
|
|
return False, ('Incorrect file output: \n{act}\n'
|
|
'Expected matching regex pattern:\n{exp}'.format(
|
|
act=file_contents,
|
|
exp=self.expected_file_contents.pattern))
|
|
return False, (
|
|
'Could not open target file ' + target_filename + ' for reading')
|
|
|
|
|
|
class ValidAssemblyFile(SuccessfulReturn, CorrectAssemblyFilePreamble):
|
|
"""Mixin class for checking that every input file generates a valid assembly
|
|
file following the assembly file naming rule, and there is no output on
|
|
stdout/stderr."""
|
|
|
|
def check_assembly_file_preamble(self, status):
|
|
for input_filename in status.input_filenames:
|
|
assembly_filename = get_assembly_filename(input_filename)
|
|
success, message = self.verify_assembly_file_preamble(
|
|
os.path.join(status.directory, assembly_filename))
|
|
if not success:
|
|
return False, message
|
|
return True, ''
|
|
|
|
|
|
class ValidAssemblyFileWithSubstr(ValidAssemblyFile):
|
|
"""Mixin class for checking that every input file generates a valid assembly
|
|
file following the assembly file naming rule, there is no output on
|
|
stdout/stderr, and all assembly files have the given substring specified
|
|
by expected_assembly_substr.
|
|
|
|
To mix in this class, subclasses need to provde expected_assembly_substr
|
|
as the expected substring.
|
|
"""
|
|
|
|
def check_assembly_with_substr(self, status):
|
|
for input_filename in status.input_filenames:
|
|
assembly_filename = get_assembly_filename(input_filename)
|
|
success, message = self.verify_assembly_file_preamble(
|
|
os.path.join(status.directory, assembly_filename))
|
|
if not success:
|
|
return False, message
|
|
with open(assembly_filename, 'r') as f:
|
|
content = f.read()
|
|
if self.expected_assembly_substr not in convert_to_unix_line_endings(
|
|
content):
|
|
return False, ('Incorrect assembly output:\n{asm}\n'
|
|
'Expected substring not found:\n{exp}'.format(
|
|
asm=content, exp=self.expected_assembly_substr))
|
|
return True, ''
|
|
|
|
|
|
class ValidAssemblyFileWithoutSubstr(ValidAssemblyFile):
|
|
"""Mixin class for checking that every input file generates a valid assembly
|
|
file following the assembly file naming rule, there is no output on
|
|
stdout/stderr, and no assembly files have the given substring specified
|
|
by unexpected_assembly_substr.
|
|
|
|
To mix in this class, subclasses need to provde unexpected_assembly_substr
|
|
as the substring we expect not to see.
|
|
"""
|
|
|
|
def check_assembly_for_substr(self, status):
|
|
for input_filename in status.input_filenames:
|
|
assembly_filename = get_assembly_filename(input_filename)
|
|
success, message = self.verify_assembly_file_preamble(
|
|
os.path.join(status.directory, assembly_filename))
|
|
if not success:
|
|
return False, message
|
|
with open(assembly_filename, 'r') as f:
|
|
content = f.read()
|
|
if self.unexpected_assembly_substr in convert_to_unix_line_endings(
|
|
content):
|
|
return False, ('Incorrect assembly output:\n{asm}\n'
|
|
'Unexpected substring found:\n{unexp}'.format(
|
|
asm=content, exp=self.unexpected_assembly_substr))
|
|
return True, ''
|
|
|
|
|
|
class ValidNamedAssemblyFile(SuccessfulReturn, CorrectAssemblyFilePreamble):
|
|
"""Mixin class for checking that a list of assembly files with the given
|
|
names are correctly generated, and there is no output on stdout/stderr.
|
|
|
|
To mix in this class, subclasses need to provide expected_assembly_filenames
|
|
as the expected assembly filenames.
|
|
"""
|
|
|
|
def check_object_file_preamble(self, status):
|
|
for assembly_filename in self.expected_assembly_filenames:
|
|
success, message = self.verify_assembly_file_preamble(
|
|
os.path.join(status.directory, assembly_filename))
|
|
if not success:
|
|
return False, message
|
|
return True, ''
|
|
|
|
|
|
class ErrorMessage(SpirvTest):
|
|
"""Mixin class for tests that fail with a specific error message.
|
|
|
|
To mix in this class, subclasses need to provide expected_error as the
|
|
expected error message.
|
|
|
|
The test should fail if the subprocess was terminated by a signal.
|
|
"""
|
|
|
|
def check_has_error_message(self, status):
|
|
if not status.returncode:
|
|
return False, ('Expected error message, but returned success from '
|
|
'command execution')
|
|
if status.returncode < 0:
|
|
# On Unix, a negative value -N for Popen.returncode indicates
|
|
# termination by signal N.
|
|
# https://docs.python.org/2/library/subprocess.html
|
|
return False, ('Expected error message, but command was terminated by '
|
|
'signal ' + str(status.returncode))
|
|
if not status.stderr:
|
|
return False, 'Expected error message, but no output on stderr'
|
|
if self.expected_error != convert_to_unix_line_endings(status.stderr):
|
|
return False, ('Incorrect stderr output:\n{act}\n'
|
|
'Expected:\n{exp}'.format(
|
|
act=status.stderr, exp=self.expected_error))
|
|
return True, ''
|
|
|
|
|
|
class ErrorMessageSubstr(SpirvTest):
|
|
"""Mixin class for tests that fail with a specific substring in the error
|
|
message.
|
|
|
|
To mix in this class, subclasses need to provide expected_error_substr as
|
|
the expected error message substring.
|
|
|
|
The test should fail if the subprocess was terminated by a signal.
|
|
"""
|
|
|
|
def check_has_error_message_as_substring(self, status):
|
|
if not status.returncode:
|
|
return False, ('Expected error message, but returned success from '
|
|
'command execution')
|
|
if status.returncode < 0:
|
|
# On Unix, a negative value -N for Popen.returncode indicates
|
|
# termination by signal N.
|
|
# https://docs.python.org/2/library/subprocess.html
|
|
return False, ('Expected error message, but command was terminated by '
|
|
'signal ' + str(status.returncode))
|
|
if not status.stderr:
|
|
return False, 'Expected error message, but no output on stderr'
|
|
if self.expected_error_substr not in convert_to_unix_line_endings(
|
|
status.stderr):
|
|
return False, ('Incorrect stderr output:\n{act}\n'
|
|
'Expected substring not found in stderr:\n{exp}'.format(
|
|
act=status.stderr, exp=self.expected_error_substr))
|
|
return True, ''
|
|
|
|
|
|
class WarningMessage(SpirvTest):
|
|
"""Mixin class for tests that succeed but have a specific warning message.
|
|
|
|
To mix in this class, subclasses need to provide expected_warning as the
|
|
expected warning message.
|
|
"""
|
|
|
|
def check_has_warning_message(self, status):
|
|
if status.returncode:
|
|
return False, ('Expected warning message, but returned failure from'
|
|
' command execution')
|
|
if not status.stderr:
|
|
return False, 'Expected warning message, but no output on stderr'
|
|
if self.expected_warning != convert_to_unix_line_endings(status.stderr):
|
|
return False, ('Incorrect stderr output:\n{act}\n'
|
|
'Expected:\n{exp}'.format(
|
|
act=status.stderr, exp=self.expected_warning))
|
|
return True, ''
|
|
|
|
|
|
class ValidObjectFileWithWarning(NoOutputOnStdout, CorrectObjectFilePreamble,
|
|
WarningMessage):
|
|
"""Mixin class for checking that every input file generates a valid object
|
|
file following the object file naming rule, with a specific warning message.
|
|
"""
|
|
|
|
def check_object_file_preamble(self, status):
|
|
for input_filename in status.input_filenames:
|
|
object_filename = get_object_filename(input_filename)
|
|
success, message = self.verify_object_file_preamble(
|
|
os.path.join(status.directory, object_filename))
|
|
if not success:
|
|
return False, message
|
|
return True, ''
|
|
|
|
|
|
class ValidAssemblyFileWithWarning(NoOutputOnStdout,
|
|
CorrectAssemblyFilePreamble, WarningMessage):
|
|
"""Mixin class for checking that every input file generates a valid assembly
|
|
file following the assembly file naming rule, with a specific warning
|
|
message."""
|
|
|
|
def check_assembly_file_preamble(self, status):
|
|
for input_filename in status.input_filenames:
|
|
assembly_filename = get_assembly_filename(input_filename)
|
|
success, message = self.verify_assembly_file_preamble(
|
|
os.path.join(status.directory, assembly_filename))
|
|
if not success:
|
|
return False, message
|
|
return True, ''
|
|
|
|
|
|
class StdoutMatch(SpirvTest):
|
|
"""Mixin class for tests that can expect output on stdout.
|
|
|
|
To mix in this class, subclasses need to provide expected_stdout as the
|
|
expected stdout output.
|
|
|
|
For expected_stdout, if it's True, then they expect something on stdout but
|
|
will not check what it is. If it's a string, expect an exact match. If it's
|
|
anything else, it is assumed to be a compiled regular expression which will
|
|
be matched against re.search(). It will expect
|
|
expected_stdout.search(status.stdout) to be true.
|
|
"""
|
|
|
|
def check_stdout_match(self, status):
|
|
# "True" in this case means we expect something on stdout, but we do not
|
|
# care what it is, we want to distinguish this from "blah" which means we
|
|
# expect exactly the string "blah".
|
|
if self.expected_stdout is True:
|
|
if not status.stdout:
|
|
return False, 'Expected something on stdout'
|
|
elif type(self.expected_stdout) == str:
|
|
if self.expected_stdout != convert_to_unix_line_endings(status.stdout):
|
|
return False, ('Incorrect stdout output:\n{ac}\n'
|
|
'Expected:\n{ex}'.format(
|
|
ac=status.stdout, ex=self.expected_stdout))
|
|
else:
|
|
converted = convert_to_unix_line_endings(status.stdout)
|
|
if not self.expected_stdout.search(converted):
|
|
return False, ('Incorrect stdout output:\n{ac}\n'
|
|
'Expected to match regex:\n{ex}'.format(
|
|
ac=status.stdout, ex=self.expected_stdout.pattern))
|
|
return True, ''
|
|
|
|
|
|
class StderrMatch(SpirvTest):
|
|
"""Mixin class for tests that can expect output on stderr.
|
|
|
|
To mix in this class, subclasses need to provide expected_stderr as the
|
|
expected stderr output.
|
|
|
|
For expected_stderr, if it's True, then they expect something on stderr,
|
|
but will not check what it is. If it's a string, expect an exact match.
|
|
If it's anything else, it is assumed to be a compiled regular expression
|
|
which will be matched against re.search(). It will expect
|
|
expected_stderr.search(status.stderr) to be true.
|
|
"""
|
|
|
|
def check_stderr_match(self, status):
|
|
# "True" in this case means we expect something on stderr, but we do not
|
|
# care what it is, we want to distinguish this from "blah" which means we
|
|
# expect exactly the string "blah".
|
|
if self.expected_stderr is True:
|
|
if not status.stderr:
|
|
return False, 'Expected something on stderr'
|
|
elif type(self.expected_stderr) == str:
|
|
if self.expected_stderr != convert_to_unix_line_endings(status.stderr):
|
|
return False, ('Incorrect stderr output:\n{ac}\n'
|
|
'Expected:\n{ex}'.format(
|
|
ac=status.stderr, ex=self.expected_stderr))
|
|
else:
|
|
if not self.expected_stderr.search(
|
|
convert_to_unix_line_endings(status.stderr)):
|
|
return False, ('Incorrect stderr output:\n{ac}\n'
|
|
'Expected to match regex:\n{ex}'.format(
|
|
ac=status.stderr, ex=self.expected_stderr.pattern))
|
|
return True, ''
|
|
|
|
|
|
class StdoutNoWiderThan80Columns(SpirvTest):
|
|
"""Mixin class for tests that require stdout to 80 characters or narrower.
|
|
|
|
To mix in this class, subclasses need to provide expected_stdout as the
|
|
expected stdout output.
|
|
"""
|
|
|
|
def check_stdout_not_too_wide(self, status):
|
|
if not status.stdout:
|
|
return True, ''
|
|
else:
|
|
for line in status.stdout.splitlines():
|
|
if len(line) > 80:
|
|
return False, ('Stdout line longer than 80 columns: %s' % line)
|
|
return True, ''
|
|
|
|
|
|
class NoObjectFile(SpirvTest):
|
|
"""Mixin class for checking that no input file has a corresponding object
|
|
file."""
|
|
|
|
def check_no_object_file(self, status):
|
|
for input_filename in status.input_filenames:
|
|
object_filename = get_object_filename(input_filename)
|
|
full_object_file = os.path.join(status.directory, object_filename)
|
|
print('checking %s' % full_object_file)
|
|
if os.path.isfile(full_object_file):
|
|
return False, (
|
|
'Expected no object file, but found: %s' % full_object_file)
|
|
return True, ''
|
|
|
|
|
|
class NoNamedOutputFiles(SpirvTest):
|
|
"""Mixin class for checking that no specified output files exist.
|
|
|
|
The expected_output_filenames member should be full pathnames."""
|
|
|
|
def check_no_named_output_files(self, status):
|
|
for object_filename in self.expected_output_filenames:
|
|
if os.path.isfile(object_filename):
|
|
return False, (
|
|
'Expected no output file, but found: %s' % object_filename)
|
|
return True, ''
|
|
|
|
|
|
class ExecutedListOfPasses(SpirvTest):
|
|
"""Mixin class for checking that a list of passes where executed.
|
|
|
|
It works by analyzing the output of the --print-all flag to spirv-opt.
|
|
|
|
For this mixin to work, the class member expected_passes should be a sequence
|
|
of pass names as returned by Pass::name().
|
|
"""
|
|
|
|
def check_list_of_executed_passes(self, status):
|
|
# Collect all the output lines containing a pass name.
|
|
pass_names = []
|
|
pass_name_re = re.compile(r'.*IR before pass (?P<pass_name>[\S]+)')
|
|
for line in status.stderr.splitlines():
|
|
match = pass_name_re.match(line)
|
|
if match:
|
|
pass_names.append(match.group('pass_name'))
|
|
|
|
for (expected, actual) in zip(self.expected_passes, pass_names):
|
|
if expected != actual:
|
|
return False, (
|
|
'Expected pass "%s" but found pass "%s"\n' % (expected, actual))
|
|
|
|
return True, ''
|