| #!/usr/bin/env python3 |
| |
| # Copyright © 2024 Valve Corporation |
| # SPDX-License-Identifier: MIT |
| |
| import argparse |
| import collections |
| import subprocess |
| import os |
| import re |
| import sys |
| import tempfile |
| import textwrap |
| from pathlib import Path |
| |
| def trim_blank_lines(string, trailing): |
| lines = string.split('\n') |
| string = '' |
| empty_line = True |
| for i in range(len(lines)): |
| line_index = len(lines) - 1 - i if trailing else i |
| if empty_line and lines[line_index].strip() == '': |
| continue |
| |
| write_newline = not empty_line if trailing else line_index < len(lines) - 1 |
| newline = '\n' if write_newline else '' |
| if trailing: |
| string = lines[line_index] + newline + string |
| else: |
| string += lines[line_index] + newline |
| |
| empty_line = False |
| |
| return string |
| |
| class TestFileChange: |
| def __init__(self, expected, result): |
| self.expected = expected |
| |
| # Apply the indentation of the expectation to the result |
| indentation = 1000 |
| for expected_line in expected.split('\n'): |
| if match := re.match(r'^(\s*)\S', expected_line): |
| line_indentation = len(match.group(1)) |
| if indentation > line_indentation: |
| indentation = line_indentation |
| |
| self.result = '' |
| result = result.split('\n') |
| for i in range(len(result)): |
| result_line = result[i] |
| indentation_str = '' if result_line.strip() == '' else ' '*indentation |
| self.result += indentation_str + result_line + ('\n' if i < len(result) - 1 else '') |
| |
| if __name__ == '__main__': |
| parser = argparse.ArgumentParser() |
| parser.add_argument('--build-dir', '-B', required=False) |
| parser.add_argument('--test-filter', '-f', required=False) |
| parser.add_argument('--update-all', '-u', action='store_true') |
| args = parser.parse_args() |
| |
| bin_path = 'src/compiler/nir/nir_tests' |
| if args.build_dir: |
| bin_path = args.build_dir + '/' + bin_path |
| |
| if not os.path.isfile(bin_path): |
| print(f'{bin_path} \033[91m does not exist!\033[0m') |
| exit(1) |
| |
| build_args = ['meson', 'compile'] |
| if args.build_dir: |
| build_args.append(f'-C{args.build_dir}') |
| subprocess.run(build_args) |
| |
| test_args = [bin_path] |
| if args.test_filter: |
| test_args.append(f'--gtest_filter={args.test_filter}') |
| |
| env = os.environ.copy() |
| if args.update_all: |
| env['NIR_TEST_DUMP_SHADERS'] = 'true' |
| |
| output = subprocess.run(test_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, env=env) |
| |
| expected_pattern = re.compile(r'BEGIN EXPECTED\(([\d\w\W/.-_]+)\)') |
| |
| expectations = collections.defaultdict(list) |
| |
| current_file = None |
| current_result = None |
| current_expected = None |
| inside_result = False |
| inside_expected = False |
| |
| # Parse the output of the test binary and gather the changed shaders. |
| for output_line in output.stdout.split('\n'): |
| if output_line.startswith('BEGIN RESULT'): |
| inside_result = True |
| current_result = '' |
| continue |
| |
| if output_line.startswith('BEGIN EXPECTED'): |
| match = expected_pattern.match(output_line) |
| current_file = match.group(1).removeprefix('../') |
| inside_expected = True |
| current_expected = '' |
| continue |
| |
| if output_line.startswith('END'): |
| if current_result is not None and current_expected is not None: |
| # remove trailing and leading blank lines |
| current_result = trim_blank_lines(current_result, True) |
| current_result = trim_blank_lines(current_result, False) |
| current_expected = trim_blank_lines(current_expected, True) |
| current_expected = trim_blank_lines(current_expected, False) |
| |
| expectations[current_file].append(TestFileChange(current_expected, current_result)) |
| |
| current_result = None |
| current_expected = None |
| |
| inside_result = False |
| inside_expected = False |
| continue |
| |
| if inside_result: |
| current_result += output_line + '\n' |
| |
| if inside_expected: |
| current_expected += output_line + '\n' |
| |
| patches = [] |
| |
| # Generate patches for the changed shaders. |
| for file in expectations: |
| changes = expectations[file] |
| |
| updated_test_file = '' |
| |
| with open(file) as test_file: |
| updated_test_file = str(test_file.read()) |
| |
| for change in changes: |
| updated_test_file = updated_test_file.replace(change.expected, change.result) |
| |
| # change.expected == change.result can be the case when using NIR_TEST_DUMP_SHADERS. |
| if change.expected in updated_test_file and change.expected != change.result: |
| print(f'Duplicate test case in {file}!') |
| exit(1) |
| |
| with tempfile.NamedTemporaryFile(delete_on_close=False) as tmp: |
| tmp.write(bytes(updated_test_file, encoding="utf-8")) |
| tmp.close() |
| |
| diff = subprocess.run( |
| ['git', 'diff', '--no-index', file, tmp.name], |
| stdout=subprocess.PIPE, |
| stderr=subprocess.STDOUT, |
| universal_newlines=True, |
| ) |
| patch = diff.stdout.replace(tmp.name, '/' + file) |
| |
| print(patch) |
| |
| patches.append(patch) |
| |
| if len(patches) != 0: |
| sys.stdout.write('\033[96mApply the changes listed above?\033[0m [Y/n]') |
| response = None |
| try: |
| response = input() |
| except KeyboardInterrupt: |
| print() |
| sys.exit(1) |
| |
| if response in ['', 'y', 'Y']: |
| for patch in patches: |
| apply = subprocess.Popen( |
| ['git', 'apply', '--allow-empty'], |
| stdin=subprocess.PIPE, |
| ) |
| apply.communicate(input=bytes(patch, encoding="utf-8")) |