blob: 41cf537143db1c24014c21738b604bb977503cb6 [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:
26 if not os.path.exists(path):
27 print >>sys.stderr,"WARNING: Invalid test \"%s\""%(path,)
28 continue
29
30 if os.path.isdir(path):
31 for dirpath,dirnames,filenames in os.walk(path):
32 dotTests = os.path.join(dirpath,'.tests')
33 if os.path.exists(dotTests):
34 for ln in open(dotTests):
35 if ln.strip():
36 yield os.path.join(dirpath,ln.strip())
37 else:
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 else:
46 yield path
47
48class TestingProgressDisplay:
49 def __init__(self, opts, numTests, progressBar=None):
50 self.opts = opts
51 self.numTests = numTests
52 self.digits = len(str(self.numTests))
53 self.current = None
54 self.lock = threading.Lock()
55 self.progressBar = progressBar
56 self.progress = 0.
57
58 def update(self, index, tr):
59 # Avoid locking overhead in quiet mode
60 if self.opts.quiet and not tr.failed():
61 return
62
63 # Output lock
64 self.lock.acquire()
65 try:
66 self.handleUpdate(index, tr)
67 finally:
68 self.lock.release()
69
70 def finish(self):
71 if self.progressBar:
72 self.progressBar.clear()
73 elif self.opts.succinct:
74 sys.stdout.write('\n')
75
76 def handleUpdate(self, index, tr):
77 if self.progressBar:
78 if tr.failed():
79 self.progressBar.clear()
80 else:
81 # Force monotonicity
82 self.progress = max(self.progress, float(index)/self.numTests)
83 self.progressBar.update(self.progress, tr.path)
84 return
85 elif self.opts.succinct:
86 if not tr.failed():
87 sys.stdout.write('.')
88 sys.stdout.flush()
89 return
90 else:
91 sys.stdout.write('\n')
92
93 extra = ''
94 if tr.code==TestStatus.Invalid:
95 extra = ' - (Invalid test)'
96 elif tr.code==TestStatus.NoRunLine:
97 extra = ' - (No RUN line)'
98 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 useValgrind=opts.useValgrind,
176 useDGCompat=opts.useDGCompat,
177 useScript=opts.testScript,
178 output=open(testresults,'w'))
Daniel Dunbar7f106812009-07-11 22:46:27 +0000179 elapsed = time.time() - startTime
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000180 except KeyboardInterrupt:
181 # This is a sad hack. Unfortunately subprocess goes
182 # bonkers with ctrl-c and we start forking merrily.
183 print 'Ctrl-C detected, goodbye.'
184 os.kill(0,9)
185
Daniel Dunbar7f106812009-07-11 22:46:27 +0000186 self.provider.setResult(index, TestResult(path, code, testresults,
187 elapsed))
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000188
189def detectCPUs():
190 """
191 Detects the number of CPUs on a system. Cribbed from pp.
192 """
193 # Linux, Unix and MacOS:
194 if hasattr(os, "sysconf"):
195 if os.sysconf_names.has_key("SC_NPROCESSORS_ONLN"):
196 # Linux & Unix:
197 ncpus = os.sysconf("SC_NPROCESSORS_ONLN")
198 if isinstance(ncpus, int) and ncpus > 0:
199 return ncpus
200 else: # OSX:
201 return int(os.popen2("sysctl -n hw.ncpu")[1].read())
202 # Windows:
203 if os.environ.has_key("NUMBER_OF_PROCESSORS"):
204 ncpus = int(os.environ["NUMBER_OF_PROCESSORS"]);
205 if ncpus > 0:
206 return ncpus
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000207 return 1 # Default
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000208
209def main():
210 global options
211 from optparse import OptionParser
212 parser = OptionParser("usage: %prog [options] {inputs}")
213 parser.add_option("-j", "--threads", dest="numThreads",
214 help="Number of testing threads",
215 type=int, action="store",
216 default=detectCPUs())
217 parser.add_option("", "--clang", dest="clang",
218 help="Program to use as \"clang\"",
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000219 action="store", default=None)
220 parser.add_option("", "--clang-cc", dest="clangcc",
221 help="Program to use as \"clang-cc\"",
222 action="store", default=None)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000223 parser.add_option("", "--vg", dest="useValgrind",
224 help="Run tests under valgrind",
225 action="store_true", default=False)
226 parser.add_option("", "--dg", dest="useDGCompat",
227 help="Use llvm dejagnu compatibility mode",
228 action="store_true", default=False)
229 parser.add_option("", "--script", dest="testScript",
230 help="Default script to use",
231 action="store", default=None)
232 parser.add_option("-v", "--verbose", dest="showOutput",
233 help="Show all test output",
234 action="store_true", default=False)
235 parser.add_option("-q", "--quiet", dest="quiet",
236 help="Suppress no error output",
237 action="store_true", default=False)
238 parser.add_option("-s", "--succinct", dest="succinct",
239 help="Reduce amount of output",
240 action="store_true", default=False)
241 parser.add_option("", "--max-tests", dest="maxTests",
242 help="Maximum number of tests to run",
243 action="store", type=int, default=None)
244 parser.add_option("", "--max-time", dest="maxTime",
245 help="Maximum time to spend testing (in seconds)",
246 action="store", type=float, default=None)
247 parser.add_option("", "--shuffle", dest="shuffle",
248 help="Run tests in random order",
249 action="store_true", default=False)
250 parser.add_option("", "--seed", dest="seed",
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000251 help="Seed for random number generator (default: random)",
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000252 action="store", default=None)
253 parser.add_option("", "--no-progress-bar", dest="useProgressBar",
254 help="Do not use curses based progress bar",
255 action="store_false", default=True)
256 parser.add_option("", "--debug-do-not-test", dest="debugDoNotTest",
257 help="DEBUG: Skip running actual test script",
258 action="store_true", default=False)
Daniel Dunbar7f106812009-07-11 22:46:27 +0000259 parser.add_option("", "--time-tests", dest="timeTests",
260 help="Track elapsed wall time for each test",
261 action="store_true", default=False)
Douglas Gregor79865192009-06-05 23:57:17 +0000262 parser.add_option("", "--path", dest="path",
263 help="Additional paths to add to testing environment",
264 action="store", type=str, default=None)
265
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000266 (opts, args) = parser.parse_args()
267
268 if not args:
269 parser.error('No inputs specified')
270
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000271 if opts.clang is None:
272 opts.clang = TestRunner.inferClang()
273 if opts.clangcc is None:
274 opts.clangcc = TestRunner.inferClangCC(opts.clang)
275
Daniel Dunbar259a5652009-04-26 01:28:51 +0000276 # FIXME: It could be worth loading these in parallel with testing.
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000277 allTests = list(getTests(args))
278 allTests.sort()
279
280 tests = allTests
281 if opts.seed is not None:
282 try:
283 seed = int(opts.seed)
284 except:
285 parser.error('--seed argument should be an integer')
286 random.seed(seed)
287 if opts.shuffle:
288 random.shuffle(tests)
289 if opts.maxTests is not None:
290 tests = tests[:opts.maxTests]
Douglas Gregor79865192009-06-05 23:57:17 +0000291 if opts.path is not None:
292 os.environ["PATH"] = opts.path + ":" + os.environ["PATH"];
Douglas Gregor79865192009-06-05 23:57:17 +0000293
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000294 extra = ''
295 if len(tests) != len(allTests):
296 extra = ' of %d'%(len(allTests),)
Daniel Dunbar69e07a72009-06-17 21:33:37 +0000297 header = '-- Testing: %d%s tests, %d threads --'%(len(tests),extra,
298 opts.numThreads)
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000299
300 progressBar = None
301 if not opts.quiet:
302 if opts.useProgressBar:
303 try:
304 tc = ProgressBar.TerminalController()
305 progressBar = ProgressBar.ProgressBar(tc, header)
306 except ValueError:
307 pass
308
309 if not progressBar:
310 print header
311
312 display = TestingProgressDisplay(opts, len(tests), progressBar)
313 provider = TestProvider(opts, tests, display)
314
315 testers = [Tester(provider) for i in range(opts.numThreads)]
316 startTime = time.time()
317 for t in testers:
318 t.start()
319 try:
320 for t in testers:
321 t.join()
322 except KeyboardInterrupt:
323 sys.exit(1)
324
325 display.finish()
326
327 if not opts.quiet:
328 print 'Testing Time: %.2fs'%(time.time() - startTime)
329
Daniel Dunbar259a5652009-04-26 01:28:51 +0000330 # List test results organized organized by kind.
331 byCode = {}
332 for t in provider.results:
333 if t:
334 if t.code not in byCode:
335 byCode[t.code] = []
336 byCode[t.code].append(t)
337 for title,code in (('Expected Failures', TestStatus.XFail),
338 ('Unexpected Passing Tests', TestStatus.XPass),
339 ('Failing Tests', TestStatus.Fail)):
340 elts = byCode.get(code)
341 if not elts:
342 continue
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000343 print '*'*20
Daniel Dunbar259a5652009-04-26 01:28:51 +0000344 print '%s (%d):' % (title, len(elts))
345 for tr in elts:
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000346 print '\t%s'%(tr.path,)
347
Daniel Dunbar259a5652009-04-26 01:28:51 +0000348 numFailures = len(byCode.get(TestStatus.Fail,[]))
349 if numFailures:
350 print '\nFailures: %d' % (numFailures,)
Douglas Gregor4d1800d2009-06-16 23:40:23 +0000351 sys.exit(1)
352
Daniel Dunbar7f106812009-07-11 22:46:27 +0000353 if opts.timeTests:
Daniel Dunbar3ed4bd12009-07-16 21:18:21 +0000354 print '\nTest Times:'
Daniel Dunbar7f106812009-07-11 22:46:27 +0000355 provider.results.sort(key=lambda t: t and t.elapsed)
356 for tr in provider.results:
357 if tr:
358 print '%.2fs: %s' % (tr.elapsed, tr.path)
359
Daniel Dunbar6fc0bdf2009-03-06 22:20:40 +0000360if __name__=='__main__':
361 main()