blob: cfbfe6b3b74836cee2bd1debcc2fe7191a1bbbdc [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 Dunbar69e07a72009-06-17 21:33:37 +000019import os
Daniel Dunbar8fe83f12009-07-25 09:42:24 +000020import platform
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000021import re
Daniel Dunbar69e07a72009-06-17 21:33:37 +000022import signal
23import subprocess
24import sys
25
Daniel Dunbar78a88e72009-07-25 10:14:19 +000026# Increase determinism by explicitly choosing the environment.
Daniel Dunbar3b2505b2009-07-25 12:57:15 +000027kChildEnv = {}
28for var in ('PATH', 'SYSTEMROOT'):
29 kChildEnv[var] = os.environ.get(var, '')
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000030
Daniel Dunbar8fe83f12009-07-25 09:42:24 +000031kSystemName = platform.system()
32
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000033class TestStatus:
34 Pass = 0
35 XFail = 1
36 Fail = 2
37 XPass = 3
Daniel Dunbar8bf0ccd2009-07-25 12:47:38 +000038 Invalid = 4
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000039
Daniel Dunbar8bf0ccd2009-07-25 12:47:38 +000040 kNames = ['Pass','XFail','Fail','XPass','Invalid']
Daniel Dunbarfbbb1e72009-07-02 23:58:07 +000041 @staticmethod
Daniel Dunbar8afede22009-07-02 23:56:37 +000042 def getName(code):
43 return TestStatus.kNames[code]
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000044
45def mkdir_p(path):
46 if not path:
47 pass
48 elif os.path.exists(path):
49 pass
50 else:
51 parent = os.path.dirname(path)
52 if parent != path:
53 mkdir_p(parent)
54 try:
55 os.mkdir(path)
56 except OSError,e:
57 if e.errno != errno.EEXIST:
58 raise
59
60def remove(path):
61 try:
62 os.remove(path)
63 except OSError:
64 pass
65
66def cat(path, output):
67 f = open(path)
68 output.writelines(f)
69 f.close()
70
Daniel Dunbar69e07a72009-06-17 21:33:37 +000071def runOneTest(FILENAME, SUBST, OUTPUT, TESTNAME, CLANG, CLANGCC,
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000072 useValgrind=False,
73 useDGCompat=False,
74 useScript=None,
75 output=sys.stdout):
Nuno Lopesa7afc452009-07-11 18:34:43 +000076 OUTPUT = os.path.abspath(OUTPUT)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000077 if useValgrind:
78 VG_OUTPUT = '%s.vg'%(OUTPUT,)
Nuno Lopesa7afc452009-07-11 18:34:43 +000079 os.system('rm -f %s.*'%(VG_OUTPUT))
80 VALGRIND = 'valgrind -q --tool=memcheck --leak-check=full --trace-children=yes --log-file=%s.%%p'%(VG_OUTPUT)
81 CLANG = '%s %s'%(VALGRIND, CLANG)
82 CLANGCC = '%s %s'%(VALGRIND, CLANGCC)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000083
84 # Create the output directory if it does not already exist.
85 mkdir_p(os.path.dirname(OUTPUT))
86
87 # FIXME
88 #ulimit -t 40
89
90 # FIXME: Load script once
91 # FIXME: Support "short" script syntax
92
93 if useScript:
94 scriptFile = useScript
95 else:
96 # See if we have a per-dir test script.
97 dirScriptFile = os.path.join(os.path.dirname(FILENAME), 'test.script')
98 if os.path.exists(dirScriptFile):
99 scriptFile = dirScriptFile
100 else:
101 scriptFile = FILENAME
102
103 # Verify the script contains a run line.
104 for ln in open(scriptFile):
105 if 'RUN:' in ln:
106 break
107 else:
108 print >>output, "******************** TEST '%s' HAS NO RUN LINE! ********************"%(TESTNAME,)
109 output.flush()
Daniel Dunbar8bf0ccd2009-07-25 12:47:38 +0000110 return TestStatus.Fail
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000111
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000112 FILENAME = os.path.abspath(FILENAME)
113 SCRIPT = OUTPUT + '.script'
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000114 if kSystemName == 'Windows':
115 SCRIPT += '.bat'
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000116 TEMPOUTPUT = OUTPUT + '.tmp'
117
118 substitutions = [('%s',SUBST),
Daniel Dunbar4d8076a2009-04-10 19:49:21 +0000119 ('%S',os.path.dirname(SUBST)),
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000120 ('%llvmgcc','llvm-gcc -emit-llvm -w'),
121 ('%llvmgxx','llvm-g++ -emit-llvm -w'),
122 ('%prcontext','prcontext.tcl'),
123 ('%t',TEMPOUTPUT),
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000124 (' clang ', ' ' + CLANG + ' '),
125 (' clang-cc ', ' ' + CLANGCC + ' ')]
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000126
127 # Collect the test lines from the script.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000128 scriptLines = []
129 xfailLines = []
130 for ln in open(scriptFile):
131 if 'RUN:' in ln:
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000132 # Isolate the command to run.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000133 index = ln.index('RUN:')
134 ln = ln[index+4:]
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000135
Daniel Dunbar322f7892009-07-25 12:23:35 +0000136 # Strip trailing newline.
137 scriptLines.append(ln)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000138 elif 'XFAIL' in ln:
139 xfailLines.append(ln)
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000140
141 # FIXME: Support something like END, in case we need to process large
142 # files.
Daniel Dunbar322f7892009-07-25 12:23:35 +0000143
144 # Apply substitutions to the script.
145 def processLine(ln):
146 # Apply substitutions
147 for a,b in substitutions:
148 ln = ln.replace(a,b)
149
150 if useDGCompat:
151 ln = re.sub(r'\{(.*)\}', r'"\1"', ln)
152
153 # Strip the trailing newline and any extra whitespace.
154 return ln.strip()
155 scriptLines = map(processLine, scriptLines)
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000156
157 # Validate interior lines for '&&', a lovely historical artifact.
158 for i in range(len(scriptLines) - 1):
159 ln = scriptLines[i]
160
161 if not ln.endswith('&&'):
162 print >>output, "MISSING \'&&\': %s" % ln
163 print >>output, "FOLLOWED BY : %s" % scriptLines[i + 1]
164 return TestStatus.Fail
165
166 # Strip off '&&'
167 scriptLines[i] = ln[:-2]
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000168
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000169 if xfailLines:
170 print >>output, "XFAILED '%s':"%(TESTNAME,)
171 output.writelines(xfailLines)
172
173 # Write script file
174 f = open(SCRIPT,'w')
Daniel Dunbar3b2505b2009-07-25 12:57:15 +0000175 if kSystemName == 'Windows':
176 f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(scriptLines))
177 f.write('\n')
178 else:
179 f.write(' &&\n'.join(scriptLines))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000180 f.close()
181
182 outputFile = open(OUTPUT,'w')
183 p = None
184 try:
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000185 if kSystemName == 'Windows':
186 command = ['cmd','/c', SCRIPT]
187 else:
188 command = ['/bin/sh', SCRIPT]
189
190 p = subprocess.Popen(command,
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000191 cwd=os.path.dirname(FILENAME),
Daniel Dunbar02cee152009-07-13 21:24:28 +0000192 stdin=subprocess.PIPE,
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000193 stdout=subprocess.PIPE,
Daniel Dunbar78a88e72009-07-25 10:14:19 +0000194 stderr=subprocess.PIPE,
195 env=kChildEnv)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000196 out,err = p.communicate()
197 outputFile.write(out)
198 outputFile.write(err)
199 SCRIPT_STATUS = p.wait()
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000200
201 # Detect Ctrl-C in subprocess.
202 if SCRIPT_STATUS == -signal.SIGINT:
203 raise KeyboardInterrupt
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000204 except KeyboardInterrupt:
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000205 raise
206 outputFile.close()
207
208 if xfailLines:
209 SCRIPT_STATUS = not SCRIPT_STATUS
210
211 if useValgrind:
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000212 if kSystemName == 'Windows':
213 raise NotImplementedError,'Cannot run valgrind on windows'
214 else:
215 VG_OUTPUT = capture(['/bin/sh','-c','cat %s.*'%(VG_OUTPUT)])
Nuno Lopesa7afc452009-07-11 18:34:43 +0000216 VG_STATUS = len(VG_OUTPUT)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000217 else:
218 VG_STATUS = 0
219
220 if SCRIPT_STATUS or VG_STATUS:
221 print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,)
222 print >>output, "Command: "
223 output.writelines(scriptLines)
224 if not SCRIPT_STATUS:
225 print >>output, "Output:"
226 else:
227 print >>output, "Incorrect Output:"
228 cat(OUTPUT, output)
229 if VG_STATUS:
230 print >>output, "Valgrind Output:"
Nuno Lopesa7afc452009-07-11 18:34:43 +0000231 print >>output, VG_OUTPUT
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000232 print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,)
233 output.flush()
234 if xfailLines:
235 return TestStatus.XPass
236 else:
237 return TestStatus.Fail
238
239 if xfailLines:
240 return TestStatus.XFail
241 else:
242 return TestStatus.Pass
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000243
244def capture(args):
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000245 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000246 out,_ = p.communicate()
247 return out
248
249def which(command):
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000250 # Check for absolute match first.
251 if os.path.exists(command):
252 return command
253
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000254 # Would be nice if Python had a lib function for this.
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000255 paths = os.environ.get('PATH')
256 if not paths:
257 paths = os.defpath
258
259 # Get suffixes to search.
260 pathext = os.environ.get('PATHEXT', '').split(os.pathsep)
261
262 # Search the paths...
263 for path in paths.split(os.pathsep):
264 for ext in pathext:
265 p = os.path.join(path, command + ext)
266 if os.path.exists(p):
267 return p
268
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000269 return None
270
271def inferClang():
272 # Determine which clang to use.
273 clang = os.getenv('CLANG')
274
275 # If the user set clang in the environment, definitely use that and don't
276 # try to validate.
277 if clang:
278 return clang
279
280 # Otherwise look in the path.
281 clang = which('clang')
282
283 if not clang:
284 print >>sys.stderr, "error: couldn't find 'clang' program, try setting CLANG in your environment"
285 sys.exit(1)
286
287 return clang
288
289def inferClangCC(clang):
290 clangcc = os.getenv('CLANGCC')
291
292 # If the user set clang in the environment, definitely use that and don't
293 # try to validate.
294 if clangcc:
295 return clangcc
296
297 # Otherwise try adding -cc since we expect to be looking in a build
298 # directory.
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000299 if clang.endswith('.exe'):
300 clangccName = clang[:-4] + '-cc.exe'
301 else:
302 clangccName = clang + '-cc'
303 clangcc = which(clangccName)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000304 if not clangcc:
305 # Otherwise ask clang.
306 res = capture([clang, '-print-prog-name=clang-cc'])
307 res = res.strip()
308 if res and os.path.exists(res):
309 clangcc = res
310
311 if not clangcc:
312 print >>sys.stderr, "error: couldn't find 'clang-cc' program, try setting CLANGCC in your environment"
313 sys.exit(1)
314
315 return clangcc
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000316
Daniel Dunbardf084892009-07-25 09:53:43 +0000317def getTestOutputBase(dir, testpath):
Daniel Dunbarfecdd002009-07-25 12:05:55 +0000318 """getTestOutputBase(dir, testpath) - Get the full path for temporary files
Daniel Dunbardf084892009-07-25 09:53:43 +0000319 corresponding to the given test path."""
320
321 # Form the output base out of the test parent directory name and the test
322 # name. FIXME: Find a better way to organize test results.
323 return os.path.join(dir,
324 os.path.basename(os.path.dirname(testpath)),
325 os.path.basename(testpath))
326
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000327def main():
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000328 global options
329 from optparse import OptionParser
330 parser = OptionParser("usage: %prog [options] {tests}")
331 parser.add_option("", "--clang", dest="clang",
332 help="Program to use as \"clang\"",
333 action="store", default=None)
334 parser.add_option("", "--clang-cc", dest="clangcc",
335 help="Program to use as \"clang-cc\"",
336 action="store", default=None)
337 parser.add_option("", "--vg", dest="useValgrind",
338 help="Run tests under valgrind",
339 action="store_true", default=False)
340 parser.add_option("", "--dg", dest="useDGCompat",
341 help="Use llvm dejagnu compatibility mode",
342 action="store_true", default=False)
343 (opts, args) = parser.parse_args()
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000344
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000345 if not args:
346 parser.error('No tests specified')
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000347
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000348 if opts.clang is None:
349 opts.clang = inferClang()
350 if opts.clangcc is None:
351 opts.clangcc = inferClangCC(opts.clang)
352
353 for path in args:
354 command = path
Daniel Dunbarfecdd002009-07-25 12:05:55 +0000355 output = getTestOutputBase('Output', path) + '.out'
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000356 testname = path
357
358 res = runOneTest(path, command, output, testname,
359 opts.clang, opts.clangcc,
360 useValgrind=opts.useValgrind,
361 useDGCompat=opts.useDGCompat,
362 useScript=os.getenv("TEST_SCRIPT"))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000363
364 sys.exit(res == TestStatus.Fail or res == TestStatus.XPass)
365
366if __name__=='__main__':
367 main()