blob: 8b48d8d6c751443474038993795e3bf5d375526b [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",
Daniel Dunbar67796472009-07-27 19:01:13 +0000239 action="append", type=str, default=[])
Douglas Gregor79865192009-06-05 23:57:17 +0000240
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 Dunbar67796472009-07-27 19:01:13 +0000249 # FIXME: Move into configuration object.
250 TestRunner.kChildEnv["PATH"] = os.pathsep.join(opts.path +
251 [TestRunner.kChildEnv['PATH']])
252
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000253 if opts.clang is None:
254 opts.clang = TestRunner.inferClang()
255 if opts.clangcc is None:
256 opts.clangcc = TestRunner.inferClangCC(opts.clang)
257
Daniel Dunbar259a5652009-04-26 01:28:51 +0000258 # FIXME: It could be worth loading these in parallel with testing.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000259 allTests = list(getTests(args))
260 allTests.sort()
261
262 tests = allTests
263 if opts.seed is not None:
264 try:
265 seed = int(opts.seed)
266 except:
267 parser.error('--seed argument should be an integer')
268 random.seed(seed)
269 if opts.shuffle:
270 random.shuffle(tests)
271 if opts.maxTests is not None:
272 tests = tests[:opts.maxTests]
Daniel Dunbar67796472009-07-27 19:01:13 +0000273
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000274 extra = ''
275 if len(tests) != len(allTests):
276 extra = ' of %d'%(len(allTests),)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000277 header = '-- Testing: %d%s tests, %d threads --'%(len(tests),extra,
278 opts.numThreads)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000279
280 progressBar = None
281 if not opts.quiet:
282 if opts.useProgressBar:
283 try:
284 tc = ProgressBar.TerminalController()
285 progressBar = ProgressBar.ProgressBar(tc, header)
286 except ValueError:
287 pass
288
289 if not progressBar:
290 print header
291
292 display = TestingProgressDisplay(opts, len(tests), progressBar)
293 provider = TestProvider(opts, tests, display)
294
295 testers = [Tester(provider) for i in range(opts.numThreads)]
296 startTime = time.time()
297 for t in testers:
298 t.start()
299 try:
300 for t in testers:
301 t.join()
302 except KeyboardInterrupt:
303 sys.exit(1)
304
305 display.finish()
306
307 if not opts.quiet:
308 print 'Testing Time: %.2fs'%(time.time() - startTime)
309
Daniel Dunbar259a5652009-04-26 01:28:51 +0000310 # List test results organized organized by kind.
311 byCode = {}
312 for t in provider.results:
313 if t:
314 if t.code not in byCode:
315 byCode[t.code] = []
316 byCode[t.code].append(t)
317 for title,code in (('Expected Failures', TestStatus.XFail),
318 ('Unexpected Passing Tests', TestStatus.XPass),
319 ('Failing Tests', TestStatus.Fail)):
320 elts = byCode.get(code)
321 if not elts:
322 continue
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000323 print '*'*20
Daniel Dunbar259a5652009-04-26 01:28:51 +0000324 print '%s (%d):' % (title, len(elts))
325 for tr in elts:
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000326 print '\t%s'%(tr.path,)
327
Daniel Dunbar259a5652009-04-26 01:28:51 +0000328 numFailures = len(byCode.get(TestStatus.Fail,[]))
329 if numFailures:
330 print '\nFailures: %d' % (numFailures,)
Douglas Gregor4d1800d2009-06-16 23:40:23 +0000331 sys.exit(1)
332
Daniel Dunbar7f106812009-07-11 22:46:27 +0000333 if opts.timeTests:
Daniel Dunbar3ed4bd12009-07-16 21:18:21 +0000334 print '\nTest Times:'
Daniel Dunbar7f106812009-07-11 22:46:27 +0000335 provider.results.sort(key=lambda t: t and t.elapsed)
336 for tr in provider.results:
337 if tr:
338 print '%.2fs: %s' % (tr.elapsed, tr.path)
339
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000340if __name__=='__main__':
341 main()