blob: f1a1365f6bf4582511ee7420b6cec73a6dd36d89 [file] [log] [blame]
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +00001#!/usr/bin/python
2#
3# TestRunner.py - This script is used to run arbitrary unit tests. Unit
4# tests must contain the command used to run them in the input file, starting
5# immediately after a "RUN:" string.
6#
7# This runner recognizes and replaces the following strings in the command:
8#
9# %s - Replaced with the input name of the program, or the program to
10# execute, as appropriate.
Daniel Dunbar4d8076a2009-04-10 19:49:21 +000011# %S - Replaced with the directory where the input resides.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000012# %llvmgcc - llvm-gcc command
13# %llvmgxx - llvm-g++ command
14# %prcontext - prcontext.tcl script
15# %t - temporary file name (derived from testcase name)
16#
17
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000018import errno
Daniel Dunbar69e07a72009-06-17 21:33:37 +000019import os
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000020import re
Daniel Dunbar69e07a72009-06-17 21:33:37 +000021import signal
22import subprocess
23import sys
24
25# Increase determinism for things that use the terminal width.
26#
27# FIXME: Find a better place for this hack.
28os.environ['COLUMNS'] = '0'
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000029
30class TestStatus:
31 Pass = 0
32 XFail = 1
33 Fail = 2
34 XPass = 3
35 NoRunLine = 4
36 Invalid = 5
37
38 kNames = ['Pass','XFail','Fail','XPass','NoRunLine','Invalid']
39 @staticmethod
40 def getName(code): return TestStatus.kNames[code]
41
42def mkdir_p(path):
43 if not path:
44 pass
45 elif os.path.exists(path):
46 pass
47 else:
48 parent = os.path.dirname(path)
49 if parent != path:
50 mkdir_p(parent)
51 try:
52 os.mkdir(path)
53 except OSError,e:
54 if e.errno != errno.EEXIST:
55 raise
56
57def remove(path):
58 try:
59 os.remove(path)
60 except OSError:
61 pass
62
63def cat(path, output):
64 f = open(path)
65 output.writelines(f)
66 f.close()
67
Daniel Dunbar69e07a72009-06-17 21:33:37 +000068def runOneTest(FILENAME, SUBST, OUTPUT, TESTNAME, CLANG, CLANGCC,
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000069 useValgrind=False,
70 useDGCompat=False,
71 useScript=None,
72 output=sys.stdout):
73 if useValgrind:
74 VG_OUTPUT = '%s.vg'%(OUTPUT,)
75 if os.path.exists:
76 remove(VG_OUTPUT)
77 CLANG = 'valgrind --leak-check=full --quiet --log-file=%s %s'%(VG_OUTPUT, CLANG)
78
79 # Create the output directory if it does not already exist.
80 mkdir_p(os.path.dirname(OUTPUT))
81
82 # FIXME
83 #ulimit -t 40
84
85 # FIXME: Load script once
86 # FIXME: Support "short" script syntax
87
88 if useScript:
89 scriptFile = useScript
90 else:
91 # See if we have a per-dir test script.
92 dirScriptFile = os.path.join(os.path.dirname(FILENAME), 'test.script')
93 if os.path.exists(dirScriptFile):
94 scriptFile = dirScriptFile
95 else:
96 scriptFile = FILENAME
97
98 # Verify the script contains a run line.
99 for ln in open(scriptFile):
100 if 'RUN:' in ln:
101 break
102 else:
103 print >>output, "******************** TEST '%s' HAS NO RUN LINE! ********************"%(TESTNAME,)
104 output.flush()
105 return TestStatus.NoRunLine
106
107 OUTPUT = os.path.abspath(OUTPUT)
108 FILENAME = os.path.abspath(FILENAME)
109 SCRIPT = OUTPUT + '.script'
110 TEMPOUTPUT = OUTPUT + '.tmp'
111
112 substitutions = [('%s',SUBST),
Daniel Dunbar4d8076a2009-04-10 19:49:21 +0000113 ('%S',os.path.dirname(SUBST)),
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000114 ('%llvmgcc','llvm-gcc -emit-llvm -w'),
115 ('%llvmgxx','llvm-g++ -emit-llvm -w'),
116 ('%prcontext','prcontext.tcl'),
117 ('%t',TEMPOUTPUT),
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000118 (' clang ', ' ' + CLANG + ' '),
119 (' clang-cc ', ' ' + CLANGCC + ' ')]
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000120 scriptLines = []
121 xfailLines = []
122 for ln in open(scriptFile):
123 if 'RUN:' in ln:
124 # Isolate run parameters
125 index = ln.index('RUN:')
126 ln = ln[index+4:]
127
128 # Apply substitutions
129 for a,b in substitutions:
130 ln = ln.replace(a,b)
131
132 if useDGCompat:
133 ln = re.sub(r'\{(.*)\}', r'"\1"', ln)
134 scriptLines.append(ln)
135 elif 'XFAIL' in ln:
136 xfailLines.append(ln)
137
138 if xfailLines:
139 print >>output, "XFAILED '%s':"%(TESTNAME,)
140 output.writelines(xfailLines)
141
142 # Write script file
143 f = open(SCRIPT,'w')
144 f.write(''.join(scriptLines))
145 f.close()
146
147 outputFile = open(OUTPUT,'w')
148 p = None
149 try:
150 p = subprocess.Popen(["/bin/sh",SCRIPT],
151 cwd=os.path.dirname(FILENAME),
152 stdout=subprocess.PIPE,
153 stderr=subprocess.PIPE)
154 out,err = p.communicate()
155 outputFile.write(out)
156 outputFile.write(err)
157 SCRIPT_STATUS = p.wait()
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000158
159 # Detect Ctrl-C in subprocess.
160 if SCRIPT_STATUS == -signal.SIGINT:
161 raise KeyboardInterrupt
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000162 except KeyboardInterrupt:
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000163 raise
164 outputFile.close()
165
166 if xfailLines:
167 SCRIPT_STATUS = not SCRIPT_STATUS
168
169 if useValgrind:
170 VG_STATUS = len(list(open(VG_OUTPUT)))
171 else:
172 VG_STATUS = 0
173
174 if SCRIPT_STATUS or VG_STATUS:
175 print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,)
176 print >>output, "Command: "
177 output.writelines(scriptLines)
178 if not SCRIPT_STATUS:
179 print >>output, "Output:"
180 else:
181 print >>output, "Incorrect Output:"
182 cat(OUTPUT, output)
183 if VG_STATUS:
184 print >>output, "Valgrind Output:"
185 cat(VG_OUTPUT, output)
186 print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,)
187 output.flush()
188 if xfailLines:
189 return TestStatus.XPass
190 else:
191 return TestStatus.Fail
192
193 if xfailLines:
194 return TestStatus.XFail
195 else:
196 return TestStatus.Pass
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000197
198def capture(args):
199 p = subprocess.Popen(args, stdout=subprocess.PIPE)
200 out,_ = p.communicate()
201 return out
202
203def which(command):
204 # Would be nice if Python had a lib function for this.
205 res = capture(['which',command])
206 res = res.strip()
207 if res and os.path.exists(res):
208 return res
209 return None
210
211def inferClang():
212 # Determine which clang to use.
213 clang = os.getenv('CLANG')
214
215 # If the user set clang in the environment, definitely use that and don't
216 # try to validate.
217 if clang:
218 return clang
219
220 # Otherwise look in the path.
221 clang = which('clang')
222
223 if not clang:
224 print >>sys.stderr, "error: couldn't find 'clang' program, try setting CLANG in your environment"
225 sys.exit(1)
226
227 return clang
228
229def inferClangCC(clang):
230 clangcc = os.getenv('CLANGCC')
231
232 # If the user set clang in the environment, definitely use that and don't
233 # try to validate.
234 if clangcc:
235 return clangcc
236
237 # Otherwise try adding -cc since we expect to be looking in a build
238 # directory.
239 clangcc = which(clang + '-cc')
240 if not clangcc:
241 # Otherwise ask clang.
242 res = capture([clang, '-print-prog-name=clang-cc'])
243 res = res.strip()
244 if res and os.path.exists(res):
245 clangcc = res
246
247 if not clangcc:
248 print >>sys.stderr, "error: couldn't find 'clang-cc' program, try setting CLANGCC in your environment"
249 sys.exit(1)
250
251 return clangcc
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000252
253def main():
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000254 global options
255 from optparse import OptionParser
256 parser = OptionParser("usage: %prog [options] {tests}")
257 parser.add_option("", "--clang", dest="clang",
258 help="Program to use as \"clang\"",
259 action="store", default=None)
260 parser.add_option("", "--clang-cc", dest="clangcc",
261 help="Program to use as \"clang-cc\"",
262 action="store", default=None)
263 parser.add_option("", "--vg", dest="useValgrind",
264 help="Run tests under valgrind",
265 action="store_true", default=False)
266 parser.add_option("", "--dg", dest="useDGCompat",
267 help="Use llvm dejagnu compatibility mode",
268 action="store_true", default=False)
269 (opts, args) = parser.parse_args()
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000270
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000271 if not args:
272 parser.error('No tests specified')
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000273
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000274 if opts.clang is None:
275 opts.clang = inferClang()
276 if opts.clangcc is None:
277 opts.clangcc = inferClangCC(opts.clang)
278
279 for path in args:
280 command = path
281 # Use hand concatentation here because we want to override
282 # absolute paths.
283 output = 'Output/' + path + '.out'
284 testname = path
285
286 res = runOneTest(path, command, output, testname,
287 opts.clang, opts.clangcc,
288 useValgrind=opts.useValgrind,
289 useDGCompat=opts.useDGCompat,
290 useScript=os.getenv("TEST_SCRIPT"))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000291
292 sys.exit(res == TestStatus.Fail or res == TestStatus.XPass)
293
294if __name__=='__main__':
295 main()