blob: 1a0148c3c473babdecae9e19d0f3192878dcad18 [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"""
Matthias Braun90b6f262016-03-26 00:36:58 +0000142 for i in range(0, len(NO_PREFIX)):
143 f = NO_PREFIX[i]
Matthias Braun7111bb52016-03-26 00:23:59 +0000144 b=baddir+"/"+f
145 if b not in BAD_FILES:
146 warn("There is no corresponding file to '%s' in %s" \
147 % (gooddir+"/"+f, baddir))
148 continue
149
Matthias Braun90b6f262016-03-26 00:36:58 +0000150 announce_test(f + " [%s/%s]" % (i+1, len(NO_PREFIX)))
Matthias Braun7111bb52016-03-26 00:23:59 +0000151
152 # combine files (everything from good except f)
153 testfiles=[]
154 skip=False
155 for c in NO_PREFIX:
156 badfile = baddir+"/"+c
157 goodfile = gooddir+"/"+c
158 if c == f:
159 testfiles.append(badfile)
160 if filecmp.cmp(goodfile, badfile):
161 announce_result("skipped", "same content")
162 skip = True
163 break
164 else:
165 testfiles.append(goodfile)
166 if skip:
167 continue
168 testrun(testfiles)
169
170def check_functions_in_file(base, goodfile, badfile):
171 functions = extract_functions(goodfile)
172 if len(functions) == 0:
173 warn("Couldn't find any function in %s, missing annotations?" % (goodfile,))
174 return
175 badfunctions = dict(extract_functions(badfile))
176 if len(functions) == 0:
177 warn("Couldn't find any function in %s, missing annotations?" % (badfile,))
178 return
179
180 COMBINED="/tmp/combined.s"
181 i = 0
182 for (func,func_text) in functions:
Matthias Braun90b6f262016-03-26 00:36:58 +0000183 announce_test(func + " [%s/%s]" % (i+1, len(functions)))
Matthias Braun7111bb52016-03-26 00:23:59 +0000184 i+=1
185 if func not in badfunctions:
186 warn("Function '%s' missing from bad file" % func)
187 continue
188 if badfunctions[func] == func_text:
189 announce_result("skipped", "same content")
190 continue
191 replace_function(goodfile, func, badfunctions[func], COMBINED)
192 testfiles=[]
193 for c in NO_PREFIX:
194 if c == base:
195 testfiles.append(COMBINED)
196 continue
197 testfiles.append(gooddir + "/" + c)
198
199 testrun(testfiles)
200
201parser = argparse.ArgumentParser()
202parser.add_argument('--a', dest='dir_a', default='before')
203parser.add_argument('--b', dest='dir_b', default='after')
204parser.add_argument('file', metavar='file', nargs='?')
205config = parser.parse_args()
206
207# Check if environment is sane
208if not os.access(LINKTEST, os.X_OK):
209 error("Expect '%s' to be present and executable" % (LINKTEST,))
210 exit(1)
211
212gooddir=config.dir_a
213baddir=config.dir_b
214
Matthias Braun90b6f262016-03-26 00:36:58 +0000215BAD_FILES=find(baddir, "*")
216GOOD_FILES=find(gooddir, "*")
Matthias Braun7111bb52016-03-26 00:23:59 +0000217NO_PREFIX=sorted([x[len(gooddir)+1:] for x in GOOD_FILES])
218
219if config.file is not None:
220 goodfile = gooddir+"/"+config.file
221 badfile = baddir+"/"+config.file
222 check_functions_in_file(config.file, goodfile, badfile)
223else:
224 check_files()