blob: d82ddac2d85dcb28a0b604475974188c81ef196d [file] [log] [blame]
Eli Friedman77a1fe92009-07-10 20:15:12 +00001#!/usr/bin/env python
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +00002#
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 Dunbar8fe83f12009-07-25 09:42:24 +000019import hashlib
Daniel Dunbar69e07a72009-06-17 21:33:37 +000020import os
Daniel Dunbar8fe83f12009-07-25 09:42:24 +000021import platform
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000022import re
Daniel Dunbar69e07a72009-06-17 21:33:37 +000023import signal
24import subprocess
25import sys
26
27# Increase determinism for things that use the terminal width.
28#
29# FIXME: Find a better place for this hack.
30os.environ['COLUMNS'] = '0'
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000031
Daniel Dunbar8fe83f12009-07-25 09:42:24 +000032kSystemName = platform.system()
33
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000034class TestStatus:
35 Pass = 0
36 XFail = 1
37 Fail = 2
38 XPass = 3
39 NoRunLine = 4
40 Invalid = 5
41
42 kNames = ['Pass','XFail','Fail','XPass','NoRunLine','Invalid']
Daniel Dunbarfbbb1e72009-07-02 23:58:07 +000043 @staticmethod
Daniel Dunbar8afede22009-07-02 23:56:37 +000044 def getName(code):
45 return TestStatus.kNames[code]
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000046
47def mkdir_p(path):
48 if not path:
49 pass
50 elif os.path.exists(path):
51 pass
52 else:
53 parent = os.path.dirname(path)
54 if parent != path:
55 mkdir_p(parent)
56 try:
57 os.mkdir(path)
58 except OSError,e:
59 if e.errno != errno.EEXIST:
60 raise
61
62def remove(path):
63 try:
64 os.remove(path)
65 except OSError:
66 pass
67
68def cat(path, output):
69 f = open(path)
70 output.writelines(f)
71 f.close()
72
Daniel Dunbar69e07a72009-06-17 21:33:37 +000073def runOneTest(FILENAME, SUBST, OUTPUT, TESTNAME, CLANG, CLANGCC,
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000074 useValgrind=False,
75 useDGCompat=False,
76 useScript=None,
77 output=sys.stdout):
Nuno Lopesa7afc452009-07-11 18:34:43 +000078 OUTPUT = os.path.abspath(OUTPUT)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000079 if useValgrind:
80 VG_OUTPUT = '%s.vg'%(OUTPUT,)
Nuno Lopesa7afc452009-07-11 18:34:43 +000081 os.system('rm -f %s.*'%(VG_OUTPUT))
82 VALGRIND = 'valgrind -q --tool=memcheck --leak-check=full --trace-children=yes --log-file=%s.%%p'%(VG_OUTPUT)
83 CLANG = '%s %s'%(VALGRIND, CLANG)
84 CLANGCC = '%s %s'%(VALGRIND, CLANGCC)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000085
86 # Create the output directory if it does not already exist.
87 mkdir_p(os.path.dirname(OUTPUT))
88
89 # FIXME
90 #ulimit -t 40
91
92 # FIXME: Load script once
93 # FIXME: Support "short" script syntax
94
95 if useScript:
96 scriptFile = useScript
97 else:
98 # See if we have a per-dir test script.
99 dirScriptFile = os.path.join(os.path.dirname(FILENAME), 'test.script')
100 if os.path.exists(dirScriptFile):
101 scriptFile = dirScriptFile
102 else:
103 scriptFile = FILENAME
104
105 # Verify the script contains a run line.
106 for ln in open(scriptFile):
107 if 'RUN:' in ln:
108 break
109 else:
110 print >>output, "******************** TEST '%s' HAS NO RUN LINE! ********************"%(TESTNAME,)
111 output.flush()
112 return TestStatus.NoRunLine
113
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000114 FILENAME = os.path.abspath(FILENAME)
115 SCRIPT = OUTPUT + '.script'
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000116 if kSystemName == 'Windows':
117 SCRIPT += '.bat'
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000118 TEMPOUTPUT = OUTPUT + '.tmp'
119
120 substitutions = [('%s',SUBST),
Daniel Dunbar4d8076a2009-04-10 19:49:21 +0000121 ('%S',os.path.dirname(SUBST)),
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000122 ('%llvmgcc','llvm-gcc -emit-llvm -w'),
123 ('%llvmgxx','llvm-g++ -emit-llvm -w'),
124 ('%prcontext','prcontext.tcl'),
125 ('%t',TEMPOUTPUT),
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000126 (' clang ', ' ' + CLANG + ' '),
127 (' clang-cc ', ' ' + CLANGCC + ' ')]
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000128 scriptLines = []
129 xfailLines = []
130 for ln in open(scriptFile):
131 if 'RUN:' in ln:
132 # Isolate run parameters
133 index = ln.index('RUN:')
134 ln = ln[index+4:]
135
136 # Apply substitutions
137 for a,b in substitutions:
138 ln = ln.replace(a,b)
139
140 if useDGCompat:
141 ln = re.sub(r'\{(.*)\}', r'"\1"', ln)
142 scriptLines.append(ln)
143 elif 'XFAIL' in ln:
144 xfailLines.append(ln)
145
146 if xfailLines:
147 print >>output, "XFAILED '%s':"%(TESTNAME,)
148 output.writelines(xfailLines)
149
150 # Write script file
151 f = open(SCRIPT,'w')
152 f.write(''.join(scriptLines))
153 f.close()
154
155 outputFile = open(OUTPUT,'w')
156 p = None
157 try:
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000158 if kSystemName == 'Windows':
159 command = ['cmd','/c', SCRIPT]
160 else:
161 command = ['/bin/sh', SCRIPT]
162
163 p = subprocess.Popen(command,
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000164 cwd=os.path.dirname(FILENAME),
Daniel Dunbar02cee152009-07-13 21:24:28 +0000165 stdin=subprocess.PIPE,
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000166 stdout=subprocess.PIPE,
167 stderr=subprocess.PIPE)
168 out,err = p.communicate()
169 outputFile.write(out)
170 outputFile.write(err)
171 SCRIPT_STATUS = p.wait()
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000172
173 # Detect Ctrl-C in subprocess.
174 if SCRIPT_STATUS == -signal.SIGINT:
175 raise KeyboardInterrupt
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000176 except KeyboardInterrupt:
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000177 raise
178 outputFile.close()
179
180 if xfailLines:
181 SCRIPT_STATUS = not SCRIPT_STATUS
182
183 if useValgrind:
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000184 if kSystemName == 'Windows':
185 raise NotImplementedError,'Cannot run valgrind on windows'
186 else:
187 VG_OUTPUT = capture(['/bin/sh','-c','cat %s.*'%(VG_OUTPUT)])
Nuno Lopesa7afc452009-07-11 18:34:43 +0000188 VG_STATUS = len(VG_OUTPUT)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000189 else:
190 VG_STATUS = 0
191
192 if SCRIPT_STATUS or VG_STATUS:
193 print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,)
194 print >>output, "Command: "
195 output.writelines(scriptLines)
196 if not SCRIPT_STATUS:
197 print >>output, "Output:"
198 else:
199 print >>output, "Incorrect Output:"
200 cat(OUTPUT, output)
201 if VG_STATUS:
202 print >>output, "Valgrind Output:"
Nuno Lopesa7afc452009-07-11 18:34:43 +0000203 print >>output, VG_OUTPUT
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000204 print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,)
205 output.flush()
206 if xfailLines:
207 return TestStatus.XPass
208 else:
209 return TestStatus.Fail
210
211 if xfailLines:
212 return TestStatus.XFail
213 else:
214 return TestStatus.Pass
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000215
216def capture(args):
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000217 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000218 out,_ = p.communicate()
219 return out
220
221def which(command):
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000222 # Check for absolute match first.
223 if os.path.exists(command):
224 return command
225
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000226 # Would be nice if Python had a lib function for this.
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000227 paths = os.environ.get('PATH')
228 if not paths:
229 paths = os.defpath
230
231 # Get suffixes to search.
232 pathext = os.environ.get('PATHEXT', '').split(os.pathsep)
233
234 # Search the paths...
235 for path in paths.split(os.pathsep):
236 for ext in pathext:
237 p = os.path.join(path, command + ext)
238 if os.path.exists(p):
239 return p
240
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000241 return None
242
243def inferClang():
244 # Determine which clang to use.
245 clang = os.getenv('CLANG')
246
247 # If the user set clang in the environment, definitely use that and don't
248 # try to validate.
249 if clang:
250 return clang
251
252 # Otherwise look in the path.
253 clang = which('clang')
254
255 if not clang:
256 print >>sys.stderr, "error: couldn't find 'clang' program, try setting CLANG in your environment"
257 sys.exit(1)
258
259 return clang
260
261def inferClangCC(clang):
262 clangcc = os.getenv('CLANGCC')
263
264 # If the user set clang in the environment, definitely use that and don't
265 # try to validate.
266 if clangcc:
267 return clangcc
268
269 # Otherwise try adding -cc since we expect to be looking in a build
270 # directory.
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000271 if clang.endswith('.exe'):
272 clangccName = clang[:-4] + '-cc.exe'
273 else:
274 clangccName = clang + '-cc'
275 clangcc = which(clangccName)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000276 if not clangcc:
277 # Otherwise ask clang.
278 res = capture([clang, '-print-prog-name=clang-cc'])
279 res = res.strip()
280 if res and os.path.exists(res):
281 clangcc = res
282
283 if not clangcc:
284 print >>sys.stderr, "error: couldn't find 'clang-cc' program, try setting CLANGCC in your environment"
285 sys.exit(1)
286
287 return clangcc
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000288
289def main():
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000290 global options
291 from optparse import OptionParser
292 parser = OptionParser("usage: %prog [options] {tests}")
293 parser.add_option("", "--clang", dest="clang",
294 help="Program to use as \"clang\"",
295 action="store", default=None)
296 parser.add_option("", "--clang-cc", dest="clangcc",
297 help="Program to use as \"clang-cc\"",
298 action="store", default=None)
299 parser.add_option("", "--vg", dest="useValgrind",
300 help="Run tests under valgrind",
301 action="store_true", default=False)
302 parser.add_option("", "--dg", dest="useDGCompat",
303 help="Use llvm dejagnu compatibility mode",
304 action="store_true", default=False)
305 (opts, args) = parser.parse_args()
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000306
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000307 if not args:
308 parser.error('No tests specified')
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000309
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000310 if opts.clang is None:
311 opts.clang = inferClang()
312 if opts.clangcc is None:
313 opts.clangcc = inferClangCC(opts.clang)
314
315 for path in args:
316 command = path
317 # Use hand concatentation here because we want to override
318 # absolute paths.
319 output = 'Output/' + path + '.out'
320 testname = path
321
322 res = runOneTest(path, command, output, testname,
323 opts.clang, opts.clangcc,
324 useValgrind=opts.useValgrind,
325 useDGCompat=opts.useDGCompat,
326 useScript=os.getenv("TEST_SCRIPT"))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000327
328 sys.exit(res == TestStatus.Fail or res == TestStatus.XPass)
329
330if __name__=='__main__':
331 main()