blob: 0e1bbd42394edaa63702eaf827139f9caf5d6af2 [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--
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
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000024def getTests(inputs):
25 for path in inputs:
Daniel Dunbarfecdd002009-07-25 12:05:55 +000026 # Always use absolte paths.
27 path = os.path.abspath(path)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000028 if not os.path.exists(path):
29 print >>sys.stderr,"WARNING: Invalid test \"%s\""%(path,)
30 continue
31
32 if os.path.isdir(path):
33 for dirpath,dirnames,filenames in os.walk(path):
34 dotTests = os.path.join(dirpath,'.tests')
35 if os.path.exists(dotTests):
36 for ln in open(dotTests):
37 if ln.strip():
38 yield os.path.join(dirpath,ln.strip())
39 else:
40 # FIXME: This doesn't belong here
41 if 'Output' in dirnames:
42 dirnames.remove('Output')
43 for f in filenames:
44 base,ext = os.path.splitext(f)
45 if ext in kTestFileExtensions:
46 yield os.path.join(dirpath,f)
47 else:
48 yield path
49
50class TestingProgressDisplay:
51 def __init__(self, opts, numTests, progressBar=None):
52 self.opts = opts
53 self.numTests = numTests
54 self.digits = len(str(self.numTests))
55 self.current = None
56 self.lock = threading.Lock()
57 self.progressBar = progressBar
58 self.progress = 0.
59
60 def update(self, index, tr):
61 # Avoid locking overhead in quiet mode
62 if self.opts.quiet and not tr.failed():
63 return
64
65 # Output lock
66 self.lock.acquire()
67 try:
68 self.handleUpdate(index, tr)
69 finally:
70 self.lock.release()
71
72 def finish(self):
73 if self.progressBar:
74 self.progressBar.clear()
75 elif self.opts.succinct:
76 sys.stdout.write('\n')
77
78 def handleUpdate(self, index, tr):
79 if self.progressBar:
80 if tr.failed():
81 self.progressBar.clear()
82 else:
83 # Force monotonicity
84 self.progress = max(self.progress, float(index)/self.numTests)
85 self.progressBar.update(self.progress, tr.path)
86 return
87 elif self.opts.succinct:
88 if not tr.failed():
89 sys.stdout.write('.')
90 sys.stdout.flush()
91 return
92 else:
93 sys.stdout.write('\n')
94
95 extra = ''
96 if tr.code==TestStatus.Invalid:
97 extra = ' - (Invalid test)'
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +000098 elif tr.failed():
99 extra = ' - %s'%(TestStatus.getName(tr.code).upper(),)
100 print '%*d/%*d - %s%s'%(self.digits, index+1, self.digits,
101 self.numTests, tr.path, extra)
102
Daniel Dunbar360d16c2009-04-23 05:03:44 +0000103 if tr.failed() and self.opts.showOutput:
104 TestRunner.cat(tr.testResults, sys.stdout)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000105
106class TestResult:
Daniel Dunbar7f106812009-07-11 22:46:27 +0000107 def __init__(self, path, code, testResults, elapsed):
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000108 self.path = path
109 self.code = code
110 self.testResults = testResults
Daniel Dunbar7f106812009-07-11 22:46:27 +0000111 self.elapsed = elapsed
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000112
113 def failed(self):
114 return self.code in (TestStatus.Fail,TestStatus.XPass)
115
116class TestProvider:
117 def __init__(self, opts, tests, display):
118 self.opts = opts
119 self.tests = tests
120 self.index = 0
121 self.lock = threading.Lock()
122 self.results = [None]*len(self.tests)
123 self.startTime = time.time()
124 self.progress = display
125
126 def get(self):
127 self.lock.acquire()
128 try:
129 if self.opts.maxTime is not None:
130 if time.time() - self.startTime > self.opts.maxTime:
131 return None
132 if self.index >= len(self.tests):
133 return None
134 item = self.tests[self.index],self.index
135 self.index += 1
136 return item
137 finally:
138 self.lock.release()
139
140 def setResult(self, index, result):
141 self.results[index] = result
142 self.progress.update(index, result)
143
144class Tester(threading.Thread):
145 def __init__(self, provider):
146 threading.Thread.__init__(self)
147 self.provider = provider
148
149 def run(self):
150 while 1:
151 item = self.provider.get()
152 if item is None:
153 break
154 self.runTest(item)
155
156 def runTest(self, (path,index)):
157 command = path
Daniel Dunbardf084892009-07-25 09:53:43 +0000158 base = TestRunner.getTestOutputBase('Output', path)
159 output = base + '.out'
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000160 testname = path
Daniel Dunbardf084892009-07-25 09:53:43 +0000161 testresults = base + '.testresults'
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000162 TestRunner.mkdir_p(os.path.dirname(testresults))
163 numTests = len(self.provider.tests)
164 digits = len(str(numTests))
165 code = None
Daniel Dunbar7f106812009-07-11 22:46:27 +0000166 elapsed = None
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000167 try:
168 opts = self.provider.opts
169 if opts.debugDoNotTest:
170 code = None
171 else:
Daniel Dunbar7f106812009-07-11 22:46:27 +0000172 startTime = time.time()
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000173 code = TestRunner.runOneTest(path, command, output, testname,
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000174 opts.clang, opts.clangcc,
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000175 useDGCompat=opts.useDGCompat,
176 useScript=opts.testScript,
177 output=open(testresults,'w'))
Daniel Dunbar7f106812009-07-11 22:46:27 +0000178 elapsed = time.time() - startTime
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000179 except KeyboardInterrupt:
180 # This is a sad hack. Unfortunately subprocess goes
181 # bonkers with ctrl-c and we start forking merrily.
182 print 'Ctrl-C detected, goodbye.'
183 os.kill(0,9)
184
Daniel Dunbar7f106812009-07-11 22:46:27 +0000185 self.provider.setResult(index, TestResult(path, code, testresults,
186 elapsed))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000187
188def detectCPUs():
189 """
190 Detects the number of CPUs on a system. Cribbed from pp.
191 """
192 # Linux, Unix and MacOS:
193 if hasattr(os, "sysconf"):
194 if os.sysconf_names.has_key("SC_NPROCESSORS_ONLN"):
195 # Linux & Unix:
196 ncpus = os.sysconf("SC_NPROCESSORS_ONLN")
197 if isinstance(ncpus, int) and ncpus > 0:
198 return ncpus
199 else: # OSX:
200 return int(os.popen2("sysctl -n hw.ncpu")[1].read())
201 # Windows:
202 if os.environ.has_key("NUMBER_OF_PROCESSORS"):
203 ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]);
204 if ncpus > 0:
205 return ncpus
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000206 return 1 # Default
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000207
208def main():
209 global options
210 from optparse import OptionParser
211 parser = OptionParser("usage: %prog [options] {inputs}")
212 parser.add_option("-j", "--threads", dest="numThreads",
213 help="Number of testing threads",
214 type=int, action="store",
215 default=detectCPUs())
216 parser.add_option("", "--clang", dest="clang",
217 help="Program to use as \"clang\"",
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000218 action="store", default=None)
219 parser.add_option("", "--clang-cc", dest="clangcc",
220 help="Program to use as \"clang-cc\"",
221 action="store", default=None)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000222 parser.add_option("", "--vg", dest="useValgrind",
223 help="Run tests under valgrind",
224 action="store_true", default=False)
225 parser.add_option("", "--dg", dest="useDGCompat",
226 help="Use llvm dejagnu compatibility mode",
227 action="store_true", default=False)
228 parser.add_option("", "--script", dest="testScript",
229 help="Default script to use",
230 action="store", default=None)
231 parser.add_option("-v", "--verbose", dest="showOutput",
232 help="Show all test output",
233 action="store_true", default=False)
234 parser.add_option("-q", "--quiet", dest="quiet",
235 help="Suppress no error output",
236 action="store_true", default=False)
237 parser.add_option("-s", "--succinct", dest="succinct",
238 help="Reduce amount of output",
239 action="store_true", default=False)
240 parser.add_option("", "--max-tests", dest="maxTests",
241 help="Maximum number of tests to run",
242 action="store", type=int, default=None)
243 parser.add_option("", "--max-time", dest="maxTime",
244 help="Maximum time to spend testing (in seconds)",
245 action="store", type=float, default=None)
246 parser.add_option("", "--shuffle", dest="shuffle",
247 help="Run tests in random order",
248 action="store_true", default=False)
249 parser.add_option("", "--seed", dest="seed",
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000250 help="Seed for random number generator (default: random)",
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000251 action="store", default=None)
252 parser.add_option("", "--no-progress-bar", dest="useProgressBar",
253 help="Do not use curses based progress bar",
254 action="store_false", default=True)
255 parser.add_option("", "--debug-do-not-test", dest="debugDoNotTest",
256 help="DEBUG: Skip running actual test script",
257 action="store_true", default=False)
Daniel Dunbar7f106812009-07-11 22:46:27 +0000258 parser.add_option("", "--time-tests", dest="timeTests",
259 help="Track elapsed wall time for each test",
260 action="store_true", default=False)
Douglas Gregor79865192009-06-05 23:57:17 +0000261 parser.add_option("", "--path", dest="path",
262 help="Additional paths to add to testing environment",
263 action="store", type=str, default=None)
264
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000265 (opts, args) = parser.parse_args()
266
267 if not args:
268 parser.error('No inputs specified')
Daniel Dunbar11980cc2009-07-25 13:13:06 +0000269 if opts.useValgrind:
270 parser.error('Support for running with valgrind is '
271 'temporarily disabled')
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000272
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000273 if opts.clang is None:
274 opts.clang = TestRunner.inferClang()
275 if opts.clangcc is None:
276 opts.clangcc = TestRunner.inferClangCC(opts.clang)
277
Daniel Dunbar259a5652009-04-26 01:28:51 +0000278 # FIXME: It could be worth loading these in parallel with testing.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000279 allTests = list(getTests(args))
280 allTests.sort()
281
282 tests = allTests
283 if opts.seed is not None:
284 try:
285 seed = int(opts.seed)
286 except:
287 parser.error('--seed argument should be an integer')
288 random.seed(seed)
289 if opts.shuffle:
290 random.shuffle(tests)
291 if opts.maxTests is not None:
292 tests = tests[:opts.maxTests]
Douglas Gregor79865192009-06-05 23:57:17 +0000293 if opts.path is not None:
294 os.environ["PATH"] = opts.path + ":" + os.environ["PATH"];
Douglas Gregor79865192009-06-05 23:57:17 +0000295
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000296 extra = ''
297 if len(tests) != len(allTests):
298 extra = ' of %d'%(len(allTests),)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000299 header = '-- Testing: %d%s tests, %d threads --'%(len(tests),extra,
300 opts.numThreads)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000301
302 progressBar = None
303 if not opts.quiet:
304 if opts.useProgressBar:
305 try:
306 tc = ProgressBar.TerminalController()
307 progressBar = ProgressBar.ProgressBar(tc, header)
308 except ValueError:
309 pass
310
311 if not progressBar:
312 print header
313
314 display = TestingProgressDisplay(opts, len(tests), progressBar)
315 provider = TestProvider(opts, tests, display)
316
317 testers = [Tester(provider) for i in range(opts.numThreads)]
318 startTime = time.time()
319 for t in testers:
320 t.start()
321 try:
322 for t in testers:
323 t.join()
324 except KeyboardInterrupt:
325 sys.exit(1)
326
327 display.finish()
328
329 if not opts.quiet:
330 print 'Testing Time: %.2fs'%(time.time() - startTime)
331
Daniel Dunbar259a5652009-04-26 01:28:51 +0000332 # List test results organized organized by kind.
333 byCode = {}
334 for t in provider.results:
335 if t:
336 if t.code not in byCode:
337 byCode[t.code] = []
338 byCode[t.code].append(t)
339 for title,code in (('Expected Failures', TestStatus.XFail),
340 ('Unexpected Passing Tests', TestStatus.XPass),
341 ('Failing Tests', TestStatus.Fail)):
342 elts = byCode.get(code)
343 if not elts:
344 continue
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000345 print '*'*20
Daniel Dunbar259a5652009-04-26 01:28:51 +0000346 print '%s (%d):' % (title, len(elts))
347 for tr in elts:
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000348 print '\t%s'%(tr.path,)
349
Daniel Dunbar259a5652009-04-26 01:28:51 +0000350 numFailures = len(byCode.get(TestStatus.Fail,[]))
351 if numFailures:
352 print '\nFailures: %d' % (numFailures,)
Douglas Gregor4d1800d2009-06-16 23:40:23 +0000353 sys.exit(1)
354
Daniel Dunbar7f106812009-07-11 22:46:27 +0000355 if opts.timeTests:
Daniel Dunbar3ed4bd12009-07-16 21:18:21 +0000356 print '\nTest Times:'
Daniel Dunbar7f106812009-07-11 22:46:27 +0000357 provider.results.sort(key=lambda t: t and t.elapsed)
358 for tr in provider.results:
359 if tr:
360 print '%.2fs: %s' % (tr.elapsed, tr.path)
361
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000362if __name__=='__main__':
363 main()