1d58ff7ccSK.Takata#!/usr/bin/env python3 2d58ff7ccSK.Takata 3646e1eb7SK.Takata# 4646e1eb7SK.Takata# units.py - Units test harness for ctags 5646e1eb7SK.Takata# 6646e1eb7SK.Takata# Copyright (C) 2019 Ken Takata 7646e1eb7SK.Takata# (Based on "units" written by Masatake YAMATO.) 8646e1eb7SK.Takata# 9646e1eb7SK.Takata# This program is free software; you can redistribute it and/or modify 10646e1eb7SK.Takata# it under the terms of the GNU General Public License as published by 11646e1eb7SK.Takata# the Free Software Foundation; either version 2 of the License, or 12646e1eb7SK.Takata# (at your option) any later version. 13646e1eb7SK.Takata# 14646e1eb7SK.Takata# This program is distributed in the hope that it will be useful, 15646e1eb7SK.Takata# but WITHOUT ANY WARRANTY; without even the implied warranty of 16646e1eb7SK.Takata# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17646e1eb7SK.Takata# GNU General Public License for more details. 18646e1eb7SK.Takata# 19646e1eb7SK.Takata# You should have received a copy of the GNU General Public License 20646e1eb7SK.Takata# along with this program. If not, see <http://www.gnu.org/licenses/>. 21646e1eb7SK.Takata# 22646e1eb7SK.Takata 23646e1eb7SK.Takata# 24646e1eb7SK.Takata# Python 3.5 or later is required. 253e288efeSK.Takata# On Windows, unix-like shell (e.g. bash) and some unix tools (sed, 263e288efeSK.Takata# diff, etc.) are needed. 27646e1eb7SK.Takata# 28646e1eb7SK.Takata 29d58ff7ccSK.Takataimport time # for debugging 30d58ff7ccSK.Takataimport argparse 31d58ff7ccSK.Takataimport filecmp 32d58ff7ccSK.Takataimport glob 33646e1eb7SK.Takataimport io 34d58ff7ccSK.Takataimport os 35d58ff7ccSK.Takataimport platform 36d58ff7ccSK.Takataimport queue 37d58ff7ccSK.Takataimport re 38d58ff7ccSK.Takataimport shutil 3928cf668aSK.Takataimport stat 40d58ff7ccSK.Takataimport subprocess 41d58ff7ccSK.Takataimport sys 42d58ff7ccSK.Takataimport threading 43d58ff7ccSK.Takata 44646e1eb7SK.Takata# 45646e1eb7SK.Takata# Global Parameters 46646e1eb7SK.Takata# 47646e1eb7SK.TakataSHELL = '/bin/sh' 48d58ff7ccSK.TakataCTAGS = './ctags' 49646e1eb7SK.TakataREADTAGS = './readtags' 50d8f6b0edSMasatake YAMATOOPTSCRIPT = './optscript' 51d58ff7ccSK.TakataWITH_TIMEOUT = 0 52d58ff7ccSK.TakataWITH_VALGRIND = False 53d58ff7ccSK.TakataCOLORIZED_OUTPUT = True 54d58ff7ccSK.TakataCATEGORIES = [] 55d58ff7ccSK.TakataUNITS = [] 56d58ff7ccSK.TakataLANGUAGES = [] 57d58ff7ccSK.TakataPRETENSE_OPTS = '' 58d58ff7ccSK.TakataRUN_SHRINK = False 59d58ff7ccSK.TakataSHOW_DIFF_OUTPUT = False 60646e1eb7SK.TakataNUM_WORKER_THREADS = 4 61ad8e6505SMasatake YAMATODIFF_U_NUM = 0 62d58ff7ccSK.Takata 63646e1eb7SK.Takata# 64646e1eb7SK.Takata# Internal variables and constants 65646e1eb7SK.Takata# 66d58ff7ccSK.Takata_FEATURE_LIST = [] 67d58ff7ccSK.Takata_PREPERE_ENV = '' 68d58ff7ccSK.Takata_DEFAULT_CATEGORY = 'ROOT' 69d58ff7ccSK.Takata_TIMEOUT_EXIT = 124 70d58ff7ccSK.Takata_VG_TIMEOUT_FACTOR = 10 71d58ff7ccSK.Takata_VALGRIND_EXIT = 58 72d58ff7ccSK.Takata_STDERR_OUTPUT_NAME = 'STDERR.tmp' 73d58ff7ccSK.Takata_DIFF_OUTPUT_NAME = 'DIFF.tmp' 74d892ff97SMasatake YAMATO_VALGRIND_OUTPUT_NAME = 'VALGRIND.tmp' 75d58ff7ccSK.Takata 76d58ff7ccSK.Takata# 77d58ff7ccSK.Takata# Results 78d58ff7ccSK.Takata# 79d58ff7ccSK.TakataL_PASSED = [] 80d58ff7ccSK.TakataL_FIXED = [] 81d58ff7ccSK.TakataL_FAILED_BY_STATUS = [] 82d58ff7ccSK.TakataL_FAILED_BY_DIFF = [] 83d58ff7ccSK.TakataL_SKIPPED_BY_FEATURES = [] 84d58ff7ccSK.TakataL_SKIPPED_BY_LANGUAGES = [] 85d58ff7ccSK.TakataL_SKIPPED_BY_ILOOP = [] 86d58ff7ccSK.TakataL_KNOWN_BUGS = [] 87d58ff7ccSK.TakataL_FAILED_BY_TIMEED_OUT = [] 88d58ff7ccSK.TakataL_BROKEN_ARGS_CTAGS = [] 89d58ff7ccSK.TakataL_VALGRIND = [] 90646e1eb7SK.TakataTMAIN_STATUS = True 91646e1eb7SK.TakataTMAIN_FAILED = [] 92d58ff7ccSK.Takata 93d58ff7ccSK.Takatadef remove_prefix(string, prefix): 94d58ff7ccSK.Takata if string.startswith(prefix): 95d58ff7ccSK.Takata return string[len(prefix):] 96d58ff7ccSK.Takata else: 97d58ff7ccSK.Takata return string 98d58ff7ccSK.Takata 99d58ff7ccSK.Takatadef is_cygwin(): 100d58ff7ccSK.Takata system = platform.system() 101d58ff7ccSK.Takata return system.startswith('CYGWIN_NT') or system.startswith('MINGW32_NT') 102d58ff7ccSK.Takata 103d58ff7ccSK.Takatadef isabs(path): 104d58ff7ccSK.Takata if is_cygwin(): 105d58ff7ccSK.Takata import ntpath 106d58ff7ccSK.Takata if ntpath.isabs(path): 107d58ff7ccSK.Takata return True 108d58ff7ccSK.Takata return os.path.isabs(path) 109d58ff7ccSK.Takata 110099a0013SK.Takatadef action_help(parser, action, *args): 111099a0013SK.Takata parser.print_help() 112099a0013SK.Takata return 0 113099a0013SK.Takata 114099a0013SK.Takatadef error_exit(status, msg): 115099a0013SK.Takata print(msg, file=sys.stderr) 116099a0013SK.Takata sys.exit(status) 117099a0013SK.Takata 118d58ff7ccSK.Takatadef line(*args, file=sys.stdout): 119d58ff7ccSK.Takata if len(args) > 0: 120d58ff7ccSK.Takata ch = args[0] 121d58ff7ccSK.Takata else: 122d58ff7ccSK.Takata ch = '-' 123d58ff7ccSK.Takata print(ch * 60, file=file) 124d58ff7ccSK.Takata 12528cf668aSK.Takatadef remove_readonly(func, path, _): 12628cf668aSK.Takata # Clear the readonly bit and reattempt the removal 12728cf668aSK.Takata os.chmod(path, stat.S_IWRITE | stat.S_IREAD) 12828cf668aSK.Takata dname = os.path.dirname(path) 12928cf668aSK.Takata os.chmod(dname, os.stat(dname).st_mode | stat.S_IWRITE) 13028cf668aSK.Takata func(path) 13128cf668aSK.Takata 132099a0013SK.Takatadef clean_bundles(bundles): 133099a0013SK.Takata if not os.path.isfile(bundles): 134099a0013SK.Takata return 135d58ff7ccSK.Takata with open(bundles, 'r') as f: 136099a0013SK.Takata for fn in f.read().splitlines(): 137d58ff7ccSK.Takata if os.path.isdir(fn): 13828cf668aSK.Takata shutil.rmtree(fn, onerror=remove_readonly) 139d58ff7ccSK.Takata elif os.path.isfile(fn): 140d58ff7ccSK.Takata os.remove(fn) 141d58ff7ccSK.Takata os.remove(bundles) 142099a0013SK.Takata 143099a0013SK.Takatadef clean_tcase(d, bundles): 144099a0013SK.Takata if os.path.isdir(d): 145099a0013SK.Takata clean_bundles(bundles) 146099a0013SK.Takata for fn in glob.glob(d + '/*.tmp'): 147099a0013SK.Takata os.remove(fn) 148099a0013SK.Takata for fn in glob.glob(d + '/*.TMP'): 149d58ff7ccSK.Takata os.remove(fn) 150d58ff7ccSK.Takata 151d58ff7ccSK.Takatadef check_availability(cmd): 152d58ff7ccSK.Takata if not shutil.which(cmd): 153d58ff7ccSK.Takata error_exit(1, cmd + ' command is not available') 154d58ff7ccSK.Takata 155d58ff7ccSK.Takatadef check_units(name, category): 156d58ff7ccSK.Takata if len(UNITS) == 0: 157d58ff7ccSK.Takata return True 158d58ff7ccSK.Takata 159d58ff7ccSK.Takata for u in UNITS: 160d58ff7ccSK.Takata ret = re.match(r'(.+)/(.+)', u) 161d58ff7ccSK.Takata if ret: 162d58ff7ccSK.Takata if ret.group(1, 2) == (category, name): 163d58ff7ccSK.Takata return True 164d58ff7ccSK.Takata elif u == name: 165d58ff7ccSK.Takata return True 166d58ff7ccSK.Takata return False 167d58ff7ccSK.Takata 168d58ff7ccSK.Takatadef init_features(): 169d58ff7ccSK.Takata global _FEATURE_LIST 1703e288efeSK.Takata ret = subprocess.run([CTAGS, '--quiet', '--options=NONE', '--list-features', '--with-list=no'], 1713e288efeSK.Takata stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) 172d58ff7ccSK.Takata _FEATURE_LIST = re.sub(r'(?m)^([^ ]+).*$', r'\1', 173d58ff7ccSK.Takata ret.stdout.decode('utf-8')).splitlines() 174d58ff7ccSK.Takata 175d58ff7ccSK.Takatadef check_features(feature, ffile): 176d58ff7ccSK.Takata features = [] 177d58ff7ccSK.Takata if feature: 178d58ff7ccSK.Takata features = [feature] 179d58ff7ccSK.Takata elif os.path.isfile(ffile): 180d58ff7ccSK.Takata with open(ffile, 'r') as f: 181d58ff7ccSK.Takata features = f.read().splitlines() 182d58ff7ccSK.Takata 183d58ff7ccSK.Takata for expected in features: 184d58ff7ccSK.Takata if expected == '': 185d58ff7ccSK.Takata continue 186d58ff7ccSK.Takata found = False 187d58ff7ccSK.Takata found_unexpectedly = False 188d58ff7ccSK.Takata if expected[0] == '!': 189d58ff7ccSK.Takata if expected[1:] in _FEATURE_LIST: 190d58ff7ccSK.Takata found_unexpectedly = True 191d58ff7ccSK.Takata else: 192d58ff7ccSK.Takata if expected in _FEATURE_LIST: 193d58ff7ccSK.Takata found = True 194d58ff7ccSK.Takata if found_unexpectedly: 195d58ff7ccSK.Takata return (False, expected) 196d58ff7ccSK.Takata elif not found: 197d58ff7ccSK.Takata return (False, expected) 198d58ff7ccSK.Takata return (True, '') 199d58ff7ccSK.Takata 200d58ff7ccSK.Takatadef check_languages(cmdline, lfile): 201d58ff7ccSK.Takata if not os.path.isfile(lfile): 202d58ff7ccSK.Takata return (True, '') 203d58ff7ccSK.Takata 2043e288efeSK.Takata ret = subprocess.run(cmdline + ['--list-languages'], 2053e288efeSK.Takata stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) 206d58ff7ccSK.Takata langs = ret.stdout.decode('utf-8').splitlines() 207d58ff7ccSK.Takata 208d58ff7ccSK.Takata with open(lfile, 'r') as f: 209d58ff7ccSK.Takata for expected in f.read().splitlines(): 210d58ff7ccSK.Takata found = False 211d58ff7ccSK.Takata if expected in langs: 212d58ff7ccSK.Takata found = True 213d58ff7ccSK.Takata if not found: 214d58ff7ccSK.Takata return (False, expected) 215d58ff7ccSK.Takata return (True, '') 216d58ff7ccSK.Takata 217d58ff7ccSK.Takatadef decorate(decorator, msg, colorized): 218d58ff7ccSK.Takata if decorator == 'red': 219d58ff7ccSK.Takata num = '31' 220d58ff7ccSK.Takata elif decorator == 'green': 221d58ff7ccSK.Takata num = '32' 222d58ff7ccSK.Takata elif decorator == 'yellow': 223d58ff7ccSK.Takata num = '33' 224d58ff7ccSK.Takata else: 225099a0013SK.Takata error_exit(1, 'INTERNAL ERROR: wrong run_result function') 226d58ff7ccSK.Takata 227d58ff7ccSK.Takata if colorized: 228d58ff7ccSK.Takata return "\x1b[" + num + 'm' + msg + "\x1b[m" 229d58ff7ccSK.Takata else: 230d58ff7ccSK.Takata return msg 231d58ff7ccSK.Takata 232d58ff7ccSK.Takatadef run_result(result_type, msg, output, *args, file=sys.stdout): 233d58ff7ccSK.Takata func_dict = { 234d58ff7ccSK.Takata 'skip': run_result_skip, 235d58ff7ccSK.Takata 'error': run_result_error, 236d58ff7ccSK.Takata 'ok': run_result_ok, 237d58ff7ccSK.Takata 'known_error': run_result_known_error, 238d58ff7ccSK.Takata } 239d58ff7ccSK.Takata 240d58ff7ccSK.Takata func_dict[result_type](msg, file, COLORIZED_OUTPUT, *args) 241d58ff7ccSK.Takata file.flush() 242d58ff7ccSK.Takata if output: 243d58ff7ccSK.Takata with open(output, 'w') as f: 244d58ff7ccSK.Takata func_dict[result_type](msg, f, False, *args) 245d58ff7ccSK.Takata 246d58ff7ccSK.Takatadef run_result_skip(msg, f, colorized, *args): 247cf2d55c4SK.Takata s = msg + decorate('yellow', 'skipped', colorized) 248d58ff7ccSK.Takata if len(args) > 0: 249cf2d55c4SK.Takata s += ' (' + args[0] + ')' 250cf2d55c4SK.Takata print(s, file=f) 251d58ff7ccSK.Takata 252d58ff7ccSK.Takatadef run_result_error(msg, f, colorized, *args): 253cf2d55c4SK.Takata s = msg + decorate('red', 'failed', colorized) 254d58ff7ccSK.Takata if len(args) > 0: 255cf2d55c4SK.Takata s += ' (' + args[0] + ')' 256cf2d55c4SK.Takata print(s, file=f) 257d58ff7ccSK.Takata 258d58ff7ccSK.Takatadef run_result_ok(msg, f, colorized, *args): 259cf2d55c4SK.Takata s = msg + decorate('green', 'passed', colorized) 260d58ff7ccSK.Takata if len(args) > 0: 261cf2d55c4SK.Takata s += ' (' + args[0] + ')' 262cf2d55c4SK.Takata print(s, file=f) 263d58ff7ccSK.Takata 264d58ff7ccSK.Takatadef run_result_known_error(msg, f, colorized, *args): 265cf2d55c4SK.Takata s = msg + decorate('yellow', 'failed', colorized) + ' (KNOWN bug)' 266cf2d55c4SK.Takata print(s, file=f) 267d58ff7ccSK.Takata 268d58ff7ccSK.Takatadef run_shrink(cmdline_template, finput, foutput, lang): 269ec80aeceSK.Takata script = sys.argv[0] 270ec80aeceSK.Takata script = os.path.splitext(script)[0] # remove '.py' 271ec80aeceSK.Takata 272ec80aeceSK.Takata print('Shrinking ' + finput + ' as ' + lang) 273ec80aeceSK.Takata # fallback to the shell script version 2746ab7d727SAlberto Fanjul subprocess.run([SHELL, script, 'shrink', 275ec80aeceSK.Takata '--timeout=1', '--foreground', 276ec80aeceSK.Takata cmdline_template, finput, foutput]) 277d58ff7ccSK.Takata 2783e288efeSK.Takata# return a filter for normalizing the basename 2793e288efeSK.Takata# 2803e288efeSK.Takata# If internal is True, return a pair of [pattern, replacement], 2813e288efeSK.Takata# otherwise return a list of command line arguments. 282d58ff7ccSK.Takatadef basename_filter(internal, output_type): 2833e288efeSK.Takata filters_external = { 2843e288efeSK.Takata 'ctags': 's%\(^[^\t]\{1,\}\t\)\(/\{0,1\}\([^/\t]\{1,\}/\)*\)%\\1%', 28565349126SMasatake YAMATO # "input" in the expresion is for finding input file names in the TAGS file. 28665349126SMasatake YAMATO # RAWOUT.tmp: 28765349126SMasatake YAMATO # 28865349126SMasatake YAMATO # ./Units/parser-ada.r/ada-etags-suffix.d/input_0.adb,238 28965349126SMasatake YAMATO # package body Input_0 is ^?Input_0/b^A1,0 29065349126SMasatake YAMATO # 29165349126SMasatake YAMATO # With the original expression, both "./Units/parser-ada.r/ada-etags-suffix.d/" 29265349126SMasatake YAMATO # and "package body Input_0 is Input_0/' are deleted. 29365349126SMasatake YAMATO # FILTERED.tmp: 29465349126SMasatake YAMATO # 29565349126SMasatake YAMATO # input_0.adb,238 29665349126SMasatake YAMATO # b^A1,0 29765349126SMasatake YAMATO # 29865349126SMasatake YAMATO # Adding "input" ot the expression is for deleting only the former one and for 29965349126SMasatake YAMATO # skpping the later one. 30065349126SMasatake YAMATO # 30165349126SMasatake YAMATO # FIXME: if "input" is included as a substring of tag entry names, filtering 30265349126SMasatake YAMATO # with this expression makes the test fail. 30365349126SMasatake YAMATO 'etags': 's%.*\/\(input[-._][[:print:]]\{1,\}\),\([0-9]\{1,\}$\)%\\1,\\2%', 3043e288efeSK.Takata 'xref': 's%\(.*[[:digit:]]\{1,\} \)\([^ ]\{1,\}[^ ]\{1,\}\)/\([^ ].\{1,\}.\{1,\}$\)%\\1\\3%', 3053e288efeSK.Takata 'json': 's%\("path": \)"[^"]\{1,\}/\([^/"]\{1,\}\)"%\\1"\\2"%', 306d58ff7ccSK.Takata } 307d58ff7ccSK.Takata filters_internal = { 3083e288efeSK.Takata 'ctags': [r'(^[^\t]+\t)(/?([^/\t]+/)*)', r'\1'], 30965349126SMasatake YAMATO # See above comments about "input". 31065349126SMasatake YAMATO 'etags': [r'.*/(input[-._]\S+),([0-9]+$)', r'\1,\2'], 3113e288efeSK.Takata 'xref': [r'(.*\d+ )([^ ]+[^ ]+)/([^ ].+.+$)', r'\1\3'], 3123e288efeSK.Takata 'json': [r'("path": )"[^"]+/([^/"]+)"', r'\1"\2"'], 313d58ff7ccSK.Takata } 314d58ff7ccSK.Takata if internal: 315d58ff7ccSK.Takata return filters_internal[output_type] 316d58ff7ccSK.Takata else: 3173e288efeSK.Takata return ['sed', '-e', filters_external[output_type]] 3183e288efeSK.Takata 3193e288efeSK.Takata# convert a command line list to a command line string 3203e288efeSK.Takatadef join_cmdline(cmdline): 321113e31c0SK.Takata # surround with '' if an argument includes spaces or '\' 3223e288efeSK.Takata # TODO: use more robust way 323113e31c0SK.Takata return ' '.join("'" + x + "'" if (' ' in x) or ('\\' in x) else x 324113e31c0SK.Takata for x in cmdline) 325d58ff7ccSK.Takata 326d58ff7ccSK.Takatadef run_record_cmdline(cmdline, ffilter, ocmdline, output_type): 327d58ff7ccSK.Takata with open(ocmdline, 'w') as f: 328d58ff7ccSK.Takata print("%s\n%s \\\n| %s \\\n| %s\n" % ( 3293e288efeSK.Takata _PREPERE_ENV, 3303e288efeSK.Takata join_cmdline(cmdline), 3313e288efeSK.Takata join_cmdline(basename_filter(False, output_type)), 332d58ff7ccSK.Takata ffilter), file=f) 333d58ff7ccSK.Takata 334d58ff7ccSK.Takatadef prepare_bundles(frm, to, obundles): 335d58ff7ccSK.Takata for src in glob.glob(frm + '/*'): 336d58ff7ccSK.Takata fn = os.path.basename(src) 337d58ff7ccSK.Takata if fn.startswith('input.'): 338d58ff7ccSK.Takata continue 339d58ff7ccSK.Takata elif fn.startswith('expected.tags'): 340d58ff7ccSK.Takata continue 341d58ff7ccSK.Takata elif fn.startswith('README'): 342d58ff7ccSK.Takata continue 343d58ff7ccSK.Takata elif fn in ['features', 'languages', 'filters']: 344d58ff7ccSK.Takata continue 345d58ff7ccSK.Takata elif fn == 'args.ctags': 346d58ff7ccSK.Takata continue 347d58ff7ccSK.Takata else: 348d58ff7ccSK.Takata dist = to + '/' + fn 349d58ff7ccSK.Takata if os.path.isdir(src): 35028cf668aSK.Takata shutil.copytree(src, dist, copy_function=shutil.copyfile) 351d58ff7ccSK.Takata else: 35228cf668aSK.Takata shutil.copyfile(src, dist) 353d58ff7ccSK.Takata with open(obundles, 'a') as f: 354d58ff7ccSK.Takata print(dist, file=f) 355d58ff7ccSK.Takata 356d58ff7ccSK.Takatadef anon_normalize_sub(internal, ctags, input_actual, *args): 357d58ff7ccSK.Takata # TODO: "Units" should not be hardcoded. 358d58ff7ccSK.Takata input_expected = './Units' + re.sub(r'^.*?/Units', r'', input_actual, 1) 359d58ff7ccSK.Takata 3603e288efeSK.Takata ret = subprocess.run([CTAGS, '--quiet', '--options=NONE', '--_anonhash=' + input_actual], 3613e288efeSK.Takata stdout=subprocess.PIPE) 362d58ff7ccSK.Takata actual = ret.stdout.decode('utf-8').splitlines()[0] 3633e288efeSK.Takata ret = subprocess.run([CTAGS, '--quiet', '--options=NONE', '--_anonhash=' + input_expected], 3643e288efeSK.Takata stdout=subprocess.PIPE) 365d58ff7ccSK.Takata expected = ret.stdout.decode('utf-8').splitlines()[0] 366d58ff7ccSK.Takata 367d58ff7ccSK.Takata if internal: 3683e288efeSK.Takata retlist = [[actual, expected]] 3693e288efeSK.Takata else: 3703e288efeSK.Takata retlist = ['-e', 's/' + actual + '/' + expected + '/g'] 371d58ff7ccSK.Takata if len(args) > 0: 3723e288efeSK.Takata return retlist + anon_normalize_sub(internal, ctags, *args) 373d58ff7ccSK.Takata else: 3743e288efeSK.Takata return retlist 375d58ff7ccSK.Takata 376d58ff7ccSK.Takatadef is_anon_normalize_needed(rawout): 377d58ff7ccSK.Takata with open(rawout, 'r', errors='ignore') as f: 378d58ff7ccSK.Takata if re.search(r'[0-9a-f]{8}', f.read()): 379d58ff7ccSK.Takata return True 380d58ff7ccSK.Takata return False 381d58ff7ccSK.Takata 3823e288efeSK.Takata# return a list of filters for normalizing anonhash 3833e288efeSK.Takata# 3843e288efeSK.Takata# If internal is True, return a list of pairs of [pattern, replacement], 3853e288efeSK.Takata# otherwise return a list of command line arguments. 386d58ff7ccSK.Takatadef anon_normalize(internal, rawout, ctags, input_actual, *args): 387d58ff7ccSK.Takata if is_anon_normalize_needed(rawout): 388d58ff7ccSK.Takata return anon_normalize_sub(internal, ctags, input_actual, *args) 389d58ff7ccSK.Takata else: 3903e288efeSK.Takata return [] 391d58ff7ccSK.Takata 392d58ff7ccSK.Takatadef run_filter(finput, foutput, base_filter, anon_filters): 393d58ff7ccSK.Takata pat1 = [re.compile(base_filter[0]), base_filter[1]] 394d58ff7ccSK.Takata pat2 = [(re.compile(p[0]), p[1]) for p in anon_filters] 395d58ff7ccSK.Takata with open(finput, 'r', errors='surrogateescape') as fin, \ 396d58ff7ccSK.Takata open(foutput, 'w', errors='surrogateescape', newline='\n') as fout: 397d58ff7ccSK.Takata for l in fin: 398d58ff7ccSK.Takata l = pat1[0].sub(pat1[1], l, 1) 399d58ff7ccSK.Takata for p in pat2: 400d58ff7ccSK.Takata l = p[0].sub(p[1], l) 401d58ff7ccSK.Takata print(l, end='', file=fout) 402d58ff7ccSK.Takata 403eafd0e70SK.Takatadef guess_lang(cmdline, finput): 404eafd0e70SK.Takata ret = subprocess.run(cmdline + ['--print-language', finput], 405eafd0e70SK.Takata stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) 406eafd0e70SK.Takata return re.sub(r'^.*: ', r'', 407eafd0e70SK.Takata ret.stdout.decode('utf-8').replace("\r\n", "\n").replace("\n", '')) 408eafd0e70SK.Takata 409eafd0e70SK.Takatadef guess_lang_from_log(log): 410eafd0e70SK.Takata with open(log, 'r', encoding='utf-8', errors='ignore') as f: 411eafd0e70SK.Takata for l in f: 412eafd0e70SK.Takata ret = re.match('OPENING.* as (.*) language .*file ', l) 413eafd0e70SK.Takata if ret: 414eafd0e70SK.Takata return ret.group(1) 415eafd0e70SK.Takata return '' 416eafd0e70SK.Takata 417d58ff7ccSK.Takatadef run_tcase(finput, t, name, tclass, category, build_t, extra_inputs): 418d58ff7ccSK.Takata global L_PASSED 419d58ff7ccSK.Takata global L_FIXED 420d58ff7ccSK.Takata global L_FAILED_BY_STATUS 421d58ff7ccSK.Takata global L_FAILED_BY_DIFF 422d58ff7ccSK.Takata global L_SKIPPED_BY_FEATURES 423d58ff7ccSK.Takata global L_SKIPPED_BY_LANGUAGES 424d58ff7ccSK.Takata global L_SKIPPED_BY_ILOOP 425d58ff7ccSK.Takata global L_KNOWN_BUGS 426d58ff7ccSK.Takata global L_FAILED_BY_TIMEED_OUT 427d58ff7ccSK.Takata global L_BROKEN_ARGS_CTAGS 428d58ff7ccSK.Takata global L_VALGRIND 429d58ff7ccSK.Takata 430d58ff7ccSK.Takata o = build_t 431d58ff7ccSK.Takata 432d58ff7ccSK.Takata fargs = t + '/args.ctags' 433d58ff7ccSK.Takata ffeatures = t + '/features' 434d58ff7ccSK.Takata flanguages = t + '/languages' 435d58ff7ccSK.Takata ffilter = t + '/filter' 436d58ff7ccSK.Takata 437d58ff7ccSK.Takata fexpected = t + '/expected.tags' 438d58ff7ccSK.Takata output_type = 'ctags' 439d58ff7ccSK.Takata output_label = '' 4403e288efeSK.Takata output_tflag = [] 441d58ff7ccSK.Takata output_feature = '' 442d58ff7ccSK.Takata output_lang_extras = '' 443d58ff7ccSK.Takata 444d58ff7ccSK.Takata if os.path.isfile(fexpected): 445d58ff7ccSK.Takata pass 446d58ff7ccSK.Takata elif os.path.isfile(t + '/expected.tags-e'): 447d58ff7ccSK.Takata fexpected = t + '/expected.tags-e' 448d58ff7ccSK.Takata output_type = 'etags' 449d58ff7ccSK.Takata output_label = '/' + output_type 4503e288efeSK.Takata output_tflag = ['-e', '--tag-relative=no'] 451d58ff7ccSK.Takata elif os.path.isfile(t + '/expected.tags-x'): 452d58ff7ccSK.Takata fexpected = t + '/expected.tags-x' 453d58ff7ccSK.Takata output_type = 'xref' 454d58ff7ccSK.Takata output_label = '/' + output_type 4553e288efeSK.Takata output_tflag = ['-x'] 456d58ff7ccSK.Takata elif os.path.isfile(t + '/expected.tags-json'): 457d58ff7ccSK.Takata fexpected = t + '/expected.tags-json' 458d58ff7ccSK.Takata output_type = 'json' 459d58ff7ccSK.Takata output_label = '/' + output_type 4603e288efeSK.Takata output_tflag = ['--output-format=json'] 461d58ff7ccSK.Takata output_feature = 'json' 462d58ff7ccSK.Takata 463d58ff7ccSK.Takata if len(extra_inputs) > 0: 464d58ff7ccSK.Takata output_lang_extras = ' (multi inputs)' 465d58ff7ccSK.Takata 466d58ff7ccSK.Takata if not shutil.which(ffilter): 467d58ff7ccSK.Takata ffilter = 'cat' 468d58ff7ccSK.Takata 469d58ff7ccSK.Takata ostderr = o + '/' + _STDERR_OUTPUT_NAME 470d58ff7ccSK.Takata orawout = o + '/RAWOUT.tmp' 471d58ff7ccSK.Takata ofiltered = o + '/FILTERED.tmp' 472d58ff7ccSK.Takata odiff = o + '/' + _DIFF_OUTPUT_NAME 473d58ff7ccSK.Takata ocmdline = o + '/CMDLINE.tmp' 474d892ff97SMasatake YAMATO ovalgrind = o + '/' + _VALGRIND_OUTPUT_NAME 475d58ff7ccSK.Takata oresult = o + '/RESULT.tmp' 476d58ff7ccSK.Takata oshrink_template = o + '/SHRINK-%s.tmp' 477d58ff7ccSK.Takata obundles = o + '/BUNDLES' 478d58ff7ccSK.Takata 479d58ff7ccSK.Takata broken_args_ctags = False 480d58ff7ccSK.Takata 481d58ff7ccSK.Takata # 482d58ff7ccSK.Takata # Filtered by UNIT 483d58ff7ccSK.Takata # 484d58ff7ccSK.Takata if not check_units(name, category): 485d58ff7ccSK.Takata return False 486d58ff7ccSK.Takata 487d58ff7ccSK.Takata # 488d58ff7ccSK.Takata # Build cmdline 489d58ff7ccSK.Takata # 490c314f261SMasatake YAMATO cmdline = [CTAGS, '--verbose', '--options=NONE', '--fields=-T'] 4913e288efeSK.Takata if PRETENSE_OPTS != '': 4923e288efeSK.Takata cmdline += [PRETENSE_OPTS] 4933e288efeSK.Takata cmdline += ['--optlib-dir=+' + t + '/optlib', '-o', '-'] 494d58ff7ccSK.Takata if os.path.isfile(fargs): 4953e288efeSK.Takata cmdline += ['--options=' + fargs] 4963e288efeSK.Takata ret = subprocess.run(cmdline + ['--_force-quit=0'], 4973e288efeSK.Takata stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 498d58ff7ccSK.Takata if ret.returncode != 0: 499d58ff7ccSK.Takata broken_args_ctags = True 5005b838a43SMasatake YAMATO 5015b838a43SMasatake YAMATO # 5025b838a43SMasatake YAMATO # make a backup (basedcmdline) of cmdline. basedcmdline is used 5035b838a43SMasatake YAMATO # as a command line template for running shrinker. basedcmdline 5045b838a43SMasatake YAMATO # should not include the name of the input file name. The 5055b838a43SMasatake YAMATO # shrinker makes a another cmdline by applying a real input file 5065b838a43SMasatake YAMATO # name to the template. On the other hand, cmdline is 5075b838a43SMasatake YAMATO # destructively updated by appending input file name in this 5085b838a43SMasatake YAMATO # function. The file name should not be included in the cmdline 5095b838a43SMasatake YAMATO # template. 5105b838a43SMasatake YAMATO # 5115b838a43SMasatake YAMATO # To avoid the updating in this function propagating to 5125b838a43SMasatake YAMATO # basecmdline, we copy the cmdline here. 5135b838a43SMasatake YAMATO # 5145b838a43SMasatake YAMATO basecmdline = cmdline[:] 515d58ff7ccSK.Takata 516fccf7bfeSK.Takata # 517fccf7bfeSK.Takata # Filtered by LANGUAGES 518fccf7bfeSK.Takata # 519fccf7bfeSK.Takata guessed_lang = None 520fccf7bfeSK.Takata if len(LANGUAGES) > 0: 521fccf7bfeSK.Takata guessed_lang = guess_lang(basecmdline, finput) 522fccf7bfeSK.Takata if not guessed_lang in LANGUAGES: 523fccf7bfeSK.Takata return False 524fccf7bfeSK.Takata 525d58ff7ccSK.Takata clean_tcase(o, obundles) 526d58ff7ccSK.Takata os.makedirs(o, exist_ok=True) 5279d6d762aSK.Takata if not os.path.samefile(o, t): 528d58ff7ccSK.Takata prepare_bundles(t, o, obundles) 529d58ff7ccSK.Takata 530d58ff7ccSK.Takata 531eafd0e70SK.Takata # helper function for building some strings based on guessed_lang 532eafd0e70SK.Takata def build_strings(guessed_lang): 533eafd0e70SK.Takata return ('%-59s ' % ('Testing ' + name + ' as ' + guessed_lang + output_lang_extras + output_label), 534eafd0e70SK.Takata join_cmdline(basecmdline) + ' --language-force=' + guessed_lang + ' %s > /dev/null 2>&1', 535eafd0e70SK.Takata oshrink_template % (guessed_lang.replace('/', '-'))) 536d58ff7ccSK.Takata 537d58ff7ccSK.Takata (tmp, feat) = check_features(output_feature, ffeatures) 538d58ff7ccSK.Takata if not tmp: 539fccf7bfeSK.Takata if not guessed_lang: 540eafd0e70SK.Takata guessed_lang = guess_lang(basecmdline, finput) 541eafd0e70SK.Takata msg = build_strings(guessed_lang)[0] 542d58ff7ccSK.Takata L_SKIPPED_BY_FEATURES += [category + '/' + name] 543d58ff7ccSK.Takata if feat.startswith('!'): 544d58ff7ccSK.Takata run_result('skip', msg, oresult, 'unwanted feature "' + feat[1:] + '" is available') 545d58ff7ccSK.Takata else: 546d58ff7ccSK.Takata run_result('skip', msg, oresult, 'required feature "' + feat + '" is not available') 547d58ff7ccSK.Takata return False 548eafd0e70SK.Takata (tmp, lang) = check_languages(basecmdline, flanguages) 549d58ff7ccSK.Takata if not tmp: 550fccf7bfeSK.Takata if not guessed_lang: 551eafd0e70SK.Takata guessed_lang = guess_lang(basecmdline, finput) 552eafd0e70SK.Takata msg = build_strings(guessed_lang)[0] 553d58ff7ccSK.Takata L_SKIPPED_BY_LANGUAGES += [category + '/' + name] 554d58ff7ccSK.Takata run_result('skip', msg, oresult, 'required language parser "' + lang + '" is not available') 555d58ff7ccSK.Takata return False 556d58ff7ccSK.Takata if WITH_TIMEOUT == 0 and tclass == 'i': 557fccf7bfeSK.Takata if not guessed_lang: 558eafd0e70SK.Takata guessed_lang = guess_lang(basecmdline, finput) 559eafd0e70SK.Takata msg = build_strings(guessed_lang)[0] 560d58ff7ccSK.Takata L_SKIPPED_BY_ILOOP += [category + '/' + name] 561d58ff7ccSK.Takata run_result('skip', msg, oresult, 'may cause an infinite loop') 562d58ff7ccSK.Takata return False 563d58ff7ccSK.Takata if broken_args_ctags: 564fccf7bfeSK.Takata if not guessed_lang: 565eafd0e70SK.Takata guessed_lang = guess_lang(basecmdline, finput) 566eafd0e70SK.Takata msg = build_strings(guessed_lang)[0] 567d58ff7ccSK.Takata L_BROKEN_ARGS_CTAGS += [category + '/' + name] 568d58ff7ccSK.Takata run_result('error', msg, None, 'broken args.ctags?') 569d58ff7ccSK.Takata return False 570d58ff7ccSK.Takata 5713e288efeSK.Takata cmdline += output_tflag + [finput] 572d58ff7ccSK.Takata if len(extra_inputs) > 0: 5733e288efeSK.Takata cmdline += extra_inputs 574d58ff7ccSK.Takata 575d58ff7ccSK.Takata timeout_value = WITH_TIMEOUT 576d58ff7ccSK.Takata if WITH_VALGRIND: 577516f81e7SMasatake YAMATO cmdline = ['valgrind', '--leak-check=full', '--track-origins=yes', 578516f81e7SMasatake YAMATO '--error-exitcode=' + str(_VALGRIND_EXIT), '--log-file=' + ovalgrind] + cmdline 579d58ff7ccSK.Takata timeout_value *= _VG_TIMEOUT_FACTOR 580d58ff7ccSK.Takata if timeout_value == 0: 581d58ff7ccSK.Takata timeout_value = None 582d58ff7ccSK.Takata 583d58ff7ccSK.Takata start = time.time() 584d58ff7ccSK.Takata try: 5853e288efeSK.Takata with open(orawout, 'wb') as fo, \ 5863e288efeSK.Takata open(ostderr, 'wb') as fe: 5873e288efeSK.Takata ret = subprocess.run(cmdline, stdout=fo, stderr=fe, 5883e288efeSK.Takata timeout=timeout_value) 589d58ff7ccSK.Takata run_record_cmdline(cmdline, ffilter, ocmdline, output_type) 590d58ff7ccSK.Takata except subprocess.TimeoutExpired: 591fccf7bfeSK.Takata if not guessed_lang: 592eafd0e70SK.Takata guessed_lang = guess_lang(basecmdline, finput) 593eafd0e70SK.Takata (msg, cmdline_template, oshrink) = build_strings(guessed_lang) 594d58ff7ccSK.Takata L_FAILED_BY_TIMEED_OUT += [category + '/' + name] 595d58ff7ccSK.Takata run_result('error', msg, oresult, 'TIMED OUT') 596d58ff7ccSK.Takata run_record_cmdline(cmdline, ffilter, ocmdline, output_type) 597d58ff7ccSK.Takata if RUN_SHRINK and len(extra_inputs) == 0: 598d58ff7ccSK.Takata run_shrink(cmdline_template, finput, oshrink, guessed_lang) 599d58ff7ccSK.Takata return False 600d58ff7ccSK.Takata #print('execute time: %f' % (time.time() - start)) 601d58ff7ccSK.Takata 602eafd0e70SK.Takata guessed_lang = guess_lang_from_log(ostderr) 603eafd0e70SK.Takata (msg, cmdline_template, oshrink) = build_strings(guessed_lang) 604eafd0e70SK.Takata 605d58ff7ccSK.Takata if ret.returncode != 0: 606d58ff7ccSK.Takata if WITH_VALGRIND and ret.returncode == _VALGRIND_EXIT and \ 607d58ff7ccSK.Takata tclass != 'v': 608d58ff7ccSK.Takata L_VALGRIND += [category + '/' + name] 609d58ff7ccSK.Takata run_result('error', msg, oresult, 'valgrind-error') 610d58ff7ccSK.Takata run_record_cmdline(cmdline, ffilter, ocmdline, output_type) 611d58ff7ccSK.Takata return False 612d58ff7ccSK.Takata elif tclass == 'b': 613d58ff7ccSK.Takata L_KNOWN_BUGS += [category + '/' + name] 614d58ff7ccSK.Takata run_result('known_error', msg, oresult) 615d58ff7ccSK.Takata run_record_cmdline(cmdline, ffilter, ocmdline, output_type) 616d58ff7ccSK.Takata if RUN_SHRINK and len(extra_inputs) == 0: 617d58ff7ccSK.Takata run_shrink(cmdline_template, finput, oshrink, guessed_lang) 618d58ff7ccSK.Takata return True 619d58ff7ccSK.Takata else: 620d58ff7ccSK.Takata L_FAILED_BY_STATUS += [category + '/' + name] 621d58ff7ccSK.Takata run_result('error', msg, oresult, 'unexpected exit status: ' + str(ret.returncode)) 622d58ff7ccSK.Takata run_record_cmdline(cmdline, ffilter, ocmdline, output_type) 623d58ff7ccSK.Takata if RUN_SHRINK and len(extra_inputs) == 0: 624d58ff7ccSK.Takata run_shrink(cmdline_template, finput, oshrink, guessed_lang) 625d58ff7ccSK.Takata return False 626d58ff7ccSK.Takata elif WITH_VALGRIND and tclass == 'v': 627d58ff7ccSK.Takata L_FIXED += [category + '/' + name] 628d58ff7ccSK.Takata 629d58ff7ccSK.Takata if not os.path.isfile(fexpected): 630d58ff7ccSK.Takata clean_tcase(o, obundles) 631d58ff7ccSK.Takata if tclass == 'b': 632d58ff7ccSK.Takata L_FIXED += [category + '/' + name] 633d58ff7ccSK.Takata elif tclass == 'i': 634d58ff7ccSK.Takata L_FIXED += [category + '/' + name] 635d58ff7ccSK.Takata 636d58ff7ccSK.Takata L_PASSED += [category + '/' + name] 637d58ff7ccSK.Takata run_result('ok', msg, None, '"expected.tags*" not found') 638d58ff7ccSK.Takata return True 639d58ff7ccSK.Takata 640d58ff7ccSK.Takata start = time.time() 641d58ff7ccSK.Takata if ffilter != 'cat': 642d58ff7ccSK.Takata # Use external filter 643d58ff7ccSK.Takata filter_cmd = basename_filter(False, output_type) + \ 644d58ff7ccSK.Takata anon_normalize(False, orawout, CTAGS, finput, *extra_inputs) + \ 6453e288efeSK.Takata ['<', orawout] 6463e288efeSK.Takata filter_cmd += ['|', ffilter] 6473e288efeSK.Takata filter_cmd += ['>', ofiltered] 648d58ff7ccSK.Takata #print(filter_cmd) 6493e288efeSK.Takata subprocess.run([SHELL, '-c', join_cmdline(filter_cmd)]) 650d58ff7ccSK.Takata else: 651d58ff7ccSK.Takata # Use internal filter 652d58ff7ccSK.Takata run_filter(orawout, ofiltered, basename_filter(True, output_type), 653d58ff7ccSK.Takata anon_normalize(True, orawout, CTAGS, finput, *extra_inputs)) 654d58ff7ccSK.Takata #print('filter time: %f' % (time.time() - start)) 655d58ff7ccSK.Takata 656d58ff7ccSK.Takata start = time.time() 657d58ff7ccSK.Takata if filecmp.cmp(fexpected, ofiltered): 658d58ff7ccSK.Takata ret.returncode = 0 659d58ff7ccSK.Takata else: 6603e288efeSK.Takata with open(odiff, 'wb') as f: 661ad8e6505SMasatake YAMATO ret = subprocess.run(['diff', '-U', str(DIFF_U_NUM), 662ad8e6505SMasatake YAMATO '-I', '^!_TAG', '--strip-trailing-cr', fexpected, ofiltered], 6633e288efeSK.Takata stdout=f) 664d58ff7ccSK.Takata #print('diff time: %f' % (time.time() - start)) 665d58ff7ccSK.Takata 666d58ff7ccSK.Takata if ret.returncode == 0: 667d58ff7ccSK.Takata clean_tcase(o, obundles) 668d58ff7ccSK.Takata if tclass == 'b': 669d58ff7ccSK.Takata L_FIXED += [category + '/' + name] 670d58ff7ccSK.Takata elif WITH_TIMEOUT != 0 and tclass == 'i': 671d58ff7ccSK.Takata L_FIXED += [category + '/' + name] 672d58ff7ccSK.Takata 673d58ff7ccSK.Takata L_PASSED += [category + '/' + name] 674d58ff7ccSK.Takata run_result('ok', msg, None) 675d58ff7ccSK.Takata return True 676d58ff7ccSK.Takata else: 677d58ff7ccSK.Takata if tclass == 'b': 678d58ff7ccSK.Takata L_KNOWN_BUGS += [category + '/' + name] 679d58ff7ccSK.Takata run_result('known_error', msg, oresult) 680d58ff7ccSK.Takata run_record_cmdline(cmdline, ffilter, ocmdline, output_type) 681d58ff7ccSK.Takata return True 682d58ff7ccSK.Takata else: 683d58ff7ccSK.Takata L_FAILED_BY_DIFF += [category + '/' + name] 684d58ff7ccSK.Takata run_result('error', msg, oresult, 'unexpected output') 685d58ff7ccSK.Takata run_record_cmdline(cmdline, ffilter, ocmdline, output_type) 686d58ff7ccSK.Takata return False 687d58ff7ccSK.Takata 688646e1eb7SK.Takatadef create_thread_queue(func): 689646e1eb7SK.Takata q = queue.Queue() 690646e1eb7SK.Takata threads = [] 691646e1eb7SK.Takata for i in range(NUM_WORKER_THREADS): 692646e1eb7SK.Takata t = threading.Thread(target=worker, args=(func, q), daemon=True) 693646e1eb7SK.Takata t.start() 694646e1eb7SK.Takata threads.append(t) 695646e1eb7SK.Takata return (q, threads) 696646e1eb7SK.Takata 697646e1eb7SK.Takatadef worker(func, q): 698d58ff7ccSK.Takata while True: 699d58ff7ccSK.Takata item = q.get() 700d58ff7ccSK.Takata if item is None: 701d58ff7ccSK.Takata break 702d58ff7ccSK.Takata try: 703646e1eb7SK.Takata func(*item) 704d58ff7ccSK.Takata except: 705d58ff7ccSK.Takata import traceback 706d58ff7ccSK.Takata traceback.print_exc() 707d58ff7ccSK.Takata q.task_done() 708d58ff7ccSK.Takata 709646e1eb7SK.Takatadef join_workers(q, threads): 710646e1eb7SK.Takata # block until all tasks are done 711646e1eb7SK.Takata try: 712646e1eb7SK.Takata q.join() 713646e1eb7SK.Takata except KeyboardInterrupt: 714646e1eb7SK.Takata # empty the queue 715646e1eb7SK.Takata while True: 716646e1eb7SK.Takata try: 717646e1eb7SK.Takata q.get_nowait() 718646e1eb7SK.Takata except queue.Empty: 719646e1eb7SK.Takata break 720646e1eb7SK.Takata # try to stop workers 721646e1eb7SK.Takata for i in range(NUM_WORKER_THREADS): 722646e1eb7SK.Takata q.put(None) 723646e1eb7SK.Takata for t in threads: 724646e1eb7SK.Takata t.join(timeout=2) 725646e1eb7SK.Takata # exit regardless that workers are stopped 726646e1eb7SK.Takata sys.exit(1) 727646e1eb7SK.Takata 728646e1eb7SK.Takata # stop workers 729646e1eb7SK.Takata for i in range(NUM_WORKER_THREADS): 730646e1eb7SK.Takata q.put(None) 731646e1eb7SK.Takata for t in threads: 732646e1eb7SK.Takata t.join() 733646e1eb7SK.Takata 734d58ff7ccSK.Takatadef accepted_file(fname): 735d58ff7ccSK.Takata # Ignore backup files 736d58ff7ccSK.Takata return not fname.endswith('~') 737d58ff7ccSK.Takata 738d58ff7ccSK.Takatadef run_dir(category, base_dir, build_base_dir): 739d58ff7ccSK.Takata # 740d58ff7ccSK.Takata # Filtered by CATEGORIES 741d58ff7ccSK.Takata # 742d58ff7ccSK.Takata if len(CATEGORIES) > 0 and not category in CATEGORIES: 743d58ff7ccSK.Takata return False 744d58ff7ccSK.Takata 745d58ff7ccSK.Takata print("\nCategory: " + category) 746d58ff7ccSK.Takata line() 747d58ff7ccSK.Takata 748646e1eb7SK.Takata (q, threads) = create_thread_queue(run_tcase) 749d58ff7ccSK.Takata 750d58ff7ccSK.Takata for finput in glob.glob(base_dir + '/*.[dbtiv]/input.*'): 751d58ff7ccSK.Takata finput = finput.replace('\\', '/') # for Windows 752d58ff7ccSK.Takata if not accepted_file(finput): 753d58ff7ccSK.Takata continue 754d58ff7ccSK.Takata 755d58ff7ccSK.Takata dname = os.path.dirname(finput) 756d58ff7ccSK.Takata extra_inputs = sorted(map(lambda x: x.replace('\\', '/'), # for Windows 757d58ff7ccSK.Takata filter(accepted_file, 758d58ff7ccSK.Takata glob.glob(dname + '/input[-_][0-9].*') + 759d58ff7ccSK.Takata glob.glob(dname + '/input[-_][0-9][-_]*.*') 760d58ff7ccSK.Takata ))) 761d58ff7ccSK.Takata 762d58ff7ccSK.Takata tcase_dir = dname 763d58ff7ccSK.Takata build_tcase_dir = build_base_dir + remove_prefix(tcase_dir, base_dir) 764d58ff7ccSK.Takata ret = re.match(r'^.*/(.*)\.([dbtiv])$', tcase_dir) 765d58ff7ccSK.Takata (name, tclass) = ret.group(1, 2) 766d58ff7ccSK.Takata q.put((finput, tcase_dir, name, tclass, category, build_tcase_dir, extra_inputs)) 767d58ff7ccSK.Takata 768646e1eb7SK.Takata join_workers(q, threads) 769d58ff7ccSK.Takata 770d58ff7ccSK.Takatadef run_show_diff_output(units_dir, t): 771d58ff7ccSK.Takata print("\t", end='') 772d58ff7ccSK.Takata line('.') 773d58ff7ccSK.Takata for fn in glob.glob(units_dir + '/' + t + '.*/' + _DIFF_OUTPUT_NAME): 774d58ff7ccSK.Takata with open(fn, 'r') as f: 775d58ff7ccSK.Takata for l in f: 776d58ff7ccSK.Takata print("\t" + l, end='') 777d58ff7ccSK.Takata print() 778d58ff7ccSK.Takata 779d58ff7ccSK.Takatadef run_show_stderr_output(units_dir, t): 780d58ff7ccSK.Takata print("\t", end='') 781d58ff7ccSK.Takata line('.') 782d58ff7ccSK.Takata for fn in glob.glob(units_dir + '/' + t + '.*/' + _STDERR_OUTPUT_NAME): 783d58ff7ccSK.Takata with open(fn, 'r') as f: 784d58ff7ccSK.Takata lines = f.readlines() 785d58ff7ccSK.Takata for l in lines[-50:]: 786d58ff7ccSK.Takata print("\t" + l, end='') 787d58ff7ccSK.Takata print() 788d58ff7ccSK.Takata 789d892ff97SMasatake YAMATOdef run_show_valgrind_output(units_dir, t): 790d892ff97SMasatake YAMATO print("\t", end='') 791d892ff97SMasatake YAMATO line('.') 792d892ff97SMasatake YAMATO for fn in glob.glob(units_dir + '/' + t + '.*/' + _VALGRIND_OUTPUT_NAME): 793d892ff97SMasatake YAMATO with open(fn, 'r') as f: 794d892ff97SMasatake YAMATO for l in f: 795d892ff97SMasatake YAMATO print("\t" + l, end='') 796d892ff97SMasatake YAMATO print() 797d892ff97SMasatake YAMATO 798d58ff7ccSK.Takatadef run_summary(build_dir): 799d58ff7ccSK.Takata print() 800d58ff7ccSK.Takata print('Summary (see CMDLINE.tmp to reproduce without test harness)') 801d58ff7ccSK.Takata line() 802d58ff7ccSK.Takata 803d58ff7ccSK.Takata fmt = ' %-40s%d' 804d58ff7ccSK.Takata print(fmt % ('#passed:', len(L_PASSED))) 805d58ff7ccSK.Takata 806d58ff7ccSK.Takata print(fmt % ('#FIXED:', len(L_FIXED))) 807d58ff7ccSK.Takata for t in L_FIXED: 808d58ff7ccSK.Takata print("\t" + remove_prefix(t, _DEFAULT_CATEGORY + '/')) 809d58ff7ccSK.Takata 810d58ff7ccSK.Takata print(fmt % ('#FAILED (broken args.ctags?):', len(L_BROKEN_ARGS_CTAGS))) 811d58ff7ccSK.Takata for t in L_BROKEN_ARGS_CTAGS: 812d58ff7ccSK.Takata print("\t" + remove_prefix(t, _DEFAULT_CATEGORY + '/')) 813d58ff7ccSK.Takata 814d58ff7ccSK.Takata print(fmt % ('#FAILED (unexpected-exit-status):', len(L_FAILED_BY_STATUS))) 815d58ff7ccSK.Takata for t in L_FAILED_BY_STATUS: 816d58ff7ccSK.Takata print("\t" + remove_prefix(t, _DEFAULT_CATEGORY + '/')) 817d58ff7ccSK.Takata if SHOW_DIFF_OUTPUT: 818d58ff7ccSK.Takata run_show_stderr_output(build_dir, remove_prefix(t, _DEFAULT_CATEGORY + '/')) 819d58ff7ccSK.Takata 820d58ff7ccSK.Takata print(fmt % ('#FAILED (unexpected-output):', len(L_FAILED_BY_DIFF))) 821d58ff7ccSK.Takata for t in L_FAILED_BY_DIFF: 822d58ff7ccSK.Takata print("\t" + remove_prefix(t, _DEFAULT_CATEGORY + '/')) 823d58ff7ccSK.Takata if SHOW_DIFF_OUTPUT: 824d58ff7ccSK.Takata run_show_stderr_output(build_dir, remove_prefix(t, _DEFAULT_CATEGORY + '/')) 825d58ff7ccSK.Takata run_show_diff_output(build_dir, remove_prefix(t, _DEFAULT_CATEGORY + '/')) 826d58ff7ccSK.Takata 827d58ff7ccSK.Takata if WITH_TIMEOUT != 0: 828d58ff7ccSK.Takata print(fmt % ('#TIMED-OUT (' + str(WITH_TIMEOUT) + 's):', len(L_FAILED_BY_TIMEED_OUT))) 829d58ff7ccSK.Takata for t in L_FAILED_BY_TIMEED_OUT: 830d58ff7ccSK.Takata print("\t" + remove_prefix(t, _DEFAULT_CATEGORY + '/')) 831d58ff7ccSK.Takata 832d58ff7ccSK.Takata print(fmt % ('#skipped (features):', len(L_SKIPPED_BY_FEATURES))) 833d58ff7ccSK.Takata for t in L_SKIPPED_BY_FEATURES: 834d58ff7ccSK.Takata print("\t" + remove_prefix(t, _DEFAULT_CATEGORY + '/')) 835d58ff7ccSK.Takata 836d58ff7ccSK.Takata print(fmt % ('#skipped (languages):', len(L_SKIPPED_BY_LANGUAGES))) 837d58ff7ccSK.Takata for t in L_SKIPPED_BY_LANGUAGES: 838d58ff7ccSK.Takata print("\t" + remove_prefix(t, _DEFAULT_CATEGORY + '/')) 839d58ff7ccSK.Takata 840d58ff7ccSK.Takata if WITH_TIMEOUT == 0: 841d58ff7ccSK.Takata print(fmt % ('#skipped (infinite-loop):', len(L_SKIPPED_BY_ILOOP))) 842d58ff7ccSK.Takata for t in L_SKIPPED_BY_ILOOP: 843d58ff7ccSK.Takata print("\t" + remove_prefix(t, _DEFAULT_CATEGORY + '/')) 844d58ff7ccSK.Takata 845d58ff7ccSK.Takata print(fmt % ('#known-bugs:', len(L_KNOWN_BUGS))) 846d58ff7ccSK.Takata for t in L_KNOWN_BUGS: 847d58ff7ccSK.Takata print("\t" + remove_prefix(t, _DEFAULT_CATEGORY + '/')) 848d58ff7ccSK.Takata 849d58ff7ccSK.Takata if WITH_VALGRIND: 850d58ff7ccSK.Takata print(fmt % ('#valgrind-error:', len(L_VALGRIND))) 851d58ff7ccSK.Takata for t in L_VALGRIND: 852d58ff7ccSK.Takata print("\t" + remove_prefix(t, _DEFAULT_CATEGORY + '/')) 853d892ff97SMasatake YAMATO if SHOW_DIFF_OUTPUT: 854d892ff97SMasatake YAMATO print(fmt % ('##valgrind-error:', len(L_VALGRIND))) 855d892ff97SMasatake YAMATO for t in L_VALGRIND: 856d892ff97SMasatake YAMATO print("\t" + remove_prefix(t, _DEFAULT_CATEGORY + '/')) 857d892ff97SMasatake YAMATO run_show_valgrind_output(build_dir, remove_prefix(t, _DEFAULT_CATEGORY + '/')) 858d58ff7ccSK.Takata 859d58ff7ccSK.Takatadef make_pretense_map(arg): 860d58ff7ccSK.Takata r = '' 861d58ff7ccSK.Takata for p in arg.split(','): 862d58ff7ccSK.Takata ret = re.match(r'(.*)/(.*)', p) 863d58ff7ccSK.Takata if not ret: 864d58ff7ccSK.Takata error_exit(1, 'wrong format of --_pretend option arg') 865d58ff7ccSK.Takata 866d58ff7ccSK.Takata (newlang, oldlang) = ret.group(1, 2) 867d58ff7ccSK.Takata if newlang == '': 868d58ff7ccSK.Takata error_exit(1, 'newlang part of --_pretend option arg is empty') 869d58ff7ccSK.Takata if oldlang == '': 870d58ff7ccSK.Takata error_exit(1, 'oldlang part of --_pretend option arg is empty') 871d58ff7ccSK.Takata 872d58ff7ccSK.Takata r += ' --_pretend-' + newlang + '=' + oldlang 873d58ff7ccSK.Takata 874d58ff7ccSK.Takata return r 875d58ff7ccSK.Takata 876d58ff7ccSK.Takatadef action_run(parser, action, *args): 877d58ff7ccSK.Takata global CATEGORIES 878d58ff7ccSK.Takata global CTAGS 879d58ff7ccSK.Takata global UNITS 880d58ff7ccSK.Takata global LANGUAGES 881d58ff7ccSK.Takata global WITH_TIMEOUT 882d58ff7ccSK.Takata global WITH_VALGRIND 883d58ff7ccSK.Takata global COLORIZED_OUTPUT 884d58ff7ccSK.Takata global RUN_SHRINK 885d58ff7ccSK.Takata global SHOW_DIFF_OUTPUT 886d58ff7ccSK.Takata global PRETENSE_OPTS 887646e1eb7SK.Takata global NUM_WORKER_THREADS 8883e288efeSK.Takata global SHELL 889d58ff7ccSK.Takata 8907a85ed06SK.Takata parser.add_argument('--categories', metavar='CATEGORY1[,CATEGORY2,...]', 8917a85ed06SK.Takata help='run only CATEGORY* related cases.') 8927a85ed06SK.Takata parser.add_argument('--ctags', 8937a85ed06SK.Takata help='ctags executable file for testing') 8947a85ed06SK.Takata parser.add_argument('--units', metavar='UNITS1[,UNITS2,...]', 8957a85ed06SK.Takata help='run only UNIT(S).') 8967a85ed06SK.Takata parser.add_argument('--languages', metavar='PARSER1[,PARSER2,...]', 8977a85ed06SK.Takata help='run only PARSER* related cases.') 8987a85ed06SK.Takata parser.add_argument('--with-timeout', type=int, default=0, 8997a85ed06SK.Takata metavar='DURATION', 9007a85ed06SK.Takata help='run a test case with specified timeout in seconds. 0 means no timeout (default).') 9017a85ed06SK.Takata parser.add_argument('--with-valgrind', action='store_true', default=False, 9027a85ed06SK.Takata help='run a test case under valgrind') 9037a85ed06SK.Takata parser.add_argument('--colorized-output', choices=['yes', 'no'], default='yes', 9047a85ed06SK.Takata help='print the result in color.') 9057a85ed06SK.Takata parser.add_argument('--run-shrink', action='store_true', default=False, 9067a85ed06SK.Takata help='(TODO: NOT IMPLEMENTED YET)') 9077a85ed06SK.Takata parser.add_argument('--show-diff-output', action='store_true', default=False, 908d892ff97SMasatake YAMATO help='show diff output (and valgrind errors) for failed test cases in the summary.') 9097a85ed06SK.Takata parser.add_argument('--with-pretense-map', 9107a85ed06SK.Takata metavar='NEWLANG0/OLDLANG0[,...]', 9117a85ed06SK.Takata help='make NEWLANG parser pretend OLDLANG.') 9127a85ed06SK.Takata parser.add_argument('--threads', type=int, default=NUM_WORKER_THREADS, 9137a85ed06SK.Takata help='number of worker threads') 9143e288efeSK.Takata parser.add_argument('--shell', 9153e288efeSK.Takata help='shell to be used.') 9167a85ed06SK.Takata parser.add_argument('units_dir', 9177a85ed06SK.Takata help='Units directory.') 9187a85ed06SK.Takata parser.add_argument('build_dir', nargs='?', default='', 9197a85ed06SK.Takata help='Build directory. If not given, units_dir is used.') 920d58ff7ccSK.Takata 921d58ff7ccSK.Takata res = parser.parse_args(args) 922d58ff7ccSK.Takata if res.categories: 923d58ff7ccSK.Takata CATEGORIES = [x if x == 'ROOT' or x.endswith('.r') else x + '.r' 924d58ff7ccSK.Takata for x in res.categories.split(',')] 925d58ff7ccSK.Takata if res.ctags: 926d58ff7ccSK.Takata CTAGS = res.ctags 927d58ff7ccSK.Takata if res.units: 928d58ff7ccSK.Takata UNITS = res.units.split(',') 929d58ff7ccSK.Takata if res.languages: 930d58ff7ccSK.Takata LANGUAGES = res.languages.split(',') 931d58ff7ccSK.Takata WITH_TIMEOUT = res.with_timeout 932d58ff7ccSK.Takata WITH_VALGRIND = res.with_valgrind 933d58ff7ccSK.Takata COLORIZED_OUTPUT = (res.colorized_output == 'yes') 934d58ff7ccSK.Takata RUN_SHRINK = res.run_shrink 935d58ff7ccSK.Takata SHOW_DIFF_OUTPUT = res.show_diff_output 936d58ff7ccSK.Takata if res.with_pretense_map: 937d58ff7ccSK.Takata PRETENSE_OPTS = make_pretense_map(res.with_pretense_map) 938646e1eb7SK.Takata NUM_WORKER_THREADS = res.threads 9393e288efeSK.Takata if res.shell: 9403e288efeSK.Takata SHELL = res.shell 941d58ff7ccSK.Takata if res.build_dir == '': 942d58ff7ccSK.Takata res.build_dir = res.units_dir 943d58ff7ccSK.Takata 944d58ff7ccSK.Takata if WITH_VALGRIND: 945d58ff7ccSK.Takata check_availability('valgrind') 946d58ff7ccSK.Takata check_availability('diff') 947d58ff7ccSK.Takata init_features() 948d58ff7ccSK.Takata 949d58ff7ccSK.Takata if isabs(res.build_dir): 950d58ff7ccSK.Takata build_dir = res.build_dir 951d58ff7ccSK.Takata else: 952d58ff7ccSK.Takata build_dir = os.path.realpath(res.build_dir) 953d58ff7ccSK.Takata 954d58ff7ccSK.Takata category = _DEFAULT_CATEGORY 955d58ff7ccSK.Takata if len(CATEGORIES) == 0 or (category in CATEGORIES): 956d58ff7ccSK.Takata run_dir(category, res.units_dir, build_dir) 957d58ff7ccSK.Takata 958d58ff7ccSK.Takata for d in glob.glob(res.units_dir + '/*.r'): 959d58ff7ccSK.Takata d = d.replace('\\', '/') # for Windows 960d58ff7ccSK.Takata if not os.path.isdir(d): 961d58ff7ccSK.Takata continue 962d58ff7ccSK.Takata category = os.path.basename(d) 963d58ff7ccSK.Takata build_d = res.build_dir + '/' + category 964d58ff7ccSK.Takata run_dir(category, d, build_d) 965d58ff7ccSK.Takata 966d58ff7ccSK.Takata run_summary(build_dir) 967d58ff7ccSK.Takata 968d58ff7ccSK.Takata if L_FAILED_BY_STATUS or L_FAILED_BY_DIFF or \ 969454960aaSMasatake YAMATO L_FAILED_BY_TIMEED_OUT or L_BROKEN_ARGS_CTAGS or \ 970454960aaSMasatake YAMATO L_VALGRIND: 971d58ff7ccSK.Takata return 1 972d58ff7ccSK.Takata else: 973d58ff7ccSK.Takata return 0 974d58ff7ccSK.Takata 975099a0013SK.Takatadef action_clean(parser, action, *args): 9767a85ed06SK.Takata parser.add_argument('units_dir', 9777a85ed06SK.Takata help='Build directory for units testing.') 978099a0013SK.Takata 979099a0013SK.Takata res = parser.parse_args(args) 980099a0013SK.Takata units_dir = res.units_dir 981099a0013SK.Takata 982099a0013SK.Takata if not os.path.isdir(units_dir): 9837d4fe82aSK.Takata error_exit(0, 'No such directory: ' + units_dir) 984099a0013SK.Takata 985099a0013SK.Takata for bundles in glob.glob(units_dir + '/**/BUNDLES', recursive=True): 986099a0013SK.Takata clean_bundles(bundles) 987099a0013SK.Takata 988099a0013SK.Takata for fn in glob.glob(units_dir + '/**/*.tmp', recursive=True): 989099a0013SK.Takata os.remove(fn) 990099a0013SK.Takata for fn in glob.glob(units_dir + '/**/*.TMP', recursive=True): 991099a0013SK.Takata os.remove(fn) 992099a0013SK.Takata return 0 993099a0013SK.Takata 994646e1eb7SK.Takatadef tmain_compare_result(build_topdir): 995646e1eb7SK.Takata for fn in glob.glob(build_topdir + '/*/*-diff.txt'): 996646e1eb7SK.Takata print(fn) 997646e1eb7SK.Takata print() 998646e1eb7SK.Takata with open(fn, 'r', errors='replace') as f: 999646e1eb7SK.Takata for l in f: 1000646e1eb7SK.Takata print("\t" + l, end='') 1001646e1eb7SK.Takata print() 1002646e1eb7SK.Takata 1003646e1eb7SK.Takata for fn in glob.glob(build_topdir + '/*/gdb-backtrace.txt'): 1004646e1eb7SK.Takata with open(fn, 'r', errors='replace') as f: 1005646e1eb7SK.Takata for l in f: 1006646e1eb7SK.Takata print("\t" + l, end='') 1007646e1eb7SK.Takata 1008646e1eb7SK.Takatadef tmain_compare(subdir, build_subdir, aspect, file): 1009646e1eb7SK.Takata msg = '%-59s ' % (aspect) 1010646e1eb7SK.Takata generated = build_subdir + '/' + aspect + '-diff.txt' 1011646e1eb7SK.Takata actual = build_subdir + '/' + aspect + '-actual.txt' 1012646e1eb7SK.Takata expected = subdir + '/' + aspect + '-expected.txt' 1013646e1eb7SK.Takata if os.path.isfile(actual) and os.path.isfile(expected) and \ 1014646e1eb7SK.Takata filecmp.cmp(actual, expected): 1015646e1eb7SK.Takata run_result('ok', msg, None, file=file) 10162b622dadSMasatake YAMATO # When successful, remove files generated in the last 10172b622dadSMasatake YAMATO # failure to make the directory clean. 10182b622dadSMasatake YAMATO # Unlike other generated files like gdb-backtrace.txt 10192b622dadSMasatake YAMATO # misc/review script looks at the -diff.txt file. 10202b622dadSMasatake YAMATO # Therefore we handle -diff.txt specially here. 10212b622dadSMasatake YAMATO if os.path.isfile(generated): 10222b622dadSMasatake YAMATO os.remove(generated) 1023646e1eb7SK.Takata return True 1024646e1eb7SK.Takata else: 10253e288efeSK.Takata with open(generated, 'wb') as f: 1026ad8e6505SMasatake YAMATO subprocess.run(['diff', '-U', 1027ad8e6505SMasatake YAMATO str(DIFF_U_NUM), '--strip-trailing-cr', 1028*a91961f0SHiroo HAYASHI expected, actual], 10293e288efeSK.Takata stdout=f, stderr=subprocess.STDOUT) 1030646e1eb7SK.Takata run_result('error', msg, None, 'diff: ' + generated, file=file) 1031646e1eb7SK.Takata return False 1032646e1eb7SK.Takata 1033646e1eb7SK.Takatadef failed_git_marker(fn): 1034646e1eb7SK.Takata if shutil.which('git'): 10353e288efeSK.Takata ret = subprocess.run(['git', 'ls-files', '--', fn], 10363e288efeSK.Takata stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) 10373e288efeSK.Takata if ret.returncode == 0 and ret.stdout == b'': 1038646e1eb7SK.Takata return '<G>' 1039646e1eb7SK.Takata return '' 1040646e1eb7SK.Takata 1041646e1eb7SK.Takatadef is_crashed(fn): 1042646e1eb7SK.Takata with open(fn, 'r') as f: 1043646e1eb7SK.Takata if 'core dump' in f.read(): 1044646e1eb7SK.Takata return True 1045646e1eb7SK.Takata return False 1046646e1eb7SK.Takata 1047646e1eb7SK.Takatadef print_backtraces(ctags_exe, cores, fn): 10483e288efeSK.Takata with open(fn, 'wb') as f: 1049646e1eb7SK.Takata for coref in cores: 10503e288efeSK.Takata subprocess.run(['gdb', ctags_exe, '-c', coref, '-ex', 'where', '-batch'], 10513e288efeSK.Takata stdout=f, stderr=subprocess.DEVNULL) 1052646e1eb7SK.Takata 1053646e1eb7SK.Takatadef tmain_sub(test_name, basedir, subdir, build_subdir): 1054646e1eb7SK.Takata global TMAIN_STATUS 1055646e1eb7SK.Takata global TMAIN_FAILED 1056646e1eb7SK.Takata 1057646e1eb7SK.Takata CODE_FOR_IGNORING_THIS_TMAIN_TEST = 77 1058646e1eb7SK.Takata 1059646e1eb7SK.Takata os.makedirs(build_subdir, exist_ok=True) 1060646e1eb7SK.Takata 1061646e1eb7SK.Takata for fn in glob.glob(build_subdir + '/*-actual.txt'): 1062646e1eb7SK.Takata os.remove(fn) 1063646e1eb7SK.Takata 1064646e1eb7SK.Takata strbuf = io.StringIO() 1065646e1eb7SK.Takata print("\nTesting " + test_name, file=strbuf) 1066646e1eb7SK.Takata line('-', file=strbuf) 1067646e1eb7SK.Takata 1068646e1eb7SK.Takata if isabs(CTAGS): 1069646e1eb7SK.Takata ctags_path = CTAGS 1070646e1eb7SK.Takata else: 1071646e1eb7SK.Takata ctags_path = os.path.join(basedir, CTAGS) 1072646e1eb7SK.Takata 1073646e1eb7SK.Takata if isabs(READTAGS): 1074646e1eb7SK.Takata readtags_path = READTAGS 1075646e1eb7SK.Takata else: 1076646e1eb7SK.Takata readtags_path = os.path.join(basedir, READTAGS) 1077646e1eb7SK.Takata 1078d8f6b0edSMasatake YAMATO if isabs(OPTSCRIPT): 1079d8f6b0edSMasatake YAMATO optscript_path = OPTSCRIPT 1080d8f6b0edSMasatake YAMATO else: 1081d8f6b0edSMasatake YAMATO optscript_path = os.path.join(basedir, OPTSCRIPT) 1082d8f6b0edSMasatake YAMATO 10833e288efeSK.Takata start = time.time() 10843e288efeSK.Takata ret = subprocess.run([SHELL, 'run.sh', 10853e288efeSK.Takata ctags_path, 10863e288efeSK.Takata build_subdir, 1087d8f6b0edSMasatake YAMATO readtags_path, 1088d8f6b0edSMasatake YAMATO optscript_path], 1089646e1eb7SK.Takata cwd=subdir, 10903e288efeSK.Takata stdout=subprocess.PIPE, stderr=subprocess.PIPE) 10913e288efeSK.Takata #print('execute time: %f' % (time.time() - start), file=strbuf) 1092646e1eb7SK.Takata 1093646e1eb7SK.Takata encoding = 'utf-8' 1094646e1eb7SK.Takata try: 1095646e1eb7SK.Takata stdout = ret.stdout.decode(encoding).replace("\r\n", "\n") 1096646e1eb7SK.Takata except UnicodeError: 1097646e1eb7SK.Takata encoding = 'iso-8859-1' 1098646e1eb7SK.Takata stdout = ret.stdout.decode(encoding).replace("\r\n", "\n") 1099646e1eb7SK.Takata stderr = ret.stderr.decode('utf-8').replace("\r\n", "\n") 1100646e1eb7SK.Takata if os.path.basename(CTAGS) != 'ctags': 1101646e1eb7SK.Takata # program name needs to be canonicalized 1102646e1eb7SK.Takata stderr = re.sub('(?m)^' + os.path.basename(CTAGS) + ':', 'ctags:', stderr) 1103646e1eb7SK.Takata 1104646e1eb7SK.Takata if ret.returncode == CODE_FOR_IGNORING_THIS_TMAIN_TEST: 1105646e1eb7SK.Takata run_result('skip', '', None, stdout.replace("\n", ''), file=strbuf) 1106646e1eb7SK.Takata print(strbuf.getvalue(), end='') 1107646e1eb7SK.Takata sys.stdout.flush() 1108646e1eb7SK.Takata strbuf.close() 1109646e1eb7SK.Takata return True 1110646e1eb7SK.Takata 1111646e1eb7SK.Takata with open(build_subdir + '/exit-actual.txt', 'w', newline='\n') as f: 1112646e1eb7SK.Takata print(ret.returncode, file=f) 1113646e1eb7SK.Takata with open(build_subdir + '/stdout-actual.txt', 'w', newline='\n', encoding=encoding) as f: 1114646e1eb7SK.Takata print(stdout, end='', file=f) 1115646e1eb7SK.Takata with open(build_subdir + '/stderr-actual.txt', 'w', newline='\n') as f: 1116646e1eb7SK.Takata print(stderr, end='', file=f) 1117646e1eb7SK.Takata 1118646e1eb7SK.Takata if os.path.isfile(build_subdir + '/tags'): 1119646e1eb7SK.Takata os.rename(build_subdir + '/tags', build_subdir + '/tags-actual.txt') 1120646e1eb7SK.Takata 1121646e1eb7SK.Takata for aspect in ['stdout', 'stderr', 'exit', 'tags']: 1122646e1eb7SK.Takata expected_txt = subdir + '/' + aspect + '-expected.txt' 1123646e1eb7SK.Takata actual_txt = build_subdir + '/' + aspect + '-actual.txt' 1124646e1eb7SK.Takata if os.path.isfile(expected_txt): 1125646e1eb7SK.Takata if tmain_compare(subdir, build_subdir, aspect, strbuf): 1126646e1eb7SK.Takata os.remove(actual_txt) 1127646e1eb7SK.Takata else: 1128646e1eb7SK.Takata TMAIN_FAILED += [test_name + '/' + aspect + '-compare' + 1129646e1eb7SK.Takata failed_git_marker(expected_txt)] 1130646e1eb7SK.Takata TMAIN_STATUS = False 1131646e1eb7SK.Takata if aspect == 'stderr' and \ 1132646e1eb7SK.Takata is_crashed(actual_txt) and \ 1133646e1eb7SK.Takata shutil.which('gdb'): 1134646e1eb7SK.Takata print_backtraces(ctags_path, 1135646e1eb7SK.Takata glob.glob(build_subdir + '/core*'), 1136646e1eb7SK.Takata build_subdir + '/gdb-backtrace.txt') 1137646e1eb7SK.Takata elif os.path.isfile(actual_txt): 1138646e1eb7SK.Takata os.remove(actual_txt) 1139646e1eb7SK.Takata 1140646e1eb7SK.Takata print(strbuf.getvalue(), end='') 1141646e1eb7SK.Takata sys.stdout.flush() 1142646e1eb7SK.Takata strbuf.close() 1143646e1eb7SK.Takata return True 1144646e1eb7SK.Takata 1145646e1eb7SK.Takatadef tmain_run(topdir, build_topdir, units): 1146646e1eb7SK.Takata global TMAIN_STATUS 1147646e1eb7SK.Takata 1148646e1eb7SK.Takata TMAIN_STATUS = True 1149646e1eb7SK.Takata 1150646e1eb7SK.Takata (q, threads) = create_thread_queue(tmain_sub) 1151646e1eb7SK.Takata 1152646e1eb7SK.Takata basedir = os.getcwd() 1153646e1eb7SK.Takata for subdir in glob.glob(topdir + '/*.d'): 1154646e1eb7SK.Takata test_name = os.path.basename(subdir)[:-2] 1155646e1eb7SK.Takata 1156646e1eb7SK.Takata if len(units) > 0 and not test_name in units: 1157646e1eb7SK.Takata continue 1158646e1eb7SK.Takata 1159646e1eb7SK.Takata build_subdir = build_topdir + '/' + os.path.basename(subdir) 1160646e1eb7SK.Takata q.put((test_name, basedir, subdir, build_subdir)) 1161646e1eb7SK.Takata 1162646e1eb7SK.Takata join_workers(q, threads) 1163646e1eb7SK.Takata 1164646e1eb7SK.Takata print() 1165646e1eb7SK.Takata if not TMAIN_STATUS: 1166646e1eb7SK.Takata print('Failed tests') 1167646e1eb7SK.Takata line('=') 1168646e1eb7SK.Takata for f in TMAIN_FAILED: 1169646e1eb7SK.Takata print(re.sub('<G>', ' (not committed/cached yet)', f)) 1170646e1eb7SK.Takata print() 1171646e1eb7SK.Takata 1172646e1eb7SK.Takata if SHOW_DIFF_OUTPUT: 1173646e1eb7SK.Takata print('Detail [compare]') 1174646e1eb7SK.Takata line('-') 1175646e1eb7SK.Takata tmain_compare_result(build_topdir) 1176646e1eb7SK.Takata 1177646e1eb7SK.Takata return TMAIN_STATUS 1178646e1eb7SK.Takata 1179646e1eb7SK.Takatadef action_tmain(parser, action, *args): 1180646e1eb7SK.Takata global CTAGS 1181646e1eb7SK.Takata global COLORIZED_OUTPUT 1182646e1eb7SK.Takata global WITH_VALGRIND 1183646e1eb7SK.Takata global SHOW_DIFF_OUTPUT 1184646e1eb7SK.Takata global READTAGS 1185d8f6b0edSMasatake YAMATO global OPTSCRIPT 1186646e1eb7SK.Takata global UNITS 1187646e1eb7SK.Takata global NUM_WORKER_THREADS 11883e288efeSK.Takata global SHELL 1189646e1eb7SK.Takata 11907a85ed06SK.Takata parser.add_argument('--ctags', 11917a85ed06SK.Takata help='ctags executable file for testing') 11927a85ed06SK.Takata parser.add_argument('--colorized-output', choices=['yes', 'no'], default='yes', 11937a85ed06SK.Takata help='print the result in color.') 11947a85ed06SK.Takata parser.add_argument('--with-valgrind', action='store_true', default=False, 11957a85ed06SK.Takata help='(not implemented) run a test case under valgrind') 11967a85ed06SK.Takata parser.add_argument('--show-diff-output', action='store_true', default=False, 11977a85ed06SK.Takata help='how diff output for failed test cases in the summary.') 11987a85ed06SK.Takata parser.add_argument('--readtags', 11997a85ed06SK.Takata help='readtags executable file for testing') 1200d8f6b0edSMasatake YAMATO parser.add_argument('--optscript', 1201d8f6b0edSMasatake YAMATO help='optscript executable file for testing') 12027a85ed06SK.Takata parser.add_argument('--units', metavar='UNITS1[,UNITS2,...]', 12037a85ed06SK.Takata help='run only Tmain/UNIT*.d (.d is not needed)') 12047a85ed06SK.Takata parser.add_argument('--threads', type=int, default=NUM_WORKER_THREADS, 12057a85ed06SK.Takata help='number of worker threads') 12063e288efeSK.Takata parser.add_argument('--shell', 12073e288efeSK.Takata help='shell to be used.') 12087a85ed06SK.Takata parser.add_argument('tmain_dir', 12097a85ed06SK.Takata help='Tmain directory.') 12107a85ed06SK.Takata parser.add_argument('build_dir', nargs='?', default='', 12117a85ed06SK.Takata help='Build directory. If not given, tmain_dir is used.') 1212646e1eb7SK.Takata 1213646e1eb7SK.Takata res = parser.parse_args(args) 1214646e1eb7SK.Takata if res.ctags: 1215646e1eb7SK.Takata CTAGS = res.ctags 1216646e1eb7SK.Takata COLORIZED_OUTPUT = (res.colorized_output == 'yes') 1217646e1eb7SK.Takata WITH_VALGRIND = res.with_valgrind 1218646e1eb7SK.Takata SHOW_DIFF_OUTPUT = res.show_diff_output 1219646e1eb7SK.Takata if res.readtags: 1220646e1eb7SK.Takata READTAGS = res.readtags 1221d8f6b0edSMasatake YAMATO if res.optscript: 1222d8f6b0edSMasatake YAMATO OPTSCRIPT = res.optscript 1223646e1eb7SK.Takata if res.units: 1224646e1eb7SK.Takata UNITS = res.units.split(',') 1225646e1eb7SK.Takata NUM_WORKER_THREADS = res.threads 12263e288efeSK.Takata if res.shell: 12273e288efeSK.Takata SHELL = res.shell 1228646e1eb7SK.Takata if res.build_dir == '': 1229646e1eb7SK.Takata res.build_dir = res.tmain_dir 1230646e1eb7SK.Takata 1231646e1eb7SK.Takata #check_availability('awk') 1232646e1eb7SK.Takata check_availability('diff') 1233646e1eb7SK.Takata 1234646e1eb7SK.Takata if isabs(res.build_dir): 1235646e1eb7SK.Takata build_dir = res.build_dir 1236646e1eb7SK.Takata else: 1237646e1eb7SK.Takata build_dir = os.path.realpath(res.build_dir) 1238646e1eb7SK.Takata 1239646e1eb7SK.Takata ret = tmain_run(res.tmain_dir, build_dir, UNITS) 1240646e1eb7SK.Takata if ret: 1241646e1eb7SK.Takata return 0 1242646e1eb7SK.Takata else: 1243646e1eb7SK.Takata return 1 1244646e1eb7SK.Takata 1245099a0013SK.Takatadef action_clean_tmain(parser, action, *args): 12467a85ed06SK.Takata parser.add_argument('tmain_dir', 12477a85ed06SK.Takata help='Build directory for tmain testing.') 1248099a0013SK.Takata 1249099a0013SK.Takata res = parser.parse_args(args) 1250099a0013SK.Takata tmain_dir = res.tmain_dir 1251099a0013SK.Takata 1252099a0013SK.Takata if not os.path.isdir(tmain_dir): 12537d4fe82aSK.Takata error_exit(0, 'No such directory: ' + tmain_dir) 1254099a0013SK.Takata 1255099a0013SK.Takata for obj in ['stdout', 'stderr', 'exit', 'tags']: 1256099a0013SK.Takata for typ in ['actual', 'diff']: 1257099a0013SK.Takata for fn in glob.glob(tmain_dir + '/**/' + obj + '-' + typ + '.txt', recursive=True): 1258099a0013SK.Takata os.remove(fn) 1259099a0013SK.Takata for fn in glob.glob(tmain_dir + '/**/gdb-backtrace.txt', recursive=True): 1260099a0013SK.Takata os.remove(fn) 1261d58ff7ccSK.Takata return 0 1262d58ff7ccSK.Takata 1263d58ff7ccSK.Takatadef prepare_environment(): 1264d58ff7ccSK.Takata global _PREPERE_ENV 1265d58ff7ccSK.Takata 1266d58ff7ccSK.Takata os.environ['LC_ALL'] = 'C' 1267db6404a9SMasatake YAMATO os.environ['MSYS2_ARG_CONV_EXCL'] = '--regex-;--_scopesep;--exclude;--exclude-exception' 1268d58ff7ccSK.Takata 1269d58ff7ccSK.Takata _PREPERE_ENV = """LC_ALL="C"; export LC_ALL 1270db6404a9SMasatake YAMATOMSYS2_ARG_CONV_EXCL='--regex-;--_scopesep;--exclude;--exclude-exception' export MSYS2_ARG_CONV_EXCL 1271d58ff7ccSK.Takata""" 1272d58ff7ccSK.Takata 12735edfa40bSK.Takata# enable ANSI escape sequences on Windows 10 1511 (10.0.10586) or later 12745edfa40bSK.Takatadef enable_esc_sequence(): 12755edfa40bSK.Takata if os.name != 'nt': 12765edfa40bSK.Takata return 12775edfa40bSK.Takata 12785edfa40bSK.Takata import ctypes 12795edfa40bSK.Takata 12805edfa40bSK.Takata kernel32 = ctypes.windll.kernel32 12815edfa40bSK.Takata 12825edfa40bSK.Takata ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 12835edfa40bSK.Takata STD_OUTPUT_HANDLE = -11 12845edfa40bSK.Takata 12855edfa40bSK.Takata out = kernel32.GetStdHandle(STD_OUTPUT_HANDLE) 12865edfa40bSK.Takata mode = ctypes.c_ulong() 12875edfa40bSK.Takata if kernel32.GetConsoleMode(out, ctypes.byref(mode)): 12885edfa40bSK.Takata kernel32.SetConsoleMode(out, 12895edfa40bSK.Takata mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING) 12905edfa40bSK.Takata 1291d58ff7ccSK.Takatadef main(): 1292d58ff7ccSK.Takata prepare_environment() 12935edfa40bSK.Takata enable_esc_sequence() 1294d58ff7ccSK.Takata 12957a85ed06SK.Takata parser = argparse.ArgumentParser( 12967a85ed06SK.Takata description='Units test harness for ctags.') 12977a85ed06SK.Takata subparsers = parser.add_subparsers(dest='action', metavar='ACTION') 1298d58ff7ccSK.Takata cmdmap = {} 12997a85ed06SK.Takata cmdmap['run'] = [action_run, 13007a85ed06SK.Takata subparsers.add_parser('run', aliases=['units'], 13017a85ed06SK.Takata description='Run all tests case under units_dir.', 13027a85ed06SK.Takata help='Run all tests case')] 1303d58ff7ccSK.Takata cmdmap['units'] = cmdmap['run'] 13047a85ed06SK.Takata cmdmap['clean'] = [action_clean, 13057a85ed06SK.Takata subparsers.add_parser('clean', 13067a85ed06SK.Takata description='Clean all files created during units testing.', 13077a85ed06SK.Takata help='Clean all files created during units testing')] 13087a85ed06SK.Takata cmdmap['tmain'] = [action_tmain, 13097a85ed06SK.Takata subparsers.add_parser('tmain', 13107a85ed06SK.Takata description='Run tests for main part of ctags.', 13117a85ed06SK.Takata help='Run tests for main part of ctags')] 13127a85ed06SK.Takata cmdmap['clean-tmain'] = [action_clean_tmain, 13137a85ed06SK.Takata subparsers.add_parser('clean-tmain', 13147a85ed06SK.Takata description='Clean all files created during tmain testing.', 13157a85ed06SK.Takata help='Clean all files created during tmain testing')] 13167a85ed06SK.Takata subparsers.add_parser('help', 13177a85ed06SK.Takata help='show this help message and exit') 1318d58ff7ccSK.Takata cmdmap['help'] = [action_help, parser] 1319d58ff7ccSK.Takata 1320d58ff7ccSK.Takata if len(sys.argv) < 2: 1321d58ff7ccSK.Takata parser.print_help() 1322d58ff7ccSK.Takata sys.exit(1) 1323d58ff7ccSK.Takata 1324d58ff7ccSK.Takata res = parser.parse_args(sys.argv[1:2]) 1325d58ff7ccSK.Takata (func, subparser) = cmdmap[res.action] 1326d58ff7ccSK.Takata sys.exit(func(subparser, *sys.argv[1:])) 1327d58ff7ccSK.Takata 1328d58ff7ccSK.Takataif __name__ == '__main__': 1329d58ff7ccSK.Takata main() 1330