blob: 5639a378e7bac4df728475d08f3573b7e54e87d7 [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.
27kChildEnv = { 'PATH' : os.environ.get('PATH','') }
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000028
Daniel Dunbar8fe83f12009-07-25 09:42:24 +000029kSystemName = platform.system()
30
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000031class TestStatus:
32 Pass = 0
33 XFail = 1
34 Fail = 2
35 XPass = 3
36 NoRunLine = 4
37 Invalid = 5
38
39 kNames = ['Pass','XFail','Fail','XPass','NoRunLine','Invalid']
Daniel Dunbarfbbb1e72009-07-02 23:58:07 +000040 @staticmethod
Daniel Dunbar8afede22009-07-02 23:56:37 +000041 def getName(code):
42 return TestStatus.kNames[code]
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000043
44def mkdir_p(path):
45 if not path:
46 pass
47 elif os.path.exists(path):
48 pass
49 else:
50 parent = os.path.dirname(path)
51 if parent != path:
52 mkdir_p(parent)
53 try:
54 os.mkdir(path)
55 except OSError,e:
56 if e.errno != errno.EEXIST:
57 raise
58
59def remove(path):
60 try:
61 os.remove(path)
62 except OSError:
63 pass
64
65def cat(path, output):
66 f = open(path)
67 output.writelines(f)
68 f.close()
69
Daniel Dunbar69e07a72009-06-17 21:33:37 +000070def runOneTest(FILENAME, SUBST, OUTPUT, TESTNAME, CLANG, CLANGCC,
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000071 useValgrind=False,
72 useDGCompat=False,
73 useScript=None,
74 output=sys.stdout):
Nuno Lopesa7afc452009-07-11 18:34:43 +000075 OUTPUT = os.path.abspath(OUTPUT)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000076 if useValgrind:
77 VG_OUTPUT = '%s.vg'%(OUTPUT,)
Nuno Lopesa7afc452009-07-11 18:34:43 +000078 os.system('rm -f %s.*'%(VG_OUTPUT))
79 VALGRIND = 'valgrind -q --tool=memcheck --leak-check=full --trace-children=yes --log-file=%s.%%p'%(VG_OUTPUT)
80 CLANG = '%s %s'%(VALGRIND, CLANG)
81 CLANGCC = '%s %s'%(VALGRIND, CLANGCC)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000082
83 # Create the output directory if it does not already exist.
84 mkdir_p(os.path.dirname(OUTPUT))
85
86 # FIXME
87 #ulimit -t 40
88
89 # FIXME: Load script once
90 # FIXME: Support "short" script syntax
91
92 if useScript:
93 scriptFile = useScript
94 else:
95 # See if we have a per-dir test script.
96 dirScriptFile = os.path.join(os.path.dirname(FILENAME), 'test.script')
97 if os.path.exists(dirScriptFile):
98 scriptFile = dirScriptFile
99 else:
100 scriptFile = FILENAME
101
102 # Verify the script contains a run line.
103 for ln in open(scriptFile):
104 if 'RUN:' in ln:
105 break
106 else:
107 print >>output, "******************** TEST '%s' HAS NO RUN LINE! ********************"%(TESTNAME,)
108 output.flush()
109 return TestStatus.NoRunLine
110
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000111 FILENAME = os.path.abspath(FILENAME)
112 SCRIPT = OUTPUT + '.script'
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000113 if kSystemName == 'Windows':
114 SCRIPT += '.bat'
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000115 TEMPOUTPUT = OUTPUT + '.tmp'
116
117 substitutions = [('%s',SUBST),
Daniel Dunbar4d8076a2009-04-10 19:49:21 +0000118 ('%S',os.path.dirname(SUBST)),
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000119 ('%llvmgcc','llvm-gcc -emit-llvm -w'),
120 ('%llvmgxx','llvm-g++ -emit-llvm -w'),
121 ('%prcontext','prcontext.tcl'),
122 ('%t',TEMPOUTPUT),
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000123 (' clang ', ' ' + CLANG + ' '),
124 (' clang-cc ', ' ' + CLANGCC + ' ')]
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000125
126 # Collect the test lines from the script.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000127 scriptLines = []
128 xfailLines = []
129 for ln in open(scriptFile):
130 if 'RUN:' in ln:
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000131 # Isolate the command to run.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000132 index = ln.index('RUN:')
133 ln = ln[index+4:]
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000134
135 # Strip whitespace and append.
136 scriptLines.append(ln.strip())
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000137 elif 'XFAIL' in ln:
138 xfailLines.append(ln)
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000139
140 # FIXME: Support something like END, in case we need to process large
141 # files.
142
143 # Validate interior lines for '&&', a lovely historical artifact.
144 for i in range(len(scriptLines) - 1):
145 ln = scriptLines[i]
146
147 if not ln.endswith('&&'):
148 print >>output, "MISSING \'&&\': %s" % ln
149 print >>output, "FOLLOWED BY : %s" % scriptLines[i + 1]
150 return TestStatus.Fail
151
152 # Strip off '&&'
153 scriptLines[i] = ln[:-2]
154
155 # Apply substitutions to the script.
156 def processLine(ln):
157 # Apply substitutions
158 for a,b in substitutions:
159 ln = ln.replace(a,b)
160
161 if useDGCompat:
162 ln = re.sub(r'\{(.*)\}', r'"\1"', ln)
163 return ln
164 scriptLines = map(processLine, scriptLines)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000165
166 if xfailLines:
167 print >>output, "XFAILED '%s':"%(TESTNAME,)
168 output.writelines(xfailLines)
169
170 # Write script file
171 f = open(SCRIPT,'w')
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000172 f.write(' &&\n'.join(scriptLines))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000173 f.close()
174
175 outputFile = open(OUTPUT,'w')
176 p = None
177 try:
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000178 if kSystemName == 'Windows':
179 command = ['cmd','/c', SCRIPT]
180 else:
181 command = ['/bin/sh', SCRIPT]
182
183 p = subprocess.Popen(command,
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000184 cwd=os.path.dirname(FILENAME),
Daniel Dunbar02cee152009-07-13 21:24:28 +0000185 stdin=subprocess.PIPE,
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000186 stdout=subprocess.PIPE,
Daniel Dunbar78a88e72009-07-25 10:14:19 +0000187 stderr=subprocess.PIPE,
188 env=kChildEnv)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000189 out,err = p.communicate()
190 outputFile.write(out)
191 outputFile.write(err)
192 SCRIPT_STATUS = p.wait()
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000193
194 # Detect Ctrl-C in subprocess.
195 if SCRIPT_STATUS == -signal.SIGINT:
196 raise KeyboardInterrupt
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000197 except KeyboardInterrupt:
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000198 raise
199 outputFile.close()
200
201 if xfailLines:
202 SCRIPT_STATUS = not SCRIPT_STATUS
203
204 if useValgrind:
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000205 if kSystemName == 'Windows':
206 raise NotImplementedError,'Cannot run valgrind on windows'
207 else:
208 VG_OUTPUT = capture(['/bin/sh','-c','cat %s.*'%(VG_OUTPUT)])
Nuno Lopesa7afc452009-07-11 18:34:43 +0000209 VG_STATUS = len(VG_OUTPUT)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000210 else:
211 VG_STATUS = 0
212
213 if SCRIPT_STATUS or VG_STATUS:
214 print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,)
215 print >>output, "Command: "
216 output.writelines(scriptLines)
217 if not SCRIPT_STATUS:
218 print >>output, "Output:"
219 else:
220 print >>output, "Incorrect Output:"
221 cat(OUTPUT, output)
222 if VG_STATUS:
223 print >>output, "Valgrind Output:"
Nuno Lopesa7afc452009-07-11 18:34:43 +0000224 print >>output, VG_OUTPUT
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000225 print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,)
226 output.flush()
227 if xfailLines:
228 return TestStatus.XPass
229 else:
230 return TestStatus.Fail
231
232 if xfailLines:
233 return TestStatus.XFail
234 else:
235 return TestStatus.Pass
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000236
237def capture(args):
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000238 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000239 out,_ = p.communicate()
240 return out
241
242def which(command):
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000243 # Check for absolute match first.
244 if os.path.exists(command):
245 return command
246
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000247 # Would be nice if Python had a lib function for this.
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000248 paths = os.environ.get('PATH')
249 if not paths:
250 paths = os.defpath
251
252 # Get suffixes to search.
253 pathext = os.environ.get('PATHEXT', '').split(os.pathsep)
254
255 # Search the paths...
256 for path in paths.split(os.pathsep):
257 for ext in pathext:
258 p = os.path.join(path, command + ext)
259 if os.path.exists(p):
260 return p
261
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000262 return None
263
264def inferClang():
265 # Determine which clang to use.
266 clang = os.getenv('CLANG')
267
268 # If the user set clang in the environment, definitely use that and don't
269 # try to validate.
270 if clang:
271 return clang
272
273 # Otherwise look in the path.
274 clang = which('clang')
275
276 if not clang:
277 print >>sys.stderr, "error: couldn't find 'clang' program, try setting CLANG in your environment"
278 sys.exit(1)
279
280 return clang
281
282def inferClangCC(clang):
283 clangcc = os.getenv('CLANGCC')
284
285 # If the user set clang in the environment, definitely use that and don't
286 # try to validate.
287 if clangcc:
288 return clangcc
289
290 # Otherwise try adding -cc since we expect to be looking in a build
291 # directory.
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000292 if clang.endswith('.exe'):
293 clangccName = clang[:-4] + '-cc.exe'
294 else:
295 clangccName = clang + '-cc'
296 clangcc = which(clangccName)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000297 if not clangcc:
298 # Otherwise ask clang.
299 res = capture([clang, '-print-prog-name=clang-cc'])
300 res = res.strip()
301 if res and os.path.exists(res):
302 clangcc = res
303
304 if not clangcc:
305 print >>sys.stderr, "error: couldn't find 'clang-cc' program, try setting CLANGCC in your environment"
306 sys.exit(1)
307
308 return clangcc
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000309
Daniel Dunbardf084892009-07-25 09:53:43 +0000310def getTestOutputBase(dir, testpath):
311 """getTestOutputPath(dir, testpath) - Get the full path for temporary files
312 corresponding to the given test path."""
313
314 # Form the output base out of the test parent directory name and the test
315 # name. FIXME: Find a better way to organize test results.
316 return os.path.join(dir,
317 os.path.basename(os.path.dirname(testpath)),
318 os.path.basename(testpath))
319
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000320def main():
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000321 global options
322 from optparse import OptionParser
323 parser = OptionParser("usage: %prog [options] {tests}")
324 parser.add_option("", "--clang", dest="clang",
325 help="Program to use as \"clang\"",
326 action="store", default=None)
327 parser.add_option("", "--clang-cc", dest="clangcc",
328 help="Program to use as \"clang-cc\"",
329 action="store", default=None)
330 parser.add_option("", "--vg", dest="useValgrind",
331 help="Run tests under valgrind",
332 action="store_true", default=False)
333 parser.add_option("", "--dg", dest="useDGCompat",
334 help="Use llvm dejagnu compatibility mode",
335 action="store_true", default=False)
336 (opts, args) = parser.parse_args()
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000337
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000338 if not args:
339 parser.error('No tests specified')
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000340
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000341 if opts.clang is None:
342 opts.clang = inferClang()
343 if opts.clangcc is None:
344 opts.clangcc = inferClangCC(opts.clang)
345
346 for path in args:
347 command = path
Daniel Dunbardf084892009-07-25 09:53:43 +0000348 output = getTestOutputPath('Output', path) + '.out'
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000349 testname = path
350
351 res = runOneTest(path, command, output, testname,
352 opts.clang, opts.clangcc,
353 useValgrind=opts.useValgrind,
354 useDGCompat=opts.useDGCompat,
355 useScript=os.getenv("TEST_SCRIPT"))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000356
357 sys.exit(res == TestStatus.Fail or res == TestStatus.XPass)
358
359if __name__=='__main__':
360 main()