# # Copyright (c) 2020 Valve Corporation # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice (including the next # paragraph) shall be included in all copies or substantial portions of the # Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. import re import sys import os.path import struct import string import copy from math import floor if os.isatty(sys.stdout.fileno()): set_red = "\033[31m" set_green = "\033[1;32m" set_normal = "\033[0m" else: set_red = '' set_green = '' set_normal = '' initial_code = ''' def insert_code(code): insert_queue.append(CodeCheck(code)) def insert_pattern(pattern): insert_queue.append(PatternCheck(pattern)) def vector_gpr(prefix, name, size, align): insert_code(f'{name} = {name}0') for i in range(size): insert_code(f'{name}{i} = {name}0 + {i}') insert_code(f'success = {name}0 + {size - 1} == {name}{size - 1}') insert_code(f'success = {name}0 % {align} == 0') return f'{prefix}[#{name}0:#{name}{size - 1}]' def sgpr_vector(name, size, align): return vector_gpr('s', name, size, align) funcs.update({ 's64': lambda name: vector_gpr('s', name, 2, 2), 's96': lambda name: vector_gpr('s', name, 3, 2), 's128': lambda name: vector_gpr('s', name, 4, 4), 's256': lambda name: vector_gpr('s', name, 8, 4), 's512': lambda name: vector_gpr('s', name, 16, 4), }) for i in range(2, 14): funcs['v%d' % (i * 32)] = lambda name: vector_gpr('v', name, i, 1) ''' class Check: def __init__(self, data): self.data = data.rstrip() def run(self, state): pass class CodeCheck(Check): def run(self, state): indent = 0 first_line = [l for l in self.data.split('\n') if l.strip() != ''][0] indent_amount = len(first_line) - len(first_line.lstrip()) indent = first_line[:indent_amount] new_lines = [] for line in self.data.split('\n'): if line.strip() == '': new_lines.append('') continue if line[:indent_amount] != indent: state.result.log += 'unexpected indent in code check:\n' state.result.log += self.data + '\n' return False new_lines.append(line[indent_amount:]) code = '\n'.join(new_lines) try: exec(code, state.g) state.result.log += state.g['log'] state.g['log'] = '' except BaseException as e: state.result.log += 'code check raised exception:\n' state.result.log += code + '\n' state.result.log += str(e) return False if not state.g['success']: state.result.log += 'code check failed:\n' state.result.log += code + '\n' return False return True class StringStream: class Pos: def __init__(self): self.line = 1 self.column = 1 def __init__(self, data, name): self.name = name self.data = data self.offset = 0 self.pos = StringStream.Pos() def reset(self): self.offset = 0 self.pos = StringStream.Pos() def peek(self, num=1): return self.data[self.offset:self.offset+num] def peek_test(self, chars): c = self.peek(1) return c != '' and c in chars def read(self, num=4294967296): res = self.peek(num) self.offset += len(res) for c in res: if c == '\n': self.pos.line += 1 self.pos.column = 1 else: self.pos.column += 1 return res def get_line(self, num): return self.data.split('\n')[num - 1].rstrip() def skip_line(self): while self.peek(1) not in ['\n', '']: self.read(1) self.read(1) def skip_whitespace(self, inc_line): chars = [' ', '\t'] + (['\n'] if inc_line else []) while self.peek(1) in chars: self.read(1) def get_number(self): num = '' while self.peek() in string.digits: num += self.read(1) return num def check_identifier(self): return self.peek_test(string.ascii_letters + '_') def get_identifier(self): res = '' if self.check_identifier(): while self.peek_test(string.ascii_letters + string.digits + '_'): res += self.read(1) return res def format_error_lines(at, line_num, column_num, ctx, line): pred = '%s line %d, column %d of %s: "' % (at, line_num, column_num, ctx) return [pred + line + '"', '-' * (column_num - 1 + len(pred)) + '^'] class MatchResult: def __init__(self, pattern): self.success = True self.func_res = None self.pattern = pattern self.pattern_pos = StringStream.Pos() self.output_pos = StringStream.Pos() self.fail_message = '' def set_pos(self, pattern, output): self.pattern_pos.line = pattern.pos.line self.pattern_pos.column = pattern.pos.column self.output_pos.line = output.pos.line self.output_pos.column = output.pos.column def fail(self, msg): self.success = False self.fail_message = msg def format_pattern_pos(self): pat_pos = self.pattern_pos pat_line = self.pattern.get_line(pat_pos.line) res = format_error_lines('at', pat_pos.line, pat_pos.column, 'pattern', pat_line) func_res = self.func_res while func_res: pat_pos = func_res.pattern_pos pat_line = func_res.pattern.get_line(pat_pos.line) res += format_error_lines('in', pat_pos.line, pat_pos.column, func_res.pattern.name, pat_line) func_res = func_res.func_res return '\n'.join(res) def do_match(g, pattern, output, skip_lines, in_func=False): assert(not in_func or not skip_lines) if not in_func: output.skip_whitespace(False) pattern.skip_whitespace(False) old_g = copy.copy(g) old_g_keys = list(g.keys()) res = MatchResult(pattern) escape = False while True: res.set_pos(pattern, output) c = pattern.read(1) fail = False if c == '': break elif output.peek() == '': res.fail('unexpected end of output') elif c == '\\': escape = True continue elif c == '\n': old_line = output.pos.line output.skip_whitespace(True) if output.pos.line == old_line: res.fail('expected newline in output') elif not escape and c == '#': num = output.get_number() if num == '': res.fail('expected number in output') elif pattern.check_identifier(): name = pattern.get_identifier() if name in g and int(num) != g[name]: res.fail('unexpected number for \'%s\': %d (expected %d)' % (name, int(num), g[name])) elif name != '_': g[name] = int(num) elif not escape and c == '$': name = pattern.get_identifier() val = '' while not output.peek_test(string.whitespace): val += output.read(1) if name in g and val != g[name]: res.fail('unexpected value for \'%s\': \'%s\' (expected \'%s\')' % (name, val, g[name])) elif name != '_': g[name] = val elif not escape and c == '%' and pattern.check_identifier(): if output.read(1) != '%': res.fail('expected \'%\' in output') else: num = output.get_number() if num == '': res.fail('expected number in output') else: name = pattern.get_identifier() if name in g and int(num) != g[name]: res.fail('unexpected number for \'%s\': %d (expected %d)' % (name, int(num), g[name])) elif name != '_': g[name] = int(num) elif not escape and c == '@' and pattern.check_identifier(): name = pattern.get_identifier() args = '' if pattern.peek_test('('): pattern.read(1) while pattern.peek() not in ['', ')']: args += pattern.read(1) assert(pattern.read(1) == ')') func_res = g['funcs'][name](args) match_res = do_match(g, StringStream(func_res, 'expansion of "%s(%s)"' % (name, args)), output, False, True) if not match_res.success: res.func_res = match_res res.output_pos = match_res.output_pos res.fail(match_res.fail_message) elif not escape and c == ' ': while pattern.peek_test(' '): pattern.read(1) read_whitespace = False while output.peek_test(' \t'): output.read(1) read_whitespace = True if not read_whitespace: res.fail('expected whitespace in output, got %r' % (output.peek(1))) else: outc = output.peek(1) if outc != c: res.fail('expected %r in output, got %r' % (c, outc)) else: output.read(1) if not res.success: if skip_lines and output.peek() != '': g.clear() g.update(old_g) res.success = True output.skip_line() pattern.reset() output.skip_whitespace(False) pattern.skip_whitespace(False) else: return res escape = False if not in_func: while output.peek() in [' ', '\t']: output.read(1) if output.read(1) not in ['', '\n']: res.fail('expected end of output') return res return res class PatternCheck(Check): def __init__(self, data, search, position): Check.__init__(self, data) self.search = search self.position = position def run(self, state): pattern_stream = StringStream(self.data.rstrip(), 'pattern') res = do_match(state.g, pattern_stream, state.g['output'], self.search) if not res.success: state.result.log += 'pattern at %s failed: %s\n' % (self.position, res.fail_message) state.result.log += res.format_pattern_pos() + '\n\n' if not self.search: out_line = state.g['output'].get_line(res.output_pos.line) state.result.log += '\n'.join(format_error_lines('at', res.output_pos.line, res.output_pos.column, 'output', out_line)) else: state.result.log += 'output was:\n' state.result.log += state.g['output'].data.rstrip() + '\n' return False return True class CheckState: def __init__(self, result, variant, checks, output): self.result = result self.variant = variant self.checks = checks self.checks.insert(0, CodeCheck(initial_code)) self.insert_queue = [] self.g = {'success': True, 'funcs': {}, 'insert_queue': self.insert_queue, 'variant': variant, 'log': '', 'output': StringStream(output, 'output'), 'CodeCheck': CodeCheck, 'PatternCheck': PatternCheck} class TestResult: def __init__(self, expected): self.result = '' self.expected = expected self.log = '' def check_output(result, variant, checks, output): state = CheckState(result, variant, checks, output) while len(state.checks): check = state.checks.pop(0) if not check.run(state): result.result = 'failed' return for check in state.insert_queue[::-1]: state.checks.insert(0, check) state.insert_queue.clear() result.result = 'passed' return def parse_check(variant, line, checks, pos): if line.startswith(';'): line = line[1:] if len(checks) and isinstance(checks[-1], CodeCheck): checks[-1].data += '\n' + line else: checks.append(CodeCheck(line)) elif line.startswith('!'): checks.append(PatternCheck(line[1:], False, pos)) elif line.startswith('>>'): checks.append(PatternCheck(line[2:], True, pos)) elif line.startswith('~'): end = len(line) start = len(line) for c in [';', '!', '>>']: if line.find(c) != -1 and line.find(c) < end: end = line.find(c) if end != len(line): match = re.match(line[1:end], variant) if match and match.end() == len(variant): parse_check(variant, line[end:], checks, pos) def parse_test_source(test_name, variant, fname): in_test = False test = [] expected_result = 'passed' line_num = 1 for line in open(fname, 'r').readlines(): if line.startswith('BEGIN_TEST(%s)' % test_name): in_test = True elif line.startswith('BEGIN_TEST_TODO(%s)' % test_name): in_test = True expected_result = 'todo' elif line.startswith('BEGIN_TEST_FAIL(%s)' % test_name): in_test = True expected_result = 'failed' elif line.startswith('END_TEST'): in_test = False elif in_test: test.append((line_num, line.strip())) line_num += 1 checks = [] for line_num, check in [(line_num, l[2:]) for line_num, l in test if l.startswith('//')]: parse_check(variant, check, checks, 'line %d of %s' % (line_num, os.path.split(fname)[1])) return checks, expected_result def parse_and_check_test(test_name, variant, test_file, output, current_result): checks, expected = parse_test_source(test_name, variant, test_file) result = TestResult(expected) if len(checks) == 0: result.result = 'empty' result.log = 'no checks found' elif current_result != None: result.result, result.log = current_result else: check_output(result, variant, checks, output) if result.result == 'failed' and expected == 'todo': result.result = 'todo' return result def print_results(results, output, expected): results = {name: result for name, result in results.items() if result.result == output} results = {name: result for name, result in results.items() if (result.result == result.expected) == expected} if not results: return 0 print('%s tests (%s):' % (output, 'expected' if expected else 'unexpected')) for test, result in results.items(): color = '' if expected else set_red print(' %s%s%s' % (color, test, set_normal)) if result.log.strip() != '': for line in result.log.rstrip().split('\n'): print(' ' + line.rstrip()) print('') return len(results) def get_cstr(fp): res = b'' while True: c = fp.read(1) if c == b'\x00': return res.decode('utf-8') else: res += c if __name__ == "__main__": results = {} stdin = sys.stdin.buffer while True: packet_type = stdin.read(4) if packet_type == b'': break; test_name = get_cstr(stdin) test_variant = get_cstr(stdin) if test_variant != '': full_name = test_name + '/' + test_variant else: full_name = test_name test_source_file = get_cstr(stdin) current_result = None if ord(stdin.read(1)): current_result = (get_cstr(stdin), get_cstr(stdin)) code_size = struct.unpack("=L", stdin.read(4))[0] code = stdin.read(code_size).decode('utf-8') results[full_name] = parse_and_check_test(test_name, test_variant, test_source_file, code, current_result) result_types = ['passed', 'failed', 'todo', 'empty'] num_expected = 0 num_unexpected = 0 for t in result_types: num_expected += print_results(results, t, True) for t in result_types: num_unexpected += print_results(results, t, False) num_expected_skipped = print_results(results, 'skipped', True) num_unexpected_skipped = print_results(results, 'skipped', False) num_unskipped = len(results) - num_expected_skipped - num_unexpected_skipped color = set_red if num_unexpected else set_green print('%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)) if num_unexpected_skipped: print('%s%d tests had been unexpectedly skipped%s' % (set_red, num_unexpected_skipped, set_normal)) if num_unexpected: sys.exit(1)