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 placeholders and their rules for expansion when used in tests. 15 16These placeholders, when used in spirv_args or expected_* variables of 17SpirvTest, have special meanings. In spirv_args, they will be substituted by 18the result of instantiate_for_spirv_args(), while in expected_*, by 19instantiate_for_expectation(). A TestCase instance will be passed in as 20argument to the instantiate_*() methods. 21""" 22 23import os 24import subprocess 25import tempfile 26from string import Template 27 28 29class PlaceHolderException(Exception): 30 """Exception class for PlaceHolder.""" 31 pass 32 33 34class PlaceHolder(object): 35 """Base class for placeholders.""" 36 37 def instantiate_for_spirv_args(self, testcase): 38 """Instantiation rules for spirv_args. 39 40 This method will be called when the current placeholder appears in 41 spirv_args. 42 43 Returns: 44 A string to replace the current placeholder in spirv_args. 45 """ 46 raise PlaceHolderException('Subclass should implement this function.') 47 48 def instantiate_for_expectation(self, testcase): 49 """Instantiation rules for expected_*. 50 51 This method will be called when the current placeholder appears in 52 expected_*. 53 54 Returns: 55 A string to replace the current placeholder in expected_*. 56 """ 57 raise PlaceHolderException('Subclass should implement this function.') 58 59 60class FileShader(PlaceHolder): 61 """Stands for a shader whose source code is in a file.""" 62 63 def __init__(self, source, suffix, assembly_substr=None): 64 assert isinstance(source, str) 65 assert isinstance(suffix, str) 66 self.source = source 67 self.suffix = suffix 68 self.filename = None 69 # If provided, this is a substring which is expected to be in 70 # the disassembly of the module generated from this input file. 71 self.assembly_substr = assembly_substr 72 73 def instantiate_for_spirv_args(self, testcase): 74 """Creates a temporary file and writes the source into it. 75 76 Returns: 77 The name of the temporary file. 78 """ 79 shader, self.filename = tempfile.mkstemp( 80 dir=testcase.directory, suffix=self.suffix) 81 shader_object = os.fdopen(shader, 'w') 82 shader_object.write(self.source) 83 shader_object.close() 84 return self.filename 85 86 def instantiate_for_expectation(self, testcase): 87 assert self.filename is not None 88 return self.filename 89 90 91class ConfigFlagsFile(PlaceHolder): 92 """Stands for a configuration file for spirv-opt generated out of a string.""" 93 94 def __init__(self, content, suffix): 95 assert isinstance(content, str) 96 assert isinstance(suffix, str) 97 self.content = content 98 self.suffix = suffix 99 self.filename = None 100 101 def instantiate_for_spirv_args(self, testcase): 102 """Creates a temporary file and writes content into it. 103 104 Returns: 105 The name of the temporary file. 106 """ 107 temp_fd, self.filename = tempfile.mkstemp( 108 dir=testcase.directory, suffix=self.suffix) 109 fd = os.fdopen(temp_fd, 'w') 110 fd.write(self.content) 111 fd.close() 112 return '-Oconfig=%s' % self.filename 113 114 def instantiate_for_expectation(self, testcase): 115 assert self.filename is not None 116 return self.filename 117 118 119class FileSPIRVShader(PlaceHolder): 120 """Stands for a source shader file which must be converted to SPIR-V.""" 121 122 def __init__(self, source, suffix, assembly_substr=None): 123 assert isinstance(source, str) 124 assert isinstance(suffix, str) 125 self.source = source 126 self.suffix = suffix 127 self.filename = None 128 # If provided, this is a substring which is expected to be in 129 # the disassembly of the module generated from this input file. 130 self.assembly_substr = assembly_substr 131 132 def instantiate_for_spirv_args(self, testcase): 133 """Creates a temporary file, writes the source into it and assembles it. 134 135 Returns: 136 The name of the assembled temporary file. 137 """ 138 shader, asm_filename = tempfile.mkstemp( 139 dir=testcase.directory, suffix=self.suffix) 140 shader_object = os.fdopen(shader, 'w') 141 shader_object.write(self.source) 142 shader_object.close() 143 self.filename = '%s.spv' % asm_filename 144 cmd = [ 145 testcase.test_manager.assembler_path, asm_filename, '-o', self.filename 146 ] 147 process = subprocess.Popen( 148 args=cmd, 149 stdin=subprocess.PIPE, 150 stdout=subprocess.PIPE, 151 stderr=subprocess.PIPE, 152 cwd=testcase.directory) 153 output = process.communicate() 154 assert process.returncode == 0 and not output[0] and not output[1] 155 return self.filename 156 157 def instantiate_for_expectation(self, testcase): 158 assert self.filename is not None 159 return self.filename 160 161 162class StdinShader(PlaceHolder): 163 """Stands for a shader whose source code is from stdin.""" 164 165 def __init__(self, source): 166 assert isinstance(source, str) 167 self.source = source 168 self.filename = None 169 170 def instantiate_for_spirv_args(self, testcase): 171 """Writes the source code back to the TestCase instance.""" 172 testcase.stdin_shader = self.source 173 self.filename = '-' 174 return self.filename 175 176 def instantiate_for_expectation(self, testcase): 177 assert self.filename is not None 178 return self.filename 179 180 181class TempFileName(PlaceHolder): 182 """Stands for a temporary file's name.""" 183 184 def __init__(self, filename): 185 assert isinstance(filename, str) 186 assert filename != '' 187 self.filename = filename 188 189 def instantiate_for_spirv_args(self, testcase): 190 return os.path.join(testcase.directory, self.filename) 191 192 def instantiate_for_expectation(self, testcase): 193 return os.path.join(testcase.directory, self.filename) 194 195 196class SpecializedString(PlaceHolder): 197 """Returns a string that has been specialized based on TestCase. 198 199 The string is specialized by expanding it as a string.Template 200 with all of the specialization being done with each $param replaced 201 by the associated member on TestCase. 202 """ 203 204 def __init__(self, filename): 205 assert isinstance(filename, str) 206 assert filename != '' 207 self.filename = filename 208 209 def instantiate_for_spirv_args(self, testcase): 210 return Template(self.filename).substitute(vars(testcase)) 211 212 def instantiate_for_expectation(self, testcase): 213 return Template(self.filename).substitute(vars(testcase)) 214