xref: /llvm-project/llvm/utils/abtest.py (revision b71edfaa4ec3c998aadb35255ce2f60bba2940b0)
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