blob: 6c6de1bf6687b1009ec1ada94a911220533b3e8c [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 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 Dunbarfbbb1e72009-07-02 23:58:07 +000039 @staticmethod
Daniel Dunbar8afede22009-07-02 23:56:37 +000040 def getName(code):
41 return TestStatus.kNames[code]
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):
Nuno Lopesa7afc452009-07-11 18:34:43 +000074 OUTPUT = os.path.abspath(OUTPUT)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000075 if useValgrind:
76 VG_OUTPUT = '%s.vg'%(OUTPUT,)
Nuno Lopesa7afc452009-07-11 18:34:43 +000077 os.system('rm -f %s.*'%(VG_OUTPUT))
78 VALGRIND = 'valgrind -q --tool=memcheck --leak-check=full --trace-children=yes --log-file=%s.%%p'%(VG_OUTPUT)
79 CLANG = '%s %s'%(VALGRIND, CLANG)
80 CLANGCC = '%s %s'%(VALGRIND, CLANGCC)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000081
82 # Create the output directory if it does not already exist.
83 mkdir_p(os.path.dirname(OUTPUT))
84
85 # FIXME
86 #ulimit -t 40
87
88 # FIXME: Load script once
89 # FIXME: Support "short" script syntax
90
91 if useScript:
92 scriptFile = useScript
93 else:
94 # See if we have a per-dir test script.
95 dirScriptFile = os.path.join(os.path.dirname(FILENAME), 'test.script')
96 if os.path.exists(dirScriptFile):
97 scriptFile = dirScriptFile
98 else:
99 scriptFile = FILENAME
100
101 # Verify the script contains a run line.
102 for ln in open(scriptFile):
103 if 'RUN:' in ln:
104 break
105 else:
106 print >>output, "******************** TEST '%s' HAS NO RUN LINE! ********************"%(TESTNAME,)
107 output.flush()
108 return TestStatus.NoRunLine
109
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000110 FILENAME = os.path.abspath(FILENAME)
111 SCRIPT = OUTPUT + '.script'
112 TEMPOUTPUT = OUTPUT + '.tmp'
113
114 substitutions = [('%s',SUBST),
Daniel Dunbar4d8076a2009-04-10 19:49:21 +0000115 ('%S',os.path.dirname(SUBST)),
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000116 ('%llvmgcc','llvm-gcc -emit-llvm -w'),
117 ('%llvmgxx','llvm-g++ -emit-llvm -w'),
118 ('%prcontext','prcontext.tcl'),
119 ('%t',TEMPOUTPUT),
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000120 (' clang ', ' ' + CLANG + ' '),
121 (' clang-cc ', ' ' + CLANGCC + ' ')]
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000122 scriptLines = []
123 xfailLines = []
124 for ln in open(scriptFile):
125 if 'RUN:' in ln:
126 # Isolate run parameters
127 index = ln.index('RUN:')
128 ln = ln[index+4:]
129
130 # Apply substitutions
131 for a,b in substitutions:
132 ln = ln.replace(a,b)
133
134 if useDGCompat:
135 ln = re.sub(r'\{(.*)\}', r'"\1"', ln)
136 scriptLines.append(ln)
137 elif 'XFAIL' in ln:
138 xfailLines.append(ln)
139
140 if xfailLines:
141 print >>output, "XFAILED '%s':"%(TESTNAME,)
142 output.writelines(xfailLines)
143
144 # Write script file
145 f = open(SCRIPT,'w')
146 f.write(''.join(scriptLines))
147 f.close()
148
149 outputFile = open(OUTPUT,'w')
150 p = None
151 try:
152 p = subprocess.Popen(["/bin/sh",SCRIPT],
153 cwd=os.path.dirname(FILENAME),
Daniel Dunbar02cee152009-07-13 21:24:28 +0000154 stdin=subprocess.PIPE,
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000155 stdout=subprocess.PIPE,
156 stderr=subprocess.PIPE)
157 out,err = p.communicate()
158 outputFile.write(out)
159 outputFile.write(err)
160 SCRIPT_STATUS = p.wait()
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000161
162 # Detect Ctrl-C in subprocess.
163 if SCRIPT_STATUS == -signal.SIGINT:
164 raise KeyboardInterrupt
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000165 except KeyboardInterrupt:
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000166 raise
167 outputFile.close()
168
169 if xfailLines:
170 SCRIPT_STATUS = not SCRIPT_STATUS
171
172 if useValgrind:
Nuno Lopesa7afc452009-07-11 18:34:43 +0000173 VG_OUTPUT = capture(['/bin/sh','-c','cat %s.*'%(VG_OUTPUT)])
174 VG_STATUS = len(VG_OUTPUT)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000175 else:
176 VG_STATUS = 0
177
178 if SCRIPT_STATUS or VG_STATUS:
179 print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,)
180 print >>output, "Command: "
181 output.writelines(scriptLines)
182 if not SCRIPT_STATUS:
183 print >>output, "Output:"
184 else:
185 print >>output, "Incorrect Output:"
186 cat(OUTPUT, output)
187 if VG_STATUS:
188 print >>output, "Valgrind Output:"
Nuno Lopesa7afc452009-07-11 18:34:43 +0000189 print >>output, VG_OUTPUT
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000190 print >>output, "******************** TEST '%s' FAILED! ********************"%(TESTNAME,)
191 output.flush()
192 if xfailLines:
193 return TestStatus.XPass
194 else:
195 return TestStatus.Fail
196
197 if xfailLines:
198 return TestStatus.XFail
199 else:
200 return TestStatus.Pass
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000201
202def capture(args):
203 p = subprocess.Popen(args, stdout=subprocess.PIPE)
204 out,_ = p.communicate()
205 return out
206
207def which(command):
208 # Would be nice if Python had a lib function for this.
209 res = capture(['which',command])
210 res = res.strip()
211 if res and os.path.exists(res):
212 return res
213 return None
214
215def inferClang():
216 # Determine which clang to use.
217 clang = os.getenv('CLANG')
218
219 # If the user set clang in the environment, definitely use that and don't
220 # try to validate.
221 if clang:
222 return clang
223
224 # Otherwise look in the path.
225 clang = which('clang')
226
227 if not clang:
228 print >>sys.stderr, "error: couldn't find 'clang' program, try setting CLANG in your environment"
229 sys.exit(1)
230
231 return clang
232
233def inferClangCC(clang):
234 clangcc = os.getenv('CLANGCC')
235
236 # If the user set clang in the environment, definitely use that and don't
237 # try to validate.
238 if clangcc:
239 return clangcc
240
241 # Otherwise try adding -cc since we expect to be looking in a build
242 # directory.
243 clangcc = which(clang + '-cc')
244 if not clangcc:
245 # Otherwise ask clang.
246 res = capture([clang, '-print-prog-name=clang-cc'])
247 res = res.strip()
248 if res and os.path.exists(res):
249 clangcc = res
250
251 if not clangcc:
252 print >>sys.stderr, "error: couldn't find 'clang-cc' program, try setting CLANGCC in your environment"
253 sys.exit(1)
254
255 return clangcc
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000256
257def main():
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000258 global options
259 from optparse import OptionParser
260 parser = OptionParser("usage: %prog [options] {tests}")
261 parser.add_option("", "--clang", dest="clang",
262 help="Program to use as \"clang\"",
263 action="store", default=None)
264 parser.add_option("", "--clang-cc", dest="clangcc",
265 help="Program to use as \"clang-cc\"",
266 action="store", default=None)
267 parser.add_option("", "--vg", dest="useValgrind",
268 help="Run tests under valgrind",
269 action="store_true", default=False)
270 parser.add_option("", "--dg", dest="useDGCompat",
271 help="Use llvm dejagnu compatibility mode",
272 action="store_true", default=False)
273 (opts, args) = parser.parse_args()
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000274
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000275 if not args:
276 parser.error('No tests specified')
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000277
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000278 if opts.clang is None:
279 opts.clang = inferClang()
280 if opts.clangcc is None:
281 opts.clangcc = inferClangCC(opts.clang)
282
283 for path in args:
284 command = path
285 # Use hand concatentation here because we want to override
286 # absolute paths.
287 output = 'Output/' + path + '.out'
288 testname = path
289
290 res = runOneTest(path, command, output, testname,
291 opts.clang, opts.clangcc,
292 useValgrind=opts.useValgrind,
293 useDGCompat=opts.useDGCompat,
294 useScript=os.getenv("TEST_SCRIPT"))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000295
296 sys.exit(res == TestStatus.Fail or res == TestStatus.XPass)
297
298if __name__=='__main__':
299 main()