blob: 04bd74f65d362901403d7a1d7502a12772e6ba42 [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
12"""
13
14# TOD
15import os, sys, re, random, time
16import threading
17import ProgressBar
18import TestRunner
19from TestRunner import TestStatus
20from Queue import Queue
21
22kTestFileExtensions = set(['.mi','.i','.c','.cpp','.m','.mm','.ll'])
23
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000024def getTests(inputs):
25 for path in inputs:
26 if not os.path.exists(path):
27 print >>sys.stderr,"WARNING: Invalid test \"%s\""%(path,)
28 continue
29
Daniel Dunbara957d992009-07-25 14:46:05 +000030 if not os.path.isdir(path):
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000031 yield path
32
Daniel Dunbara957d992009-07-25 14:46:05 +000033 for dirpath,dirnames,filenames in os.walk(path):
34 # FIXME: This doesn't belong here
35 if 'Output' in dirnames:
36 dirnames.remove('Output')
37 for f in filenames:
38 base,ext = os.path.splitext(f)
39 if ext in kTestFileExtensions:
40 yield os.path.join(dirpath,f)
41
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000042class TestingProgressDisplay:
43 def __init__(self, opts, numTests, progressBar=None):
44 self.opts = opts
45 self.numTests = numTests
46 self.digits = len(str(self.numTests))
47 self.current = None
48 self.lock = threading.Lock()
49 self.progressBar = progressBar
50 self.progress = 0.
51
52 def update(self, index, tr):
53 # Avoid locking overhead in quiet mode
54 if self.opts.quiet and not tr.failed():
55 return
56
57 # Output lock
58 self.lock.acquire()
59 try:
60 self.handleUpdate(index, tr)
61 finally:
62 self.lock.release()
63
64 def finish(self):
65 if self.progressBar:
66 self.progressBar.clear()
67 elif self.opts.succinct:
68 sys.stdout.write('\n')
69
70 def handleUpdate(self, index, tr):
71 if self.progressBar:
72 if tr.failed():
73 self.progressBar.clear()
74 else:
75 # Force monotonicity
76 self.progress = max(self.progress, float(index)/self.numTests)
77 self.progressBar.update(self.progress, tr.path)
78 return
79 elif self.opts.succinct:
80 if not tr.failed():
81 sys.stdout.write('.')
82 sys.stdout.flush()
83 return
84 else:
85 sys.stdout.write('\n')
86
Daniel Dunbara957d992009-07-25 14:46:05 +000087 status = TestStatus.getName(tr.code).upper()
88 print '%s: %s (%*d of %*d)' % (status, tr.path,
89 self.digits, index+1,
90 self.digits, self.numTests)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000091
Daniel Dunbar360d16c2009-04-23 05:03:44 +000092 if tr.failed() and self.opts.showOutput:
Daniel Dunbara957d992009-07-25 14:46:05 +000093 print "%s TEST '%s' FAILED %s" % ('*'*20, tr.path, '*'*20)
94 print tr.output
95 print "*" * 20
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000096
97class TestResult:
Daniel Dunbara957d992009-07-25 14:46:05 +000098 def __init__(self, path, code, output, elapsed):
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000099 self.path = path
100 self.code = code
Daniel Dunbara957d992009-07-25 14:46:05 +0000101 self.output = output
Daniel Dunbar7f106812009-07-11 22:46:27 +0000102 self.elapsed = elapsed
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000103
104 def failed(self):
105 return self.code in (TestStatus.Fail,TestStatus.XPass)
106
107class TestProvider:
108 def __init__(self, opts, tests, display):
109 self.opts = opts
110 self.tests = tests
111 self.index = 0
112 self.lock = threading.Lock()
113 self.results = [None]*len(self.tests)
114 self.startTime = time.time()
115 self.progress = display
116
117 def get(self):
118 self.lock.acquire()
119 try:
120 if self.opts.maxTime is not None:
121 if time.time() - self.startTime > self.opts.maxTime:
122 return None
123 if self.index >= len(self.tests):
124 return None
125 item = self.tests[self.index],self.index
126 self.index += 1
127 return item
128 finally:
129 self.lock.release()
130
131 def setResult(self, index, result):
132 self.results[index] = result
133 self.progress.update(index, result)
134
135class Tester(threading.Thread):
136 def __init__(self, provider):
137 threading.Thread.__init__(self)
138 self.provider = provider
139
140 def run(self):
141 while 1:
142 item = self.provider.get()
143 if item is None:
144 break
145 self.runTest(item)
146
Daniel Dunbara957d992009-07-25 14:46:05 +0000147 def runTest(self, (path, index)):
Daniel Dunbardf084892009-07-25 09:53:43 +0000148 base = TestRunner.getTestOutputBase('Output', path)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000149 numTests = len(self.provider.tests)
150 digits = len(str(numTests))
151 code = None
Daniel Dunbar7f106812009-07-11 22:46:27 +0000152 elapsed = None
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000153 try:
154 opts = self.provider.opts
155 if opts.debugDoNotTest:
156 code = None
157 else:
Daniel Dunbar7f106812009-07-11 22:46:27 +0000158 startTime = time.time()
Daniel Dunbara957d992009-07-25 14:46:05 +0000159 code, output = TestRunner.runOneTest(path, base,
160 opts.clang, opts.clangcc)
Daniel Dunbar7f106812009-07-11 22:46:27 +0000161 elapsed = time.time() - startTime
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000162 except KeyboardInterrupt:
163 # This is a sad hack. Unfortunately subprocess goes
164 # bonkers with ctrl-c and we start forking merrily.
165 print 'Ctrl-C detected, goodbye.'
166 os.kill(0,9)
167
Daniel Dunbara957d992009-07-25 14:46:05 +0000168 self.provider.setResult(index, TestResult(path, code, output, elapsed))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000169
170def detectCPUs():
171 """
172 Detects the number of CPUs on a system. Cribbed from pp.
173 """
174 # Linux, Unix and MacOS:
175 if hasattr(os, "sysconf"):
176 if os.sysconf_names.has_key("SC_NPROCESSORS_ONLN"):
177 # Linux & Unix:
178 ncpus = os.sysconf("SC_NPROCESSORS_ONLN")
179 if isinstance(ncpus, int) and ncpus > 0:
180 return ncpus
181 else: # OSX:
182 return int(os.popen2("sysctl -n hw.ncpu")[1].read())
183 # Windows:
184 if os.environ.has_key("NUMBER_OF_PROCESSORS"):
185 ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]);
186 if ncpus > 0:
187 return ncpus
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000188 return 1 # Default
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000189
190def main():
191 global options
192 from optparse import OptionParser
193 parser = OptionParser("usage: %prog [options] {inputs}")
194 parser.add_option("-j", "--threads", dest="numThreads",
195 help="Number of testing threads",
196 type=int, action="store",
197 default=detectCPUs())
198 parser.add_option("", "--clang", dest="clang",
199 help="Program to use as \"clang\"",
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000200 action="store", default=None)
201 parser.add_option("", "--clang-cc", dest="clangcc",
202 help="Program to use as \"clang-cc\"",
203 action="store", default=None)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000204 parser.add_option("", "--vg", dest="useValgrind",
205 help="Run tests under valgrind",
206 action="store_true", default=False)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000207 parser.add_option("-v", "--verbose", dest="showOutput",
208 help="Show all test output",
209 action="store_true", default=False)
210 parser.add_option("-q", "--quiet", dest="quiet",
211 help="Suppress no error output",
212 action="store_true", default=False)
213 parser.add_option("-s", "--succinct", dest="succinct",
214 help="Reduce amount of output",
215 action="store_true", default=False)
216 parser.add_option("", "--max-tests", dest="maxTests",
217 help="Maximum number of tests to run",
218 action="store", type=int, default=None)
219 parser.add_option("", "--max-time", dest="maxTime",
220 help="Maximum time to spend testing (in seconds)",
221 action="store", type=float, default=None)
222 parser.add_option("", "--shuffle", dest="shuffle",
223 help="Run tests in random order",
224 action="store_true", default=False)
225 parser.add_option("", "--seed", dest="seed",
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000226 help="Seed for random number generator (default: random)",
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000227 action="store", default=None)
228 parser.add_option("", "--no-progress-bar", dest="useProgressBar",
229 help="Do not use curses based progress bar",
230 action="store_false", default=True)
231 parser.add_option("", "--debug-do-not-test", dest="debugDoNotTest",
232 help="DEBUG: Skip running actual test script",
233 action="store_true", default=False)
Daniel Dunbar7f106812009-07-11 22:46:27 +0000234 parser.add_option("", "--time-tests", dest="timeTests",
235 help="Track elapsed wall time for each test",
236 action="store_true", default=False)
Douglas Gregor79865192009-06-05 23:57:17 +0000237 parser.add_option("", "--path", dest="path",
238 help="Additional paths to add to testing environment",
239 action="store", type=str, default=None)
240
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000241 (opts, args) = parser.parse_args()
242
243 if not args:
244 parser.error('No inputs specified')
Daniel Dunbar11980cc2009-07-25 13:13:06 +0000245 if opts.useValgrind:
246 parser.error('Support for running with valgrind is '
247 'temporarily disabled')
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000248
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000249 if opts.clang is None:
250 opts.clang = TestRunner.inferClang()
251 if opts.clangcc is None:
252 opts.clangcc = TestRunner.inferClangCC(opts.clang)
253
Daniel Dunbar259a5652009-04-26 01:28:51 +0000254 # FIXME: It could be worth loading these in parallel with testing.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000255 allTests = list(getTests(args))
256 allTests.sort()
257
258 tests = allTests
259 if opts.seed is not None:
260 try:
261 seed = int(opts.seed)
262 except:
263 parser.error('--seed argument should be an integer')
264 random.seed(seed)
265 if opts.shuffle:
266 random.shuffle(tests)
267 if opts.maxTests is not None:
268 tests = tests[:opts.maxTests]
Douglas Gregor79865192009-06-05 23:57:17 +0000269 if opts.path is not None:
270 os.environ["PATH"] = opts.path + ":" + os.environ["PATH"];
Douglas Gregor79865192009-06-05 23:57:17 +0000271
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000272 extra = ''
273 if len(tests) != len(allTests):
274 extra = ' of %d'%(len(allTests),)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000275 header = '-- Testing: %d%s tests, %d threads --'%(len(tests),extra,
276 opts.numThreads)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000277
278 progressBar = None
279 if not opts.quiet:
280 if opts.useProgressBar:
281 try:
282 tc = ProgressBar.TerminalController()
283 progressBar = ProgressBar.ProgressBar(tc, header)
284 except ValueError:
285 pass
286
287 if not progressBar:
288 print header
289
290 display = TestingProgressDisplay(opts, len(tests), progressBar)
291 provider = TestProvider(opts, tests, display)
292
293 testers = [Tester(provider) for i in range(opts.numThreads)]
294 startTime = time.time()
295 for t in testers:
296 t.start()
297 try:
298 for t in testers:
299 t.join()
300 except KeyboardInterrupt:
301 sys.exit(1)
302
303 display.finish()
304
305 if not opts.quiet:
306 print 'Testing Time: %.2fs'%(time.time() - startTime)
307
Daniel Dunbar259a5652009-04-26 01:28:51 +0000308 # List test results organized organized by kind.
309 byCode = {}
310 for t in provider.results:
311 if t:
312 if t.code not in byCode:
313 byCode[t.code] = []
314 byCode[t.code].append(t)
315 for title,code in (('Expected Failures', TestStatus.XFail),
316 ('Unexpected Passing Tests', TestStatus.XPass),
317 ('Failing Tests', TestStatus.Fail)):
318 elts = byCode.get(code)
319 if not elts:
320 continue
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000321 print '*'*20
Daniel Dunbar259a5652009-04-26 01:28:51 +0000322 print '%s (%d):' % (title, len(elts))
323 for tr in elts:
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000324 print '\t%s'%(tr.path,)
325
Daniel Dunbar259a5652009-04-26 01:28:51 +0000326 numFailures = len(byCode.get(TestStatus.Fail,[]))
327 if numFailures:
328 print '\nFailures: %d' % (numFailures,)
Douglas Gregor4d1800d2009-06-16 23:40:23 +0000329 sys.exit(1)
330
Daniel Dunbar7f106812009-07-11 22:46:27 +0000331 if opts.timeTests:
Daniel Dunbar3ed4bd12009-07-16 21:18:21 +0000332 print '\nTest Times:'
Daniel Dunbar7f106812009-07-11 22:46:27 +0000333 provider.results.sort(key=lambda t: t and t.elapsed)
334 for tr in provider.results:
335 if tr:
336 print '%.2fs: %s' % (tr.elapsed, tr.path)
337
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000338if __name__=='__main__':
339 main()