blob: 9980e651a3f4cd8b3d6eedd740e49e829f0d15e7 [file] [log] [blame]
Eli Friedman77a1fe92009-07-10 20:15:12 +00001#!/usr/bin/env python
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +00002
3"""
4MultiTestRunner - Harness for running multiple tests in the simple clang style.
5
6TODO
7--
Daniel Dunbara0e52d62009-07-25 13:19:40 +00008 - Use configuration file for clang specific stuff
9 - Use a timeout / ulimit
10 - Detect signaled failures (abort)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000011 - Better support for finding tests
Daniel Dunbar9a676b72009-07-29 02:57:25 +000012
13 - Support "disabling" tests? The advantage of making this distinct from XFAIL
14 is it makes it more obvious that it is a temporary measure (and MTR can put
15 in a separate category).
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000016"""
17
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000018import os, sys, re, random, time
19import threading
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000020from Queue import Queue
21
Daniel Dunbar1db467f2009-07-31 05:54:17 +000022import ProgressBar
23import TestRunner
24import Util
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000025
Daniel Dunbar1db467f2009-07-31 05:54:17 +000026from TestingConfig import TestingConfig
27from TestRunner import TestStatus
28
29kConfigName = 'lit.cfg'
30
31def getTests(cfg, inputs):
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000032 for path in inputs:
33 if not os.path.exists(path):
Daniel Dunbar1db467f2009-07-31 05:54:17 +000034 Util.warning('Invalid test %r' % path)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000035 continue
36
Daniel Dunbara957d992009-07-25 14:46:05 +000037 if not os.path.isdir(path):
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000038 yield path
39
Daniel Dunbar1db467f2009-07-31 05:54:17 +000040 foundOne = False
Daniel Dunbara957d992009-07-25 14:46:05 +000041 for dirpath,dirnames,filenames in os.walk(path):
42 # FIXME: This doesn't belong here
43 if 'Output' in dirnames:
44 dirnames.remove('Output')
45 for f in filenames:
46 base,ext = os.path.splitext(f)
Daniel Dunbar1db467f2009-07-31 05:54:17 +000047 if ext in cfg.suffixes:
Daniel Dunbara957d992009-07-25 14:46:05 +000048 yield os.path.join(dirpath,f)
Daniel Dunbar1db467f2009-07-31 05:54:17 +000049 foundOne = True
50 if not foundOne:
51 Util.warning('No tests in input directory %r' % path)
Daniel Dunbara957d992009-07-25 14:46:05 +000052
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000053class TestingProgressDisplay:
54 def __init__(self, opts, numTests, progressBar=None):
55 self.opts = opts
56 self.numTests = numTests
57 self.digits = len(str(self.numTests))
58 self.current = None
59 self.lock = threading.Lock()
60 self.progressBar = progressBar
61 self.progress = 0.
62
63 def update(self, index, tr):
64 # Avoid locking overhead in quiet mode
65 if self.opts.quiet and not tr.failed():
66 return
67
68 # Output lock
69 self.lock.acquire()
70 try:
71 self.handleUpdate(index, tr)
72 finally:
73 self.lock.release()
74
75 def finish(self):
76 if self.progressBar:
77 self.progressBar.clear()
78 elif self.opts.succinct:
79 sys.stdout.write('\n')
80
81 def handleUpdate(self, index, tr):
82 if self.progressBar:
83 if tr.failed():
84 self.progressBar.clear()
85 else:
86 # Force monotonicity
87 self.progress = max(self.progress, float(index)/self.numTests)
88 self.progressBar.update(self.progress, tr.path)
89 return
90 elif self.opts.succinct:
91 if not tr.failed():
92 sys.stdout.write('.')
93 sys.stdout.flush()
94 return
95 else:
96 sys.stdout.write('\n')
97
Daniel Dunbara957d992009-07-25 14:46:05 +000098 status = TestStatus.getName(tr.code).upper()
99 print '%s: %s (%*d of %*d)' % (status, tr.path,
100 self.digits, index+1,
101 self.digits, self.numTests)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000102
Daniel Dunbar360d16c2009-04-23 05:03:44 +0000103 if tr.failed() and self.opts.showOutput:
Daniel Dunbara957d992009-07-25 14:46:05 +0000104 print "%s TEST '%s' FAILED %s" % ('*'*20, tr.path, '*'*20)
105 print tr.output
106 print "*" * 20
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000107
108class TestResult:
Daniel Dunbara957d992009-07-25 14:46:05 +0000109 def __init__(self, path, code, output, elapsed):
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000110 self.path = path
111 self.code = code
Daniel Dunbara957d992009-07-25 14:46:05 +0000112 self.output = output
Daniel Dunbar7f106812009-07-11 22:46:27 +0000113 self.elapsed = elapsed
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000114
115 def failed(self):
116 return self.code in (TestStatus.Fail,TestStatus.XPass)
117
118class TestProvider:
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000119 def __init__(self, config, opts, tests, display):
120 self.config = config
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000121 self.opts = opts
122 self.tests = tests
123 self.index = 0
124 self.lock = threading.Lock()
125 self.results = [None]*len(self.tests)
126 self.startTime = time.time()
127 self.progress = display
128
129 def get(self):
130 self.lock.acquire()
131 try:
132 if self.opts.maxTime is not None:
133 if time.time() - self.startTime > self.opts.maxTime:
134 return None
135 if self.index >= len(self.tests):
136 return None
137 item = self.tests[self.index],self.index
138 self.index += 1
139 return item
140 finally:
141 self.lock.release()
142
143 def setResult(self, index, result):
144 self.results[index] = result
145 self.progress.update(index, result)
146
147class Tester(threading.Thread):
148 def __init__(self, provider):
149 threading.Thread.__init__(self)
150 self.provider = provider
151
152 def run(self):
153 while 1:
154 item = self.provider.get()
155 if item is None:
156 break
157 self.runTest(item)
158
Daniel Dunbara957d992009-07-25 14:46:05 +0000159 def runTest(self, (path, index)):
Daniel Dunbardf084892009-07-25 09:53:43 +0000160 base = TestRunner.getTestOutputBase('Output', path)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000161 numTests = len(self.provider.tests)
162 digits = len(str(numTests))
163 code = None
Daniel Dunbar7f106812009-07-11 22:46:27 +0000164 elapsed = None
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000165 try:
166 opts = self.provider.opts
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000167 startTime = time.time()
168 code, output = TestRunner.runOneTest(self.provider.config,
169 path, base,
170 opts.clang, opts.clangcc,
171 opts.useValgrind)
172 elapsed = time.time() - startTime
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000173 except KeyboardInterrupt:
174 # This is a sad hack. Unfortunately subprocess goes
175 # bonkers with ctrl-c and we start forking merrily.
176 print 'Ctrl-C detected, goodbye.'
177 os.kill(0,9)
178
Daniel Dunbara957d992009-07-25 14:46:05 +0000179 self.provider.setResult(index, TestResult(path, code, output, elapsed))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000180
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000181def findConfigPath(root):
182 prev = None
183 while root != prev:
184 cfg = os.path.join(root, kConfigName)
185 if os.path.exists(cfg):
186 return cfg
187
188 prev,root = root,os.path.dirname(root)
189
190 raise ValueError,"Unable to find config file %r" % kConfigName
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000191
192def main():
193 global options
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000194 from optparse import OptionParser, OptionGroup
195 parser = OptionParser("usage: %prog [options] {file-or-path}")
196
197 parser.add_option("", "--root", dest="root",
198 help="Path to root test directory",
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000199 action="store", default=None)
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000200 parser.add_option("", "--config", dest="config",
201 help="Testing configuration file [default='%default']",
202 action="store", default=kConfigName)
203
204 group = OptionGroup(parser, "Output Format")
205 # FIXME: I find these names very confusing, although I like the
206 # functionality.
207 group.add_option("-q", "--quiet", dest="quiet",
208 help="Suppress no error output",
209 action="store_true", default=False)
210 group.add_option("-s", "--succinct", dest="succinct",
211 help="Reduce amount of output",
212 action="store_true", default=False)
213 group.add_option("-v", "--verbose", dest="showOutput",
214 help="Show all test output",
215 action="store_true", default=False)
216 group.add_option("", "--no-progress-bar", dest="useProgressBar",
217 help="Do not use curses based progress bar",
218 action="store_false", default=True)
219 parser.add_option_group(group)
220
221 group = OptionGroup(parser, "Test Execution")
222 group.add_option("-j", "--threads", dest="numThreads",
223 help="Number of testing threads",
224 type=int, action="store",
225 default=None)
226 group.add_option("", "--clang", dest="clang",
227 help="Program to use as \"clang\"",
228 action="store", default=None)
229 group.add_option("", "--clang-cc", dest="clangcc",
230 help="Program to use as \"clang-cc\"",
231 action="store", default=None)
232 group.add_option("", "--path", dest="path",
233 help="Additional paths to add to testing environment",
234 action="append", type=str, default=[])
235 group.add_option("", "--vg", dest="useValgrind",
236 help="Run tests under valgrind",
237 action="store_true", default=False)
238 group.add_option("", "--time-tests", dest="timeTests",
239 help="Track elapsed wall time for each test",
240 action="store_true", default=False)
241 parser.add_option_group(group)
242
243 group = OptionGroup(parser, "Test Selection")
244 group.add_option("", "--max-tests", dest="maxTests",
245 help="Maximum number of tests to run",
246 action="store", type=int, default=None)
247 group.add_option("", "--max-time", dest="maxTime",
248 help="Maximum time to spend testing (in seconds)",
249 action="store", type=float, default=None)
250 group.add_option("", "--shuffle", dest="shuffle",
251 help="Run tests in random order",
252 action="store_true", default=False)
253 parser.add_option_group(group)
Douglas Gregor79865192009-06-05 23:57:17 +0000254
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000255 (opts, args) = parser.parse_args()
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000256
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000257 if not args:
258 parser.error('No inputs specified')
259
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000260 if opts.numThreads is None:
261 opts.numThreads = Util.detectCPUs()
262
263 inputs = args
264
265 # Resolve root if not given, either infer it from the config file if given,
266 # otherwise from the inputs.
267 if not opts.root:
268 if opts.config:
269 opts.root = os.path.dirname(opts.config)
270 else:
271 opts.root = os.path.commonprefix(inputs)
272
273 # Find the config file, if not specified.
274 if not opts.config:
275 try:
276 opts.config = findConfigPath(opts.root)
277 except ValueError,e:
278 parser.error(e.args[0])
279
280 cfg = TestingConfig.frompath(opts.config)
281
282 # Update the configuration based on the command line arguments.
283 for name in ('PATH','SYSTEMROOT'):
284 if name in cfg.environment:
285 parser.error("'%s' should not be set in configuration!" % name)
286
287 cfg.root = opts.root
288 cfg.environment['PATH'] = os.pathsep.join(opts.path +
289 [os.environ.get('PATH','')])
290 cfg.environment['SYSTEMROOT'] = os.environ.get('SYSTEMROOT','')
Daniel Dunbar67796472009-07-27 19:01:13 +0000291
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000292 if opts.clang is None:
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000293 opts.clang = TestRunner.inferClang(cfg)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000294 if opts.clangcc is None:
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000295 opts.clangcc = TestRunner.inferClangCC(cfg, opts.clang)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000296
Daniel Dunbar259a5652009-04-26 01:28:51 +0000297 # FIXME: It could be worth loading these in parallel with testing.
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000298 allTests = list(getTests(cfg, args))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000299 allTests.sort()
300
301 tests = allTests
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000302 if opts.shuffle:
303 random.shuffle(tests)
304 if opts.maxTests is not None:
305 tests = tests[:opts.maxTests]
Daniel Dunbar67796472009-07-27 19:01:13 +0000306
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000307 extra = ''
308 if len(tests) != len(allTests):
309 extra = ' of %d'%(len(allTests),)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000310 header = '-- Testing: %d%s tests, %d threads --'%(len(tests),extra,
311 opts.numThreads)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000312
313 progressBar = None
314 if not opts.quiet:
315 if opts.useProgressBar:
316 try:
317 tc = ProgressBar.TerminalController()
318 progressBar = ProgressBar.ProgressBar(tc, header)
319 except ValueError:
320 pass
321
322 if not progressBar:
323 print header
324
325 display = TestingProgressDisplay(opts, len(tests), progressBar)
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000326 provider = TestProvider(cfg, opts, tests, display)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000327
328 testers = [Tester(provider) for i in range(opts.numThreads)]
329 startTime = time.time()
330 for t in testers:
331 t.start()
332 try:
333 for t in testers:
334 t.join()
335 except KeyboardInterrupt:
336 sys.exit(1)
337
338 display.finish()
339
340 if not opts.quiet:
341 print 'Testing Time: %.2fs'%(time.time() - startTime)
342
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000343 # List test results organized by kind.
Daniel Dunbar259a5652009-04-26 01:28:51 +0000344 byCode = {}
345 for t in provider.results:
346 if t:
347 if t.code not in byCode:
348 byCode[t.code] = []
349 byCode[t.code].append(t)
Daniel Dunbarcddab4a2009-07-30 01:57:45 +0000350 for title,code in (('Unexpected Passing Tests', TestStatus.XPass),
Daniel Dunbar259a5652009-04-26 01:28:51 +0000351 ('Failing Tests', TestStatus.Fail)):
352 elts = byCode.get(code)
353 if not elts:
354 continue
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000355 print '*'*20
Daniel Dunbar259a5652009-04-26 01:28:51 +0000356 print '%s (%d):' % (title, len(elts))
357 for tr in elts:
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000358 print '\t%s'%(tr.path,)
359
Daniel Dunbar259a5652009-04-26 01:28:51 +0000360 numFailures = len(byCode.get(TestStatus.Fail,[]))
361 if numFailures:
362 print '\nFailures: %d' % (numFailures,)
Douglas Gregor4d1800d2009-06-16 23:40:23 +0000363 sys.exit(1)
364
Daniel Dunbar7f106812009-07-11 22:46:27 +0000365 if opts.timeTests:
Daniel Dunbar3ed4bd12009-07-16 21:18:21 +0000366 print '\nTest Times:'
Daniel Dunbar7f106812009-07-11 22:46:27 +0000367 provider.results.sort(key=lambda t: t and t.elapsed)
368 for tr in provider.results:
369 if tr:
370 print '%.2fs: %s' % (tr.elapsed, tr.path)
371
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000372if __name__=='__main__':
373 main()