blob: 1f302372f6096d27cb42412c35aaa060b4235be7 [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']
Daniel Dunbar8afede22009-07-02 23:56:37 +000039 def getName(code):
40 return TestStatus.kNames[code]
41 getName = staticmethod(getName)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000042
43def mkdir_p(path):
44 if not path:
45 pass
46 elif os.path.exists(path):
47 pass
48 else:
49 parent = os.path.dirname(path)
50 if parent != path:
51 mkdir_p(parent)
52 try:
53 os.mkdir(path)
54 except OSError,e:
55 if e.errno != errno.EEXIST:
56 raise
57
58def remove(path):
59 try:
60 os.remove(path)
61 except OSError:
62 pass
63
64def cat(path, output):
65 f = open(path)
66 output.writelines(f)
67 f.close()
68
Daniel Dunbar69e07a72009-06-17 21:33:37 +000069def runOneTest(FILENAME, SUBST, OUTPUT, TESTNAME, CLANG, CLANGCC,
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000070 useValgrind=False,
71 useDGCompat=False,
72 useScript=None,
73 output=sys.stdout):
74 if useValgrind:
75 VG_OUTPUT = '%s.vg'%(OUTPUT,)
76 if os.path.exists:
77 remove(VG_OUTPUT)
78 CLANG = 'valgrind --leak-check=full --quiet --log-file=%s %s'%(VG_OUTPUT, CLANG)
79
80 # Create the output directory if it does not already exist.
81 mkdir_p(os.path.dirname(OUTPUT))
82
83 # FIXME
84 #ulimit -t 40
85
86 # FIXME: Load script once
87 # FIXME: Support "short" script syntax
88
89 if useScript:
90 scriptFile = useScript
91 else:
92 # See if we have a per-dir test script.
93 dirScriptFile = os.path.join(os.path.dirname(FILENAME), 'test.script')
94 if os.path.exists(dirScriptFile):
95 scriptFile = dirScriptFile
96 else:
97 scriptFile = FILENAME
98
99 # Verify the script contains a run line.
100 for ln in open(scriptFile):
101 if 'RUN:' in ln:
102 break
103 else:
104 print >>output, "******************** TEST '%s' HAS NO RUN LINE! ********************"%(TESTNAME,)
105 output.flush()
106 return TestStatus.NoRunLine
107
108 OUTPUT = os.path.abspath(OUTPUT)
109 FILENAME = os.path.abspath(FILENAME)
110 SCRIPT = OUTPUT + '.script'
111 TEMPOUTPUT = OUTPUT + '.tmp'
112
113 substitutions = [('%s',SUBST),
Daniel Dunbar4d8076a2009-04-10 19:49:21 +0000114 ('%S',os.path.dirname(SUBST)),
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000115 ('%llvmgcc','llvm-gcc -emit-llvm -w'),
116 ('%llvmgxx','llvm-g++ -emit-llvm -w'),
117 ('%prcontext','prcontext.tcl'),
118 ('%t',TEMPOUTPUT),
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000119 (' clang ', ' ' + CLANG + ' '),
120 (' clang-cc ', ' ' + CLANGCC + ' ')]
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000121 scriptLines = []
122 xfailLines = []
123 for ln in open(scriptFile):
124 if 'RUN:' in ln:
125 # Isolate run parameters
126 index = ln.index('RUN:')
127 ln = ln[index+4:]
128
129 # Apply substitutions
130 for a,b in substitutions:
131 ln = ln.replace(a,b)
132
133 if useDGCompat:
134 ln = re.sub(r'\{(.*)\}', r'"\1"', ln)
135 scriptLines.append(ln)
136 elif 'XFAIL' in ln:
137 xfailLines.append(ln)
138
139 if xfailLines:
140 print >>output, "XFAILED '%s':"%(TESTNAME,)
141 output.writelines(xfailLines)
142
143 # Write script file
144 f = open(SCRIPT,'w')
145 f.write(''.join(scriptLines))
146 f.close()
147
148 outputFile = open(OUTPUT,'w')
149 p = None
150 try:
151 p = subprocess.Popen(["/bin/sh",SCRIPT],
152 cwd=os.path.dirname(FILENAME),
153 stdout=subprocess.PIPE,
154 stderr=subprocess.PIPE)
155 out,err = p.communicate()
156 outputFile.write(out)
157 outputFile.write(err)
158 SCRIPT_STATUS = p.wait()
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000159
160 # Detect Ctrl-C in subprocess.
161 if SCRIPT_STATUS == -signal.SIGINT:
162 raise KeyboardInterrupt
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000163 except KeyboardInterrupt:
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000164 raise
165 outputFile.close()
166
167 if xfailLines:
168 SCRIPT_STATUS = not SCRIPT_STATUS
169
170 if useValgrind:
171 VG_STATUS = len(list(open(VG_OUTPUT)))
172 else:
173 VG_STATUS = 0
174
175 if SCRIPT_STATUS or VG_STATUS:
176 print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,)
177 print >>output, "Command: "
178 output.writelines(scriptLines)
179 if not SCRIPT_STATUS:
180 print >>output, "Output:"
181 else:
182 print >>output, "Incorrect Output:"
183 cat(OUTPUT, output)
184 if VG_STATUS:
185 print >>output, "Valgrind Output:"
186 cat(VG_OUTPUT, output)
187 print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,)
188 output.flush()
189 if xfailLines:
190 return TestStatus.XPass
191 else:
192 return TestStatus.Fail
193
194 if xfailLines:
195 return TestStatus.XFail
196 else:
197 return TestStatus.Pass
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000198
199def capture(args):
200 p = subprocess.Popen(args, stdout=subprocess.PIPE)
201 out,_ = p.communicate()
202 return out
203
204def which(command):
205 # Would be nice if Python had a lib function for this.
206 res = capture(['which',command])
207 res = res.strip()
208 if res and os.path.exists(res):
209 return res
210 return None
211
212def inferClang():
213 # Determine which clang to use.
214 clang = os.getenv('CLANG')
215
216 # If the user set clang in the environment, definitely use that and don't
217 # try to validate.
218 if clang:
219 return clang
220
221 # Otherwise look in the path.
222 clang = which('clang')
223
224 if not clang:
225 print >>sys.stderr, "error: couldn't find 'clang' program, try setting CLANG in your environment"
226 sys.exit(1)
227
228 return clang
229
230def inferClangCC(clang):
231 clangcc = os.getenv('CLANGCC')
232
233 # If the user set clang in the environment, definitely use that and don't
234 # try to validate.
235 if clangcc:
236 return clangcc
237
238 # Otherwise try adding -cc since we expect to be looking in a build
239 # directory.
240 clangcc = which(clang + '-cc')
241 if not clangcc:
242 # Otherwise ask clang.
243 res = capture([clang, '-print-prog-name=clang-cc'])
244 res = res.strip()
245 if res and os.path.exists(res):
246 clangcc = res
247
248 if not clangcc:
249 print >>sys.stderr, "error: couldn't find 'clang-cc' program, try setting CLANGCC in your environment"
250 sys.exit(1)
251
252 return clangcc
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000253
254def main():
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000255 global options
256 from optparse import OptionParser
257 parser = OptionParser("usage: %prog [options] {tests}")
258 parser.add_option("", "--clang", dest="clang",
259 help="Program to use as \"clang\"",
260 action="store", default=None)
261 parser.add_option("", "--clang-cc", dest="clangcc",
262 help="Program to use as \"clang-cc\"",
263 action="store", default=None)
264 parser.add_option("", "--vg", dest="useValgrind",
265 help="Run tests under valgrind",
266 action="store_true", default=False)
267 parser.add_option("", "--dg", dest="useDGCompat",
268 help="Use llvm dejagnu compatibility mode",
269 action="store_true", default=False)
270 (opts, args) = parser.parse_args()
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000271
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000272 if not args:
273 parser.error('No tests specified')
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000274
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000275 if opts.clang is None:
276 opts.clang = inferClang()
277 if opts.clangcc is None:
278 opts.clangcc = inferClangCC(opts.clang)
279
280 for path in args:
281 command = path
282 # Use hand concatentation here because we want to override
283 # absolute paths.
284 output = 'Output/' + path + '.out'
285 testname = path
286
287 res = runOneTest(path, command, output, testname,
288 opts.clang, opts.clangcc,
289 useValgrind=opts.useValgrind,
290 useDGCompat=opts.useDGCompat,
291 useScript=os.getenv("TEST_SCRIPT"))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000292
293 sys.exit(res == TestStatus.Fail or res == TestStatus.XPass)
294
295if __name__=='__main__':
296 main()