blob: 6e15f1cd8f3aac484d1e67635055a1e021e345b7 [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
Daniel Dunbared92df02009-07-31 18:12:18 +000039 continue
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000040
Daniel Dunbar1db467f2009-07-31 05:54:17 +000041 foundOne = False
Daniel Dunbara957d992009-07-25 14:46:05 +000042 for dirpath,dirnames,filenames in os.walk(path):
43 # FIXME: This doesn't belong here
44 if 'Output' in dirnames:
45 dirnames.remove('Output')
46 for f in filenames:
47 base,ext = os.path.splitext(f)
Daniel Dunbar1db467f2009-07-31 05:54:17 +000048 if ext in cfg.suffixes:
Daniel Dunbara957d992009-07-25 14:46:05 +000049 yield os.path.join(dirpath,f)
Daniel Dunbar1db467f2009-07-31 05:54:17 +000050 foundOne = True
51 if not foundOne:
52 Util.warning('No tests in input directory %r' % path)
Daniel Dunbara957d992009-07-25 14:46:05 +000053
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000054class TestingProgressDisplay:
55 def __init__(self, opts, numTests, progressBar=None):
56 self.opts = opts
57 self.numTests = numTests
58 self.digits = len(str(self.numTests))
59 self.current = None
60 self.lock = threading.Lock()
61 self.progressBar = progressBar
62 self.progress = 0.
63
64 def update(self, index, tr):
65 # Avoid locking overhead in quiet mode
66 if self.opts.quiet and not tr.failed():
67 return
68
69 # Output lock
70 self.lock.acquire()
71 try:
72 self.handleUpdate(index, tr)
73 finally:
74 self.lock.release()
75
76 def finish(self):
77 if self.progressBar:
78 self.progressBar.clear()
79 elif self.opts.succinct:
80 sys.stdout.write('\n')
81
82 def handleUpdate(self, index, tr):
83 if self.progressBar:
84 if tr.failed():
85 self.progressBar.clear()
86 else:
87 # Force monotonicity
88 self.progress = max(self.progress, float(index)/self.numTests)
89 self.progressBar.update(self.progress, tr.path)
90 return
91 elif self.opts.succinct:
92 if not tr.failed():
93 sys.stdout.write('.')
94 sys.stdout.flush()
95 return
96 else:
97 sys.stdout.write('\n')
98
Daniel Dunbara957d992009-07-25 14:46:05 +000099 status = TestStatus.getName(tr.code).upper()
100 print '%s: %s (%*d of %*d)' % (status, tr.path,
101 self.digits, index+1,
102 self.digits, self.numTests)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000103
Daniel Dunbar360d16c2009-04-23 05:03:44 +0000104 if tr.failed() and self.opts.showOutput:
Daniel Dunbara957d992009-07-25 14:46:05 +0000105 print "%s TEST '%s' FAILED %s" % ('*'*20, tr.path, '*'*20)
106 print tr.output
107 print "*" * 20
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000108
Benjamin Kramerbbbd9862009-08-14 23:34:45 +0000109 sys.stdout.flush()
110
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000111class TestResult:
Daniel Dunbara957d992009-07-25 14:46:05 +0000112 def __init__(self, path, code, output, elapsed):
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000113 self.path = path
114 self.code = code
Daniel Dunbara957d992009-07-25 14:46:05 +0000115 self.output = output
Daniel Dunbar7f106812009-07-11 22:46:27 +0000116 self.elapsed = elapsed
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000117
118 def failed(self):
119 return self.code in (TestStatus.Fail,TestStatus.XPass)
120
121class TestProvider:
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000122 def __init__(self, config, opts, tests, display):
123 self.config = config
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000124 self.opts = opts
125 self.tests = tests
126 self.index = 0
127 self.lock = threading.Lock()
128 self.results = [None]*len(self.tests)
129 self.startTime = time.time()
130 self.progress = display
131
132 def get(self):
133 self.lock.acquire()
134 try:
135 if self.opts.maxTime is not None:
136 if time.time() - self.startTime > self.opts.maxTime:
137 return None
138 if self.index >= len(self.tests):
139 return None
140 item = self.tests[self.index],self.index
141 self.index += 1
142 return item
143 finally:
144 self.lock.release()
145
146 def setResult(self, index, result):
147 self.results[index] = result
148 self.progress.update(index, result)
149
150class Tester(threading.Thread):
151 def __init__(self, provider):
152 threading.Thread.__init__(self)
153 self.provider = provider
154
155 def run(self):
156 while 1:
157 item = self.provider.get()
158 if item is None:
159 break
160 self.runTest(item)
161
Daniel Dunbara957d992009-07-25 14:46:05 +0000162 def runTest(self, (path, index)):
Daniel Dunbardf084892009-07-25 09:53:43 +0000163 base = TestRunner.getTestOutputBase('Output', path)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000164 numTests = len(self.provider.tests)
165 digits = len(str(numTests))
166 code = None
Daniel Dunbar7f106812009-07-11 22:46:27 +0000167 elapsed = None
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000168 try:
169 opts = self.provider.opts
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000170 startTime = time.time()
171 code, output = TestRunner.runOneTest(self.provider.config,
Daniel Dunbar5928ccd2009-08-01 04:06:02 +0000172 path, base)
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000173 elapsed = time.time() - startTime
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000174 except KeyboardInterrupt:
175 # This is a sad hack. Unfortunately subprocess goes
176 # bonkers with ctrl-c and we start forking merrily.
Daniel Dunbar5928ccd2009-08-01 04:06:02 +0000177 print '\nCtrl-C detected, goodbye.'
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000178 os.kill(0,9)
179
Daniel Dunbara957d992009-07-25 14:46:05 +0000180 self.provider.setResult(index, TestResult(path, code, output, elapsed))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000181
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000182def findConfigPath(root):
183 prev = None
184 while root != prev:
185 cfg = os.path.join(root, kConfigName)
186 if os.path.exists(cfg):
187 return cfg
188
189 prev,root = root,os.path.dirname(root)
190
191 raise ValueError,"Unable to find config file %r" % kConfigName
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000192
Daniel Dunbar2bdccea2009-08-01 03:35:40 +0000193def runTests(opts, provider):
194 # If only using one testing thread, don't use threads at all; this lets us
195 # profile, among other things.
196 if opts.numThreads == 1:
197 t = Tester(provider)
198 t.run()
199 return
200
201 # Otherwise spin up the testing threads and wait for them to finish.
202 testers = [Tester(provider) for i in range(opts.numThreads)]
203 for t in testers:
204 t.start()
205 try:
206 for t in testers:
207 t.join()
208 except KeyboardInterrupt:
209 sys.exit(1)
210
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000211def main():
212 global options
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000213 from optparse import OptionParser, OptionGroup
214 parser = OptionParser("usage: %prog [options] {file-or-path}")
215
216 parser.add_option("", "--root", dest="root",
217 help="Path to root test directory",
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000218 action="store", default=None)
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000219 parser.add_option("", "--config", dest="config",
Daniel Dunbar414be142009-08-01 23:09:12 +0000220 help="Testing configuration file [default='%s']" % kConfigName,
221 action="store", default=None)
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000222
223 group = OptionGroup(parser, "Output Format")
224 # FIXME: I find these names very confusing, although I like the
225 # functionality.
226 group.add_option("-q", "--quiet", dest="quiet",
227 help="Suppress no error output",
228 action="store_true", default=False)
229 group.add_option("-s", "--succinct", dest="succinct",
230 help="Reduce amount of output",
231 action="store_true", default=False)
232 group.add_option("-v", "--verbose", dest="showOutput",
233 help="Show all test output",
234 action="store_true", default=False)
235 group.add_option("", "--no-progress-bar", dest="useProgressBar",
236 help="Do not use curses based progress bar",
237 action="store_false", default=True)
238 parser.add_option_group(group)
239
240 group = OptionGroup(parser, "Test Execution")
241 group.add_option("-j", "--threads", dest="numThreads",
242 help="Number of testing threads",
243 type=int, action="store",
244 default=None)
245 group.add_option("", "--clang", dest="clang",
246 help="Program to use as \"clang\"",
247 action="store", default=None)
248 group.add_option("", "--clang-cc", dest="clangcc",
249 help="Program to use as \"clang-cc\"",
250 action="store", default=None)
251 group.add_option("", "--path", dest="path",
252 help="Additional paths to add to testing environment",
253 action="append", type=str, default=[])
Daniel Dunbar0dec8382009-08-01 10:18:01 +0000254 group.add_option("", "--no-sh", dest="useExternalShell",
255 help="Run tests using an external shell",
256 action="store_false", default=True)
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000257 group.add_option("", "--vg", dest="useValgrind",
258 help="Run tests under valgrind",
259 action="store_true", default=False)
Daniel Dunbar6ac8b872009-09-04 02:53:50 +0000260 group.add_option("", "--vg-arg", dest="valgrindArgs",
261 help="Specify an extra argument for valgrind",
262 type=str, action="append", default=[])
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000263 group.add_option("", "--time-tests", dest="timeTests",
264 help="Track elapsed wall time for each test",
265 action="store_true", default=False)
266 parser.add_option_group(group)
267
268 group = OptionGroup(parser, "Test Selection")
269 group.add_option("", "--max-tests", dest="maxTests",
270 help="Maximum number of tests to run",
271 action="store", type=int, default=None)
272 group.add_option("", "--max-time", dest="maxTime",
273 help="Maximum time to spend testing (in seconds)",
274 action="store", type=float, default=None)
275 group.add_option("", "--shuffle", dest="shuffle",
276 help="Run tests in random order",
277 action="store_true", default=False)
278 parser.add_option_group(group)
Douglas Gregor79865192009-06-05 23:57:17 +0000279
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000280 (opts, args) = parser.parse_args()
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000281
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000282 if not args:
283 parser.error('No inputs specified')
284
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000285 if opts.numThreads is None:
286 opts.numThreads = Util.detectCPUs()
287
288 inputs = args
289
290 # Resolve root if not given, either infer it from the config file if given,
291 # otherwise from the inputs.
292 if not opts.root:
293 if opts.config:
294 opts.root = os.path.dirname(opts.config)
295 else:
Daniel Dunbar65c4b102009-08-06 01:41:03 +0000296 opts.root = os.path.commonprefix([os.path.abspath(p)
297 for p in inputs])
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000298
299 # Find the config file, if not specified.
300 if not opts.config:
301 try:
302 opts.config = findConfigPath(opts.root)
303 except ValueError,e:
304 parser.error(e.args[0])
305
306 cfg = TestingConfig.frompath(opts.config)
307
308 # Update the configuration based on the command line arguments.
309 for name in ('PATH','SYSTEMROOT'):
310 if name in cfg.environment:
311 parser.error("'%s' should not be set in configuration!" % name)
312
313 cfg.root = opts.root
314 cfg.environment['PATH'] = os.pathsep.join(opts.path +
315 [os.environ.get('PATH','')])
316 cfg.environment['SYSTEMROOT'] = os.environ.get('SYSTEMROOT','')
Daniel Dunbar67796472009-07-27 19:01:13 +0000317
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000318 if opts.clang is None:
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000319 opts.clang = TestRunner.inferClang(cfg)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000320 if opts.clangcc is None:
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000321 opts.clangcc = TestRunner.inferClangCC(cfg, opts.clang)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000322
Daniel Dunbar5928ccd2009-08-01 04:06:02 +0000323 cfg.clang = opts.clang
324 cfg.clangcc = opts.clangcc
325 cfg.useValgrind = opts.useValgrind
Daniel Dunbar6ac8b872009-09-04 02:53:50 +0000326 cfg.valgrindArgs = opts.valgrindArgs
Daniel Dunbar0dec8382009-08-01 10:18:01 +0000327 cfg.useExternalShell = opts.useExternalShell
Daniel Dunbar5928ccd2009-08-01 04:06:02 +0000328
Daniel Dunbar259a5652009-04-26 01:28:51 +0000329 # FIXME: It could be worth loading these in parallel with testing.
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000330 allTests = list(getTests(cfg, args))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000331 allTests.sort()
332
333 tests = allTests
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000334 if opts.shuffle:
335 random.shuffle(tests)
336 if opts.maxTests is not None:
337 tests = tests[:opts.maxTests]
Daniel Dunbar67796472009-07-27 19:01:13 +0000338
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000339 extra = ''
340 if len(tests) != len(allTests):
341 extra = ' of %d'%(len(allTests),)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000342 header = '-- Testing: %d%s tests, %d threads --'%(len(tests),extra,
343 opts.numThreads)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000344
345 progressBar = None
346 if not opts.quiet:
347 if opts.useProgressBar:
348 try:
349 tc = ProgressBar.TerminalController()
350 progressBar = ProgressBar.ProgressBar(tc, header)
351 except ValueError:
352 pass
353
354 if not progressBar:
355 print header
356
Daniel Dunbar2bdccea2009-08-01 03:35:40 +0000357 # Don't create more threads than tests.
358 opts.numThreads = min(len(tests), opts.numThreads)
359
360 startTime = time.time()
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000361 display = TestingProgressDisplay(opts, len(tests), progressBar)
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000362 provider = TestProvider(cfg, opts, tests, display)
Daniel Dunbar2bdccea2009-08-01 03:35:40 +0000363 runTests(opts, provider)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000364 display.finish()
365
366 if not opts.quiet:
367 print 'Testing Time: %.2fs'%(time.time() - startTime)
368
Daniel Dunbar1db467f2009-07-31 05:54:17 +0000369 # List test results organized by kind.
Daniel Dunbar259a5652009-04-26 01:28:51 +0000370 byCode = {}
371 for t in provider.results:
372 if t:
373 if t.code not in byCode:
374 byCode[t.code] = []
375 byCode[t.code].append(t)
Daniel Dunbarcddab4a2009-07-30 01:57:45 +0000376 for title,code in (('Unexpected Passing Tests', TestStatus.XPass),
Daniel Dunbar259a5652009-04-26 01:28:51 +0000377 ('Failing Tests', TestStatus.Fail)):
378 elts = byCode.get(code)
379 if not elts:
380 continue
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000381 print '*'*20
Daniel Dunbar259a5652009-04-26 01:28:51 +0000382 print '%s (%d):' % (title, len(elts))
383 for tr in elts:
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000384 print '\t%s'%(tr.path,)
385
Daniel Dunbar259a5652009-04-26 01:28:51 +0000386 numFailures = len(byCode.get(TestStatus.Fail,[]))
387 if numFailures:
388 print '\nFailures: %d' % (numFailures,)
Douglas Gregor4d1800d2009-06-16 23:40:23 +0000389 sys.exit(1)
390
Daniel Dunbar7f106812009-07-11 22:46:27 +0000391 if opts.timeTests:
Daniel Dunbar3ed4bd12009-07-16 21:18:21 +0000392 print '\nTest Times:'
Daniel Dunbar7f106812009-07-11 22:46:27 +0000393 provider.results.sort(key=lambda t: t and t.elapsed)
394 for tr in provider.results:
395 if tr:
396 print '%.2fs: %s' % (tr.elapsed, tr.path)
397
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000398if __name__=='__main__':
399 main()