11c98701eSFrancis Visoiu Mistrih#!/usr/bin/env python 21c98701eSFrancis Visoiu Mistrih# 31c98701eSFrancis Visoiu Mistrih# Given a previous good compile narrow down miscompiles. 41c98701eSFrancis Visoiu Mistrih# Expects two directories named "before" and "after" each containing a set of 51c98701eSFrancis Visoiu Mistrih# assembly or object files where the "after" version is assumed to be broken. 6e2dc6929SMatthias Braun# You also have to provide a script called "link_test". It is called with a 7e2dc6929SMatthias Braun# list of files which should be linked together and result tested. "link_test" 8e2dc6929SMatthias Braun# should returns with exitcode 0 if the linking and testing succeeded. 91c98701eSFrancis Visoiu Mistrih# 10bcb8ef2dSYuanfang Chen# If a response file is provided, only the object files that are listed in the 11bcb8ef2dSYuanfang Chen# file are inspected. In addition, the "link_test" is called with a temporary 12bcb8ef2dSYuanfang Chen# response file representing one iteration of bisection. 13bcb8ef2dSYuanfang Chen# 141c98701eSFrancis Visoiu Mistrih# abtest.py operates by taking all files from the "before" directory and 151c98701eSFrancis Visoiu Mistrih# in each step replacing one of them with a file from the "bad" directory. 161c98701eSFrancis Visoiu Mistrih# 171c98701eSFrancis Visoiu Mistrih# Additionally you can perform the same steps with a single .s file. In this 181c98701eSFrancis Visoiu Mistrih# mode functions are identified by " -- Begin function FunctionName" and 191c98701eSFrancis Visoiu Mistrih# " -- End function" markers. The abtest.py then takes all 201c98701eSFrancis Visoiu Mistrih# function from the file in the "before" directory and replaces one function 211c98701eSFrancis Visoiu Mistrih# with the corresponding function from the "bad" file in each step. 221c98701eSFrancis Visoiu Mistrih# 231c98701eSFrancis Visoiu Mistrih# Example usage to identify miscompiled files: 241c98701eSFrancis Visoiu Mistrih# 1. Create a link_test script, make it executable. Simple Example: 251c98701eSFrancis Visoiu Mistrih# clang "$@" -o /tmp/test && /tmp/test || echo "PROBLEM" 261c98701eSFrancis Visoiu Mistrih# 2. Run the script to figure out which files are miscompiled: 271c98701eSFrancis Visoiu Mistrih# > ./abtest.py 281c98701eSFrancis Visoiu Mistrih# somefile.s: ok 291c98701eSFrancis Visoiu Mistrih# someotherfile.s: skipped: same content 301c98701eSFrancis Visoiu Mistrih# anotherfile.s: failed: './link_test' exitcode != 0 311c98701eSFrancis Visoiu Mistrih# ... 321c98701eSFrancis Visoiu Mistrih# Example usage to identify miscompiled functions inside a file: 331c98701eSFrancis Visoiu Mistrih# 3. Run the tests on a single file (assuming before/file.s and 341c98701eSFrancis Visoiu Mistrih# after/file.s exist) 351c98701eSFrancis Visoiu Mistrih# > ./abtest.py file.s 361c98701eSFrancis Visoiu Mistrih# funcname1 [0/XX]: ok 371c98701eSFrancis Visoiu Mistrih# funcname2 [1/XX]: ok 381c98701eSFrancis Visoiu Mistrih# funcname3 [2/XX]: skipped: same content 391c98701eSFrancis Visoiu Mistrih# funcname4 [3/XX]: failed: './link_test' exitcode != 0 401c98701eSFrancis Visoiu Mistrih# ... 411c98701eSFrancis Visoiu Mistrihfrom fnmatch import filter 421c98701eSFrancis Visoiu Mistrihfrom sys import stderr 431c98701eSFrancis Visoiu Mistrihimport argparse 441c98701eSFrancis Visoiu Mistrihimport filecmp 451c98701eSFrancis Visoiu Mistrihimport os 461c98701eSFrancis Visoiu Mistrihimport subprocess 471c98701eSFrancis Visoiu Mistrihimport sys 48bcb8ef2dSYuanfang Chenimport tempfile 491c98701eSFrancis Visoiu Mistrih 50bcb8ef2dSYuanfang Chen# Specify LINKTEST via `--test`. Default value is './link_test'. 51bcb8ef2dSYuanfang ChenLINKTEST = "" 521c98701eSFrancis Visoiu MistrihESCAPE = "\033[%sm" 531c98701eSFrancis Visoiu MistrihBOLD = ESCAPE % "1" 541c98701eSFrancis Visoiu MistrihRED = ESCAPE % "31" 551c98701eSFrancis Visoiu MistrihNORMAL = ESCAPE % "0" 561c98701eSFrancis Visoiu MistrihFAILED = RED + "failed" + NORMAL 571c98701eSFrancis Visoiu Mistrih 58e2dc6929SMatthias Braun 591c98701eSFrancis Visoiu Mistrihdef find(dir, file_filter=None): 60*b71edfaaSTobias Hieta files = [walkdir[0] + "/" + file for walkdir in os.walk(dir) for file in walkdir[2]] 61e2dc6929SMatthias Braun if file_filter is not None: 621c98701eSFrancis Visoiu Mistrih files = filter(files, file_filter) 63e2dc6929SMatthias Braun return sorted(files) 64e2dc6929SMatthias Braun 651c98701eSFrancis Visoiu Mistrih 661c98701eSFrancis Visoiu Mistrihdef error(message): 671c98701eSFrancis Visoiu Mistrih stderr.write("Error: %s\n" % (message,)) 681c98701eSFrancis Visoiu Mistrih 69e2dc6929SMatthias Braun 701c98701eSFrancis Visoiu Mistrihdef warn(message): 711c98701eSFrancis Visoiu Mistrih stderr.write("Warning: %s\n" % (message,)) 721c98701eSFrancis Visoiu Mistrih 73e2dc6929SMatthias Braun 74e2dc6929SMatthias Braundef info(message): 75e2dc6929SMatthias Braun stderr.write("Info: %s\n" % (message,)) 76e2dc6929SMatthias Braun 77e2dc6929SMatthias Braun 78e2dc6929SMatthias Braundef announce_test(name): 79e2dc6929SMatthias Braun stderr.write("%s%s%s: " % (BOLD, name, NORMAL)) 80e2dc6929SMatthias Braun stderr.flush() 81e2dc6929SMatthias Braun 82e2dc6929SMatthias Braun 83e2dc6929SMatthias Braundef announce_result(result): 84e2dc6929SMatthias Braun stderr.write(result) 85e2dc6929SMatthias Braun stderr.write("\n") 86e2dc6929SMatthias Braun stderr.flush() 87e2dc6929SMatthias Braun 88e2dc6929SMatthias Braun 89e2dc6929SMatthias Braundef format_namelist(l): 90e2dc6929SMatthias Braun result = ", ".join(l[0:3]) 91e2dc6929SMatthias Braun if len(l) > 3: 92e2dc6929SMatthias Braun result += "... (%d total)" % len(l) 93e2dc6929SMatthias Braun return result 94e2dc6929SMatthias Braun 95e2dc6929SMatthias Braun 96e2dc6929SMatthias Braundef check_sanity(choices, perform_test): 97e2dc6929SMatthias Braun announce_test("sanity check A") 98e2dc6929SMatthias Braun all_a = {name: a_b[0] for name, a_b in choices} 99e2dc6929SMatthias Braun res_a = perform_test(all_a) 100e2dc6929SMatthias Braun if res_a is not True: 101e2dc6929SMatthias Braun error("Picking all choices from A failed to pass the test") 102e2dc6929SMatthias Braun sys.exit(1) 103e2dc6929SMatthias Braun 104e2dc6929SMatthias Braun announce_test("sanity check B (expecting failure)") 105e2dc6929SMatthias Braun all_b = {name: a_b[1] for name, a_b in choices} 106e2dc6929SMatthias Braun res_b = perform_test(all_b) 107e2dc6929SMatthias Braun if res_b is not False: 108e2dc6929SMatthias Braun error("Picking all choices from B did unexpectedly pass the test") 109e2dc6929SMatthias Braun sys.exit(1) 110e2dc6929SMatthias Braun 111e2dc6929SMatthias Braun 112e2dc6929SMatthias Braundef check_sequentially(choices, perform_test): 113e2dc6929SMatthias Braun known_good = set() 114e2dc6929SMatthias Braun all_a = {name: a_b[0] for name, a_b in choices} 115e2dc6929SMatthias Braun n = 1 116e2dc6929SMatthias Braun for name, a_b in sorted(choices): 117e2dc6929SMatthias Braun picks = dict(all_a) 118e2dc6929SMatthias Braun picks[name] = a_b[1] 119e2dc6929SMatthias Braun announce_test("checking %s [%d/%d]" % (name, n, len(choices))) 120e2dc6929SMatthias Braun n += 1 121e2dc6929SMatthias Braun res = perform_test(picks) 122e2dc6929SMatthias Braun if res is True: 123e2dc6929SMatthias Braun known_good.add(name) 124e2dc6929SMatthias Braun return known_good 125e2dc6929SMatthias Braun 126e2dc6929SMatthias Braun 127e2dc6929SMatthias Braundef check_bisect(choices, perform_test): 128e2dc6929SMatthias Braun known_good = set() 129e2dc6929SMatthias Braun if len(choices) == 0: 130e2dc6929SMatthias Braun return known_good 131e2dc6929SMatthias Braun 132e2dc6929SMatthias Braun choice_map = dict(choices) 133e2dc6929SMatthias Braun all_a = {name: a_b[0] for name, a_b in choices} 134e2dc6929SMatthias Braun 135e2dc6929SMatthias Braun def test_partition(partition, upcoming_partition): 136e2dc6929SMatthias Braun # Compute the maximum number of checks we have to do in the worst case. 137e2dc6929SMatthias Braun max_remaining_steps = len(partition) * 2 - 1 138e2dc6929SMatthias Braun if upcoming_partition is not None: 139e2dc6929SMatthias Braun max_remaining_steps += len(upcoming_partition) * 2 - 1 140e2dc6929SMatthias Braun for x in partitions_to_split: 141e2dc6929SMatthias Braun max_remaining_steps += (len(x) - 1) * 2 142e2dc6929SMatthias Braun 143e2dc6929SMatthias Braun picks = dict(all_a) 144e2dc6929SMatthias Braun for x in partition: 145e2dc6929SMatthias Braun picks[x] = choice_map[x][1] 146*b71edfaaSTobias Hieta announce_test( 147*b71edfaaSTobias Hieta "checking %s [<=%d remaining]" 148*b71edfaaSTobias Hieta % (format_namelist(partition), max_remaining_steps) 149*b71edfaaSTobias Hieta ) 150e2dc6929SMatthias Braun res = perform_test(picks) 151e2dc6929SMatthias Braun if res is True: 152e2dc6929SMatthias Braun known_good.update(partition) 153e2dc6929SMatthias Braun elif len(partition) > 1: 154e2dc6929SMatthias Braun partitions_to_split.insert(0, partition) 155e2dc6929SMatthias Braun 156e2dc6929SMatthias Braun # TODO: 157e2dc6929SMatthias Braun # - We could optimize based on the knowledge that when splitting a failed 158e2dc6929SMatthias Braun # partition into two and one side checks out okay then we can deduce that 159e2dc6929SMatthias Braun # the other partition must be a failure. 160e2dc6929SMatthias Braun all_choice_names = [name for name, _ in choices] 161e2dc6929SMatthias Braun partitions_to_split = [all_choice_names] 162e2dc6929SMatthias Braun while len(partitions_to_split) > 0: 163e2dc6929SMatthias Braun partition = partitions_to_split.pop() 164e2dc6929SMatthias Braun 165e2dc6929SMatthias Braun middle = len(partition) // 2 166e2dc6929SMatthias Braun left = partition[0:middle] 167e2dc6929SMatthias Braun right = partition[middle:] 168e2dc6929SMatthias Braun 169e2dc6929SMatthias Braun if len(left) > 0: 170e2dc6929SMatthias Braun test_partition(left, right) 171e2dc6929SMatthias Braun assert len(right) > 0 172e2dc6929SMatthias Braun test_partition(right, None) 173e2dc6929SMatthias Braun 174e2dc6929SMatthias Braun return known_good 175e2dc6929SMatthias Braun 176e2dc6929SMatthias Braun 1771c98701eSFrancis Visoiu Mistrihdef extract_functions(file): 1781c98701eSFrancis Visoiu Mistrih functions = [] 1791c98701eSFrancis Visoiu Mistrih in_function = None 1801c98701eSFrancis Visoiu Mistrih for line in open(file): 1811c98701eSFrancis Visoiu Mistrih marker = line.find(" -- Begin function ") 1821c98701eSFrancis Visoiu Mistrih if marker != -1: 183e2dc6929SMatthias Braun if in_function is not None: 1841c98701eSFrancis Visoiu Mistrih warn("Missing end of function %s" % (in_function,)) 1851c98701eSFrancis Visoiu Mistrih funcname = line[marker + 19 : -1] 1861c98701eSFrancis Visoiu Mistrih in_function = funcname 1871c98701eSFrancis Visoiu Mistrih text = line 1881c98701eSFrancis Visoiu Mistrih continue 1891c98701eSFrancis Visoiu Mistrih 1901c98701eSFrancis Visoiu Mistrih marker = line.find(" -- End function") 1911c98701eSFrancis Visoiu Mistrih if marker != -1: 1921c98701eSFrancis Visoiu Mistrih text += line 1931c98701eSFrancis Visoiu Mistrih functions.append((in_function, text)) 1941c98701eSFrancis Visoiu Mistrih in_function = None 1951c98701eSFrancis Visoiu Mistrih continue 1961c98701eSFrancis Visoiu Mistrih 197e2dc6929SMatthias Braun if in_function is not None: 1981c98701eSFrancis Visoiu Mistrih text += line 1991c98701eSFrancis Visoiu Mistrih return functions 2001c98701eSFrancis Visoiu Mistrih 201e2dc6929SMatthias Braun 202e2dc6929SMatthias Braundef replace_functions(source, dest, replacements): 2031c98701eSFrancis Visoiu Mistrih out = open(dest, "w") 2041c98701eSFrancis Visoiu Mistrih skip = False 2051c98701eSFrancis Visoiu Mistrih in_function = None 206e2dc6929SMatthias Braun for line in open(source): 2071c98701eSFrancis Visoiu Mistrih marker = line.find(" -- Begin function ") 2081c98701eSFrancis Visoiu Mistrih if marker != -1: 209e2dc6929SMatthias Braun if in_function is not None: 2101c98701eSFrancis Visoiu Mistrih warn("Missing end of function %s" % (in_function,)) 2111c98701eSFrancis Visoiu Mistrih funcname = line[marker + 19 : -1] 2121c98701eSFrancis Visoiu Mistrih in_function = funcname 213e2dc6929SMatthias Braun replacement = replacements.get(in_function) 214e2dc6929SMatthias Braun if replacement is not None: 2151c98701eSFrancis Visoiu Mistrih out.write(replacement) 2161c98701eSFrancis Visoiu Mistrih skip = True 2171c98701eSFrancis Visoiu Mistrih else: 2181c98701eSFrancis Visoiu Mistrih marker = line.find(" -- End function") 2191c98701eSFrancis Visoiu Mistrih if marker != -1: 2201c98701eSFrancis Visoiu Mistrih in_function = None 2211c98701eSFrancis Visoiu Mistrih if skip: 2221c98701eSFrancis Visoiu Mistrih skip = False 2231c98701eSFrancis Visoiu Mistrih continue 2241c98701eSFrancis Visoiu Mistrih 2251c98701eSFrancis Visoiu Mistrih if not skip: 2261c98701eSFrancis Visoiu Mistrih out.write(line) 2271c98701eSFrancis Visoiu Mistrih 2281c98701eSFrancis Visoiu Mistrih 2291c98701eSFrancis Visoiu Mistrihdef testrun(files): 230*b71edfaaSTobias Hieta linkline = "%s %s" % ( 231*b71edfaaSTobias Hieta LINKTEST, 232*b71edfaaSTobias Hieta " ".join(files), 233*b71edfaaSTobias Hieta ) 2341c98701eSFrancis Visoiu Mistrih res = subprocess.call(linkline, shell=True) 2351c98701eSFrancis Visoiu Mistrih if res != 0: 236e2dc6929SMatthias Braun announce_result(FAILED + ": '%s' exitcode != 0" % LINKTEST) 2371c98701eSFrancis Visoiu Mistrih return False 2381c98701eSFrancis Visoiu Mistrih else: 239e2dc6929SMatthias Braun announce_result("ok") 2401c98701eSFrancis Visoiu Mistrih return True 2411c98701eSFrancis Visoiu Mistrih 242e2dc6929SMatthias Braun 243bcb8ef2dSYuanfang Chendef prepare_files(gooddir, baddir, rspfile): 244bcb8ef2dSYuanfang Chen files_a = [] 245bcb8ef2dSYuanfang Chen files_b = [] 246bcb8ef2dSYuanfang Chen 247bcb8ef2dSYuanfang Chen if rspfile is not None: 248*b71edfaaSTobias Hieta 249bcb8ef2dSYuanfang Chen def get_basename(name): 250bcb8ef2dSYuanfang Chen # remove prefix 251bcb8ef2dSYuanfang Chen if name.startswith(gooddir): 252bcb8ef2dSYuanfang Chen return name[len(gooddir) :] 253bcb8ef2dSYuanfang Chen if name.startswith(baddir): 254bcb8ef2dSYuanfang Chen return name[len(baddir) :] 255bcb8ef2dSYuanfang Chen assert False, "" 256bcb8ef2dSYuanfang Chen 257bcb8ef2dSYuanfang Chen with open(rspfile, "r") as rf: 258bcb8ef2dSYuanfang Chen for line in rf.read().splitlines(): 259bcb8ef2dSYuanfang Chen for obj in line.split(): 260bcb8ef2dSYuanfang Chen assert not os.path.isabs(obj), "TODO: support abs path" 261bcb8ef2dSYuanfang Chen files_a.append(gooddir + "/" + obj) 262bcb8ef2dSYuanfang Chen files_b.append(baddir + "/" + obj) 263bcb8ef2dSYuanfang Chen else: 264bcb8ef2dSYuanfang Chen get_basename = lambda name: os.path.basename(name) 265e2dc6929SMatthias Braun files_a = find(gooddir, "*") 266e2dc6929SMatthias Braun files_b = find(baddir, "*") 267e2dc6929SMatthias Braun 268bcb8ef2dSYuanfang Chen basenames_a = set(map(get_basename, files_a)) 269bcb8ef2dSYuanfang Chen basenames_b = set(map(get_basename, files_b)) 270e2dc6929SMatthias Braun 271e2dc6929SMatthias Braun for name in files_b: 272bcb8ef2dSYuanfang Chen basename = get_basename(name) 273e2dc6929SMatthias Braun if basename not in basenames_a: 274*b71edfaaSTobias Hieta warn("There is no corresponding file to '%s' in %s" % (name, gooddir)) 275e2dc6929SMatthias Braun choices = [] 276e2dc6929SMatthias Braun skipped = [] 277e2dc6929SMatthias Braun for name in files_a: 278bcb8ef2dSYuanfang Chen basename = get_basename(name) 279e2dc6929SMatthias Braun if basename not in basenames_b: 280*b71edfaaSTobias Hieta warn("There is no corresponding file to '%s' in %s" % (name, baddir)) 281e2dc6929SMatthias Braun 282e2dc6929SMatthias Braun file_a = gooddir + "/" + basename 283e2dc6929SMatthias Braun file_b = baddir + "/" + basename 284e2dc6929SMatthias Braun if filecmp.cmp(file_a, file_b): 285e2dc6929SMatthias Braun skipped.append(basename) 2861c98701eSFrancis Visoiu Mistrih continue 2871c98701eSFrancis Visoiu Mistrih 288e2dc6929SMatthias Braun choice = (basename, (file_a, file_b)) 289e2dc6929SMatthias Braun choices.append(choice) 2901c98701eSFrancis Visoiu Mistrih 291e2dc6929SMatthias Braun if len(skipped) > 0: 292e2dc6929SMatthias Braun info("Skipped (same content): %s" % format_namelist(skipped)) 293e2dc6929SMatthias Braun 294e2dc6929SMatthias Braun def perform_test(picks): 295e2dc6929SMatthias Braun files = [] 296e2dc6929SMatthias Braun # Note that we iterate over files_a so we don't change the order 297e2dc6929SMatthias Braun # (cannot use `picks` as it is a dictionary without order) 298e2dc6929SMatthias Braun for x in files_a: 299bcb8ef2dSYuanfang Chen basename = get_basename(x) 300e2dc6929SMatthias Braun picked = picks.get(basename) 301e2dc6929SMatthias Braun if picked is None: 302e2dc6929SMatthias Braun assert basename in skipped 303e2dc6929SMatthias Braun files.append(x) 3041c98701eSFrancis Visoiu Mistrih else: 305e2dc6929SMatthias Braun files.append(picked) 306bcb8ef2dSYuanfang Chen 307bcb8ef2dSYuanfang Chen # If response file is used, create a temporary response file for the 308bcb8ef2dSYuanfang Chen # picked files. 309bcb8ef2dSYuanfang Chen if rspfile is not None: 310*b71edfaaSTobias Hieta with tempfile.NamedTemporaryFile("w", suffix=".rsp", delete=False) as tf: 311bcb8ef2dSYuanfang Chen tf.write(" ".join(files)) 312bcb8ef2dSYuanfang Chen tf.flush() 313bcb8ef2dSYuanfang Chen ret = testrun([tf.name]) 314bcb8ef2dSYuanfang Chen os.remove(tf.name) 315bcb8ef2dSYuanfang Chen return ret 316bcb8ef2dSYuanfang Chen 317e2dc6929SMatthias Braun return testrun(files) 3181c98701eSFrancis Visoiu Mistrih 319e2dc6929SMatthias Braun return perform_test, choices 3201c98701eSFrancis Visoiu Mistrih 3211c98701eSFrancis Visoiu Mistrih 322e2dc6929SMatthias Braundef prepare_functions(to_check, gooddir, goodfile, badfile): 323e2dc6929SMatthias Braun files_good = find(gooddir, "*") 3241c98701eSFrancis Visoiu Mistrih 325e2dc6929SMatthias Braun functions_a = extract_functions(goodfile) 326e2dc6929SMatthias Braun functions_a_map = dict(functions_a) 327e2dc6929SMatthias Braun functions_b_map = dict(extract_functions(badfile)) 328e2dc6929SMatthias Braun 329e2dc6929SMatthias Braun for name in functions_b_map.keys(): 330e2dc6929SMatthias Braun if name not in functions_a_map: 331e2dc6929SMatthias Braun warn("Function '%s' missing from good file" % name) 332e2dc6929SMatthias Braun choices = [] 333e2dc6929SMatthias Braun skipped = [] 334e2dc6929SMatthias Braun for name, candidate_a in functions_a: 335e2dc6929SMatthias Braun candidate_b = functions_b_map.get(name) 336e2dc6929SMatthias Braun if candidate_b is None: 337e2dc6929SMatthias Braun warn("Function '%s' missing from bad file" % name) 338e2dc6929SMatthias Braun continue 339e2dc6929SMatthias Braun if candidate_a == candidate_b: 340e2dc6929SMatthias Braun skipped.append(name) 341e2dc6929SMatthias Braun continue 342e2dc6929SMatthias Braun choice = name, (candidate_a, candidate_b) 343e2dc6929SMatthias Braun choices.append(choice) 344e2dc6929SMatthias Braun 345e2dc6929SMatthias Braun if len(skipped) > 0: 346e2dc6929SMatthias Braun info("Skipped (same content): %s" % format_namelist(skipped)) 347e2dc6929SMatthias Braun 348*b71edfaaSTobias Hieta combined_file = "/tmp/combined2.s" 349e2dc6929SMatthias Braun files = [] 350e2dc6929SMatthias Braun found_good_file = False 351e2dc6929SMatthias Braun for c in files_good: 352e2dc6929SMatthias Braun if os.path.basename(c) == to_check: 353e2dc6929SMatthias Braun found_good_file = True 354e2dc6929SMatthias Braun files.append(combined_file) 355e2dc6929SMatthias Braun continue 356e2dc6929SMatthias Braun files.append(c) 357e2dc6929SMatthias Braun assert found_good_file 358e2dc6929SMatthias Braun 359e2dc6929SMatthias Braun def perform_test(picks): 360e2dc6929SMatthias Braun for name, x in picks.items(): 361e2dc6929SMatthias Braun assert x == functions_a_map[name] or x == functions_b_map[name] 362e2dc6929SMatthias Braun replace_functions(goodfile, combined_file, picks) 363e2dc6929SMatthias Braun return testrun(files) 364*b71edfaaSTobias Hieta 365e2dc6929SMatthias Braun return perform_test, choices 366e2dc6929SMatthias Braun 367e2dc6929SMatthias Braun 368e2dc6929SMatthias Braundef main(): 3691c98701eSFrancis Visoiu Mistrih parser = argparse.ArgumentParser() 370*b71edfaaSTobias Hieta parser.add_argument("--a", dest="dir_a", default="before") 371*b71edfaaSTobias Hieta parser.add_argument("--b", dest="dir_b", default="after") 372*b71edfaaSTobias Hieta parser.add_argument("--rsp", default=None) 373*b71edfaaSTobias Hieta parser.add_argument("--test", default="./link_test") 374*b71edfaaSTobias Hieta parser.add_argument("--insane", help="Skip sanity check", action="store_true") 375*b71edfaaSTobias Hieta parser.add_argument( 376*b71edfaaSTobias Hieta "--seq", help="Check sequentially instead of bisection", action="store_true" 377*b71edfaaSTobias Hieta ) 378*b71edfaaSTobias Hieta parser.add_argument("file", metavar="file", nargs="?") 3791c98701eSFrancis Visoiu Mistrih config = parser.parse_args() 3801c98701eSFrancis Visoiu Mistrih 3811c98701eSFrancis Visoiu Mistrih gooddir = config.dir_a 3821c98701eSFrancis Visoiu Mistrih baddir = config.dir_b 383bcb8ef2dSYuanfang Chen rspfile = config.rsp 384bcb8ef2dSYuanfang Chen global LINKTEST 385bcb8ef2dSYuanfang Chen LINKTEST = config.test 3861c98701eSFrancis Visoiu Mistrih 387e2dc6929SMatthias Braun # Preparation phase: Creates a dictionary mapping names to a list of two 388e2dc6929SMatthias Braun # choices each. The bisection algorithm will pick one choice for each name 389e2dc6929SMatthias Braun # and then run the perform_test function on it. 390e2dc6929SMatthias Braun if config.file is not None: 391e2dc6929SMatthias Braun goodfile = gooddir + "/" + config.file 392e2dc6929SMatthias Braun badfile = baddir + "/" + config.file 393*b71edfaaSTobias Hieta perform_test, choices = prepare_functions( 394*b71edfaaSTobias Hieta config.file, gooddir, goodfile, badfile 395*b71edfaaSTobias Hieta ) 396e2dc6929SMatthias Braun else: 397bcb8ef2dSYuanfang Chen perform_test, choices = prepare_files(gooddir, baddir, rspfile) 398e2dc6929SMatthias Braun 399e2dc6929SMatthias Braun info("%d bisection choices" % len(choices)) 4001c98701eSFrancis Visoiu Mistrih 4011c98701eSFrancis Visoiu Mistrih # "Checking whether build environment is sane ..." 4021c98701eSFrancis Visoiu Mistrih if not config.insane: 4031c98701eSFrancis Visoiu Mistrih if not os.access(LINKTEST, os.X_OK): 4041c98701eSFrancis Visoiu Mistrih error("Expect '%s' to be present and executable" % (LINKTEST,)) 4051c98701eSFrancis Visoiu Mistrih exit(1) 4061c98701eSFrancis Visoiu Mistrih 407e2dc6929SMatthias Braun check_sanity(choices, perform_test) 4081c98701eSFrancis Visoiu Mistrih 409e2dc6929SMatthias Braun if config.seq: 410e2dc6929SMatthias Braun known_good = check_sequentially(choices, perform_test) 4111c98701eSFrancis Visoiu Mistrih else: 412e2dc6929SMatthias Braun known_good = check_bisect(choices, perform_test) 413e2dc6929SMatthias Braun 414e2dc6929SMatthias Braun stderr.write("") 415e2dc6929SMatthias Braun if len(known_good) != len(choices): 416e2dc6929SMatthias Braun stderr.write("== Failing ==\n") 417e2dc6929SMatthias Braun for name, _ in choices: 418e2dc6929SMatthias Braun if name not in known_good: 419e2dc6929SMatthias Braun stderr.write("%s\n" % name) 420e2dc6929SMatthias Braun else: 421e2dc6929SMatthias Braun # This shouldn't happen when the sanity check works... 422e2dc6929SMatthias Braun # Maybe link_test isn't deterministic? 423e2dc6929SMatthias Braun stderr.write("Could not identify failing parts?!?") 424e2dc6929SMatthias Braun 425e2dc6929SMatthias Braun 426*b71edfaaSTobias Hietaif __name__ == "__main__": 427e2dc6929SMatthias Braun main() 428