1#!/usr/bin/env python 2# Copyright 2018 The Amber Authors. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15 16import base64 17import difflib 18import optparse 19import os 20import platform 21import re 22import subprocess 23import sys 24import tempfile 25 26SUPPRESSIONS = { 27 "Darwin": [ 28 # No geometry shader on MoltenVK 29 "draw_triangle_list_using_geom_shader.vkscript", 30 # No tessellation shader on MoltenVK 31 "draw_triangle_list_using_tessellation.vkscript", 32 # No std140 support for matrices in SPIRV-Cross 33 "compute_mat2x2.vkscript", 34 "compute_mat2x2float.vkscript", 35 "compute_mat2x2.amber", 36 "compute_mat3x2.vkscript", 37 "compute_mat3x2float.vkscript", 38 "compute_mat3x2.amber", 39 # Metal vertex shaders cannot simultaneously write to a buffer and return 40 # a value to the rasterizer rdar://48348476 41 # https://github.com/KhronosGroup/MoltenVK/issues/527 42 "multiple_ssbo_update_with_graphics_pipeline.vkscript", 43 "multiple_ubo_update_with_graphics_pipeline.vkscript", 44 ], 45 "Linux": [ 46 ], 47 "Win": [ 48 ] 49} 50 51SUPPRESSIONS_SWIFTSHADER = [ 52 # Incorrect rendering: github.com/google/amber/issues/727 53 "draw_array_instanced.vkscript", 54 # Exceeds device limit maxComputeWorkGroupInvocations 55 "draw_sampled_image.amber", 56 # No geometry shader support 57 "draw_triangle_list_using_geom_shader.vkscript", 58 # No tessellation shader support 59 "draw_triangle_list_using_tessellation.vkscript", 60 # Vertex buffer format not supported 61 "draw_triangle_list_in_r8g8b8a8_srgb_color_frame.vkscript", 62 "draw_triangle_list_in_r32g32b32a32_sfloat_color_frame.vkscript", 63 "draw_triangle_list_in_r16g16b16a16_uint_color_frame.vkscript", 64 # Color attachment format is not supported 65 "draw_triangle_list_in_r16g16b16a16_snorm_color_frame.vkscript", 66 "draw_triangle_list_in_r8g8b8a8_snorm_color_frame.vkscript", 67 # No supporting device for Float16Int8Features 68 "float16.amber", 69 "int8.amber", 70 # No supporting device for the required 16-bit storage features 71 "storage16.amber", 72 # Exceeded maxBoundDescriptorSets limit of physical device 73 "multiple_ssbo_with_sparse_descriptor_set_in_compute_pipeline.vkscript", 74 # shaderStorageImageWriteWithoutFormat but is not enabled on the device 75 "opencl_read_and_write_image3d_rgba32i.amber", 76 "opencl_write_image.amber", 77 "glsl_read_and_write_image3d_rgba32i.amber", 78 # shaderStorageImageMultisample feature not supported 79 "draw_storageimage_multisample.amber", 80 # Unsupported depth/stencil formats 81 "draw_rectangles_depth_test_d24s8.amber", 82 "draw_rectangles_depth_test_x8d24.amber", 83 # Tessellation not supported 84 "tessellation_isolines.amber", 85 # 8 bit indices not supported 86 "draw_indexed_uint8.amber", 87] 88 89OPENCL_CASES = [ 90 "opencl_bind_buffer.amber", 91 "opencl_c_copy.amber", 92 "opencl_generated_push_constants.amber", 93 "opencl_read_and_write_image3d_rgba32i.amber", 94 "opencl_read_image.amber", 95 "opencl_read_image_literal_sampler.amber", 96 "opencl_set_arg.amber", 97 "opencl_write_image.amber", 98 ] 99 100DXC_CASES = [ 101 "draw_triangle_list_hlsl.amber", 102 "relative_includes_hlsl.amber", 103] 104 105SUPPRESSIONS_DAWN = [ 106 # Dawn does not support push constants 107 "graphics_push_constants.amber", 108 "graphics_push_constants.vkscript", 109 "compute_push_const_mat2x2.vkscript", 110 "compute_push_const_mat2x2float.vkscript", 111 "compute_push_const_mat2x3.vkscript", 112 "compute_push_const_mat2x3float.vkscript", 113 "compute_push_const_mat3x2.vkscript", 114 "compute_push_const_mat3x2float.vkscript", 115 "compute_push_const_mat3x3.vkscript", 116 "compute_push_const_mat3x3float.vkscript", 117 "compute_push_const_mat3x4.vkscript", 118 "compute_push_const_mat3x4float.vkscript", 119 "compute_push_const_mat4x3.vkscript", 120 "compute_push_const_mat4x3float.vkscript", 121 "compute_push_constant_and_ssbo.amber", 122 "compute_push_constant_and_ssbo.vkscript", 123 # Dawn does not support tessellation or geometry shader 124 "draw_triangle_list_using_geom_shader.vkscript", 125 "draw_triangle_list_using_tessellation.vkscript", 126 # Dawn requires a fragmentStage now and in the medium term. 127 # issue #556 (temp dawn limitation) 128 "position_to_ssbo.amber", 129 # DrawRect command is not supported in a pipeline with more than one vertex 130 # buffer attached 131 "draw_array_after_draw_rect.vkscript", 132 "draw_rect_after_draw_array.vkscript", 133 "draw_rect_and_draw_array_mixed.vkscript", 134 # Dawn DoCommands require a pipeline 135 "probe_no_compute_with_multiple_ssbo_commands.vkscript", 136 "probe_no_compute_with_ssbo.vkscript", 137 # Max number of descriptor sets is 4 in Dawn 138 "multiple_ssbo_with_sparse_descriptor_set_in_compute_pipeline.vkscript", 139 # Dawn entry point has to be "main" as a result it does not support 140 # doEntryPoint or opencl (in opencl using "main" as entry point is invalid). 141 # issue #607 (temp dawn limitation) 142 "compute_ssbo_with_entrypoint_command.vkscript", 143 "entry_point.amber", 144 "non_default_entry_point.amber", 145 "opencl_bind_buffer.amber", 146 "opencl_c_copy.amber", 147 "opencl_set_arg.amber", 148 "shader_specialization.amber", 149 # framebuffer format is not supported according to table "Mandatory format 150 # support" in Vulkan spec: VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT = 0 151 "draw_triangle_list_in_r16g16b16a16_snorm_color_frame.vkscript", 152 "draw_triangle_list_in_r16g16b16a16_uint_color_frame.vkscript", 153 "draw_triangle_list_in_r32g32b32a32_sfloat_color_frame.vkscript", 154 "draw_triangle_list_in_r8g8b8a8_snorm_color_frame.vkscript", 155 "draw_triangle_list_in_r8g8b8a8_srgb_color_frame.vkscript", 156 # Dawn does not support vertexPipelineStoresAndAtomics 157 "multiple_ubo_update_with_graphics_pipeline.vkscript", 158 "multiple_ssbo_update_with_graphics_pipeline.vkscript", 159 # Currently not working, under investigation 160 "draw_triangle_list_with_depth.vkscript", 161 # draw_grid not implemented for dawn yet 162 "draw_grid.amber", 163 "draw_grid_multiple_color_attachment.amber", 164 "draw_grid_multiple_pipeline.amber", 165 "draw_grids.amber", 166 "draw_grid.vkscript", 167 "draw_grid_with_buffer.amber", 168 "draw_grid_with_two_vertex_data_attached.expect_fail.amber", 169] 170 171class TestCase: 172 def __init__(self, input_path, parse_only, use_dawn, use_opencl, use_dxc, 173 use_swiftshader): 174 self.input_path = input_path 175 self.parse_only = parse_only 176 self.use_dawn = use_dawn 177 self.use_opencl = use_opencl 178 self.use_dxc = use_dxc 179 self.use_swiftshader = use_swiftshader 180 181 self.results = {} 182 183 def IsExpectedFail(self): 184 fail_re = re.compile('^.+[.]expect_fail[.][amber|vkscript]') 185 return fail_re.match(self.GetInputPath()) 186 187 def IsSuppressed(self): 188 system = platform.system() 189 190 base = os.path.basename(self.input_path) 191 is_dawn_suppressed = base in SUPPRESSIONS_DAWN 192 if self.use_dawn and is_dawn_suppressed: 193 return True 194 195 is_swiftshader_suppressed = base in SUPPRESSIONS_SWIFTSHADER 196 if self.use_swiftshader and is_swiftshader_suppressed: 197 return True 198 199 is_opencl_test = base in OPENCL_CASES 200 if not self.use_opencl and is_opencl_test: 201 return True 202 203 is_dxc_test = base in DXC_CASES 204 if not self.use_dxc and is_dxc_test: 205 return True 206 207 if system in SUPPRESSIONS.keys(): 208 is_system_suppressed = base in SUPPRESSIONS[system] 209 return is_system_suppressed 210 211 return False 212 213 def IsParseOnly(self): 214 return self.parse_only 215 216 def IsUseDawn(self): 217 return self.use_dawn 218 219 def GetInputPath(self): 220 return self.input_path 221 222 def GetResult(self, fmt): 223 return self.results[fmt] 224 225 226class TestRunner: 227 def RunTest(self, tc): 228 print("Testing {}".format(tc.GetInputPath())) 229 230 cmd = [self.options.test_prog_path, '-q'] 231 if tc.IsParseOnly(): 232 cmd += ['-p'] 233 if tc.IsUseDawn(): 234 cmd += ['-e', 'dawn'] 235 cmd += [tc.GetInputPath()] 236 237 try: 238 err = subprocess.check_output(cmd, stderr=subprocess.STDOUT) 239 if len(err) != 0 and not tc.IsExpectedFail() and not tc.IsSuppressed(): 240 sys.stdout.write(err.decode('utf-8')) 241 return False 242 243 except Exception as e: 244 if not tc.IsExpectedFail() and not tc.IsSuppressed(): 245 print("{}".format("".join(map(chr, bytearray(e.output))))) 246 print(e) 247 return False 248 249 return True 250 251 252 def RunTests(self): 253 for tc in self.test_cases: 254 result = self.RunTest(tc) 255 256 if tc.IsSuppressed(): 257 self.suppressed.append(tc.GetInputPath()) 258 else: 259 if not tc.IsExpectedFail() and not result: 260 self.failures.append(tc.GetInputPath()) 261 elif tc.IsExpectedFail() and result: 262 print("Expected: " + tc.GetInputPath() + " to fail but passed.") 263 self.failures.append(tc.GetInputPath()) 264 265 def SummarizeResults(self): 266 if len(self.failures) > 0: 267 self.failures.sort() 268 269 print('\nSummary of Failures:') 270 for failure in self.failures: 271 print(failure) 272 273 if len(self.suppressed) > 0: 274 self.suppressed.sort() 275 276 print('\nSummary of Suppressions:') 277 for suppression in self.suppressed: 278 print(suppression) 279 280 print('') 281 print('Test cases executed: {}'.format(len(self.test_cases))) 282 print(' Successes: {}'.format((len(self.test_cases) - len(self.suppressed) - len(self.failures)))) 283 print(' Failures: {}'.format(len(self.failures))) 284 print(' Suppressed: {}'.format(len(self.suppressed))) 285 print('') 286 287 288 def Run(self): 289 base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) 290 291 usage = 'usage: %prog [options] (file)' 292 parser = optparse.OptionParser(usage=usage) 293 parser.add_option('--build-dir', 294 default=os.path.join(base_path, 'out', 'Debug'), 295 help='path to build directory') 296 parser.add_option('--test-dir', 297 default=os.path.join(os.path.dirname(__file__), 'cases'), 298 help='path to directory containing test files') 299 parser.add_option('--test-prog-path', default=None, 300 help='path to program to test') 301 parser.add_option('--parse-only', 302 action="store_true", default=False, 303 help='only parse test cases; do not execute') 304 parser.add_option('--use-dawn', 305 action="store_true", default=False, 306 help='Use dawn as the backend; Default is Vulkan') 307 parser.add_option('--use-opencl', 308 action="store_true", default=False, 309 help='Enable OpenCL tests') 310 parser.add_option('--use-dxc', 311 action="store_true", default=False, 312 help='Enable DXC tests') 313 parser.add_option('--use-swiftshader', 314 action="store_true", default=False, 315 help='Tells test runner swiftshader is the device') 316 317 self.options, self.args = parser.parse_args() 318 319 if self.options.test_prog_path == None: 320 test_prog = os.path.abspath(os.path.join(self.options.build_dir, 'amber')) 321 if not os.path.isfile(test_prog): 322 print("Cannot find test program {}".format(test_prog)) 323 return 1 324 325 self.options.test_prog_path = test_prog 326 327 if not os.path.isfile(self.options.test_prog_path): 328 print("--test-prog-path must point to an executable") 329 return 1 330 331 input_file_re = re.compile('^.+[\.](amber|vkscript)') 332 self.test_cases = [] 333 334 if self.args: 335 for filename in self.args: 336 input_path = os.path.join(self.options.test_dir, filename) 337 if not os.path.isfile(input_path): 338 print("Cannot find test file '{}'".format(filename)) 339 return 1 340 341 self.test_cases.append(TestCase(input_path, self.options.parse_only, 342 self.options.use_dawn, self.options.use_opencl, 343 self.options.use_dxc, self.options.use_swiftshader)) 344 345 else: 346 for file_dir, _, filename_list in os.walk(self.options.test_dir): 347 for input_filename in filename_list: 348 if input_file_re.match(input_filename): 349 input_path = os.path.join(file_dir, input_filename) 350 if os.path.isfile(input_path): 351 self.test_cases.append( 352 TestCase(input_path, self.options.parse_only, 353 self.options.use_dawn, self.options.use_opencl, 354 self.options.use_dxc, self.options.use_swiftshader)) 355 356 self.failures = [] 357 self.suppressed = [] 358 359 self.RunTests() 360 self.SummarizeResults() 361 362 return len(self.failures) != 0 363 364def main(): 365 runner = TestRunner() 366 return runner.Run() 367 368 369if __name__ == '__main__': 370 sys.exit(main()) 371