| 1 | # modified copy of some functions from test/regrtest.py from PyXml |
|---|
| 2 | """Copyright (c) 2003-2006 LOGILAB S.A. (Paris, FRANCE). |
|---|
| 3 | http://www.logilab.fr/ -- mailto:contact@logilab.fr |
|---|
| 4 | |
|---|
| 5 | Run tests. |
|---|
| 6 | |
|---|
| 7 | This will find all modules whose name match a given prefix in the test |
|---|
| 8 | directory, and run them. Various command line options provide |
|---|
| 9 | additional facilities. |
|---|
| 10 | |
|---|
| 11 | Command line options: |
|---|
| 12 | |
|---|
| 13 | -v: verbose -- run tests in verbose mode with output to stdout |
|---|
| 14 | -q: quiet -- don't print anything except if a test fails |
|---|
| 15 | -t: testdir -- directory where the tests will be found |
|---|
| 16 | -x: exclude -- add a test to exclude |
|---|
| 17 | -p: profile -- profiled execution |
|---|
| 18 | -c: capture -- capture standard out/err during tests |
|---|
| 19 | -d: dbc -- enable design-by-contract |
|---|
| 20 | |
|---|
| 21 | If no non-option arguments are present, prefixes used are 'test', |
|---|
| 22 | 'regrtest', 'smoketest' and 'unittest'. |
|---|
| 23 | |
|---|
| 24 | """ |
|---|
| 25 | import sys |
|---|
| 26 | import os, os.path as osp |
|---|
| 27 | import re |
|---|
| 28 | import time |
|---|
| 29 | import getopt |
|---|
| 30 | import traceback |
|---|
| 31 | import unittest |
|---|
| 32 | import difflib |
|---|
| 33 | import types |
|---|
| 34 | from warnings import warn |
|---|
| 35 | from compiler.consts import CO_GENERATOR |
|---|
| 36 | try: |
|---|
| 37 | import readline |
|---|
| 38 | except ImportError: |
|---|
| 39 | readline = None |
|---|
| 40 | |
|---|
| 41 | # PRINT_ = file('stdout.txt', 'w').write |
|---|
| 42 | |
|---|
| 43 | try: |
|---|
| 44 | from test import test_support |
|---|
| 45 | except ImportError: |
|---|
| 46 | # not always available |
|---|
| 47 | class TestSupport: |
|---|
| 48 | def unload(self, test): |
|---|
| 49 | pass |
|---|
| 50 | test_support = TestSupport() |
|---|
| 51 | |
|---|
| 52 | from logilab.common.deprecation import class_renamed, deprecated_function, \ |
|---|
| 53 | obsolete |
|---|
| 54 | from logilab.common.compat import set, enumerate, any |
|---|
| 55 | from logilab.common.modutils import load_module_from_name |
|---|
| 56 | from logilab.common.debugger import Debugger |
|---|
| 57 | from logilab.common.decorators import cached |
|---|
| 58 | |
|---|
| 59 | __all__ = ['main', 'unittest_main', 'find_tests', 'run_test', 'spawn'] |
|---|
| 60 | |
|---|
| 61 | DEFAULT_PREFIXES = ('test', 'regrtest', 'smoketest', 'unittest', |
|---|
| 62 | 'func', 'validation') |
|---|
| 63 | |
|---|
| 64 | ENABLE_DBC = False |
|---|
| 65 | |
|---|
| 66 | def main(testdir=None, exitafter=True): |
|---|
| 67 | """Execute a test suite. |
|---|
| 68 | |
|---|
| 69 | This also parses command-line options and modifies its behaviour |
|---|
| 70 | accordingly. |
|---|
| 71 | |
|---|
| 72 | tests -- a list of strings containing test names (optional) |
|---|
| 73 | testdir -- the directory in which to look for tests (optional) |
|---|
| 74 | |
|---|
| 75 | Users other than the Python test suite will certainly want to |
|---|
| 76 | specify testdir; if it's omitted, the directory containing the |
|---|
| 77 | Python test suite is searched for. |
|---|
| 78 | |
|---|
| 79 | If the tests argument is omitted, the tests listed on the |
|---|
| 80 | command-line will be used. If that's empty, too, then all *.py |
|---|
| 81 | files beginning with test_ will be used. |
|---|
| 82 | |
|---|
| 83 | """ |
|---|
| 84 | |
|---|
| 85 | try: |
|---|
| 86 | opts, args = getopt.getopt(sys.argv[1:], 'hvqx:t:pcd', ['help']) |
|---|
| 87 | except getopt.error, msg: |
|---|
| 88 | print msg |
|---|
| 89 | print __doc__ |
|---|
| 90 | return 2 |
|---|
| 91 | verbose = 0 |
|---|
| 92 | quiet = False |
|---|
| 93 | profile = False |
|---|
| 94 | exclude = [] |
|---|
| 95 | capture = 0 |
|---|
| 96 | for o, a in opts: |
|---|
| 97 | if o == '-v': |
|---|
| 98 | verbose += 1 |
|---|
| 99 | elif o == '-q': |
|---|
| 100 | quiet = True |
|---|
| 101 | verbose = 0 |
|---|
| 102 | elif o == '-x': |
|---|
| 103 | exclude.append(a) |
|---|
| 104 | elif o == '-t': |
|---|
| 105 | testdir = a |
|---|
| 106 | elif o == '-p': |
|---|
| 107 | profile = True |
|---|
| 108 | elif o == '-c': |
|---|
| 109 | capture += 1 |
|---|
| 110 | elif o == '-d': |
|---|
| 111 | global ENABLE_DBC |
|---|
| 112 | ENABLE_DBC = True |
|---|
| 113 | elif o in ('-h', '--help'): |
|---|
| 114 | print __doc__ |
|---|
| 115 | sys.exit(0) |
|---|
| 116 | |
|---|
| 117 | args = [item.rstrip('.py') for item in args] |
|---|
| 118 | exclude = [item.rstrip('.py') for item in exclude] |
|---|
| 119 | |
|---|
| 120 | if testdir is not None: |
|---|
| 121 | os.chdir(testdir) |
|---|
| 122 | sys.path.insert(0, '') |
|---|
| 123 | tests = find_tests('.', args or DEFAULT_PREFIXES, excludes=exclude) |
|---|
| 124 | # Tell tests to be moderately quiet |
|---|
| 125 | test_support.verbose = verbose |
|---|
| 126 | if profile: |
|---|
| 127 | print >> sys.stderr, '** profiled run' |
|---|
| 128 | from hotshot import Profile |
|---|
| 129 | prof = Profile('stones.prof') |
|---|
| 130 | start_time, start_ctime = time.time(), time.clock() |
|---|
| 131 | good, bad, skipped, all_result = prof.runcall(run_tests, tests, quiet, |
|---|
| 132 | verbose, None, capture) |
|---|
| 133 | end_time, end_ctime = time.time(), time.clock() |
|---|
| 134 | prof.close() |
|---|
| 135 | else: |
|---|
| 136 | start_time, start_ctime = time.time(), time.clock() |
|---|
| 137 | good, bad, skipped, all_result = run_tests(tests, quiet, verbose, None, capture) |
|---|
| 138 | end_time, end_ctime = time.time(), time.clock() |
|---|
| 139 | if not quiet: |
|---|
| 140 | print '*'*80 |
|---|
| 141 | if all_result: |
|---|
| 142 | print 'Ran %s test cases in %0.2fs (%0.2fs CPU)' % (all_result.testsRun, |
|---|
| 143 | end_time - start_time, |
|---|
| 144 | end_ctime - start_ctime), |
|---|
| 145 | if all_result.errors: |
|---|
| 146 | print ', %s errors' % len(all_result.errors), |
|---|
| 147 | if all_result.failures: |
|---|
| 148 | print ', %s failed' % len(all_result.failures), |
|---|
| 149 | if all_result.skipped: |
|---|
| 150 | print ', %s skipped' % len(all_result.skipped), |
|---|
| 151 | print |
|---|
| 152 | if good: |
|---|
| 153 | if not bad and not skipped and len(good) > 1: |
|---|
| 154 | print "All", |
|---|
| 155 | print _count(len(good), "test"), "OK." |
|---|
| 156 | if bad: |
|---|
| 157 | print _count(len(bad), "test"), "failed:", |
|---|
| 158 | print ', '.join(bad) |
|---|
| 159 | if skipped: |
|---|
| 160 | print _count(len(skipped), "test"), "skipped:", |
|---|
| 161 | print ', '.join(['%s (%s)' % (test, msg) for test, msg in skipped]) |
|---|
| 162 | if profile: |
|---|
| 163 | from hotshot import stats |
|---|
| 164 | stats = stats.load('stones.prof') |
|---|
| 165 | stats.sort_stats('time', 'calls') |
|---|
| 166 | stats.print_stats(30) |
|---|
| 167 | if exitafter: |
|---|
| 168 | sys.exit(len(bad) + len(skipped)) |
|---|
| 169 | else: |
|---|
| 170 | sys.path.pop(0) |
|---|
| 171 | return len(bad) |
|---|
| 172 | main = obsolete("testlib.main() is obsolete, use the pytest tool instead")(main) |
|---|
| 173 | |
|---|
| 174 | |
|---|
| 175 | def run_tests(tests, quiet, verbose, runner=None, capture=0): |
|---|
| 176 | """ execute a list of tests |
|---|
| 177 | return a 3-uple with : |
|---|
| 178 | _ the list of passed tests |
|---|
| 179 | _ the list of failed tests |
|---|
| 180 | _ the list of skipped tests |
|---|
| 181 | """ |
|---|
| 182 | good = [] |
|---|
| 183 | bad = [] |
|---|
| 184 | skipped = [] |
|---|
| 185 | all_result = None |
|---|
| 186 | for test in tests: |
|---|
| 187 | if not quiet: |
|---|
| 188 | print |
|---|
| 189 | print '-'*80 |
|---|
| 190 | print "Executing", test |
|---|
| 191 | result = run_test(test, verbose, runner, capture) |
|---|
| 192 | if type(result) is type(''): |
|---|
| 193 | # an unexpected error occured |
|---|
| 194 | skipped.append( (test, result)) |
|---|
| 195 | else: |
|---|
| 196 | if all_result is None: |
|---|
| 197 | all_result = result |
|---|
| 198 | else: |
|---|
| 199 | all_result.testsRun += result.testsRun |
|---|
| 200 | all_result.failures += result.failures |
|---|
| 201 | all_result.errors += result.errors |
|---|
| 202 | all_result.skipped += result.skipped |
|---|
| 203 | if result.errors or result.failures: |
|---|
| 204 | bad.append(test) |
|---|
| 205 | if verbose: |
|---|
| 206 | print "test", test, \ |
|---|
| 207 | "failed -- %s errors, %s failures" % ( |
|---|
| 208 | len(result.errors), len(result.failures)) |
|---|
| 209 | else: |
|---|
| 210 | good.append(test) |
|---|
| 211 | |
|---|
| 212 | return good, bad, skipped, all_result |
|---|
| 213 | |
|---|
| 214 | def find_tests(testdir, |
|---|
| 215 | prefixes=DEFAULT_PREFIXES, suffix=".py", |
|---|
| 216 | excludes=(), |
|---|
| 217 | remove_suffix=True): |
|---|
| 218 | """ |
|---|
| 219 | Return a list of all applicable test modules. |
|---|
| 220 | """ |
|---|
| 221 | tests = [] |
|---|
| 222 | for name in os.listdir(testdir): |
|---|
| 223 | if not suffix or name.endswith(suffix): |
|---|
| 224 | for prefix in prefixes: |
|---|
| 225 | if name.startswith(prefix): |
|---|
| 226 | if remove_suffix and name.endswith(suffix): |
|---|
| 227 | name = name[:-len(suffix)] |
|---|
| 228 | if name not in excludes: |
|---|
| 229 | tests.append(name) |
|---|
| 230 | tests.sort() |
|---|
| 231 | return tests |
|---|
| 232 | |
|---|
| 233 | |
|---|
| 234 | def run_test(test, verbose, runner=None, capture=0): |
|---|
| 235 | """ |
|---|
| 236 | Run a single test. |
|---|
| 237 | |
|---|
| 238 | test -- the name of the test |
|---|
| 239 | verbose -- if true, print more messages |
|---|
| 240 | """ |
|---|
| 241 | test_support.unload(test) |
|---|
| 242 | try: |
|---|
| 243 | m = load_module_from_name(test, path=sys.path) |
|---|
| 244 | # m = __import__(test, globals(), locals(), sys.path) |
|---|
| 245 | try: |
|---|
| 246 | suite = m.suite |
|---|
| 247 | if callable(suite): |
|---|
| 248 | suite = suite() |
|---|
| 249 | except AttributeError: |
|---|
| 250 | loader = unittest.TestLoader() |
|---|
| 251 | suite = loader.loadTestsFromModule(m) |
|---|
| 252 | if runner is None: |
|---|
| 253 | runner = SkipAwareTextTestRunner(capture=capture) # verbosity=0) |
|---|
| 254 | return runner.run(suite) |
|---|
| 255 | except KeyboardInterrupt, v: |
|---|
| 256 | raise KeyboardInterrupt, v, sys.exc_info()[2] |
|---|
| 257 | except: |
|---|
| 258 | # raise |
|---|
| 259 | type, value = sys.exc_info()[:2] |
|---|
| 260 | msg = "test %s crashed -- %s : %s" % (test, type, value) |
|---|
| 261 | if verbose: |
|---|
| 262 | traceback.print_exc() |
|---|
| 263 | return msg |
|---|
| 264 | |
|---|
| 265 | def _count(n, word): |
|---|
| 266 | """format word according to n""" |
|---|
| 267 | if n == 1: |
|---|
| 268 | return "%d %s" % (n, word) |
|---|
| 269 | else: |
|---|
| 270 | return "%d %ss" % (n, word) |
|---|
| 271 | |
|---|
| 272 | |
|---|
| 273 | |
|---|
| 274 | |
|---|
| 275 | ## PostMortem Debug facilities ##### |
|---|
| 276 | def start_interactive_mode(result): |
|---|
| 277 | """starts an interactive shell so that the user can inspect errors |
|---|
| 278 | """ |
|---|
| 279 | debuggers = result.debuggers |
|---|
| 280 | descrs = result.error_descrs + result.fail_descrs |
|---|
| 281 | if len(debuggers) == 1: |
|---|
| 282 | # don't ask for test name if there's only one failure |
|---|
| 283 | debuggers[0].start() |
|---|
| 284 | else: |
|---|
| 285 | while True: |
|---|
| 286 | testindex = 0 |
|---|
| 287 | print "Choose a test to debug:" |
|---|
| 288 | # order debuggers in the same way than errors were printed |
|---|
| 289 | print "\n".join(['\t%s : %s' % (i, descr) for i, (_, descr) in enumerate(descrs)]) |
|---|
| 290 | print "Type 'exit' (or ^D) to quit" |
|---|
| 291 | print |
|---|
| 292 | try: |
|---|
| 293 | todebug = raw_input('Enter a test name: ') |
|---|
| 294 | if todebug.strip().lower() == 'exit': |
|---|
| 295 | print |
|---|
| 296 | break |
|---|
| 297 | else: |
|---|
| 298 | try: |
|---|
| 299 | testindex = int(todebug) |
|---|
| 300 | debugger = debuggers[descrs[testindex][0]] |
|---|
| 301 | except (ValueError, IndexError): |
|---|
| 302 | print "ERROR: invalid test number %r" % (todebug,) |
|---|
| 303 | else: |
|---|
| 304 | debugger.start() |
|---|
| 305 | except (EOFError, KeyboardInterrupt): |
|---|
| 306 | print |
|---|
| 307 | break |
|---|
| 308 | |
|---|
| 309 | |
|---|
| 310 | # test utils ################################################################## |
|---|
| 311 | from cStringIO import StringIO |
|---|
| 312 | |
|---|
| 313 | class SkipAwareTestResult(unittest._TextTestResult): |
|---|
| 314 | |
|---|
| 315 | def __init__(self, stream, descriptions, verbosity, |
|---|
| 316 | exitfirst=False, capture=0, printonly=None, |
|---|
| 317 | pdbmode=False, cvg=None): |
|---|
| 318 | super(SkipAwareTestResult, self).__init__(stream, |
|---|
| 319 | descriptions, verbosity) |
|---|
| 320 | self.skipped = [] |
|---|
| 321 | self.debuggers = [] |
|---|
| 322 | self.fail_descrs = [] |
|---|
| 323 | self.error_descrs = [] |
|---|
| 324 | self.exitfirst = exitfirst |
|---|
| 325 | self.capture = capture |
|---|
| 326 | self.printonly = printonly |
|---|
| 327 | self.pdbmode = pdbmode |
|---|
| 328 | self.cvg = cvg |
|---|
| 329 | self.pdbclass = Debugger |
|---|
| 330 | |
|---|
| 331 | def descrs_for(self, flavour): |
|---|
| 332 | return getattr(self, '%s_descrs' % flavour.lower()) |
|---|
| 333 | |
|---|
| 334 | def _create_pdb(self, test_descr, flavour): |
|---|
| 335 | self.descrs_for(flavour).append( (len(self.debuggers), test_descr) ) |
|---|
| 336 | if self.pdbmode: |
|---|
| 337 | self.debuggers.append(self.pdbclass(sys.exc_info()[2])) |
|---|
| 338 | |
|---|
| 339 | def addError(self, test, err): |
|---|
| 340 | exc_type, exc, tcbk = err |
|---|
| 341 | if exc_type == TestSkipped: |
|---|
| 342 | self.addSkipped(test, exc) |
|---|
| 343 | else: |
|---|
| 344 | if self.exitfirst: |
|---|
| 345 | self.shouldStop = True |
|---|
| 346 | descr = self.getDescription(test) |
|---|
| 347 | super(SkipAwareTestResult, self).addError(test, err) |
|---|
| 348 | self._create_pdb(descr, 'error') |
|---|
| 349 | |
|---|
| 350 | def addFailure(self, test, err): |
|---|
| 351 | if self.exitfirst: |
|---|
| 352 | self.shouldStop = True |
|---|
| 353 | descr = self.getDescription(test) |
|---|
| 354 | super(SkipAwareTestResult, self).addFailure(test, err) |
|---|
| 355 | self._create_pdb(descr, 'fail') |
|---|
| 356 | |
|---|
| 357 | def addSkipped(self, test, reason): |
|---|
| 358 | self.skipped.append((test, self.getDescription(test), reason)) |
|---|
| 359 | if self.showAll: |
|---|
| 360 | self.stream.writeln("SKIPPED") |
|---|
| 361 | elif self.dots: |
|---|
| 362 | self.stream.write('S') |
|---|
| 363 | |
|---|
| 364 | def printErrors(self): |
|---|
| 365 | super(SkipAwareTestResult, self).printErrors() |
|---|
| 366 | self.printSkippedList() |
|---|
| 367 | |
|---|
| 368 | def printSkippedList(self): |
|---|
| 369 | for test, descr, err in self.skipped: |
|---|
| 370 | self.stream.writeln(self.separator1) |
|---|
| 371 | self.stream.writeln("%s: %s" % ('SKIPPED', descr)) |
|---|
| 372 | self.stream.writeln("\t%s" % err) |
|---|
| 373 | |
|---|
| 374 | def printErrorList(self, flavour, errors): |
|---|
| 375 | for (_, descr), (test, err) in zip(self.descrs_for(flavour), errors): |
|---|
| 376 | self.stream.writeln(self.separator1) |
|---|
| 377 | self.stream.writeln("%s: %s" % (flavour, descr)) |
|---|
| 378 | self.stream.writeln(self.separator2) |
|---|
| 379 | self.stream.writeln("%s" % err) |
|---|
| 380 | try: |
|---|
| 381 | output, errput = test.captured_output() |
|---|
| 382 | except AttributeError: |
|---|
| 383 | pass # original unittest |
|---|
| 384 | else: |
|---|
| 385 | if output: |
|---|
| 386 | self.stream.writeln(self.separator2) |
|---|
| 387 | self.stream.writeln("captured stdout".center(len(self.separator2))) |
|---|
| 388 | self.stream.writeln(self.separator2) |
|---|
| 389 | self.stream.writeln(output) |
|---|
| 390 | else: |
|---|
| 391 | self.stream.writeln('no stdout'.center(len(self.separator2))) |
|---|
| 392 | if errput: |
|---|
| 393 | self.stream.writeln(self.separator2) |
|---|
| 394 | self.stream.writeln("captured stderr".center(len(self.separator2))) |
|---|
| 395 | self.stream.writeln(self.separator2) |
|---|
| 396 | self.stream.writeln(errput) |
|---|
| 397 | |
|---|