__LINE__ can be inconsistent when using different compilers. This patch changes the test runner to do a simple string find/replace of the test source file instead of looking for the line where the reference string starts. Acked-by: Alyssa Rosenzweig <alyssa@rosenzweig.io> Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/33980>
183 lines
5.9 KiB
Python
Executable File
183 lines
5.9 KiB
Python
Executable File
#!/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"))
|