xref: /aosp_15_r20/external/llvm/utils/abtest/abtest.py (revision 9880d6810fe72a1726cb53787c6711e909410d58)
1*9880d681SAndroid Build Coastguard Worker#!/usr/bin/env python
2*9880d681SAndroid Build Coastguard Worker#
3*9880d681SAndroid Build Coastguard Worker# Given a previous good compile narrow down miscompiles.
4*9880d681SAndroid Build Coastguard Worker# Expects two directories named "before" and "after" each containing a set of
5*9880d681SAndroid Build Coastguard Worker# assembly or object files where the "after" version is assumed to be broken.
6*9880d681SAndroid Build Coastguard Worker# You also have to provide a script called "link_test". It is called with a list
7*9880d681SAndroid Build Coastguard Worker# of files which should be linked together and result tested. "link_test" should
8*9880d681SAndroid Build Coastguard Worker# returns with exitcode 0 if the linking and testing succeeded.
9*9880d681SAndroid Build Coastguard Worker#
10*9880d681SAndroid Build Coastguard Worker# abtest.py operates by taking all files from the "before" directory and
11*9880d681SAndroid Build Coastguard Worker# in each step replacing one of them with a file from the "bad" directory.
12*9880d681SAndroid Build Coastguard Worker#
13*9880d681SAndroid Build Coastguard Worker# Additionally you can perform the same steps with a single .s file. In this
14*9880d681SAndroid Build Coastguard Worker# mode functions are identified by "# -- Begin FunctionName" and
15*9880d681SAndroid Build Coastguard Worker# "# -- End FunctionName" markers. The abtest.py then takes all functions from
16*9880d681SAndroid Build Coastguard Worker# the file in the "before" directory and replaces one function with the
17*9880d681SAndroid Build Coastguard Worker# corresponding function from the "bad" file in each step.
18*9880d681SAndroid Build Coastguard Worker#
19*9880d681SAndroid Build Coastguard Worker# Example usage to identify miscompiled files:
20*9880d681SAndroid Build Coastguard Worker#    1. Create a link_test script, make it executable. Simple Example:
21*9880d681SAndroid Build Coastguard Worker#          clang "$@" -o /tmp/test && /tmp/test || echo "PROBLEM"
22*9880d681SAndroid Build Coastguard Worker#    2. Run the script to figure out which files are miscompiled:
23*9880d681SAndroid Build Coastguard Worker#       > ./abtest.py
24*9880d681SAndroid Build Coastguard Worker#       somefile.s: ok
25*9880d681SAndroid Build Coastguard Worker#       someotherfile.s: skipped: same content
26*9880d681SAndroid Build Coastguard Worker#       anotherfile.s: failed: './link_test' exitcode != 0
27*9880d681SAndroid Build Coastguard Worker#       ...
28*9880d681SAndroid Build Coastguard Worker# Example usage to identify miscompiled functions inside a file:
29*9880d681SAndroid Build Coastguard Worker#    3. First you have to mark begin and end of the functions.
30*9880d681SAndroid Build Coastguard Worker#       The script comes with some examples called mark_xxx.py.
31*9880d681SAndroid Build Coastguard Worker#       Unfortunately this is very specific to your environment and it is likely
32*9880d681SAndroid Build Coastguard Worker#       that you have to write a custom version for your environment.
33*9880d681SAndroid Build Coastguard Worker#       > for i in before/*.s after/*.s; do mark_xxx.py $i; done
34*9880d681SAndroid Build Coastguard Worker#    4. Run the tests on a single file (assuming before/file.s and
35*9880d681SAndroid Build Coastguard Worker#       after/file.s exist)
36*9880d681SAndroid Build Coastguard Worker#       > ./abtest.py file.s
37*9880d681SAndroid Build Coastguard Worker#       funcname1 [0/XX]: ok
38*9880d681SAndroid Build Coastguard Worker#       funcname2 [1/XX]: ok
39*9880d681SAndroid Build Coastguard Worker#       funcname3 [2/XX]: skipped: same content
40*9880d681SAndroid Build Coastguard Worker#       funcname4 [3/XX]: failed: './link_test' exitcode != 0
41*9880d681SAndroid Build Coastguard Worker#       ...
42*9880d681SAndroid Build Coastguard Workerfrom fnmatch import filter
43*9880d681SAndroid Build Coastguard Workerfrom sys import stderr
44*9880d681SAndroid Build Coastguard Workerimport argparse
45*9880d681SAndroid Build Coastguard Workerimport filecmp
46*9880d681SAndroid Build Coastguard Workerimport os
47*9880d681SAndroid Build Coastguard Workerimport subprocess
48*9880d681SAndroid Build Coastguard Workerimport sys
49*9880d681SAndroid Build Coastguard Worker
50*9880d681SAndroid Build Coastguard WorkerLINKTEST="./link_test"
51*9880d681SAndroid Build Coastguard WorkerESCAPE="\033[%sm"
52*9880d681SAndroid Build Coastguard WorkerBOLD=ESCAPE % "1"
53*9880d681SAndroid Build Coastguard WorkerRED=ESCAPE % "31"
54*9880d681SAndroid Build Coastguard WorkerNORMAL=ESCAPE % "0"
55*9880d681SAndroid Build Coastguard WorkerFAILED=RED+"failed"+NORMAL
56*9880d681SAndroid Build Coastguard Worker
57*9880d681SAndroid Build Coastguard Workerdef find(dir, file_filter=None):
58*9880d681SAndroid Build Coastguard Worker    files = [walkdir[0]+"/"+file for walkdir in os.walk(dir) for file in walkdir[2]]
59*9880d681SAndroid Build Coastguard Worker    if file_filter != None:
60*9880d681SAndroid Build Coastguard Worker        files = filter(files, file_filter)
61*9880d681SAndroid Build Coastguard Worker    return files
62*9880d681SAndroid Build Coastguard Worker
63*9880d681SAndroid Build Coastguard Workerdef error(message):
64*9880d681SAndroid Build Coastguard Worker    stderr.write("Error: %s\n" % (message,))
65*9880d681SAndroid Build Coastguard Worker
66*9880d681SAndroid Build Coastguard Workerdef warn(message):
67*9880d681SAndroid Build Coastguard Worker    stderr.write("Warning: %s\n" % (message,))
68*9880d681SAndroid Build Coastguard Worker
69*9880d681SAndroid Build Coastguard Workerdef extract_functions(file):
70*9880d681SAndroid Build Coastguard Worker    functions = []
71*9880d681SAndroid Build Coastguard Worker    in_function = None
72*9880d681SAndroid Build Coastguard Worker    for line in open(file):
73*9880d681SAndroid Build Coastguard Worker        if line.startswith("# -- Begin  "):
74*9880d681SAndroid Build Coastguard Worker            if in_function != None:
75*9880d681SAndroid Build Coastguard Worker                warn("Missing end of function %s" % (in_function,))
76*9880d681SAndroid Build Coastguard Worker            funcname = line[12:-1]
77*9880d681SAndroid Build Coastguard Worker            in_function = funcname
78*9880d681SAndroid Build Coastguard Worker            text = line
79*9880d681SAndroid Build Coastguard Worker        elif line.startswith("# -- End  "):
80*9880d681SAndroid Build Coastguard Worker            function_name = line[10:-1]
81*9880d681SAndroid Build Coastguard Worker            if in_function != function_name:
82*9880d681SAndroid Build Coastguard Worker                warn("End %s does not match begin %s" % (function_name, in_function))
83*9880d681SAndroid Build Coastguard Worker            else:
84*9880d681SAndroid Build Coastguard Worker                text += line
85*9880d681SAndroid Build Coastguard Worker                functions.append( (in_function, text) )
86*9880d681SAndroid Build Coastguard Worker            in_function = None
87*9880d681SAndroid Build Coastguard Worker        elif in_function != None:
88*9880d681SAndroid Build Coastguard Worker            text += line
89*9880d681SAndroid Build Coastguard Worker    return functions
90*9880d681SAndroid Build Coastguard Worker
91*9880d681SAndroid Build Coastguard Workerdef replace_function(file, function, replacement, dest):
92*9880d681SAndroid Build Coastguard Worker    out = open(dest, "w")
93*9880d681SAndroid Build Coastguard Worker    skip = False
94*9880d681SAndroid Build Coastguard Worker    found = False
95*9880d681SAndroid Build Coastguard Worker    in_function = None
96*9880d681SAndroid Build Coastguard Worker    for line in open(file):
97*9880d681SAndroid Build Coastguard Worker        if line.startswith("# -- Begin  "):
98*9880d681SAndroid Build Coastguard Worker            if in_function != None:
99*9880d681SAndroid Build Coastguard Worker                warn("Missing end of function %s" % (in_function,))
100*9880d681SAndroid Build Coastguard Worker            funcname = line[12:-1]
101*9880d681SAndroid Build Coastguard Worker            in_function = funcname
102*9880d681SAndroid Build Coastguard Worker            if in_function == function:
103*9880d681SAndroid Build Coastguard Worker                out.write(replacement)
104*9880d681SAndroid Build Coastguard Worker                skip = True
105*9880d681SAndroid Build Coastguard Worker        elif line.startswith("# -- End  "):
106*9880d681SAndroid Build Coastguard Worker            function_name = line[10:-1]
107*9880d681SAndroid Build Coastguard Worker            if in_function != function_name:
108*9880d681SAndroid Build Coastguard Worker                warn("End %s does not match begin %s" % (function_name, in_function))
109*9880d681SAndroid Build Coastguard Worker            in_function = None
110*9880d681SAndroid Build Coastguard Worker            if skip:
111*9880d681SAndroid Build Coastguard Worker                skip = False
112*9880d681SAndroid Build Coastguard Worker                continue
113*9880d681SAndroid Build Coastguard Worker        if not skip:
114*9880d681SAndroid Build Coastguard Worker            out.write(line)
115*9880d681SAndroid Build Coastguard Worker
116*9880d681SAndroid Build Coastguard Workerdef announce_test(name):
117*9880d681SAndroid Build Coastguard Worker    stderr.write("%s%s%s: " % (BOLD, name, NORMAL))
118*9880d681SAndroid Build Coastguard Worker    stderr.flush()
119*9880d681SAndroid Build Coastguard Worker
120*9880d681SAndroid Build Coastguard Workerdef announce_result(result, info):
121*9880d681SAndroid Build Coastguard Worker    stderr.write(result)
122*9880d681SAndroid Build Coastguard Worker    if info != "":
123*9880d681SAndroid Build Coastguard Worker        stderr.write(": %s" % info)
124*9880d681SAndroid Build Coastguard Worker    stderr.write("\n")
125*9880d681SAndroid Build Coastguard Worker    stderr.flush()
126*9880d681SAndroid Build Coastguard Worker
127*9880d681SAndroid Build Coastguard Workerdef testrun(files):
128*9880d681SAndroid Build Coastguard Worker    linkline="%s %s" % (LINKTEST, " ".join(files),)
129*9880d681SAndroid Build Coastguard Worker    res = subprocess.call(linkline, shell=True)
130*9880d681SAndroid Build Coastguard Worker    if res != 0:
131*9880d681SAndroid Build Coastguard Worker        announce_result(FAILED, "'%s' exitcode != 0" % LINKTEST)
132*9880d681SAndroid Build Coastguard Worker        return False
133*9880d681SAndroid Build Coastguard Worker    else:
134*9880d681SAndroid Build Coastguard Worker        announce_result("ok", "")
135*9880d681SAndroid Build Coastguard Worker        return True
136*9880d681SAndroid Build Coastguard Worker
137*9880d681SAndroid Build Coastguard Workerdef check_files():
138*9880d681SAndroid Build Coastguard Worker    """Check files mode"""
139*9880d681SAndroid Build Coastguard Worker    for i in range(0, len(NO_PREFIX)):
140*9880d681SAndroid Build Coastguard Worker        f = NO_PREFIX[i]
141*9880d681SAndroid Build Coastguard Worker        b=baddir+"/"+f
142*9880d681SAndroid Build Coastguard Worker        if b not in BAD_FILES:
143*9880d681SAndroid Build Coastguard Worker            warn("There is no corresponding file to '%s' in %s" \
144*9880d681SAndroid Build Coastguard Worker                 % (gooddir+"/"+f, baddir))
145*9880d681SAndroid Build Coastguard Worker            continue
146*9880d681SAndroid Build Coastguard Worker
147*9880d681SAndroid Build Coastguard Worker        announce_test(f + " [%s/%s]" % (i+1, len(NO_PREFIX)))
148*9880d681SAndroid Build Coastguard Worker
149*9880d681SAndroid Build Coastguard Worker        # combine files (everything from good except f)
150*9880d681SAndroid Build Coastguard Worker        testfiles=[]
151*9880d681SAndroid Build Coastguard Worker        skip=False
152*9880d681SAndroid Build Coastguard Worker        for c in NO_PREFIX:
153*9880d681SAndroid Build Coastguard Worker            badfile = baddir+"/"+c
154*9880d681SAndroid Build Coastguard Worker            goodfile = gooddir+"/"+c
155*9880d681SAndroid Build Coastguard Worker            if c == f:
156*9880d681SAndroid Build Coastguard Worker                testfiles.append(badfile)
157*9880d681SAndroid Build Coastguard Worker                if filecmp.cmp(goodfile, badfile):
158*9880d681SAndroid Build Coastguard Worker                    announce_result("skipped", "same content")
159*9880d681SAndroid Build Coastguard Worker                    skip = True
160*9880d681SAndroid Build Coastguard Worker                    break
161*9880d681SAndroid Build Coastguard Worker            else:
162*9880d681SAndroid Build Coastguard Worker                testfiles.append(goodfile)
163*9880d681SAndroid Build Coastguard Worker        if skip:
164*9880d681SAndroid Build Coastguard Worker            continue
165*9880d681SAndroid Build Coastguard Worker        testrun(testfiles)
166*9880d681SAndroid Build Coastguard Worker
167*9880d681SAndroid Build Coastguard Workerdef check_functions_in_file(base, goodfile, badfile):
168*9880d681SAndroid Build Coastguard Worker    functions = extract_functions(goodfile)
169*9880d681SAndroid Build Coastguard Worker    if len(functions) == 0:
170*9880d681SAndroid Build Coastguard Worker        warn("Couldn't find any function in %s, missing annotations?" % (goodfile,))
171*9880d681SAndroid Build Coastguard Worker        return
172*9880d681SAndroid Build Coastguard Worker    badfunctions = dict(extract_functions(badfile))
173*9880d681SAndroid Build Coastguard Worker    if len(functions) == 0:
174*9880d681SAndroid Build Coastguard Worker        warn("Couldn't find any function in %s, missing annotations?" % (badfile,))
175*9880d681SAndroid Build Coastguard Worker        return
176*9880d681SAndroid Build Coastguard Worker
177*9880d681SAndroid Build Coastguard Worker    COMBINED="/tmp/combined.s"
178*9880d681SAndroid Build Coastguard Worker    i = 0
179*9880d681SAndroid Build Coastguard Worker    for (func,func_text) in functions:
180*9880d681SAndroid Build Coastguard Worker        announce_test(func + " [%s/%s]" % (i+1, len(functions)))
181*9880d681SAndroid Build Coastguard Worker        i+=1
182*9880d681SAndroid Build Coastguard Worker        if func not in badfunctions:
183*9880d681SAndroid Build Coastguard Worker            warn("Function '%s' missing from bad file" % func)
184*9880d681SAndroid Build Coastguard Worker            continue
185*9880d681SAndroid Build Coastguard Worker        if badfunctions[func] == func_text:
186*9880d681SAndroid Build Coastguard Worker            announce_result("skipped", "same content")
187*9880d681SAndroid Build Coastguard Worker            continue
188*9880d681SAndroid Build Coastguard Worker        replace_function(goodfile, func, badfunctions[func], COMBINED)
189*9880d681SAndroid Build Coastguard Worker        testfiles=[]
190*9880d681SAndroid Build Coastguard Worker        for c in NO_PREFIX:
191*9880d681SAndroid Build Coastguard Worker            if c == base:
192*9880d681SAndroid Build Coastguard Worker                testfiles.append(COMBINED)
193*9880d681SAndroid Build Coastguard Worker                continue
194*9880d681SAndroid Build Coastguard Worker            testfiles.append(gooddir + "/" + c)
195*9880d681SAndroid Build Coastguard Worker
196*9880d681SAndroid Build Coastguard Worker        testrun(testfiles)
197*9880d681SAndroid Build Coastguard Worker
198*9880d681SAndroid Build Coastguard Workerparser = argparse.ArgumentParser()
199*9880d681SAndroid Build Coastguard Workerparser.add_argument('--a', dest='dir_a', default='before')
200*9880d681SAndroid Build Coastguard Workerparser.add_argument('--b', dest='dir_b', default='after')
201*9880d681SAndroid Build Coastguard Workerparser.add_argument('--insane', help='Skip sanity check', action='store_true')
202*9880d681SAndroid Build Coastguard Workerparser.add_argument('file', metavar='file', nargs='?')
203*9880d681SAndroid Build Coastguard Workerconfig = parser.parse_args()
204*9880d681SAndroid Build Coastguard Worker
205*9880d681SAndroid Build Coastguard Workergooddir=config.dir_a
206*9880d681SAndroid Build Coastguard Workerbaddir=config.dir_b
207*9880d681SAndroid Build Coastguard Worker
208*9880d681SAndroid Build Coastguard WorkerBAD_FILES=find(baddir, "*")
209*9880d681SAndroid Build Coastguard WorkerGOOD_FILES=find(gooddir, "*")
210*9880d681SAndroid Build Coastguard WorkerNO_PREFIX=sorted([x[len(gooddir)+1:] for x in GOOD_FILES])
211*9880d681SAndroid Build Coastguard Worker
212*9880d681SAndroid Build Coastguard Worker# "Checking whether build environment is sane ..."
213*9880d681SAndroid Build Coastguard Workerif not config.insane:
214*9880d681SAndroid Build Coastguard Worker    announce_test("sanity check")
215*9880d681SAndroid Build Coastguard Worker    if not os.access(LINKTEST, os.X_OK):
216*9880d681SAndroid Build Coastguard Worker        error("Expect '%s' to be present and executable" % (LINKTEST,))
217*9880d681SAndroid Build Coastguard Worker        exit(1)
218*9880d681SAndroid Build Coastguard Worker
219*9880d681SAndroid Build Coastguard Worker    res = testrun(GOOD_FILES)
220*9880d681SAndroid Build Coastguard Worker    if not res:
221*9880d681SAndroid Build Coastguard Worker        # "build environment is grinning and holding a spatula. Guess not."
222*9880d681SAndroid Build Coastguard Worker        linkline="%s %s" % (LINKTEST, " ".join(GOOD_FILES),)
223*9880d681SAndroid Build Coastguard Worker        stderr.write("\n%s\n\n" % linkline)
224*9880d681SAndroid Build Coastguard Worker        stderr.write("Returned with exitcode != 0\n")
225*9880d681SAndroid Build Coastguard Worker        sys.exit(1)
226*9880d681SAndroid Build Coastguard Worker
227*9880d681SAndroid Build Coastguard Workerif config.file is not None:
228*9880d681SAndroid Build Coastguard Worker    # File exchange mode
229*9880d681SAndroid Build Coastguard Worker    goodfile = gooddir+"/"+config.file
230*9880d681SAndroid Build Coastguard Worker    badfile = baddir+"/"+config.file
231*9880d681SAndroid Build Coastguard Worker    check_functions_in_file(config.file, goodfile, badfile)
232*9880d681SAndroid Build Coastguard Workerelse:
233*9880d681SAndroid Build Coastguard Worker    # Function exchange mode
234*9880d681SAndroid Build Coastguard Worker    check_files()
235