Package logilab-common-0 :: Package 36 :: Package 1 :: Module pytest
[frames] | no frames]

Source Code for Module logilab-common-0.36.1.pytest

  1  """pytest is a tool that eases test running and debugging. 
  2   
  3  To be able to use pytest, you should either write tests using 
  4  the logilab.common.testlib's framework or the unittest module of the 
  5  Python's standard library. 
  6   
  7  You can customize pytest's behaviour by defining a ``pytestconf.py`` file 
  8  somewhere in your test directory. In this file, you can add options or 
  9  change the way tests are run. 
 10   
 11  To add command line options, you must define a ``update_parser`` function in 
 12  your ``pytestconf.py`` file. The function must accept a single parameter 
 13  that will be the OptionParser's instance to customize. 
 14   
 15  If you wish to customize the tester, you'll have to define a class named 
 16  ``CustomPyTester``. This class should extend the default `PyTester` class 
 17  defined in the pytest module. Take a look at the `PyTester` and `DjangoTester` 
 18  classes for more information about what can be done. 
 19   
 20  For instance, if you wish to add a custom -l option to specify a loglevel, you 
 21  could define the following ``pytestconf.py`` file :: 
 22   
 23      import logging 
 24      from logilab.common.pytest import PyTester 
 25       
 26      def update_parser(parser): 
 27          parser.add_option('-l', '--loglevel', dest='loglevel', action='store', 
 28                            choices=('debug', 'info', 'warning', 'error', 'critical'), 
 29                            default='critical', help="the default log level possible choices are " 
 30                            "('debug', 'info', 'warning', 'error', 'critical')") 
 31          return parser 
 32       
 33       
 34      class CustomPyTester(PyTester): 
 35          def __init__(self, cvg, options): 
 36              super(CustomPyTester, self).__init__(cvg, options) 
 37              loglevel = options.loglevel.upper() 
 38              logger = logging.getLogger('erudi') 
 39              logger.setLevel(logging.getLevelName(loglevel)) 
 40   
 41   
 42  In your TestCase class you can then get the value of a specific option with 
 43  the ``optval`` method:: 
 44       
 45      class MyTestCase(TestCase): 
 46          def test_foo(self): 
 47              loglevel = self.optval('loglevel') 
 48              # ... 
 49   
 50   
 51  You can also tag your tag your test for fine filtering 
 52   
 53  With those tag:: 
 54   
 55      from logilab.common.testlib import tag, TestCase 
 56   
 57      class Exemple(TestCase): 
 58   
 59          @tag('rouge', 'carre') 
 60          def toto(self): 
 61              pass 
 62   
 63          @tag('carre', 'vert') 
 64          def tata(self): 
 65              pass 
 66   
 67          @tag('rouge') 
 68          def titi(test): 
 69              pass 
 70   
 71  you can filter the function with a simpe python expression 
 72   
 73   * ``toto`` and ``titi`` match ``rouge`` 
 74   
 75   * ``toto``, ``tata`` and ``titi``, match ``rouge or carre`` 
 76   
 77   * ``tata`` and ``titi`` match``rouge ^ carre`` 
 78   
 79   * ``titi`` match ``rouge and not carre`` 
 80   
 81   
 82   
 83   
 84               
 85   
 86  :copyright: 2000-2008 LOGILAB S.A. (Paris, FRANCE), all rights reserved. 
 87  :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr 
 88  :license: General Public License version 2 - http://www.gnu.org/licenses 
 89  """ 
 90  __docformat__ = "restructuredtext en" 
 91   
 92  PYTEST_DOC = """%prog [OPTIONS] [testfile [testpattern]] 
 93   
 94  examples: 
 95   
 96  pytest path/to/mytests.py 
 97  pytest path/to/mytests.py TheseTests 
 98  pytest path/to/mytests.py TheseTests.test_thisone 
 99  pytest path/to/mytests.py -m '(not long and database) or regr' 
100   
101  pytest one (will run both test_thisone and test_thatone) 
102  pytest path/to/mytests.py -s not (will skip test_notthisone) 
103   
104  pytest --coverage test_foo.py 
105    (only if logilab.devtools is available) 
106  """ 
107   
108  import os, sys, re 
109  import os.path as osp 
110  from time import time, clock 
111   
112   
113  from logilab.common.fileutils import abspath_listdir 
114  from logilab.common import testlib 
115  import doctest 
116  import unittest 
117   
118   
119  import imp 
120   
121  import __builtin__ 
122   
123   
124  try: 
125      import django 
126      from logilab.common.modutils import modpath_from_file, load_module_from_modpath 
127      DJANGO_FOUND = True 
128  except ImportError: 
129      DJANGO_FOUND = False 
130   
131  CONF_FILE = 'pytestconf.py' 
132   
133  ## coverage hacks, do not read this, do not read this, do not read this 
134   
135  # hey, but this is an aspect, right ?!!! 
136 -class TraceController(object):
137 nesting = 0 138
139 - def pause_tracing(cls):
140 if not cls.nesting: 141 cls.tracefunc = staticmethod(getattr(sys, '__settrace__', sys.settrace)) 142 cls.oldtracer = getattr(sys, '__tracer__', None) 143 sys.__notrace__ = True 144 cls.tracefunc(None) 145 cls.nesting += 1
146 pause_tracing = classmethod(pause_tracing) 147
148 - def resume_tracing(cls):
149 cls.nesting -= 1 150 assert cls.nesting >= 0 151 if not cls.nesting: 152 cls.tracefunc(cls.oldtracer) 153 delattr(sys, '__notrace__')
154 resume_tracing = classmethod(resume_tracing)
155 156 157 pause_tracing = TraceController.pause_tracing 158 resume_tracing = TraceController.resume_tracing 159 160
161 -def nocoverage(func):
162 if hasattr(func, 'uncovered'): 163 return func 164 func.uncovered = True 165 def not_covered(*args, **kwargs): 166 pause_tracing() 167 try: 168 return func(*args, **kwargs) 169 finally: 170 resume_tracing()
171 not_covered.uncovered = True 172 return not_covered 173 174 175 ## end of coverage hacks 176 177 178 # monkeypatch unittest and doctest (ouch !) 179 unittest.TestCase = testlib.TestCase 180 unittest.main = testlib.unittest_main 181 unittest._TextTestResult = testlib.SkipAwareTestResult 182 unittest.TextTestRunner = testlib.SkipAwareTextTestRunner 183 unittest.TestLoader = testlib.NonStrictTestLoader 184 unittest.TestProgram = testlib.SkipAwareTestProgram 185 if sys.version_info >= (2, 4): 186 doctest.DocTestCase.__bases__ = (testlib.TestCase,) 187 else: 188 unittest.FunctionTestCase.__bases__ = (testlib.TestCase,) 189 190 191 192 TESTFILE_RE = re.compile("^((unit)?test.*|smoketest)\.py$")
193 -def this_is_a_testfile(filename):
194 """returns True if `filename` seems to be a test file""" 195 return TESTFILE_RE.match(osp.basename(filename))
196 197 TESTDIR_RE = re.compile("^(unit)?tests?$")
198 -def this_is_a_testdir(dirpath):
199 """returns True if `filename` seems to be a test directory""" 200 return TESTDIR_RE.match(osp.basename(dirpath))
201 202
203 -def load_pytest_conf(path, parser):
204 """loads a ``pytestconf.py`` file and update default parser 205 and / or tester. 206 """ 207 namespace = {} 208 execfile(path, namespace) 209 if 'update_parser' in namespace: 210 namespace['update_parser'](parser) 211 return namespace.get('CustomPyTester', PyTester)
212 213
214 -def project_root(parser, projdir=os.getcwd()):
215 """try to find project's root and add it to sys.path""" 216 curdir = osp.abspath(projdir) 217 previousdir = curdir 218 testercls = PyTester 219 conf_file_path = osp.join(curdir, CONF_FILE) 220 if osp.isfile(conf_file_path): 221 testercls = load_pytest_conf(conf_file_path, parser) 222 while this_is_a_testdir(curdir) or \ 223 osp.isfile(osp.join(curdir, '__init__.py')): 224 newdir = osp.normpath(osp.join(curdir, os.pardir)) 225 if newdir == curdir: 226 break 227 previousdir = curdir 228 curdir = newdir 229 conf_file_path = osp.join(curdir, CONF_FILE) 230 if osp.isfile(conf_file_path): 231 testercls = load_pytest_conf(conf_file_path, parser) 232 return previousdir, testercls
233 234
235 -class GlobalTestReport(object):
236 """this class holds global test statistics"""
237 - def __init__(self):
238 self.ran = 0 239 self.skipped = 0 240 self.failures = 0 241 self.errors = 0 242 self.ttime = 0 243 self.ctime = 0 244 self.modulescount = 0 245 self.errmodules = []
246
247 - def feed(self, filename, testresult, ttime, ctime):
248 """integrates new test information into internal statistics""" 249 ran = testresult.testsRun 250 self.ran += ran 251 self.skipped += len(getattr(testresult, 'skipped', ())) 252 self.failures += len(testresult.failures) 253 self.errors += len(testresult.errors) 254 self.ttime += ttime 255 self.ctime += ctime 256 self.modulescount += 1 257 if not testresult.wasSuccessful(): 258 problems = len(testresult.failures) + len(testresult.errors) 259 self.errmodules.append((filename[:-3], problems, ran))
260 261
262 - def failed_to_test_module(self, filename):
263 """called when the test module could not be imported by unittest 264 """ 265 self.errors += 1 266 self.errmodules.append((filename[:-3], 1, 1))
267 268
269 - def __str__(self):
270 """this is just presentation stuff""" 271 line1 = ['Ran %s test cases in %.2fs (%.2fs CPU)' 272 % (self.ran, self.ttime, self.ctime)] 273 if self.errors: 274 line1.append('%s errors' % self.errors) 275 if self.failures: 276 line1.append('%s failures' % self.failures) 277 if self.skipped: 278 line1.append('%s skipped' % self.skipped) 279 modulesok = self.modulescount - len(self.errmodules) 280 if self.errors or self.failures: 281 line2 = '%s modules OK (%s failed)' % (modulesok, 282 len(self.errmodules)) 283 descr = ', '.join(['%s [%s/%s]' % info for info in self.errmodules]) 284 line3 = '\nfailures: %s' % descr 285 elif modulesok: 286 line2 = 'All %s modules OK' % modulesok 287 line3 = '' 288 else: 289 return '' 290 return '%s\n%s%s' % (', '.join(line1), line2, line3)
291 292 293
294 -def remove_local_modules_from_sys(testdir):
295 """remove all modules from cache that come from `testdir` 296 297 This is used to avoid strange side-effects when using the 298 testall() mode of pytest. 299 For instance, if we run pytest on this tree:: 300 301 A/test/test_utils.py 302 B/test/test_utils.py 303 304 we **have** to clean sys.modules to make sure the correct test_utils 305 module is ran in B 306 """ 307 for modname, mod in sys.modules.items(): 308 if mod is None: 309 continue 310 if not hasattr(mod, '__file__'): 311 # this is the case of some built-in modules like sys, imp, marshal 312 continue 313 modfile = mod.__file__ 314 # if modfile is not an asbolute path, it was probably loaded locally 315 # during the tests 316 if not osp.isabs(modfile) or modfile.startswith(testdir): 317 del sys.modules[modname]
318 319 320
321 -class PyTester(object):
322 """encaspulates testrun logic""" 323
324 - def __init__(self, cvg, options):
325 self.report = GlobalTestReport() 326 self.cvg = cvg 327 self.options = options 328 self.firstwrite = True
329
330 - def show_report(self):
331 """prints the report and returns appropriate exitcode""" 332 # everything has been ran, print report 333 print "*" * 79 334 print self.report 335 return self.report.failures + self.report.errors
336 337
338 - def testall(self, exitfirst=False):
339 """walks trhough current working directory, finds something 340 which can be considered as a testdir and runs every test there 341 """ 342 here = os.getcwd() 343 for dirname, dirs, _ in os.walk(here): 344 for skipped in ('CVS', '.svn', '.hg'): 345 if skipped in dirs: 346 dirs.remove(skipped) 347 basename = osp.basename(dirname) 348 if this_is_a_testdir(basename): 349 print "going into", dirname 350 # we found a testdir, let's explore it ! 351 self.testonedir(dirname, exitfirst) 352 dirs[:] = [] 353 if self.report.ran == 0: 354 print "no test dir found testing here:", here 355 # if no test was found during the visit, consider 356 # the local directory as a test directory even if 357 # it doesn't have a traditional test directory name 358 self.testonedir(here)
359
360 - def testonedir(self, testdir, exitfirst=False):
361 """finds each testfile in the `testdir` and runs it""" 362 for filename in abspath_listdir(testdir): 363 if this_is_a_testfile(filename): 364 if self.options.exitfirst and not self.options.restart: 365 # overwrite restart file 366 try: 367 restartfile = open(testlib.FILE_RESTART, "w") 368 restartfile.close() 369 except Exception, e: 370 print >> sys.__stderr__, "Error while overwriting \ 371 succeeded test file :", osp.join(os.getcwd(),testlib.FILE_RESTART) 372 raise e 373 # run test and collect information 374 prog = self.testfile(filename, batchmode=True) 375 if exitfirst and (prog is None or not prog.result.wasSuccessful()): 376 break 377 self.firstwrite = True 378 # clean local modules 379 remove_local_modules_from_sys(testdir)
380 381
382 - def testfile(self, filename, batchmode=False):
383 """runs every test in `filename` 384 385 :param filename: an absolute path pointing to a unittest file 386 """ 387 here = os.getcwd() 388 dirname = osp.dirname(filename) 389 if dirname: 390 os.chdir(dirname) 391 # overwrite restart file if it has not been done already 392 if self.options.exitfirst and not self.options.restart and self.firstwrite: 393 try: 394 restartfile = open(testlib.FILE_RESTART, "w") 395 restartfile.close() 396 except Exception, e: 397 print >> sys.__stderr__, "Error while overwriting \ 398 succeeded test file :", osp.join(os.getcwd(),testlib.FILE_RESTART) 399 raise e 400 modname = osp.basename(filename)[:-3] 401 if batchmode: 402 from cStringIO import StringIO 403 outstream = StringIO() 404 else: 405 outstream = sys.stderr 406 try: 407 print >> outstream, (' %s ' % osp.basename(filename)).center(70, '=') 408 except TypeError: # < py 2.4 bw compat 409 print >> outstream, (' %s ' % osp.basename(filename)).center(70) 410 try: 411 try: 412 tstart, cstart = time(), clock() 413 testprog = testlib.unittest_main(modname, batchmode=batchmode, cvg=self.cvg, 414 options=self.options, outstream=outstream) 415 tend, cend = time(), clock() 416 ttime, ctime = (tend - tstart), (cend - cstart) 417 if testprog.result.testsRun and batchmode: 418 print >> sys.stderr, outstream.getvalue() 419 self.report.feed(filename, testprog.result, ttime, ctime) 420 return testprog 421 except (KeyboardInterrupt, SystemExit): 422 raise 423 except Exception: 424 self.report.failed_to_test_module(filename) 425 print 'unhandled exception occured while testing', modname 426 import traceback 427 traceback.print_exc() 428 return None 429 finally: 430 if dirname: 431 os.chdir(here)
432 433 434
435 -class DjangoTester(PyTester):
436
437 - def load_django_settings(self, dirname):
438 """try to find project's setting and load it""" 439 curdir = osp.abspath(dirname) 440 previousdir = curdir 441 while not osp.isfile(osp.join(curdir, 'settings.py')) and \ 442 osp.isfile(osp.join(curdir, '__init__.py')): 443 newdir = osp.normpath(osp.join(curdir, os.pardir)) 444 if newdir == curdir: 445 raise AssertionError('could not find settings.py') 446 previousdir = curdir 447 curdir = newdir 448 # late django initialization 449 settings = load_module_from_modpath(modpath_from_file(osp.join(curdir, 'settings.py'))) 450 from django.core.management import setup_environ 451 setup_environ(settings) 452 settings.DEBUG = False 453 self.settings = settings 454 # add settings dir to pythonpath since it's the project's root 455 if curdir not in sys.path: 456 sys.path.insert(1, curdir)
457
458 - def before_testfile(self):
459 # Those imports must be done **after** setup_environ was called 460 from django.test.utils import setup_test_environment 461 from django.test.utils import create_test_db 462 setup_test_environment() 463 create_test_db(verbosity=0) 464 self.dbname = self.settings.TEST_DATABASE_NAME
465 466
467 - def after_testfile(self):
468 # Those imports must be done **after** setup_environ was called 469 from django.test.utils import teardown_test_environment 470 from django.test.utils import destroy_test_db 471 teardown_test_environment() 472 print 'destroying', self.dbname 473 destroy_test_db(self.dbname, verbosity=0)
474 475
476 - def testall(self, exitfirst=False):
477 """walks trhough current working directory, finds something 478 which can be considered as a testdir and runs every test there 479 """ 480 for dirname, dirs, _ in os.walk(os.getcwd()): 481 for skipped in ('CVS', '.svn', '.hg'): 482 if skipped in dirs: 483 dirs.remove(skipped) 484 if 'tests.py' in files: 485 self.testonedir(dirname, exitfirst) 486 dirs[:] = [] 487 else: 488 basename = osp.basename(dirname) 489 if basename in ('test', 'tests'): 490 print "going into", dirname 491 # we found a testdir, let's explore it ! 492 self.testonedir(dirname, exitfirst) 493 dirs[:] = []
494 495
496 - def testonedir(self, testdir, exitfirst=False):
497 """finds each testfile in the `testdir` and runs it""" 498 # special django behaviour : if tests are splited in several files, 499 # remove the main tests.py file and tests each test file separately 500 testfiles = [fpath for fpath in abspath_listdir(testdir) 501 if this_is_a_testfile(fpath)] 502 if len(testfiles) > 1: 503 try: 504 testfiles.remove(osp.join(testdir, 'tests.py')) 505 except ValueError: 506 pass 507 for filename in testfiles: 508 # run test and collect information 509 prog = self.testfile(filename, batchmode=True) 510 if exitfirst and (prog is None or not prog.result.wasSuccessful()): 511 break 512 # clean local modules 513 remove_local_modules_from_sys(testdir)
514 515
516 - def testfile(self, filename, batchmode=False):
517 """runs every test in `filename` 518 519 :param filename: an absolute path pointing to a unittest file 520 """ 521 here = os.getcwd() 522 dirname = osp.dirname(filename) 523 if dirname: 524 os.chdir(dirname) 525 self.load_django_settings(dirname) 526 modname = osp.basename(filename)[:-3] 527 print >>sys.stderr, (' %s ' % osp.basename(filename)).center(70, '=') 528 try: 529 try: 530 tstart, cstart = time(), clock() 531 self.before_testfile() 532 testprog = testlib.unittest_main(modname, batchmode=batchmode, cvg=self.cvg) 533 tend, cend = time(), clock() 534 ttime, ctime = (tend - tstart), (cend - cstart) 535 self.report.feed(filename, testprog.result, ttime, ctime) 536 return testprog 537 except SystemExit: 538 raise 539 except Exception, exc: 540 import traceback 541 traceback.print_exc() 542 self.report.failed_to_test_module(filename) 543 print 'unhandled exception occured while testing', modname 544 print 'error: %s' % exc 545 return None 546 finally: 547 self.after_testfile() 548 if dirname: 549 os.chdir(here)
550 551
552 -def make_parser():
553 """creates the OptionParser instance 554 """ 555 from optparse import OptionParser 556 parser = OptionParser(usage=PYTEST_DOC) 557 558 parser.newargs = [] 559 def rebuild_cmdline(option, opt, value, parser): 560 """carry the option to unittest_main""" 561 parser.newargs.append(opt)
562 563 564 def rebuild_and_store(option, opt, value, parser): 565 """carry the option to unittest_main and store 566 the value on current parser 567 """ 568 parser.newargs.append(opt) 569 setattr(parser.values, option.dest, True) 570 571 # pytest options 572 parser.add_option('-t', dest='testdir', default=None, 573 help="directory where the tests will be found") 574 parser.add_option('-d', dest='dbc', default=False, 575 action="store_true", help="enable design-by-contract") 576 # unittest_main options provided and passed through pytest 577 parser.add_option('-v', '--verbose', callback=rebuild_cmdline, 578 action="callback", help="Verbose output") 579 parser.add_option('-i', '--pdb', callback=rebuild_and_store, 580 dest="pdb", action="callback", 581 help="Enable test failure inspection (conflicts with --coverage)") 582 parser.add_option('-x', '--exitfirst', callback=rebuild_and_store, 583 dest="exitfirst", default=False, 584 action="callback", help="Exit on first failure " 585 "(only make sense when pytest run one test file)") 586 parser.add_option('-R', '--restart', callback=rebuild_and_store, 587 dest="restart", default=False, 588 action="callback", 589 help="Restart tests from where it failed (implies exitfirst) " 590 "(only make sense if tests previously ran with exitfirst only)") 591 parser.add_option('-c', '--capture', callback=rebuild_cmdline, 592 action="callback", 593 help="Captures and prints standard out/err only on errors " 594 "(only make sense when pytest run one test file)") 595 parser.add_option('-p', '--printonly', 596 # XXX: I wish I could use the callback action but it 597 # doesn't seem to be able to get the value 598 # associated to the option 599 action="store", dest="printonly", default=None, 600 help="Only prints lines matching specified pattern (implies capture) " 601 "(only make sense when pytest run one test file)") 602 parser.add_option('-s', '--skip', 603 # XXX: I wish I could use the callback action but it 604 # doesn't seem to be able to get the value 605 # associated to the option 606 action="store", dest="skipped", default=None, 607 help="test names matching this name will be skipped " 608 "to skip several patterns, use commas") 609 parser.add_option('-q', '--quiet', callback=rebuild_cmdline, 610 action="callback", help="Minimal output") 611 parser.add_option('-P', '--profile', default=None, dest='profile', 612 help="Profile execution and store data in the given file") 613 parser.add_option('-m', '--match', default=None, dest='tags_pattern', 614 help="only execute test whose tag macht the current pattern") 615 616 try: 617 from logilab.devtools.lib.coverage import Coverage 618 parser.add_option('--coverage', dest="coverage", default=False, 619 action="store_true", 620 help="run tests with pycoverage (conflicts with --pdb)") 621 except ImportError: 622 pass 623 624 if DJANGO_FOUND: 625 parser.add_option('-J', '--django', dest='django', default=False, 626 action="store_true", 627 help='use pytest for django test cases') 628 return parser 629 630
631 -def parseargs(parser):
632 """Parse the command line and return (options processed), (options to pass to 633 unittest_main()), (explicitfile or None). 634 """ 635 # parse the command line 636 options, args = parser.parse_args() 637 if options.pdb and getattr(options, 'coverage', False): 638 parser.error("'pdb' and 'coverage' options are exclusive") 639 filenames = [arg for arg in args if arg.endswith('.py')] 640 if filenames: 641 if len(filenames) > 1: 642 parser.error("only one filename is acceptable") 643 explicitfile = filenames[0] 644 args.remove(explicitfile) 645 else: 646 explicitfile = None 647 # someone wants DBC 648 testlib.ENABLE_DBC = options.dbc 649 newargs = parser.newargs 650 if options.printonly: 651 newargs.extend(['--printonly', options.printonly]) 652 if options.skipped: 653 newargs.extend(['--skip', options.skipped]) 654 # restart implies exitfirst 655 if options.restart: 656 options.exitfirst = True 657 # append additional args to the new sys.argv and let unittest_main 658 # do the rest 659 newargs += args 660 return options, explicitfile
661 662 663
664 -def run():
665 parser = make_parser() 666 rootdir, testercls = project_root(parser) 667 options, explicitfile = parseargs(parser) 668 # mock a new command line 669 sys.argv[1:] = parser.newargs 670 covermode = getattr(options, 'coverage', None) 671 cvg = None 672 if not '' in sys.path: 673 sys.path.insert(0, '') 674 if covermode: 675 # control_import_coverage(rootdir) 676 from logilab.devtools.lib.coverage import Coverage 677 cvg = Coverage([rootdir]) 678 cvg.erase() 679 cvg.start() 680 if DJANGO_FOUND and options.django: 681 tester = DjangoTester(cvg, options) 682 else: 683 tester = testercls(cvg, options) 684 if explicitfile: 685 cmd, args = tester.testfile, (explicitfile,) 686 elif options.testdir: 687 cmd, args = tester.testonedir, (options.testdir, options.exitfirst) 688 else: 689 cmd, args = tester.testall, (options.exitfirst,) 690 try: 691 try: 692 if options.profile: 693 import hotshot 694 prof = hotshot.Profile(options.profile) 695 prof.runcall(cmd, *args) 696 prof.close() 697 print 'profile data saved in', options.profile 698 else: 699 cmd(*args) 700 except SystemExit: 701 raise 702 except: 703 import traceback 704 traceback.print_exc() 705 finally: 706 errcode = tester.show_report() 707 if covermode: 708 cvg.stop() 709 cvg.save() 710 here = osp.abspath(os.getcwd()) 711 if this_is_a_testdir(here): 712 morfdir = osp.normpath(osp.join(here, '..')) 713 else: 714 morfdir = here 715 print "computing code coverage (%s), this might take some time" % \ 716 morfdir 717 cvg.annotate([morfdir]) 718 cvg.report([morfdir], False) 719 sys.exit(errcode)
720