blob: db5c560c03c6ef41561f7400bfa877972edc12ef [file] [log] [blame]
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +00001#!/usr/bin/python
2
3"""
4MultiTestRunner - Harness for running multiple tests in the simple clang style.
5
6TODO
7--
8 - Fix Ctrl-c issues
9 - Use a timeout
10 - Detect signalled failures (abort)
11 - 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
24kClangErrorRE = re.compile('(.*):([0-9]+):([0-9]+): error: (.*)')
25kClangWarningRE = re.compile('(.*):([0-9]+):([0-9]+): warning: (.*)')
26kAssertionRE = re.compile('Assertion failed: (.*, function .*, file .*, line [0-9]+\\.)')
27
28def 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
34 if os.path.isdir(path):
35 for dirpath,dirnames,filenames in os.walk(path):
36 dotTests = os.path.join(dirpath,'.tests')
37 if os.path.exists(dotTests):
38 for ln in open(dotTests):
39 if ln.strip():
40 yield os.path.join(dirpath,ln.strip())
41 else:
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)
47 if ext in kTestFileExtensions:
48 yield os.path.join(dirpath,f)
49 else:
50 yield path
51
52class TestingProgressDisplay:
53 def __init__(self, opts, numTests, progressBar=None):
54 self.opts = opts
55 self.numTests = numTests
56 self.digits = len(str(self.numTests))
57 self.current = None
58 self.lock = threading.Lock()
59 self.progressBar = progressBar
60 self.progress = 0.
61
62 def update(self, index, tr):
63 # Avoid locking overhead in quiet mode
64 if self.opts.quiet and not tr.failed():
65 return
66
67 # Output lock
68 self.lock.acquire()
69 try:
70 self.handleUpdate(index, tr)
71 finally:
72 self.lock.release()
73
74 def finish(self):
75 if self.progressBar:
76 self.progressBar.clear()
77 elif self.opts.succinct:
78 sys.stdout.write('\n')
79
80 def handleUpdate(self, index, tr):
81 if self.progressBar:
82 if tr.failed():
83 self.progressBar.clear()
84 else:
85 # Force monotonicity
86 self.progress = max(self.progress, float(index)/self.numTests)
87 self.progressBar.update(self.progress, tr.path)
88 return
89 elif self.opts.succinct:
90 if not tr.failed():
91 sys.stdout.write('.')
92 sys.stdout.flush()
93 return
94 else:
95 sys.stdout.write('\n')
96
97 extra = ''
98 if tr.code==TestStatus.Invalid:
99 extra = ' - (Invalid test)'
100 elif tr.code==TestStatus.NoRunLine:
101 extra = ' - (No RUN line)'
102 elif tr.failed():
103 extra = ' - %s'%(TestStatus.getName(tr.code).upper(),)
104 print '%*d/%*d - %s%s'%(self.digits, index+1, self.digits,
105 self.numTests, tr.path, extra)
106
107 if tr.failed():
108 msgs = []
109 if tr.warnings:
110 msgs.append('%d warnings'%(len(tr.warnings),))
111 if tr.errors:
112 msgs.append('%d errors'%(len(tr.errors),))
113 if tr.assertions:
114 msgs.append('%d assertions'%(len(tr.assertions),))
115
116 if msgs:
117 print '\tFAIL (%s)'%(', '.join(msgs))
118 for i,error in enumerate(set([e for (_,_,_,e) in tr.errors])):
119 print '\t\tERROR: %s'%(error,)
120 if i>20:
121 print '\t\t\t(too many errors, skipping)'
122 break
123 for assertion in set(tr.assertions):
124 print '\t\tASSERTION: %s'%(assertion,)
125 if self.opts.showOutput:
126 TestRunner.cat(tr.testResults, sys.stdout)
127
128class TestResult:
129 def __init__(self, path, code, testResults):
130 self.path = path
131 self.code = code
132 self.testResults = testResults
133 self.warnings = []
134 self.errors = []
135 self.assertions = []
136
137 if self.failed():
138 f = open(self.testResults)
139 data = f.read()
140 f.close()
141 self.warnings = [m.groups() for m in kClangWarningRE.finditer(data)]
142 self.errors = [m.groups() for m in kClangErrorRE.finditer(data)]
143 self.assertions = [m.group(1) for m in kAssertionRE.finditer(data)]
144
145 def failed(self):
146 return self.code in (TestStatus.Fail,TestStatus.XPass)
147
148class TestProvider:
149 def __init__(self, opts, tests, display):
150 self.opts = opts
151 self.tests = tests
152 self.index = 0
153 self.lock = threading.Lock()
154 self.results = [None]*len(self.tests)
155 self.startTime = time.time()
156 self.progress = display
157
158 def get(self):
159 self.lock.acquire()
160 try:
161 if self.opts.maxTime is not None:
162 if time.time() - self.startTime > self.opts.maxTime:
163 return None
164 if self.index >= len(self.tests):
165 return None
166 item = self.tests[self.index],self.index
167 self.index += 1
168 return item
169 finally:
170 self.lock.release()
171
172 def setResult(self, index, result):
173 self.results[index] = result
174 self.progress.update(index, result)
175
176class Tester(threading.Thread):
177 def __init__(self, provider):
178 threading.Thread.__init__(self)
179 self.provider = provider
180
181 def run(self):
182 while 1:
183 item = self.provider.get()
184 if item is None:
185 break
186 self.runTest(item)
187
188 def runTest(self, (path,index)):
189 command = path
190 # Use hand concatentation here because we want to override
191 # absolute paths.
192 output = 'Output/' + path + '.out'
193 testname = path
194 testresults = 'Output/' + path + '.testresults'
195 TestRunner.mkdir_p(os.path.dirname(testresults))
196 numTests = len(self.provider.tests)
197 digits = len(str(numTests))
198 code = None
199 try:
200 opts = self.provider.opts
201 if opts.debugDoNotTest:
202 code = None
203 else:
204 code = TestRunner.runOneTest(path, command, output, testname,
205 opts.clang,
206 useValgrind=opts.useValgrind,
207 useDGCompat=opts.useDGCompat,
208 useScript=opts.testScript,
209 output=open(testresults,'w'))
210 except KeyboardInterrupt:
211 # This is a sad hack. Unfortunately subprocess goes
212 # bonkers with ctrl-c and we start forking merrily.
213 print 'Ctrl-C detected, goodbye.'
214 os.kill(0,9)
215
216 self.provider.setResult(index, TestResult(path, code, testresults))
217
218def detectCPUs():
219 """
220 Detects the number of CPUs on a system. Cribbed from pp.
221 """
222 # Linux, Unix and MacOS:
223 if hasattr(os, "sysconf"):
224 if os.sysconf_names.has_key("SC_NPROCESSORS_ONLN"):
225 # Linux & Unix:
226 ncpus = os.sysconf("SC_NPROCESSORS_ONLN")
227 if isinstance(ncpus, int) and ncpus > 0:
228 return ncpus
229 else: # OSX:
230 return int(os.popen2("sysctl -n hw.ncpu")[1].read())
231 # Windows:
232 if os.environ.has_key("NUMBER_OF_PROCESSORS"):
233 ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]);
234 if ncpus > 0:
235 return ncpus
236 return 1 # Default
237
238def main():
239 global options
240 from optparse import OptionParser
241 parser = OptionParser("usage: %prog [options] {inputs}")
242 parser.add_option("-j", "--threads", dest="numThreads",
243 help="Number of testing threads",
244 type=int, action="store",
245 default=detectCPUs())
246 parser.add_option("", "--clang", dest="clang",
247 help="Program to use as \"clang\"",
248 action="store", default="clang")
249 parser.add_option("", "--vg", dest="useValgrind",
250 help="Run tests under valgrind",
251 action="store_true", default=False)
252 parser.add_option("", "--dg", dest="useDGCompat",
253 help="Use llvm dejagnu compatibility mode",
254 action="store_true", default=False)
255 parser.add_option("", "--script", dest="testScript",
256 help="Default script to use",
257 action="store", default=None)
258 parser.add_option("-v", "--verbose", dest="showOutput",
259 help="Show all test output",
260 action="store_true", default=False)
261 parser.add_option("-q", "--quiet", dest="quiet",
262 help="Suppress no error output",
263 action="store_true", default=False)
264 parser.add_option("-s", "--succinct", dest="succinct",
265 help="Reduce amount of output",
266 action="store_true", default=False)
267 parser.add_option("", "--max-tests", dest="maxTests",
268 help="Maximum number of tests to run",
269 action="store", type=int, default=None)
270 parser.add_option("", "--max-time", dest="maxTime",
271 help="Maximum time to spend testing (in seconds)",
272 action="store", type=float, default=None)
273 parser.add_option("", "--shuffle", dest="shuffle",
274 help="Run tests in random order",
275 action="store_true", default=False)
276 parser.add_option("", "--seed", dest="seed",
277 help="Seed for random number generator (default: random).",
278 action="store", default=None)
279 parser.add_option("", "--no-progress-bar", dest="useProgressBar",
280 help="Do not use curses based progress bar",
281 action="store_false", default=True)
282 parser.add_option("", "--debug-do-not-test", dest="debugDoNotTest",
283 help="DEBUG: Skip running actual test script",
284 action="store_true", default=False)
285 (opts, args) = parser.parse_args()
286
287 if not args:
288 parser.error('No inputs specified')
289
290 allTests = list(getTests(args))
291 allTests.sort()
292
293 tests = allTests
294 if opts.seed is not None:
295 try:
296 seed = int(opts.seed)
297 except:
298 parser.error('--seed argument should be an integer')
299 random.seed(seed)
300 if opts.shuffle:
301 random.shuffle(tests)
302 if opts.maxTests is not None:
303 tests = tests[:opts.maxTests]
304
305 extra = ''
306 if len(tests) != len(allTests):
307 extra = ' of %d'%(len(allTests),)
308 header = '-- Testing: %d%s tests, %d threads --'%(len(tests),extra,opts.numThreads)
309
310 progressBar = None
311 if not opts.quiet:
312 if opts.useProgressBar:
313 try:
314 tc = ProgressBar.TerminalController()
315 progressBar = ProgressBar.ProgressBar(tc, header)
316 except ValueError:
317 pass
318
319 if not progressBar:
320 print header
321
322 display = TestingProgressDisplay(opts, len(tests), progressBar)
323 provider = TestProvider(opts, tests, display)
324
325 testers = [Tester(provider) for i in range(opts.numThreads)]
326 startTime = time.time()
327 for t in testers:
328 t.start()
329 try:
330 for t in testers:
331 t.join()
332 except KeyboardInterrupt:
333 sys.exit(1)
334
335 display.finish()
336
337 if not opts.quiet:
338 print 'Testing Time: %.2fs'%(time.time() - startTime)
339
340 xfails = [i for i in provider.results if i and i.code==TestStatus.XFail]
341 if xfails:
342 print '*'*20
343 print 'Expected Failures (%d):' % len(xfails)
344 for tr in xfails:
345 print '\t%s'%(tr.path,)
346
347 xpasses = [i for i in provider.results if i and i.code==TestStatus.XPass]
348 if xpasses:
349 print '*'*20
350 print 'Unexpected Passing Tests (%d):' % len(xpasses)
351 for tr in xpasses:
352 print '\t%s'%(tr.path,)
353
354 failures = [i for i in provider.results if i and i.code==TestStatus.Fail]
355 if failures:
356 print '*'*20
357 print 'Failing Tests (%d):' % len(failures)
358 for tr in failures:
359 if tr.code != TestStatus.XPass:
360 print '\t%s'%(tr.path,)
361
362 print '\nFailures: %d'%(len(failures),)
363
364 assertions = {}
365 errors = {}
366 errorFree = []
367 for tr in failures:
368 if not tr.errors and not tr.assertions:
369 errorFree.append(tr)
370 for (_,_,_,error) in tr.errors:
371 errors[error] = errors.get(error,0) + 1
372 for assertion in tr.assertions:
373 assertions[assertion] = assertions.get(assertion,0) + 1
374 if errorFree:
375 print 'Failures w/o Errors (%d):' % len(errorFree)
376 for tr in errorFree:
377 print '\t%s'%(tr.path,)
378
379 if errors:
380 print 'Error Summary (%d):' % sum(errors.values())
381 items = errors.items()
382 items.sort(key = lambda (_,v): -v)
383 for i,(error,count) in enumerate(items):
384 print '\t%3d: %s'%(count,error)
385 if i>100:
386 print '\t\t(too many errors, skipping)'
387 break
388
389 if assertions:
390 print 'Assertion Summary (%d):' % sum(assertions.values())
391 items = assertions.items()
392 items.sort(key = lambda (_,v): -v)
393 for assertion,count in items:
394 print '\t%3d: %s'%(count,assertion)
395
396if __name__=='__main__':
397 main()