blob: 7e72dcacd9c2fcf55cef49fc5a1d68d689e01c0b [file] [log] [blame]
Matthias Braun7111bb52016-03-26 00:23:59 +00001#!/usr/bin/env python
2#
3# Given a previous good compile narrow down miscompiles.
4# Expectes two directories named "before" and "after" each containing a set of
5# assembly files where the "after" version is assumed to be broken.
6# Also assumes the presence of a executable or script "link_test" which when
7# called with a set of assembly files will link them together and test if the
8# resulting executable is "good".
9#
10# Example usage:
11# 1. Create a link_test script, make it executable. Simple Example:
12# clang "$@" -o /tmp/test && /tmp/test || echo "PROBLEM"
13# 2. Run the script to figure out which files are miscompiled:
14# > ./abtest.py
15# somefile.s: ok
16# someotherfile.s: skipped: same content
17# anotherfile.s: failed: './link_test' exitcode != 0
18# ...
19# 3. If you want to replace+test with the functions inside a single file
20# you first have to mark function begins and ends in the .s files
21# this directory comes with some: mark_XXX.py example scripts,
22# unfortunately you usually have to adapt them to each environment.
23# > for i in before/*.s after/*.s; do mark_xxx.py $i; done
24# 4. Run the tests on a single file
25# > ./abtest.py
26# funcname1 [0/XX]: ok
27# funcname2 [1/XX]: ok
28# funcname3 [2/XX]: skipped: same content
29# funcname4 [3/XX]: failed: './link_test' exitcode != 0
30# ...
31import sys
32import os
33from os import system, mkdir, walk, makedirs, errno, getenv
34from os.path import dirname, isdir
35from shutil import rmtree, copyfile
36from fnmatch import filter
37from itertools import chain
38from subprocess import call
39from sys import stderr
40import argparse
41import filecmp
42
43LINKTEST="./link_test"
44ESCAPE="\033[%sm"
45BOLD=ESCAPE % "1"
46RED=ESCAPE % "31"
47NORMAL=ESCAPE % "0"
48FAILED=RED+"failed"+NORMAL
49
50def mkdirtree(path):
51 try:
52 makedirs(path)
53 except OSError as exc:
54 if exc.errno == errno.EEXIST and isdir(path):
55 pass
56 else:
57 raise
58
59def find(dir, file_filter=None):
60 files = [walkdir[0]+"/"+file for walkdir in walk(dir) for file in walkdir[2]]
61 if file_filter != None:
62 files = filter(files, file_filter)
63 return files
64
65def error(message):
66 stderr.write("Error: %s\n" % (message,))
67
68def warn(message):
69 stderr.write("Warning: %s\n" % (message,))
70
71def notice(message):
72 stderr.write("%s\n" % message)
73
74def extract_functions(file):
75 functions = []
76 in_function = None
77 for line in open(file):
78 if line.startswith("# -- Begin "):
79 if in_function != None:
80 warn("Missing end of function %s" % (in_function,))
81 funcname = line[12:-1]
82 in_function = funcname
83 text = line
84 elif line.startswith("# -- End "):
85 function_name = line[10:-1]
86 if in_function != function_name:
87 warn("End %s does not match begin %s" % (function_name, in_function))
88 else:
89 text += line
90 functions.append( (in_function, text) )
91 in_function = None
92 elif in_function != None:
93 text += line
94 return functions
95
96def replace_function(file, function, replacement, dest):
97 out = open(dest, "w")
98 skip = False
99 found = False
100 in_function = None
101 for line in open(file):
102 if line.startswith("# -- Begin "):
103 if in_function != None:
104 warn("Missing end of function %s" % (in_function,))
105 funcname = line[12:-1]
106 in_function = funcname
107 if in_function == function:
108 out.write(replacement)
109 skip = True
110 elif line.startswith("# -- End "):
111 function_name = line[10:-1]
112 if in_function != function_name:
113 warn("End %s does not match begin %s" % (function_name, in_function))
114 in_function = None
115 if skip:
116 skip = False
117 continue
118 if not skip:
119 out.write(line)
120
121def announce_test(name):
122 stderr.write("%s%s%s: " % (BOLD, name, NORMAL))
123 stderr.flush()
124
125def announce_result(result, info):
126 stderr.write(result)
127 if info != "":
128 stderr.write(": %s" % info)
129 stderr.write("\n")
130 stderr.flush()
131
132def testrun(files):
133 linkline="%s %s" % (LINKTEST, " ".join(files),)
134 res = call(linkline, shell=True)
135 if res != 0:
136 announce_result(FAILED, "'%s' exitcode != 0" % LINKTEST)
137 else:
138 announce_result("ok", "")
139
140def check_files():
141 """Check files mode"""
142 for f in NO_PREFIX:
143 b=baddir+"/"+f
144 if b not in BAD_FILES:
145 warn("There is no corresponding file to '%s' in %s" \
146 % (gooddir+"/"+f, baddir))
147 continue
148
149 announce_test(f)
150
151 # combine files (everything from good except f)
152 testfiles=[]
153 skip=False
154 for c in NO_PREFIX:
155 badfile = baddir+"/"+c
156 goodfile = gooddir+"/"+c
157 if c == f:
158 testfiles.append(badfile)
159 if filecmp.cmp(goodfile, badfile):
160 announce_result("skipped", "same content")
161 skip = True
162 break
163 else:
164 testfiles.append(goodfile)
165 if skip:
166 continue
167 testrun(testfiles)
168
169def check_functions_in_file(base, goodfile, badfile):
170 functions = extract_functions(goodfile)
171 if len(functions) == 0:
172 warn("Couldn't find any function in %s, missing annotations?" % (goodfile,))
173 return
174 badfunctions = dict(extract_functions(badfile))
175 if len(functions) == 0:
176 warn("Couldn't find any function in %s, missing annotations?" % (badfile,))
177 return
178
179 COMBINED="/tmp/combined.s"
180 i = 0
181 for (func,func_text) in functions:
182 announce_test(func + " [%s/%s]" % (i, len(functions)))
183 i+=1
184 if func not in badfunctions:
185 warn("Function '%s' missing from bad file" % func)
186 continue
187 if badfunctions[func] == func_text:
188 announce_result("skipped", "same content")
189 continue
190 replace_function(goodfile, func, badfunctions[func], COMBINED)
191 testfiles=[]
192 for c in NO_PREFIX:
193 if c == base:
194 testfiles.append(COMBINED)
195 continue
196 testfiles.append(gooddir + "/" + c)
197
198 testrun(testfiles)
199
200parser = argparse.ArgumentParser()
201parser.add_argument('--a', dest='dir_a', default='before')
202parser.add_argument('--b', dest='dir_b', default='after')
203parser.add_argument('file', metavar='file', nargs='?')
204config = parser.parse_args()
205
206# Check if environment is sane
207if not os.access(LINKTEST, os.X_OK):
208 error("Expect '%s' to be present and executable" % (LINKTEST,))
209 exit(1)
210
211gooddir=config.dir_a
212baddir=config.dir_b
213
214BAD_FILES=find(baddir, "*.s")
215GOOD_FILES=find(gooddir, "*.s")
216NO_PREFIX=sorted([x[len(gooddir)+1:] for x in GOOD_FILES])
217
218if config.file is not None:
219 goodfile = gooddir+"/"+config.file
220 badfile = baddir+"/"+config.file
221 check_functions_in_file(config.file, goodfile, badfile)
222else:
223 check_files()