Path: blob/21.2-virgl/src/amd/compiler/tests/check_output.py
7132 views
#1# Copyright (c) 2020 Valve Corporation2#3# Permission is hereby granted, free of charge, to any person obtaining a4# copy of this software and associated documentation files (the "Software"),5# to deal in the Software without restriction, including without limitation6# the rights to use, copy, modify, merge, publish, distribute, sublicense,7# and/or sell copies of the Software, and to permit persons to whom the8# Software is furnished to do so, subject to the following conditions:9#10# The above copyright notice and this permission notice (including the next11# paragraph) shall be included in all copies or substantial portions of the12# Software.13#14# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR15# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,16# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL17# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER18# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING19# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS20# IN THE SOFTWARE.21import re22import sys23import os.path24import struct25import string26import copy27from math import floor2829if os.isatty(sys.stdout.fileno()):30set_red = "\033[31m"31set_green = "\033[1;32m"32set_normal = "\033[0m"33else:34set_red = ''35set_green = ''36set_normal = ''3738initial_code = '''39import re4041def insert_code(code):42insert_queue.append(CodeCheck(code, current_position))4344def insert_pattern(pattern):45insert_queue.append(PatternCheck(pattern, False, current_position))4647def vector_gpr(prefix, name, size, align):48insert_code(f'{name} = {name}0')49for i in range(size):50insert_code(f'{name}{i} = {name}0 + {i}')51insert_code(f'success = {name}0 + {size - 1} == {name}{size - 1}')52insert_code(f'success = {name}0 % {align} == 0')53return f'{prefix}[#{name}0:#{name}{size - 1}]'5455def sgpr_vector(name, size, align):56return vector_gpr('s', name, size, align)5758funcs.update({59's64': lambda name: vector_gpr('s', name, 2, 2),60's96': lambda name: vector_gpr('s', name, 3, 2),61's128': lambda name: vector_gpr('s', name, 4, 4),62's256': lambda name: vector_gpr('s', name, 8, 4),63's512': lambda name: vector_gpr('s', name, 16, 4),64})65for i in range(2, 14):66funcs['v%d' % (i * 32)] = lambda name: vector_gpr('v', name, i, 1)6768def _match_func(names):69for name in names.split(' '):70insert_code(f'funcs["{name}"] = lambda _: {name}')71return ' '.join(f'${name}' for name in names.split(' '))7273funcs['match_func'] = _match_func7475def search_re(pattern):76global success77success = re.search(pattern, output.read_line()) != None and success7879'''8081class Check:82def __init__(self, data, position):83self.data = data.rstrip()84self.position = position8586def run(self, state):87pass8889class CodeCheck(Check):90def run(self, state):91indent = 092first_line = [l for l in self.data.split('\n') if l.strip() != ''][0]93indent_amount = len(first_line) - len(first_line.lstrip())94indent = first_line[:indent_amount]95new_lines = []96for line in self.data.split('\n'):97if line.strip() == '':98new_lines.append('')99continue100if line[:indent_amount] != indent:101state.result.log += 'unexpected indent in code check:\n'102state.result.log += self.data + '\n'103return False104new_lines.append(line[indent_amount:])105code = '\n'.join(new_lines)106107try:108exec(code, state.g)109state.result.log += state.g['log']110state.g['log'] = ''111except BaseException as e:112state.result.log += 'code check at %s raised exception:\n' % self.position113state.result.log += code + '\n'114state.result.log += str(e)115return False116if not state.g['success']:117state.result.log += 'code check at %s failed:\n' % self.position118state.result.log += code + '\n'119return False120return True121122class StringStream:123class Pos:124def __init__(self):125self.line = 1126self.column = 1127128def __init__(self, data, name):129self.name = name130self.data = data131self.offset = 0132self.pos = StringStream.Pos()133134def reset(self):135self.offset = 0136self.pos = StringStream.Pos()137138def peek(self, num=1):139return self.data[self.offset:self.offset+num]140141def peek_test(self, chars):142c = self.peek(1)143return c != '' and c in chars144145def read(self, num=4294967296):146res = self.peek(num)147self.offset += len(res)148for c in res:149if c == '\n':150self.pos.line += 1151self.pos.column = 1152else:153self.pos.column += 1154return res155156def get_line(self, num):157return self.data.split('\n')[num - 1].rstrip()158159def read_line(self):160line = ''161while self.peek(1) not in ['\n', '']:162line += self.read(1)163self.read(1)164return line165166def skip_whitespace(self, inc_line):167chars = [' ', '\t'] + (['\n'] if inc_line else [])168while self.peek(1) in chars:169self.read(1)170171def get_number(self):172num = ''173while self.peek() in string.digits:174num += self.read(1)175return num176177def check_identifier(self):178return self.peek_test(string.ascii_letters + '_')179180def get_identifier(self):181res = ''182if self.check_identifier():183while self.peek_test(string.ascii_letters + string.digits + '_'):184res += self.read(1)185return res186187def format_error_lines(at, line_num, column_num, ctx, line):188pred = '%s line %d, column %d of %s: "' % (at, line_num, column_num, ctx)189return [pred + line + '"',190'-' * (column_num - 1 + len(pred)) + '^']191192class MatchResult:193def __init__(self, pattern):194self.success = True195self.func_res = None196self.pattern = pattern197self.pattern_pos = StringStream.Pos()198self.output_pos = StringStream.Pos()199self.fail_message = ''200201def set_pos(self, pattern, output):202self.pattern_pos.line = pattern.pos.line203self.pattern_pos.column = pattern.pos.column204self.output_pos.line = output.pos.line205self.output_pos.column = output.pos.column206207def fail(self, msg):208self.success = False209self.fail_message = msg210211def format_pattern_pos(self):212pat_pos = self.pattern_pos213pat_line = self.pattern.get_line(pat_pos.line)214res = format_error_lines('at', pat_pos.line, pat_pos.column, 'pattern', pat_line)215func_res = self.func_res216while func_res:217pat_pos = func_res.pattern_pos218pat_line = func_res.pattern.get_line(pat_pos.line)219res += format_error_lines('in', pat_pos.line, pat_pos.column, func_res.pattern.name, pat_line)220func_res = func_res.func_res221return '\n'.join(res)222223def do_match(g, pattern, output, skip_lines, in_func=False):224assert(not in_func or not skip_lines)225226if not in_func:227output.skip_whitespace(False)228pattern.skip_whitespace(False)229230old_g = copy.copy(g)231old_g_keys = list(g.keys())232res = MatchResult(pattern)233escape = False234while True:235res.set_pos(pattern, output)236237c = pattern.read(1)238fail = False239if c == '':240break241elif output.peek() == '':242res.fail('unexpected end of output')243elif c == '\\':244escape = True245continue246elif c == '\n':247old_line = output.pos.line248output.skip_whitespace(True)249if output.pos.line == old_line:250res.fail('expected newline in output')251elif not escape and c == '#':252num = output.get_number()253if num == '':254res.fail('expected number in output')255elif pattern.check_identifier():256name = pattern.get_identifier()257if name in g and int(num) != g[name]:258res.fail('unexpected number for \'%s\': %d (expected %d)' % (name, int(num), g[name]))259elif name != '_':260g[name] = int(num)261elif not escape and c == '$':262name = pattern.get_identifier()263264val = ''265while not output.peek_test(string.whitespace):266val += output.read(1)267268if name in g and val != g[name]:269res.fail('unexpected value for \'%s\': \'%s\' (expected \'%s\')' % (name, val, g[name]))270elif name != '_':271g[name] = val272elif not escape and c == '%' and pattern.check_identifier():273if output.read(1) != '%':274res.fail('expected \'%\' in output')275else:276num = output.get_number()277if num == '':278res.fail('expected number in output')279else:280name = pattern.get_identifier()281if name in g and int(num) != g[name]:282res.fail('unexpected number for \'%s\': %d (expected %d)' % (name, int(num), g[name]))283elif name != '_':284g[name] = int(num)285elif not escape and c == '@' and pattern.check_identifier():286name = pattern.get_identifier()287args = ''288if pattern.peek_test('('):289pattern.read(1)290while pattern.peek() not in ['', ')']:291args += pattern.read(1)292assert(pattern.read(1) == ')')293func_res = g['funcs'][name](args)294match_res = do_match(g, StringStream(func_res, 'expansion of "%s(%s)"' % (name, args)), output, False, True)295if not match_res.success:296res.func_res = match_res297res.output_pos = match_res.output_pos298res.fail(match_res.fail_message)299elif not escape and c == ' ':300while pattern.peek_test(' '):301pattern.read(1)302303read_whitespace = False304while output.peek_test(' \t'):305output.read(1)306read_whitespace = True307if not read_whitespace:308res.fail('expected whitespace in output, got %r' % (output.peek(1)))309else:310outc = output.peek(1)311if outc != c:312res.fail('expected %r in output, got %r' % (c, outc))313else:314output.read(1)315if not res.success:316if skip_lines and output.peek() != '':317g.clear()318g.update(old_g)319res.success = True320output.read_line()321pattern.reset()322output.skip_whitespace(False)323pattern.skip_whitespace(False)324else:325return res326327escape = False328329if not in_func:330while output.peek() in [' ', '\t']:331output.read(1)332333if output.read(1) not in ['', '\n']:334res.fail('expected end of output')335return res336337return res338339class PatternCheck(Check):340def __init__(self, data, search, position):341Check.__init__(self, data, position)342self.search = search343344def run(self, state):345pattern_stream = StringStream(self.data.rstrip(), 'pattern')346res = do_match(state.g, pattern_stream, state.g['output'], self.search)347if not res.success:348state.result.log += 'pattern at %s failed: %s\n' % (self.position, res.fail_message)349state.result.log += res.format_pattern_pos() + '\n\n'350if not self.search:351out_line = state.g['output'].get_line(res.output_pos.line)352state.result.log += '\n'.join(format_error_lines('at', res.output_pos.line, res.output_pos.column, 'output', out_line))353else:354state.result.log += 'output was:\n'355state.result.log += state.g['output'].data.rstrip() + '\n'356return False357return True358359class CheckState:360def __init__(self, result, variant, checks, output):361self.result = result362self.variant = variant363self.checks = checks364365self.checks.insert(0, CodeCheck(initial_code, None))366self.insert_queue = []367368self.g = {'success': True, 'funcs': {}, 'insert_queue': self.insert_queue,369'variant': variant, 'log': '', 'output': StringStream(output, 'output'),370'CodeCheck': CodeCheck, 'PatternCheck': PatternCheck,371'current_position': ''}372373class TestResult:374def __init__(self, expected):375self.result = ''376self.expected = expected377self.log = ''378379def check_output(result, variant, checks, output):380state = CheckState(result, variant, checks, output)381382while len(state.checks):383check = state.checks.pop(0)384state.current_position = check.position385if not check.run(state):386result.result = 'failed'387return388389for check in state.insert_queue[::-1]:390state.checks.insert(0, check)391state.insert_queue.clear()392393result.result = 'passed'394return395396def parse_check(variant, line, checks, pos):397if line.startswith(';'):398line = line[1:]399if len(checks) and isinstance(checks[-1], CodeCheck):400checks[-1].data += '\n' + line401else:402checks.append(CodeCheck(line, pos))403elif line.startswith('!'):404checks.append(PatternCheck(line[1:], False, pos))405elif line.startswith('>>'):406checks.append(PatternCheck(line[2:], True, pos))407elif line.startswith('~'):408end = len(line)409start = len(line)410for c in [';', '!', '>>']:411if line.find(c) != -1 and line.find(c) < end:412end = line.find(c)413if end != len(line):414match = re.match(line[1:end], variant)415if match and match.end() == len(variant):416parse_check(variant, line[end:], checks, pos)417418def parse_test_source(test_name, variant, fname):419in_test = False420test = []421expected_result = 'passed'422line_num = 1423for line in open(fname, 'r').readlines():424if line.startswith('BEGIN_TEST(%s)' % test_name):425in_test = True426elif line.startswith('BEGIN_TEST_TODO(%s)' % test_name):427in_test = True428expected_result = 'todo'429elif line.startswith('BEGIN_TEST_FAIL(%s)' % test_name):430in_test = True431expected_result = 'failed'432elif line.startswith('END_TEST'):433in_test = False434elif in_test:435test.append((line_num, line.strip()))436line_num += 1437438checks = []439for line_num, check in [(line_num, l[2:]) for line_num, l in test if l.startswith('//')]:440parse_check(variant, check, checks, 'line %d of %s' % (line_num, os.path.split(fname)[1]))441442return checks, expected_result443444def parse_and_check_test(test_name, variant, test_file, output, current_result):445checks, expected = parse_test_source(test_name, variant, test_file)446447result = TestResult(expected)448if len(checks) == 0:449result.result = 'empty'450result.log = 'no checks found'451elif current_result != None:452result.result, result.log = current_result453else:454check_output(result, variant, checks, output)455if result.result == 'failed' and expected == 'todo':456result.result = 'todo'457458return result459460def print_results(results, output, expected):461results = {name: result for name, result in results.items() if result.result == output}462results = {name: result for name, result in results.items() if (result.result == result.expected) == expected}463464if not results:465return 0466467print('%s tests (%s):' % (output, 'expected' if expected else 'unexpected'))468for test, result in results.items():469color = '' if expected else set_red470print(' %s%s%s' % (color, test, set_normal))471if result.log.strip() != '':472for line in result.log.rstrip().split('\n'):473print(' ' + line.rstrip())474print('')475476return len(results)477478def get_cstr(fp):479res = b''480while True:481c = fp.read(1)482if c == b'\x00':483return res.decode('utf-8')484else:485res += c486487if __name__ == "__main__":488results = {}489490stdin = sys.stdin.buffer491while True:492packet_type = stdin.read(4)493if packet_type == b'':494break;495496test_name = get_cstr(stdin)497test_variant = get_cstr(stdin)498if test_variant != '':499full_name = test_name + '/' + test_variant500else:501full_name = test_name502503test_source_file = get_cstr(stdin)504current_result = None505if ord(stdin.read(1)):506current_result = (get_cstr(stdin), get_cstr(stdin))507code_size = struct.unpack("=L", stdin.read(4))[0]508code = stdin.read(code_size).decode('utf-8')509510results[full_name] = parse_and_check_test(test_name, test_variant, test_source_file, code, current_result)511512result_types = ['passed', 'failed', 'todo', 'empty']513num_expected = 0514num_unexpected = 0515for t in result_types:516num_expected += print_results(results, t, True)517for t in result_types:518num_unexpected += print_results(results, t, False)519num_expected_skipped = print_results(results, 'skipped', True)520num_unexpected_skipped = print_results(results, 'skipped', False)521522num_unskipped = len(results) - num_expected_skipped - num_unexpected_skipped523color = set_red if num_unexpected else set_green524print('%s%d (%.0f%%) of %d unskipped tests had an expected result%s' % (color, num_expected, floor(num_expected / num_unskipped * 100), num_unskipped, set_normal))525if num_unexpected_skipped:526print('%s%d tests had been unexpectedly skipped%s' % (set_red, num_unexpected_skipped, set_normal))527528if num_unexpected:529sys.exit(1)530531532