blob: 9e8b77e59b6f4bf90c821545bffd9aafb42ddf2e [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
Daniel Dunbara957d992009-07-25 14:46:05 +000060import StringIO
61def runOneTest(testPath, tmpBase, clang, clangcc):
62 # Make paths absolute.
63 tmpBase = os.path.abspath(tmpBase)
64 testPath = os.path.abspath(testPath)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000065
66 # Create the output directory if it does not already exist.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000067
Daniel Dunbara957d992009-07-25 14:46:05 +000068 mkdir_p(os.path.dirname(tmpBase))
69 script = tmpBase + '.script'
Daniel Dunbar8fe83f12009-07-25 09:42:24 +000070 if kSystemName == 'Windows':
Daniel Dunbara957d992009-07-25 14:46:05 +000071 script += '.bat'
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000072
Daniel Dunbara957d992009-07-25 14:46:05 +000073 substitutions = [('%s', testPath),
74 ('%S', os.path.dirname(testPath)),
75 ('%t', tmpBase + '.tmp'),
76 (' clang ', ' ' + clang + ' '),
77 (' clang-cc ', ' ' + clangcc + ' ')]
Daniel Dunbar025f80d2009-07-25 11:27:37 +000078
79 # Collect the test lines from the script.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000080 scriptLines = []
81 xfailLines = []
Daniel Dunbara957d992009-07-25 14:46:05 +000082 for ln in open(testPath):
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000083 if 'RUN:' in ln:
Daniel Dunbar025f80d2009-07-25 11:27:37 +000084 # Isolate the command to run.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000085 index = ln.index('RUN:')
86 ln = ln[index+4:]
Daniel Dunbar025f80d2009-07-25 11:27:37 +000087
Daniel Dunbar322f7892009-07-25 12:23:35 +000088 # Strip trailing newline.
89 scriptLines.append(ln)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000090 elif 'XFAIL' in ln:
91 xfailLines.append(ln)
Daniel Dunbar025f80d2009-07-25 11:27:37 +000092
93 # FIXME: Support something like END, in case we need to process large
94 # files.
Daniel Dunbara957d992009-07-25 14:46:05 +000095
96 # Verify the script contains a run line.
97 if not scriptLines:
98 return (TestStatus.Fail, "Test has no run line!")
Daniel Dunbar322f7892009-07-25 12:23:35 +000099
100 # Apply substitutions to the script.
101 def processLine(ln):
102 # Apply substitutions
103 for a,b in substitutions:
104 ln = ln.replace(a,b)
105
Daniel Dunbar322f7892009-07-25 12:23:35 +0000106 # Strip the trailing newline and any extra whitespace.
107 return ln.strip()
108 scriptLines = map(processLine, scriptLines)
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000109
110 # Validate interior lines for '&&', a lovely historical artifact.
111 for i in range(len(scriptLines) - 1):
112 ln = scriptLines[i]
113
114 if not ln.endswith('&&'):
Daniel Dunbara957d992009-07-25 14:46:05 +0000115 return (TestStatus.Fail,
116 "MISSING \'&&\': %s\n" +
117 "FOLLOWED BY : %s\n" % (ln,scriptLines[i + 1]))
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000118
119 # Strip off '&&'
120 scriptLines[i] = ln[:-2]
Daniel Dunbar025f80d2009-07-25 11:27:37 +0000121
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000122 # Write script file
Daniel Dunbara957d992009-07-25 14:46:05 +0000123 f = open(script,'w')
Daniel Dunbar3b2505b2009-07-25 12:57:15 +0000124 if kSystemName == 'Windows':
125 f.write('\nif %ERRORLEVEL% NEQ 0 EXIT\n'.join(scriptLines))
Daniel Dunbar3b2505b2009-07-25 12:57:15 +0000126 else:
127 f.write(' &&\n'.join(scriptLines))
Daniel Dunbar11980cc2009-07-25 13:13:06 +0000128 f.write('\n')
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000129 f.close()
130
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000131 p = None
132 try:
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000133 if kSystemName == 'Windows':
Daniel Dunbara957d992009-07-25 14:46:05 +0000134 command = ['cmd','/c', script]
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000135 else:
Daniel Dunbara957d992009-07-25 14:46:05 +0000136 command = ['/bin/sh', script]
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000137
138 p = subprocess.Popen(command,
Daniel Dunbara957d992009-07-25 14:46:05 +0000139 cwd=os.path.dirname(testPath),
Daniel Dunbar02cee152009-07-13 21:24:28 +0000140 stdin=subprocess.PIPE,
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000141 stdout=subprocess.PIPE,
Daniel Dunbar78a88e72009-07-25 10:14:19 +0000142 stderr=subprocess.PIPE,
143 env=kChildEnv)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000144 out,err = p.communicate()
Daniel Dunbara957d992009-07-25 14:46:05 +0000145 exitCode = p.wait()
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000146
147 # Detect Ctrl-C in subprocess.
Daniel Dunbara957d992009-07-25 14:46:05 +0000148 if exitCode == -signal.SIGINT:
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000149 raise KeyboardInterrupt
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000150 except KeyboardInterrupt:
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000151 raise
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000152
153 if xfailLines:
Daniel Dunbara957d992009-07-25 14:46:05 +0000154 ok = exitCode != 0
155 status = (TestStatus.XPass, TestStatus.XFail)[ok]
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000156 else:
Daniel Dunbara957d992009-07-25 14:46:05 +0000157 ok = exitCode == 0
158 status = (TestStatus.Fail, TestStatus.Pass)[ok]
159
160 if ok:
161 return (status,'')
162
163 output = StringIO.StringIO()
164 print >>output, "Script:"
165 print >>output, "--"
166 print >>output, '\n'.join(scriptLines)
167 print >>output, "--"
168 print >>output, "Exit Code: %r" % exitCode
169 print >>output, "Command Output (stdout):"
170 print >>output, "--"
171 output.write(out)
172 print >>output, "--"
173 print >>output, "Command Output (stderr):"
174 print >>output, "--"
175 output.write(err)
176 print >>output, "--"
177 return (status, output.getvalue())
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000178
179def capture(args):
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000180 p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000181 out,_ = p.communicate()
182 return out
183
184def which(command):
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000185 # Check for absolute match first.
186 if os.path.exists(command):
187 return command
188
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000189 # Would be nice if Python had a lib function for this.
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000190 paths = os.environ.get('PATH')
191 if not paths:
192 paths = os.defpath
193
194 # Get suffixes to search.
195 pathext = os.environ.get('PATHEXT', '').split(os.pathsep)
196
197 # Search the paths...
198 for path in paths.split(os.pathsep):
199 for ext in pathext:
200 p = os.path.join(path, command + ext)
201 if os.path.exists(p):
202 return p
203
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000204 return None
205
206def inferClang():
207 # Determine which clang to use.
208 clang = os.getenv('CLANG')
209
210 # If the user set clang in the environment, definitely use that and don't
211 # try to validate.
212 if clang:
213 return clang
214
215 # Otherwise look in the path.
216 clang = which('clang')
217
218 if not clang:
219 print >>sys.stderr, "error: couldn't find 'clang' program, try setting CLANG in your environment"
220 sys.exit(1)
221
222 return clang
223
224def inferClangCC(clang):
225 clangcc = os.getenv('CLANGCC')
226
227 # If the user set clang in the environment, definitely use that and don't
228 # try to validate.
229 if clangcc:
230 return clangcc
231
232 # Otherwise try adding -cc since we expect to be looking in a build
233 # directory.
Daniel Dunbar8fe83f12009-07-25 09:42:24 +0000234 if clang.endswith('.exe'):
235 clangccName = clang[:-4] + '-cc.exe'
236 else:
237 clangccName = clang + '-cc'
238 clangcc = which(clangccName)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000239 if not clangcc:
240 # Otherwise ask clang.
241 res = capture([clang, '-print-prog-name=clang-cc'])
242 res = res.strip()
243 if res and os.path.exists(res):
244 clangcc = res
245
246 if not clangcc:
247 print >>sys.stderr, "error: couldn't find 'clang-cc' program, try setting CLANGCC in your environment"
248 sys.exit(1)
249
250 return clangcc
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000251
Daniel Dunbardf084892009-07-25 09:53:43 +0000252def getTestOutputBase(dir, testpath):
Daniel Dunbarfecdd002009-07-25 12:05:55 +0000253 """getTestOutputBase(dir, testpath) - Get the full path for temporary files
Daniel Dunbardf084892009-07-25 09:53:43 +0000254 corresponding to the given test path."""
255
256 # Form the output base out of the test parent directory name and the test
257 # name. FIXME: Find a better way to organize test results.
258 return os.path.join(dir,
259 os.path.basename(os.path.dirname(testpath)),
260 os.path.basename(testpath))
261
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000262def main():
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000263 global options
264 from optparse import OptionParser
265 parser = OptionParser("usage: %prog [options] {tests}")
266 parser.add_option("", "--clang", dest="clang",
267 help="Program to use as \"clang\"",
268 action="store", default=None)
269 parser.add_option("", "--clang-cc", dest="clangcc",
270 help="Program to use as \"clang-cc\"",
271 action="store", default=None)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000272 (opts, args) = parser.parse_args()
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000273
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000274 if not args:
275 parser.error('No tests specified')
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000276
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000277 if opts.clang is None:
278 opts.clang = inferClang()
279 if opts.clangcc is None:
280 opts.clangcc = inferClangCC(opts.clang)
281
282 for path in args:
Daniel Dunbara957d992009-07-25 14:46:05 +0000283 base = getTestOutputBase('Output', path) + '.out'
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000284
Daniel Dunbara957d992009-07-25 14:46:05 +0000285 status,output = runOneTest(path, base, opts.clang, opts.clangcc)
286 print '%s: %s' % (TestStatus.getName(status).upper(), path)
287 if status == TestStatus.Fail or status == TestStatus.XPass:
288 print "%s TEST '%s' FAILED %s" % ('*'*20, path, '*'*20)
289 sys.stdout.write(output)
290 print "*" * 20
291 sys.exit(1)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000292
Daniel Dunbara957d992009-07-25 14:46:05 +0000293 sys.exit(0)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000294
295if __name__=='__main__':
296 main()