blob: 3ac40e15c13a1f33fbc1cb81532e8cffd86ecbba [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
18# TOD
19import os, sys, re, random, time
20import threading
21import ProgressBar
22import TestRunner
23from TestRunner import TestStatus
24from Queue import Queue
25
26kTestFileExtensions = set(['.mi','.i','.c','.cpp','.m','.mm','.ll'])
27
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000028def getTests(inputs):
29 for path in inputs:
30 if not os.path.exists(path):
31 print >>sys.stderr,"WARNING: Invalid test \"%s\""%(path,)
32 continue
33
Daniel Dunbara957d992009-07-25 14:46:05 +000034 if not os.path.isdir(path):
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000035 yield path
36
Daniel Dunbara957d992009-07-25 14:46:05 +000037 for dirpath,dirnames,filenames in os.walk(path):
38 # FIXME: This doesn't belong here
39 if 'Output' in dirnames:
40 dirnames.remove('Output')
41 for f in filenames:
42 base,ext = os.path.splitext(f)
43 if ext in kTestFileExtensions:
44 yield os.path.join(dirpath,f)
45
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000046class TestingProgressDisplay:
47 def __init__(self, opts, numTests, progressBar=None):
48 self.opts = opts
49 self.numTests = numTests
50 self.digits = len(str(self.numTests))
51 self.current = None
52 self.lock = threading.Lock()
53 self.progressBar = progressBar
54 self.progress = 0.
55
56 def update(self, index, tr):
57 # Avoid locking overhead in quiet mode
58 if self.opts.quiet and not tr.failed():
59 return
60
61 # Output lock
62 self.lock.acquire()
63 try:
64 self.handleUpdate(index, tr)
65 finally:
66 self.lock.release()
67
68 def finish(self):
69 if self.progressBar:
70 self.progressBar.clear()
71 elif self.opts.succinct:
72 sys.stdout.write('\n')
73
74 def handleUpdate(self, index, tr):
75 if self.progressBar:
76 if tr.failed():
77 self.progressBar.clear()
78 else:
79 # Force monotonicity
80 self.progress = max(self.progress, float(index)/self.numTests)
81 self.progressBar.update(self.progress, tr.path)
82 return
83 elif self.opts.succinct:
84 if not tr.failed():
85 sys.stdout.write('.')
86 sys.stdout.flush()
87 return
88 else:
89 sys.stdout.write('\n')
90
Daniel Dunbara957d992009-07-25 14:46:05 +000091 status = TestStatus.getName(tr.code).upper()
92 print '%s: %s (%*d of %*d)' % (status, tr.path,
93 self.digits, index+1,
94 self.digits, self.numTests)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000095
Daniel Dunbar360d16c2009-04-23 05:03:44 +000096 if tr.failed() and self.opts.showOutput:
Daniel Dunbara957d992009-07-25 14:46:05 +000097 print "%s TEST '%s' FAILED %s" % ('*'*20, tr.path, '*'*20)
98 print tr.output
99 print "*" * 20
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000100
101class TestResult:
Daniel Dunbara957d992009-07-25 14:46:05 +0000102 def __init__(self, path, code, output, elapsed):
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000103 self.path = path
104 self.code = code
Daniel Dunbara957d992009-07-25 14:46:05 +0000105 self.output = output
Daniel Dunbar7f106812009-07-11 22:46:27 +0000106 self.elapsed = elapsed
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000107
108 def failed(self):
109 return self.code in (TestStatus.Fail,TestStatus.XPass)
110
111class TestProvider:
112 def __init__(self, opts, tests, display):
113 self.opts = opts
114 self.tests = tests
115 self.index = 0
116 self.lock = threading.Lock()
117 self.results = [None]*len(self.tests)
118 self.startTime = time.time()
119 self.progress = display
120
121 def get(self):
122 self.lock.acquire()
123 try:
124 if self.opts.maxTime is not None:
125 if time.time() - self.startTime > self.opts.maxTime:
126 return None
127 if self.index >= len(self.tests):
128 return None
129 item = self.tests[self.index],self.index
130 self.index += 1
131 return item
132 finally:
133 self.lock.release()
134
135 def setResult(self, index, result):
136 self.results[index] = result
137 self.progress.update(index, result)
138
139class Tester(threading.Thread):
140 def __init__(self, provider):
141 threading.Thread.__init__(self)
142 self.provider = provider
143
144 def run(self):
145 while 1:
146 item = self.provider.get()
147 if item is None:
148 break
149 self.runTest(item)
150
Daniel Dunbara957d992009-07-25 14:46:05 +0000151 def runTest(self, (path, index)):
Daniel Dunbardf084892009-07-25 09:53:43 +0000152 base = TestRunner.getTestOutputBase('Output', path)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000153 numTests = len(self.provider.tests)
154 digits = len(str(numTests))
155 code = None
Daniel Dunbar7f106812009-07-11 22:46:27 +0000156 elapsed = None
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000157 try:
158 opts = self.provider.opts
159 if opts.debugDoNotTest:
160 code = None
161 else:
Daniel Dunbar7f106812009-07-11 22:46:27 +0000162 startTime = time.time()
Daniel Dunbara957d992009-07-25 14:46:05 +0000163 code, output = TestRunner.runOneTest(path, base,
Daniel Dunbar9a676b72009-07-29 02:57:25 +0000164 opts.clang, opts.clangcc,
165 opts.useValgrind)
Daniel Dunbar7f106812009-07-11 22:46:27 +0000166 elapsed = time.time() - startTime
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000167 except KeyboardInterrupt:
168 # This is a sad hack. Unfortunately subprocess goes
169 # bonkers with ctrl-c and we start forking merrily.
170 print 'Ctrl-C detected, goodbye.'
171 os.kill(0,9)
172
Daniel Dunbara957d992009-07-25 14:46:05 +0000173 self.provider.setResult(index, TestResult(path, code, output, elapsed))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000174
175def detectCPUs():
176 """
177 Detects the number of CPUs on a system. Cribbed from pp.
178 """
179 # Linux, Unix and MacOS:
180 if hasattr(os, "sysconf"):
181 if os.sysconf_names.has_key("SC_NPROCESSORS_ONLN"):
182 # Linux & Unix:
183 ncpus = os.sysconf("SC_NPROCESSORS_ONLN")
184 if isinstance(ncpus, int) and ncpus > 0:
185 return ncpus
186 else: # OSX:
187 return int(os.popen2("sysctl -n hw.ncpu")[1].read())
188 # Windows:
189 if os.environ.has_key("NUMBER_OF_PROCESSORS"):
190 ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]);
191 if ncpus > 0:
192 return ncpus
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000193 return 1 # Default
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000194
195def main():
196 global options
197 from optparse import OptionParser
198 parser = OptionParser("usage: %prog [options] {inputs}")
199 parser.add_option("-j", "--threads", dest="numThreads",
200 help="Number of testing threads",
201 type=int, action="store",
202 default=detectCPUs())
203 parser.add_option("", "--clang", dest="clang",
204 help="Program to use as \"clang\"",
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000205 action="store", default=None)
206 parser.add_option("", "--clang-cc", dest="clangcc",
207 help="Program to use as \"clang-cc\"",
208 action="store", default=None)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000209 parser.add_option("", "--vg", dest="useValgrind",
210 help="Run tests under valgrind",
211 action="store_true", default=False)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000212 parser.add_option("-v", "--verbose", dest="showOutput",
213 help="Show all test output",
214 action="store_true", default=False)
215 parser.add_option("-q", "--quiet", dest="quiet",
216 help="Suppress no error output",
217 action="store_true", default=False)
218 parser.add_option("-s", "--succinct", dest="succinct",
219 help="Reduce amount of output",
220 action="store_true", default=False)
221 parser.add_option("", "--max-tests", dest="maxTests",
222 help="Maximum number of tests to run",
223 action="store", type=int, default=None)
224 parser.add_option("", "--max-time", dest="maxTime",
225 help="Maximum time to spend testing (in seconds)",
226 action="store", type=float, default=None)
227 parser.add_option("", "--shuffle", dest="shuffle",
228 help="Run tests in random order",
229 action="store_true", default=False)
230 parser.add_option("", "--seed", dest="seed",
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000231 help="Seed for random number generator (default: random)",
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000232 action="store", default=None)
233 parser.add_option("", "--no-progress-bar", dest="useProgressBar",
234 help="Do not use curses based progress bar",
235 action="store_false", default=True)
236 parser.add_option("", "--debug-do-not-test", dest="debugDoNotTest",
237 help="DEBUG: Skip running actual test script",
238 action="store_true", default=False)
Daniel Dunbar7f106812009-07-11 22:46:27 +0000239 parser.add_option("", "--time-tests", dest="timeTests",
240 help="Track elapsed wall time for each test",
241 action="store_true", default=False)
Douglas Gregor79865192009-06-05 23:57:17 +0000242 parser.add_option("", "--path", dest="path",
243 help="Additional paths to add to testing environment",
Daniel Dunbar67796472009-07-27 19:01:13 +0000244 action="append", type=str, default=[])
Douglas Gregor79865192009-06-05 23:57:17 +0000245
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000246 (opts, args) = parser.parse_args()
247
248 if not args:
249 parser.error('No inputs specified')
250
Daniel Dunbar67796472009-07-27 19:01:13 +0000251 # FIXME: Move into configuration object.
252 TestRunner.kChildEnv["PATH"] = os.pathsep.join(opts.path +
253 [TestRunner.kChildEnv['PATH']])
254
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000255 if opts.clang is None:
256 opts.clang = TestRunner.inferClang()
257 if opts.clangcc is None:
258 opts.clangcc = TestRunner.inferClangCC(opts.clang)
259
Daniel Dunbar259a5652009-04-26 01:28:51 +0000260 # FIXME: It could be worth loading these in parallel with testing.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000261 allTests = list(getTests(args))
262 allTests.sort()
263
264 tests = allTests
265 if opts.seed is not None:
266 try:
267 seed = int(opts.seed)
268 except:
269 parser.error('--seed argument should be an integer')
270 random.seed(seed)
271 if opts.shuffle:
272 random.shuffle(tests)
273 if opts.maxTests is not None:
274 tests = tests[:opts.maxTests]
Daniel Dunbar67796472009-07-27 19:01:13 +0000275
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000276 extra = ''
277 if len(tests) != len(allTests):
278 extra = ' of %d'%(len(allTests),)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000279 header = '-- Testing: %d%s tests, %d threads --'%(len(tests),extra,
280 opts.numThreads)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000281
282 progressBar = None
283 if not opts.quiet:
284 if opts.useProgressBar:
285 try:
286 tc = ProgressBar.TerminalController()
287 progressBar = ProgressBar.ProgressBar(tc, header)
288 except ValueError:
289 pass
290
291 if not progressBar:
292 print header
293
294 display = TestingProgressDisplay(opts, len(tests), progressBar)
295 provider = TestProvider(opts, tests, display)
296
297 testers = [Tester(provider) for i in range(opts.numThreads)]
298 startTime = time.time()
299 for t in testers:
300 t.start()
301 try:
302 for t in testers:
303 t.join()
304 except KeyboardInterrupt:
305 sys.exit(1)
306
307 display.finish()
308
309 if not opts.quiet:
310 print 'Testing Time: %.2fs'%(time.time() - startTime)
311
Daniel Dunbar259a5652009-04-26 01:28:51 +0000312 # List test results organized organized by kind.
313 byCode = {}
314 for t in provider.results:
315 if t:
316 if t.code not in byCode:
317 byCode[t.code] = []
318 byCode[t.code].append(t)
319 for title,code in (('Expected Failures', TestStatus.XFail),
320 ('Unexpected Passing Tests', TestStatus.XPass),
321 ('Failing Tests', TestStatus.Fail)):
322 elts = byCode.get(code)
323 if not elts:
324 continue
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000325 print '*'*20
Daniel Dunbar259a5652009-04-26 01:28:51 +0000326 print '%s (%d):' % (title, len(elts))
327 for tr in elts:
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000328 print '\t%s'%(tr.path,)
329
Daniel Dunbar259a5652009-04-26 01:28:51 +0000330 numFailures = len(byCode.get(TestStatus.Fail,[]))
331 if numFailures:
332 print '\nFailures: %d' % (numFailures,)
Douglas Gregor4d1800d2009-06-16 23:40:23 +0000333 sys.exit(1)
334
Daniel Dunbar7f106812009-07-11 22:46:27 +0000335 if opts.timeTests:
Daniel Dunbar3ed4bd12009-07-16 21:18:21 +0000336 print '\nTest Times:'
Daniel Dunbar7f106812009-07-11 22:46:27 +0000337 provider.results.sort(key=lambda t: t and t.elapsed)
338 for tr in provider.results:
339 if tr:
340 print '%.2fs: %s' % (tr.elapsed, tr.path)
341
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000342if __name__=='__main__':
343 main()