| #! /usr/bin/python3 |
| # |
| # Copyright (c) 2022 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. |
| |
| import glob |
| import os |
| import subprocess |
| import sys |
| |
| # A handful of relevant tests are hand-picked to generate extra unit tests with |
| # specific options of spirv-diff. |
| IGNORE_SET_BINDING_TESTS = ['different_decorations_vertex'] |
| IGNORE_LOCATION_TESTS = ['different_decorations_fragment'] |
| IGNORE_DECORATIONS_TESTS = ['different_decorations_vertex', 'different_decorations_fragment'] |
| DUMP_IDS_TESTS = ['basic', 'int_vs_uint_constants', 'multiple_same_entry_points', 'small_functions_small_diffs'] |
| |
| LICENSE = u"""Copyright (c) 2022 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. |
| """ |
| |
| TEMPLATE_TEST_FILE = u"""// GENERATED FILE - DO NOT EDIT. |
| // Generated by {script_name} |
| // |
| {license} |
| |
| #include "../diff_test_utils.h" |
| |
| #include "gtest/gtest.h" |
| |
| namespace spvtools {{ |
| namespace diff {{ |
| namespace {{ |
| |
| {test_comment} |
| constexpr char kSrc[] = R"({src_spirv})"; |
| constexpr char kDst[] = R"({dst_spirv})"; |
| |
| TEST(DiffTest, {test_name}) {{ |
| constexpr char kDiff[] = R"({diff_spirv})"; |
| Options options; |
| DoStringDiffTest(kSrc, kDst, kDiff, options); |
| }} |
| |
| TEST(DiffTest, {test_name}NoDebug) {{ |
| constexpr char kSrcNoDebug[] = R"({src_spirv_no_debug})"; |
| constexpr char kDstNoDebug[] = R"({dst_spirv_no_debug})"; |
| constexpr char kDiff[] = R"({diff_spirv_no_debug})"; |
| Options options; |
| DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options); |
| }} |
| {extra_tests} |
| }} // namespace |
| }} // namespace diff |
| }} // namespace spvtools |
| """ |
| |
| TEMPLATE_TEST_FUNC = u""" |
| TEST(DiffTest, {test_name}{test_tag}) {{ |
| constexpr char kDiff[] = R"({diff_spirv})"; |
| Options options; |
| {test_options} |
| DoStringDiffTest(kSrc, kDst, kDiff, options); |
| }} |
| """ |
| |
| TEMPLATE_TEST_FILES_CMAKE = u"""# GENERATED FILE - DO NOT EDIT. |
| # Generated by {script_name} |
| # |
| {license} |
| |
| list(APPEND DIFF_TEST_FILES |
| {test_files} |
| ) |
| """ |
| |
| VARIANT_NONE = 0 |
| VARIANT_IGNORE_SET_BINDING = 1 |
| VARIANT_IGNORE_LOCATION = 2 |
| VARIANT_IGNORE_DECORATIONS = 3 |
| VARIANT_DUMP_IDS = 4 |
| |
| def print_usage(): |
| print("Usage: {} <path-to-spirv-diff>".format(sys.argv[0])) |
| |
| def remove_debug_info(in_path): |
| tmp_dir = '.no_dbg' |
| |
| if not os.path.exists(tmp_dir): |
| os.makedirs(tmp_dir) |
| |
| (in_basename, in_ext) = os.path.splitext(in_path) |
| out_name = in_basename + '_no_dbg' + in_ext |
| out_path = os.path.join(tmp_dir, out_name) |
| |
| with open(in_path, 'r') as fin: |
| with open(out_path, 'w') as fout: |
| for line in fin: |
| ops = line.strip().split() |
| op = ops[0] if len(ops) > 0 else '' |
| if (op != ';;' and op != 'OpName' and op != 'OpMemberName' and op != 'OpString' and |
| op != 'OpLine' and op != 'OpNoLine' and op != 'OpModuleProcessed'): |
| fout.write(line) |
| |
| return out_path |
| |
| def make_src_file(test_name): |
| return '{}_src.spvasm'.format(test_name) |
| |
| def make_dst_file(test_name): |
| return '{}_dst.spvasm'.format(test_name) |
| |
| def make_cpp_file(test_name): |
| return '{}_autogen.cpp'.format(test_name) |
| |
| def make_camel_case(test_name): |
| return test_name.replace('_', ' ').title().replace(' ', '') |
| |
| def make_comment(text, comment_prefix): |
| return '\n'.join([comment_prefix + (' ' if line.strip() else '') + line for line in text.splitlines()]) |
| |
| def read_file(file_name): |
| with open(file_name, 'r') as f: |
| content = f.read() |
| |
| # Use unix line endings. |
| content = content.replace('\r\n', '\n') |
| |
| return content |
| |
| def parse_test_comment(src_spirv_file_name, src_spirv): |
| src_spirv_lines = src_spirv.splitlines() |
| comment_line_count = 0 |
| while comment_line_count < len(src_spirv_lines): |
| if not src_spirv_lines[comment_line_count].strip().startswith(';;'): |
| break |
| comment_line_count += 1 |
| |
| if comment_line_count == 0: |
| print("Expected comment on test file '{}'. See README.md next to this file.".format(src_spirv_file_name)) |
| sys.exit(1) |
| |
| comment_block = src_spirv_lines[:comment_line_count] |
| spirv_block = src_spirv_lines[comment_line_count:] |
| |
| comment_block = ['// ' + line.replace(';;', '').strip() for line in comment_block] |
| |
| return '\n'.join(spirv_block), '\n'.join(comment_block) |
| |
| def run_diff_tool(diff_tool, src_file, dst_file, variant): |
| args = [diff_tool] |
| |
| if variant == VARIANT_IGNORE_SET_BINDING or variant == VARIANT_IGNORE_DECORATIONS: |
| args.append('--ignore-set-binding') |
| |
| if variant == VARIANT_IGNORE_LOCATION or variant == VARIANT_IGNORE_DECORATIONS: |
| args.append('--ignore-location') |
| |
| if variant == VARIANT_DUMP_IDS: |
| args.append('--with-id-map') |
| |
| args.append('--no-color') |
| args.append('--no-indent') |
| |
| args.append(src_file) |
| args.append(dst_file) |
| |
| success = True |
| print(' '.join(args)) |
| process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) |
| out, err = process.communicate() |
| |
| if process.returncode != 0: |
| print(err) |
| sys.exit(process.returncode) |
| |
| # Use unix line endings. |
| out = out.replace('\r\n', '\n') |
| |
| return out |
| |
| def generate_extra_test(diff_tool, src_file, dst_file, variant, test_name_camel_case, test_tag, test_options): |
| diff = run_diff_tool(diff_tool, src_file, dst_file, variant) |
| return TEMPLATE_TEST_FUNC.format( |
| test_name = test_name_camel_case, |
| test_tag = test_tag, |
| test_options = test_options, |
| diff_spirv = diff) |
| |
| def generate_test(diff_tool, test_name): |
| src_file = make_src_file(test_name) |
| dst_file = make_dst_file(test_name) |
| src_file_no_debug = remove_debug_info(src_file) |
| dst_file_no_debug = remove_debug_info(dst_file) |
| |
| src_spirv = read_file(src_file) |
| dst_spirv = read_file(dst_file) |
| src_spirv_no_debug = read_file(src_file_no_debug) |
| dst_spirv_no_debug = read_file(dst_file_no_debug) |
| |
| test_name_camel_case = make_camel_case(test_name) |
| |
| diff_spirv = run_diff_tool(diff_tool, src_file, dst_file, VARIANT_NONE) |
| diff_spirv_no_debug = run_diff_tool(diff_tool, src_file_no_debug, dst_file_no_debug, VARIANT_NONE) |
| |
| extra_tests = [] |
| |
| if test_name in IGNORE_SET_BINDING_TESTS: |
| extra_tests.append(generate_extra_test(diff_tool, src_file, dst_file, VARIANT_IGNORE_SET_BINDING, |
| test_name_camel_case, 'IgnoreSetBinding', 'options.ignore_set_binding = true;')) |
| |
| if test_name in IGNORE_LOCATION_TESTS: |
| extra_tests.append(generate_extra_test(diff_tool, src_file, dst_file, VARIANT_IGNORE_LOCATION, |
| test_name_camel_case, 'IgnoreLocation', 'options.ignore_location = true;')) |
| |
| if test_name in IGNORE_DECORATIONS_TESTS: |
| extra_tests.append(generate_extra_test(diff_tool, src_file, dst_file, VARIANT_IGNORE_DECORATIONS, |
| test_name_camel_case, 'IgnoreSetBindingLocation', |
| '\n '.join(['options.ignore_set_binding = true;', 'options.ignore_location = true;']))) |
| |
| if test_name in DUMP_IDS_TESTS: |
| extra_tests.append(generate_extra_test(diff_tool, src_file, dst_file, VARIANT_DUMP_IDS, |
| test_name_camel_case, 'DumpIds', 'options.dump_id_map = true;')) |
| |
| src_spirv, test_comment = parse_test_comment(src_file, src_spirv) |
| |
| test_file = TEMPLATE_TEST_FILE.format( |
| script_name = os.path.basename(__file__), |
| license = make_comment(LICENSE, '//'), |
| test_comment = test_comment, |
| test_name = test_name_camel_case, |
| src_spirv = src_spirv, |
| dst_spirv = dst_spirv, |
| diff_spirv = diff_spirv, |
| src_spirv_no_debug = src_spirv_no_debug, |
| dst_spirv_no_debug = dst_spirv_no_debug, |
| diff_spirv_no_debug = diff_spirv_no_debug, |
| extra_tests = ''.join(extra_tests)) |
| |
| test_file_name = make_cpp_file(test_name) |
| with open(test_file_name, 'wb') as fout: |
| fout.write(str.encode(test_file)) |
| |
| return test_file_name |
| |
| def generate_tests(diff_tool, test_names): |
| return [generate_test(diff_tool, test_name) for test_name in test_names] |
| |
| def generate_cmake(test_files): |
| cmake = TEMPLATE_TEST_FILES_CMAKE.format( |
| script_name = os.path.basename(__file__), |
| license = make_comment(LICENSE, '#'), |
| test_files = '\n'.join(['"diff_files/{}"'.format(f) for f in test_files])) |
| |
| with open('diff_test_files_autogen.cmake', 'wb') as fout: |
| fout.write(str.encode(cmake)) |
| |
| def main(): |
| |
| if len(sys.argv) != 2: |
| print_usage() |
| return 1 |
| |
| diff_tool = sys.argv[1] |
| if not os.path.exists(diff_tool): |
| print("No such file: {}".format(diff_tool)) |
| print_usage() |
| return 1 |
| |
| diff_tool = os.path.realpath(diff_tool) |
| os.chdir(os.path.dirname(__file__)) |
| |
| test_names = sorted([f[:-11] for f in glob.glob("*_src.spvasm")]) |
| |
| test_files = generate_tests(diff_tool, test_names) |
| |
| generate_cmake(test_files) |
| |
| return 0 |
| |
| if __name__ == '__main__': |
| sys.exit(main()) |