• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#! /usr/bin/python3
2#
3# Copyright (c) 2022 Google LLC
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import glob
18import os
19import subprocess
20import sys
21
22# A handful of relevant tests are hand-picked to generate extra unit tests with
23# specific options of spirv-diff.
24IGNORE_SET_BINDING_TESTS = ['different_decorations_vertex']
25IGNORE_LOCATION_TESTS = ['different_decorations_fragment']
26IGNORE_DECORATIONS_TESTS = ['different_decorations_vertex', 'different_decorations_fragment']
27DUMP_IDS_TESTS = ['basic', 'int_vs_uint_constants', 'multiple_same_entry_points', 'small_functions_small_diffs']
28
29LICENSE = u"""Copyright (c) 2022 Google LLC.
30
31Licensed under the Apache License, Version 2.0 (the "License");
32you may not use this file except in compliance with the License.
33You may obtain a copy of the License at
34
35    http://www.apache.org/licenses/LICENSE-2.0
36
37Unless required by applicable law or agreed to in writing, software
38distributed under the License is distributed on an "AS IS" BASIS,
39WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
40See the License for the specific language governing permissions and
41limitations under the License.
42"""
43
44TEMPLATE_TEST_FILE = u"""// GENERATED FILE - DO NOT EDIT.
45// Generated by {script_name}
46//
47{license}
48
49#include "../diff_test_utils.h"
50
51#include "gtest/gtest.h"
52
53namespace spvtools {{
54namespace diff {{
55namespace {{
56
57{test_comment}
58constexpr char kSrc[] = R"({src_spirv})";
59constexpr char kDst[] = R"({dst_spirv})";
60
61TEST(DiffTest, {test_name}) {{
62  constexpr char kDiff[] = R"({diff_spirv})";
63  Options options;
64  DoStringDiffTest(kSrc, kDst, kDiff, options);
65}}
66
67TEST(DiffTest, {test_name}NoDebug) {{
68  constexpr char kSrcNoDebug[] = R"({src_spirv_no_debug})";
69  constexpr char kDstNoDebug[] = R"({dst_spirv_no_debug})";
70  constexpr char kDiff[] = R"({diff_spirv_no_debug})";
71  Options options;
72  DoStringDiffTest(kSrcNoDebug, kDstNoDebug, kDiff, options);
73}}
74{extra_tests}
75}}  // namespace
76}}  // namespace diff
77}}  // namespace spvtools
78"""
79
80TEMPLATE_TEST_FUNC = u"""
81TEST(DiffTest, {test_name}{test_tag}) {{
82  constexpr char kDiff[] = R"({diff_spirv})";
83  Options options;
84  {test_options}
85  DoStringDiffTest(kSrc, kDst, kDiff, options);
86}}
87"""
88
89TEMPLATE_TEST_FILES_CMAKE = u"""# GENERATED FILE - DO NOT EDIT.
90# Generated by {script_name}
91#
92{license}
93
94list(APPEND DIFF_TEST_FILES
95{test_files}
96)
97"""
98
99VARIANT_NONE = 0
100VARIANT_IGNORE_SET_BINDING = 1
101VARIANT_IGNORE_LOCATION = 2
102VARIANT_IGNORE_DECORATIONS = 3
103VARIANT_DUMP_IDS = 4
104
105def print_usage():
106    print("Usage: {} <path-to-spirv-diff>".format(sys.argv[0]))
107
108def remove_debug_info(in_path):
109    tmp_dir = '.no_dbg'
110
111    if not os.path.exists(tmp_dir):
112        os.makedirs(tmp_dir)
113
114    (in_basename, in_ext) = os.path.splitext(in_path)
115    out_name = in_basename + '_no_dbg' + in_ext
116    out_path = os.path.join(tmp_dir, out_name)
117
118    with open(in_path, 'r') as fin:
119        with open(out_path, 'w') as fout:
120            for line in fin:
121                ops = line.strip().split()
122                op = ops[0] if len(ops) > 0 else ''
123                if (op != ';;' and op != 'OpName' and op != 'OpMemberName' and op != 'OpString' and
124                    op != 'OpLine' and op != 'OpNoLine' and op != 'OpModuleProcessed'):
125                    fout.write(line)
126
127    return out_path
128
129def make_src_file(test_name):
130    return '{}_src.spvasm'.format(test_name)
131
132def make_dst_file(test_name):
133    return '{}_dst.spvasm'.format(test_name)
134
135def make_cpp_file(test_name):
136    return '{}_autogen.cpp'.format(test_name)
137
138def make_camel_case(test_name):
139    return test_name.replace('_', ' ').title().replace(' ', '')
140
141def make_comment(text, comment_prefix):
142    return '\n'.join([comment_prefix + (' ' if line.strip() else '') + line for line in text.splitlines()])
143
144def read_file(file_name):
145    with open(file_name, 'r') as f:
146        content = f.read()
147
148    # Use unix line endings.
149    content = content.replace('\r\n', '\n')
150
151    return content
152
153def parse_test_comment(src_spirv_file_name, src_spirv):
154    src_spirv_lines = src_spirv.splitlines()
155    comment_line_count = 0
156    while comment_line_count < len(src_spirv_lines):
157        if not src_spirv_lines[comment_line_count].strip().startswith(';;'):
158            break
159        comment_line_count += 1
160
161    if comment_line_count == 0:
162        print("Expected comment on test file '{}'.  See README.md next to this file.".format(src_spirv_file_name))
163        sys.exit(1)
164
165    comment_block = src_spirv_lines[:comment_line_count]
166    spirv_block = src_spirv_lines[comment_line_count:]
167
168    comment_block = ['// ' + line.replace(';;', '').strip() for line in comment_block]
169
170    return '\n'.join(spirv_block), '\n'.join(comment_block)
171
172def run_diff_tool(diff_tool, src_file, dst_file, variant):
173    args = [diff_tool]
174
175    if variant == VARIANT_IGNORE_SET_BINDING or variant == VARIANT_IGNORE_DECORATIONS:
176        args.append('--ignore-set-binding')
177
178    if variant == VARIANT_IGNORE_LOCATION or variant == VARIANT_IGNORE_DECORATIONS:
179        args.append('--ignore-location')
180
181    if variant == VARIANT_DUMP_IDS:
182        args.append('--with-id-map')
183
184    args.append('--no-color')
185    args.append('--no-indent')
186
187    args.append(src_file)
188    args.append(dst_file)
189
190    success = True
191    print(' '.join(args))
192    process = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
193    out, err = process.communicate()
194
195    if process.returncode != 0:
196        print(err)
197        sys.exit(process.returncode)
198
199    # Use unix line endings.
200    out = out.replace('\r\n', '\n')
201
202    return out
203
204def generate_extra_test(diff_tool, src_file, dst_file, variant, test_name_camel_case, test_tag, test_options):
205    diff = run_diff_tool(diff_tool, src_file, dst_file, variant)
206    return TEMPLATE_TEST_FUNC.format(
207        test_name = test_name_camel_case,
208        test_tag = test_tag,
209        test_options = test_options,
210        diff_spirv = diff)
211
212def generate_test(diff_tool, test_name):
213    src_file = make_src_file(test_name)
214    dst_file = make_dst_file(test_name)
215    src_file_no_debug = remove_debug_info(src_file)
216    dst_file_no_debug = remove_debug_info(dst_file)
217
218    src_spirv = read_file(src_file)
219    dst_spirv = read_file(dst_file)
220    src_spirv_no_debug = read_file(src_file_no_debug)
221    dst_spirv_no_debug = read_file(dst_file_no_debug)
222
223    test_name_camel_case = make_camel_case(test_name)
224
225    diff_spirv = run_diff_tool(diff_tool, src_file, dst_file, VARIANT_NONE)
226    diff_spirv_no_debug = run_diff_tool(diff_tool, src_file_no_debug, dst_file_no_debug, VARIANT_NONE)
227
228    extra_tests = []
229
230    if test_name in IGNORE_SET_BINDING_TESTS:
231        extra_tests.append(generate_extra_test(diff_tool, src_file, dst_file, VARIANT_IGNORE_SET_BINDING,
232            test_name_camel_case, 'IgnoreSetBinding', 'options.ignore_set_binding = true;'))
233
234    if test_name in IGNORE_LOCATION_TESTS:
235        extra_tests.append(generate_extra_test(diff_tool, src_file, dst_file, VARIANT_IGNORE_LOCATION,
236            test_name_camel_case, 'IgnoreLocation', 'options.ignore_location = true;'))
237
238    if test_name in IGNORE_DECORATIONS_TESTS:
239        extra_tests.append(generate_extra_test(diff_tool, src_file, dst_file, VARIANT_IGNORE_DECORATIONS,
240            test_name_camel_case, 'IgnoreSetBindingLocation',
241            '\n  '.join(['options.ignore_set_binding = true;', 'options.ignore_location = true;'])))
242
243    if test_name in DUMP_IDS_TESTS:
244        extra_tests.append(generate_extra_test(diff_tool, src_file, dst_file, VARIANT_DUMP_IDS,
245            test_name_camel_case, 'DumpIds', 'options.dump_id_map = true;'))
246
247    src_spirv, test_comment = parse_test_comment(src_file, src_spirv)
248
249    test_file = TEMPLATE_TEST_FILE.format(
250            script_name = os.path.basename(__file__),
251            license = make_comment(LICENSE, '//'),
252            test_comment = test_comment,
253            test_name = test_name_camel_case,
254            src_spirv = src_spirv,
255            dst_spirv = dst_spirv,
256            diff_spirv = diff_spirv,
257            src_spirv_no_debug = src_spirv_no_debug,
258            dst_spirv_no_debug = dst_spirv_no_debug,
259            diff_spirv_no_debug = diff_spirv_no_debug,
260            extra_tests = ''.join(extra_tests))
261
262    test_file_name = make_cpp_file(test_name)
263    with open(test_file_name, 'wb') as fout:
264        fout.write(str.encode(test_file))
265
266    return test_file_name
267
268def generate_tests(diff_tool, test_names):
269    return [generate_test(diff_tool, test_name) for test_name in test_names]
270
271def generate_cmake(test_files):
272    cmake = TEMPLATE_TEST_FILES_CMAKE.format(
273            script_name = os.path.basename(__file__),
274            license = make_comment(LICENSE, '#'),
275            test_files = '\n'.join(['"diff_files/{}"'.format(f) for f in test_files]))
276
277    with open('diff_test_files_autogen.cmake', 'wb') as fout:
278        fout.write(str.encode(cmake))
279
280def main():
281
282    if len(sys.argv) != 2:
283        print_usage()
284        return 1
285
286    diff_tool = sys.argv[1]
287    if not os.path.exists(diff_tool):
288        print("No such file: {}".format(diff_tool))
289        print_usage()
290        return 1
291
292    diff_tool = os.path.realpath(diff_tool)
293    os.chdir(os.path.dirname(__file__))
294
295    test_names = sorted([f[:-11] for f in glob.glob("*_src.spvasm")])
296
297    test_files = generate_tests(diff_tool, test_names)
298
299    generate_cmake(test_files)
300
301    return 0
302
303if __name__ == '__main__':
304    sys.exit(main())
305