File: nir-test-runner.py

package info (click to toggle)
mesa 25.2.8-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 312,152 kB
  • sloc: ansic: 2,185,354; xml: 1,028,239; cpp: 512,236; python: 76,148; asm: 38,329; yacc: 12,198; lisp: 4,114; lex: 3,429; sh: 855; makefile: 237
file content (182 lines) | stat: -rwxr-xr-x 6,056 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
#!/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"))