| OLD | NEW |
| 1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. | 1 # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved. |
| 2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr | 2 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr |
| 3 # | 3 # |
| 4 # This file is part of logilab-common. | 4 # This file is part of logilab-common. |
| 5 # | 5 # |
| 6 # logilab-common is free software: you can redistribute it and/or modify it unde
r | 6 # logilab-common is free software: you can redistribute it and/or modify it unde
r |
| 7 # the terms of the GNU Lesser General Public License as published by the Free | 7 # the terms of the GNU Lesser General Public License as published by the Free |
| 8 # Software Foundation, either version 2.1 of the License, or (at your option) an
y | 8 # Software Foundation, either version 2.1 of the License, or (at your option) an
y |
| 9 # later version. | 9 # later version. |
| 10 # | 10 # |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 85 def titi(test): | 85 def titi(test): |
| 86 pass | 86 pass |
| 87 | 87 |
| 88 you can filter the function with a simple python expression | 88 you can filter the function with a simple python expression |
| 89 | 89 |
| 90 * ``toto`` and ``titi`` match ``rouge`` | 90 * ``toto`` and ``titi`` match ``rouge`` |
| 91 * ``toto``, ``tata`` and ``titi``, match ``rouge or carre`` | 91 * ``toto``, ``tata`` and ``titi``, match ``rouge or carre`` |
| 92 * ``tata`` and ``titi`` match``rouge ^ carre`` | 92 * ``tata`` and ``titi`` match``rouge ^ carre`` |
| 93 * ``titi`` match ``rouge and not carre`` | 93 * ``titi`` match ``rouge and not carre`` |
| 94 """ | 94 """ |
| 95 |
| 96 from __future__ import print_function |
| 97 |
| 95 __docformat__ = "restructuredtext en" | 98 __docformat__ = "restructuredtext en" |
| 96 | 99 |
| 97 PYTEST_DOC = """%prog [OPTIONS] [testfile [testpattern]] | 100 PYTEST_DOC = """%prog [OPTIONS] [testfile [testpattern]] |
| 98 | 101 |
| 99 examples: | 102 examples: |
| 100 | 103 |
| 101 pytest path/to/mytests.py | 104 pytest path/to/mytests.py |
| 102 pytest path/to/mytests.py TheseTests | 105 pytest path/to/mytests.py TheseTests |
| 103 pytest path/to/mytests.py TheseTests.test_thisone | 106 pytest path/to/mytests.py TheseTests.test_thisone |
| 104 pytest path/to/mytests.py -m '(not long and database) or regr' | 107 pytest path/to/mytests.py -m '(not long and database) or regr' |
| 105 | 108 |
| 106 pytest one (will run both test_thisone and test_thatone) | 109 pytest one (will run both test_thisone and test_thatone) |
| 107 pytest path/to/mytests.py -s not (will skip test_notthisone) | 110 pytest path/to/mytests.py -s not (will skip test_notthisone) |
| 108 | |
| 109 pytest --coverage test_foo.py | |
| 110 (only if logilab.devtools is available) | |
| 111 """ | 111 """ |
| 112 | 112 |
| 113 ENABLE_DBC = False | 113 ENABLE_DBC = False |
| 114 FILE_RESTART = ".pytest.restart" | 114 FILE_RESTART = ".pytest.restart" |
| 115 | 115 |
| 116 import os, sys, re | 116 import os, sys, re |
| 117 import os.path as osp | 117 import os.path as osp |
| 118 from time import time, clock | 118 from time import time, clock |
| 119 import warnings | 119 import warnings |
| 120 import types | 120 import types |
| 121 from inspect import isgeneratorfunction, isclass |
| 121 | 122 |
| 122 from logilab.common.fileutils import abspath_listdir | 123 from logilab.common.fileutils import abspath_listdir |
| 123 from logilab.common import textutils | 124 from logilab.common import textutils |
| 124 from logilab.common import testlib, STD_BLACKLIST | 125 from logilab.common import testlib, STD_BLACKLIST |
| 125 # use the same unittest module as testlib | 126 # use the same unittest module as testlib |
| 126 from logilab.common.testlib import unittest, start_interactive_mode | 127 from logilab.common.testlib import unittest, start_interactive_mode |
| 127 from logilab.common.compat import any | |
| 128 import doctest | 128 import doctest |
| 129 | 129 |
| 130 import unittest as unittest_legacy | 130 import unittest as unittest_legacy |
| 131 if not getattr(unittest_legacy, "__package__", None): | 131 if not getattr(unittest_legacy, "__package__", None): |
| 132 try: | 132 try: |
| 133 import unittest2.suite as unittest_suite | 133 import unittest2.suite as unittest_suite |
| 134 except ImportError: | 134 except ImportError: |
| 135 sys.exit("You have to install python-unittest2 to use this module") | 135 sys.exit("You have to install python-unittest2 to use this module") |
| 136 else: | 136 else: |
| 137 import unittest.suite as unittest_suite | 137 import unittest.suite as unittest_suite |
| (...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 199 def this_is_a_testdir(dirpath): | 199 def this_is_a_testdir(dirpath): |
| 200 """returns True if `filename` seems to be a test directory""" | 200 """returns True if `filename` seems to be a test directory""" |
| 201 return TESTDIR_RE.match(osp.basename(dirpath)) | 201 return TESTDIR_RE.match(osp.basename(dirpath)) |
| 202 | 202 |
| 203 | 203 |
| 204 def load_pytest_conf(path, parser): | 204 def load_pytest_conf(path, parser): |
| 205 """loads a ``pytestconf.py`` file and update default parser | 205 """loads a ``pytestconf.py`` file and update default parser |
| 206 and / or tester. | 206 and / or tester. |
| 207 """ | 207 """ |
| 208 namespace = {} | 208 namespace = {} |
| 209 execfile(path, namespace) | 209 exec(open(path, 'rb').read(), namespace) |
| 210 if 'update_parser' in namespace: | 210 if 'update_parser' in namespace: |
| 211 namespace['update_parser'](parser) | 211 namespace['update_parser'](parser) |
| 212 return namespace.get('CustomPyTester', PyTester) | 212 return namespace.get('CustomPyTester', PyTester) |
| 213 | 213 |
| 214 | 214 |
| 215 def project_root(parser, projdir=os.getcwd()): | 215 def project_root(parser, projdir=os.getcwd()): |
| 216 """try to find project's root and add it to sys.path""" | 216 """try to find project's root and add it to sys.path""" |
| 217 previousdir = curdir = osp.abspath(projdir) | 217 previousdir = curdir = osp.abspath(projdir) |
| 218 testercls = PyTester | 218 testercls = PyTester |
| 219 conf_file_path = osp.join(curdir, CONF_FILE) | 219 conf_file_path = osp.join(curdir, CONF_FILE) |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 302 This is used to avoid strange side-effects when using the | 302 This is used to avoid strange side-effects when using the |
| 303 testall() mode of pytest. | 303 testall() mode of pytest. |
| 304 For instance, if we run pytest on this tree:: | 304 For instance, if we run pytest on this tree:: |
| 305 | 305 |
| 306 A/test/test_utils.py | 306 A/test/test_utils.py |
| 307 B/test/test_utils.py | 307 B/test/test_utils.py |
| 308 | 308 |
| 309 we **have** to clean sys.modules to make sure the correct test_utils | 309 we **have** to clean sys.modules to make sure the correct test_utils |
| 310 module is ran in B | 310 module is ran in B |
| 311 """ | 311 """ |
| 312 for modname, mod in sys.modules.items(): | 312 for modname, mod in list(sys.modules.items()): |
| 313 if mod is None: | 313 if mod is None: |
| 314 continue | 314 continue |
| 315 if not hasattr(mod, '__file__'): | 315 if not hasattr(mod, '__file__'): |
| 316 # this is the case of some built-in modules like sys, imp, marshal | 316 # this is the case of some built-in modules like sys, imp, marshal |
| 317 continue | 317 continue |
| 318 modfile = mod.__file__ | 318 modfile = mod.__file__ |
| 319 # if modfile is not an absolute path, it was probably loaded locally | 319 # if modfile is not an absolute path, it was probably loaded locally |
| 320 # during the tests | 320 # during the tests |
| 321 if not osp.isabs(modfile) or modfile.startswith(testdir): | 321 if not osp.isabs(modfile) or modfile.startswith(testdir): |
| 322 del sys.modules[modname] | 322 del sys.modules[modname] |
| 323 | 323 |
| 324 | 324 |
| 325 | 325 |
| 326 class PyTester(object): | 326 class PyTester(object): |
| 327 """encapsulates testrun logic""" | 327 """encapsulates testrun logic""" |
| 328 | 328 |
| 329 def __init__(self, cvg, options): | 329 def __init__(self, cvg, options): |
| 330 self.report = GlobalTestReport() | 330 self.report = GlobalTestReport() |
| 331 self.cvg = cvg | 331 self.cvg = cvg |
| 332 self.options = options | 332 self.options = options |
| 333 self.firstwrite = True | 333 self.firstwrite = True |
| 334 self._errcode = None | 334 self._errcode = None |
| 335 | 335 |
| 336 def show_report(self): | 336 def show_report(self): |
| 337 """prints the report and returns appropriate exitcode""" | 337 """prints the report and returns appropriate exitcode""" |
| 338 # everything has been ran, print report | 338 # everything has been ran, print report |
| 339 print "*" * 79 | 339 print("*" * 79) |
| 340 print self.report | 340 print(self.report) |
| 341 | 341 |
| 342 def get_errcode(self): | 342 def get_errcode(self): |
| 343 # errcode set explicitly | 343 # errcode set explicitly |
| 344 if self._errcode is not None: | 344 if self._errcode is not None: |
| 345 return self._errcode | 345 return self._errcode |
| 346 return self.report.failures + self.report.errors | 346 return self.report.failures + self.report.errors |
| 347 | 347 |
| 348 def set_errcode(self, errcode): | 348 def set_errcode(self, errcode): |
| 349 self._errcode = errcode | 349 self._errcode = errcode |
| 350 errcode = property(get_errcode, set_errcode) | 350 errcode = property(get_errcode, set_errcode) |
| 351 | 351 |
| 352 def testall(self, exitfirst=False): | 352 def testall(self, exitfirst=False): |
| 353 """walks through current working directory, finds something | 353 """walks through current working directory, finds something |
| 354 which can be considered as a testdir and runs every test there | 354 which can be considered as a testdir and runs every test there |
| 355 """ | 355 """ |
| 356 here = os.getcwd() | 356 here = os.getcwd() |
| 357 for dirname, dirs, _ in os.walk(here): | 357 for dirname, dirs, _ in os.walk(here): |
| 358 for skipped in STD_BLACKLIST: | 358 for skipped in STD_BLACKLIST: |
| 359 if skipped in dirs: | 359 if skipped in dirs: |
| 360 dirs.remove(skipped) | 360 dirs.remove(skipped) |
| 361 basename = osp.basename(dirname) | 361 basename = osp.basename(dirname) |
| 362 if this_is_a_testdir(basename): | 362 if this_is_a_testdir(basename): |
| 363 print "going into", dirname | 363 print("going into", dirname) |
| 364 # we found a testdir, let's explore it ! | 364 # we found a testdir, let's explore it ! |
| 365 if not self.testonedir(dirname, exitfirst): | 365 if not self.testonedir(dirname, exitfirst): |
| 366 break | 366 break |
| 367 dirs[:] = [] | 367 dirs[:] = [] |
| 368 if self.report.ran == 0: | 368 if self.report.ran == 0: |
| 369 print "no test dir found testing here:", here | 369 print("no test dir found testing here:", here) |
| 370 # if no test was found during the visit, consider | 370 # if no test was found during the visit, consider |
| 371 # the local directory as a test directory even if | 371 # the local directory as a test directory even if |
| 372 # it doesn't have a traditional test directory name | 372 # it doesn't have a traditional test directory name |
| 373 self.testonedir(here) | 373 self.testonedir(here) |
| 374 | 374 |
| 375 def testonedir(self, testdir, exitfirst=False): | 375 def testonedir(self, testdir, exitfirst=False): |
| 376 """finds each testfile in the `testdir` and runs it | 376 """finds each testfile in the `testdir` and runs it |
| 377 | 377 |
| 378 return true when all tests has been executed, false if exitfirst and | 378 return true when all tests has been executed, false if exitfirst and |
| 379 some test has failed. | 379 some test has failed. |
| 380 """ | 380 """ |
| 381 for filename in abspath_listdir(testdir): | 381 for filename in abspath_listdir(testdir): |
| 382 if this_is_a_testfile(filename): | 382 if this_is_a_testfile(filename): |
| 383 if self.options.exitfirst and not self.options.restart: | 383 if self.options.exitfirst and not self.options.restart: |
| 384 # overwrite restart file | 384 # overwrite restart file |
| 385 try: | 385 try: |
| 386 restartfile = open(FILE_RESTART, "w") | 386 restartfile = open(FILE_RESTART, "w") |
| 387 restartfile.close() | 387 restartfile.close() |
| 388 except Exception, e: | 388 except Exception: |
| 389 print >> sys.__stderr__, "Error while overwriting \ | 389 print("Error while overwriting succeeded test file :", |
| 390 succeeded test file :", osp.join(os.getcwd(), FILE_RESTART) | 390 osp.join(os.getcwd(), FILE_RESTART), |
| 391 raise e | 391 file=sys.__stderr__) |
| 392 raise |
| 392 # run test and collect information | 393 # run test and collect information |
| 393 prog = self.testfile(filename, batchmode=True) | 394 prog = self.testfile(filename, batchmode=True) |
| 394 if exitfirst and (prog is None or not prog.result.wasSuccessful(
)): | 395 if exitfirst and (prog is None or not prog.result.wasSuccessful(
)): |
| 395 return False | 396 return False |
| 396 self.firstwrite = True | 397 self.firstwrite = True |
| 397 # clean local modules | 398 # clean local modules |
| 398 remove_local_modules_from_sys(testdir) | 399 remove_local_modules_from_sys(testdir) |
| 399 return True | 400 return True |
| 400 | 401 |
| 401 def testfile(self, filename, batchmode=False): | 402 def testfile(self, filename, batchmode=False): |
| 402 """runs every test in `filename` | 403 """runs every test in `filename` |
| 403 | 404 |
| 404 :param filename: an absolute path pointing to a unittest file | 405 :param filename: an absolute path pointing to a unittest file |
| 405 """ | 406 """ |
| 406 here = os.getcwd() | 407 here = os.getcwd() |
| 407 dirname = osp.dirname(filename) | 408 dirname = osp.dirname(filename) |
| 408 if dirname: | 409 if dirname: |
| 409 os.chdir(dirname) | 410 os.chdir(dirname) |
| 410 # overwrite restart file if it has not been done already | 411 # overwrite restart file if it has not been done already |
| 411 if self.options.exitfirst and not self.options.restart and self.firstwri
te: | 412 if self.options.exitfirst and not self.options.restart and self.firstwri
te: |
| 412 try: | 413 try: |
| 413 restartfile = open(FILE_RESTART, "w") | 414 restartfile = open(FILE_RESTART, "w") |
| 414 restartfile.close() | 415 restartfile.close() |
| 415 except Exception, e: | 416 except Exception: |
| 416 print >> sys.__stderr__, "Error while overwriting \ | 417 print("Error while overwriting succeeded test file :", |
| 417 succeeded test file :", osp.join(os.getcwd(), FILE_RESTART) | 418 osp.join(os.getcwd(), FILE_RESTART), file=sys.__stderr__) |
| 418 raise e | 419 raise |
| 419 modname = osp.basename(filename)[:-3] | 420 modname = osp.basename(filename)[:-3] |
| 420 try: | 421 print((' %s ' % osp.basename(filename)).center(70, '='), |
| 421 print >> sys.stderr, (' %s ' % osp.basename(filename)).center(70,
'=') | 422 file=sys.__stderr__) |
| 422 except TypeError: # < py 2.4 bw compat | |
| 423 print >> sys.stderr, (' %s ' % osp.basename(filename)).center(70) | |
| 424 try: | 423 try: |
| 425 tstart, cstart = time(), clock() | 424 tstart, cstart = time(), clock() |
| 426 try: | 425 try: |
| 427 testprog = SkipAwareTestProgram(modname, batchmode=batchmode, cv
g=self.cvg, | 426 testprog = SkipAwareTestProgram(modname, batchmode=batchmode, cv
g=self.cvg, |
| 428 options=self.options, outstream
=sys.stderr) | 427 options=self.options, outstream
=sys.stderr) |
| 429 except KeyboardInterrupt: | 428 except KeyboardInterrupt: |
| 430 raise | 429 raise |
| 431 except SystemExit, exc: | 430 except SystemExit as exc: |
| 432 self.errcode = exc.code | 431 self.errcode = exc.code |
| 433 raise | 432 raise |
| 434 except testlib.SkipTest: | 433 except testlib.SkipTest: |
| 435 print "Module skipped:", filename | 434 print("Module skipped:", filename) |
| 436 self.report.skip_module(filename) | 435 self.report.skip_module(filename) |
| 437 return None | 436 return None |
| 438 except Exception: | 437 except Exception: |
| 439 self.report.failed_to_test_module(filename) | 438 self.report.failed_to_test_module(filename) |
| 440 print >> sys.stderr, 'unhandled exception occurred while testing
', modname | 439 print('unhandled exception occurred while testing', modname, |
| 440 file=sys.stderr) |
| 441 import traceback | 441 import traceback |
| 442 traceback.print_exc(file=sys.stderr) | 442 traceback.print_exc(file=sys.stderr) |
| 443 return None | 443 return None |
| 444 | 444 |
| 445 tend, cend = time(), clock() | 445 tend, cend = time(), clock() |
| 446 ttime, ctime = (tend - tstart), (cend - cstart) | 446 ttime, ctime = (tend - tstart), (cend - cstart) |
| 447 self.report.feed(filename, testprog.result, ttime, ctime) | 447 self.report.feed(filename, testprog.result, ttime, ctime) |
| 448 return testprog | 448 return testprog |
| 449 finally: | 449 finally: |
| 450 if dirname: | 450 if dirname: |
| (...skipping 30 matching lines...) Expand all Loading... |
| 481 from django.test.utils import create_test_db | 481 from django.test.utils import create_test_db |
| 482 setup_test_environment() | 482 setup_test_environment() |
| 483 create_test_db(verbosity=0) | 483 create_test_db(verbosity=0) |
| 484 self.dbname = self.settings.TEST_DATABASE_NAME | 484 self.dbname = self.settings.TEST_DATABASE_NAME |
| 485 | 485 |
| 486 def after_testfile(self): | 486 def after_testfile(self): |
| 487 # Those imports must be done **after** setup_environ was called | 487 # Those imports must be done **after** setup_environ was called |
| 488 from django.test.utils import teardown_test_environment | 488 from django.test.utils import teardown_test_environment |
| 489 from django.test.utils import destroy_test_db | 489 from django.test.utils import destroy_test_db |
| 490 teardown_test_environment() | 490 teardown_test_environment() |
| 491 print 'destroying', self.dbname | 491 print('destroying', self.dbname) |
| 492 destroy_test_db(self.dbname, verbosity=0) | 492 destroy_test_db(self.dbname, verbosity=0) |
| 493 | 493 |
| 494 def testall(self, exitfirst=False): | 494 def testall(self, exitfirst=False): |
| 495 """walks through current working directory, finds something | 495 """walks through current working directory, finds something |
| 496 which can be considered as a testdir and runs every test there | 496 which can be considered as a testdir and runs every test there |
| 497 """ | 497 """ |
| 498 for dirname, dirs, files in os.walk(os.getcwd()): | 498 for dirname, dirs, files in os.walk(os.getcwd()): |
| 499 for skipped in ('CVS', '.svn', '.hg'): | 499 for skipped in ('CVS', '.svn', '.hg'): |
| 500 if skipped in dirs: | 500 if skipped in dirs: |
| 501 dirs.remove(skipped) | 501 dirs.remove(skipped) |
| 502 if 'tests.py' in files: | 502 if 'tests.py' in files: |
| 503 if not self.testonedir(dirname, exitfirst): | 503 if not self.testonedir(dirname, exitfirst): |
| 504 break | 504 break |
| 505 dirs[:] = [] | 505 dirs[:] = [] |
| 506 else: | 506 else: |
| 507 basename = osp.basename(dirname) | 507 basename = osp.basename(dirname) |
| 508 if basename in ('test', 'tests'): | 508 if basename in ('test', 'tests'): |
| 509 print "going into", dirname | 509 print("going into", dirname) |
| 510 # we found a testdir, let's explore it ! | 510 # we found a testdir, let's explore it ! |
| 511 if not self.testonedir(dirname, exitfirst): | 511 if not self.testonedir(dirname, exitfirst): |
| 512 break | 512 break |
| 513 dirs[:] = [] | 513 dirs[:] = [] |
| 514 | 514 |
| 515 def testonedir(self, testdir, exitfirst=False): | 515 def testonedir(self, testdir, exitfirst=False): |
| 516 """finds each testfile in the `testdir` and runs it | 516 """finds each testfile in the `testdir` and runs it |
| 517 | 517 |
| 518 return true when all tests has been executed, false if exitfirst and | 518 return true when all tests has been executed, false if exitfirst and |
| 519 some test has failed. | 519 some test has failed. |
| (...skipping 20 matching lines...) Expand all Loading... |
| 540 """runs every test in `filename` | 540 """runs every test in `filename` |
| 541 | 541 |
| 542 :param filename: an absolute path pointing to a unittest file | 542 :param filename: an absolute path pointing to a unittest file |
| 543 """ | 543 """ |
| 544 here = os.getcwd() | 544 here = os.getcwd() |
| 545 dirname = osp.dirname(filename) | 545 dirname = osp.dirname(filename) |
| 546 if dirname: | 546 if dirname: |
| 547 os.chdir(dirname) | 547 os.chdir(dirname) |
| 548 self.load_django_settings(dirname) | 548 self.load_django_settings(dirname) |
| 549 modname = osp.basename(filename)[:-3] | 549 modname = osp.basename(filename)[:-3] |
| 550 print >>sys.stderr, (' %s ' % osp.basename(filename)).center(70, '=') | 550 print((' %s ' % osp.basename(filename)).center(70, '='), |
| 551 file=sys.stderr) |
| 551 try: | 552 try: |
| 552 try: | 553 try: |
| 553 tstart, cstart = time(), clock() | 554 tstart, cstart = time(), clock() |
| 554 self.before_testfile() | 555 self.before_testfile() |
| 555 testprog = SkipAwareTestProgram(modname, batchmode=batchmode, cv
g=self.cvg) | 556 testprog = SkipAwareTestProgram(modname, batchmode=batchmode, cv
g=self.cvg) |
| 556 tend, cend = time(), clock() | 557 tend, cend = time(), clock() |
| 557 ttime, ctime = (tend - tstart), (cend - cstart) | 558 ttime, ctime = (tend - tstart), (cend - cstart) |
| 558 self.report.feed(filename, testprog.result, ttime, ctime) | 559 self.report.feed(filename, testprog.result, ttime, ctime) |
| 559 return testprog | 560 return testprog |
| 560 except SystemExit: | 561 except SystemExit: |
| 561 raise | 562 raise |
| 562 except Exception, exc: | 563 except Exception as exc: |
| 563 import traceback | 564 import traceback |
| 564 traceback.print_exc() | 565 traceback.print_exc() |
| 565 self.report.failed_to_test_module(filename) | 566 self.report.failed_to_test_module(filename) |
| 566 print 'unhandled exception occurred while testing', modname | 567 print('unhandled exception occurred while testing', modname) |
| 567 print 'error: %s' % exc | 568 print('error: %s' % exc) |
| 568 return None | 569 return None |
| 569 finally: | 570 finally: |
| 570 self.after_testfile() | 571 self.after_testfile() |
| 571 if dirname: | 572 if dirname: |
| 572 os.chdir(here) | 573 os.chdir(here) |
| 573 | 574 |
| 574 | 575 |
| 575 def make_parser(): | 576 def make_parser(): |
| 576 """creates the OptionParser instance | 577 """creates the OptionParser instance |
| 577 """ | 578 """ |
| (...skipping 19 matching lines...) Expand all Loading... |
| 597 # pytest options | 598 # pytest options |
| 598 parser.add_option('-t', dest='testdir', default=None, | 599 parser.add_option('-t', dest='testdir', default=None, |
| 599 help="directory where the tests will be found") | 600 help="directory where the tests will be found") |
| 600 parser.add_option('-d', dest='dbc', default=False, | 601 parser.add_option('-d', dest='dbc', default=False, |
| 601 action="store_true", help="enable design-by-contract") | 602 action="store_true", help="enable design-by-contract") |
| 602 # unittest_main options provided and passed through pytest | 603 # unittest_main options provided and passed through pytest |
| 603 parser.add_option('-v', '--verbose', callback=rebuild_cmdline, | 604 parser.add_option('-v', '--verbose', callback=rebuild_cmdline, |
| 604 action="callback", help="Verbose output") | 605 action="callback", help="Verbose output") |
| 605 parser.add_option('-i', '--pdb', callback=rebuild_and_store, | 606 parser.add_option('-i', '--pdb', callback=rebuild_and_store, |
| 606 dest="pdb", action="callback", | 607 dest="pdb", action="callback", |
| 607 help="Enable test failure inspection (conflicts with --cov
erage)") | 608 help="Enable test failure inspection") |
| 608 parser.add_option('-x', '--exitfirst', callback=rebuild_and_store, | 609 parser.add_option('-x', '--exitfirst', callback=rebuild_and_store, |
| 609 dest="exitfirst", default=False, | 610 dest="exitfirst", default=False, |
| 610 action="callback", help="Exit on first failure " | 611 action="callback", help="Exit on first failure " |
| 611 "(only make sense when pytest run one test file)") | 612 "(only make sense when pytest run one test file)") |
| 612 parser.add_option('-R', '--restart', callback=rebuild_and_store, | 613 parser.add_option('-R', '--restart', callback=rebuild_and_store, |
| 613 dest="restart", default=False, | 614 dest="restart", default=False, |
| 614 action="callback", | 615 action="callback", |
| 615 help="Restart tests from where it failed (implies exitfirs
t) " | 616 help="Restart tests from where it failed (implies exitfirs
t) " |
| 616 "(only make sense if tests previously ran with exitfirst
only)") | 617 "(only make sense if tests previously ran with exitfirst
only)") |
| 617 parser.add_option('--color', callback=rebuild_cmdline, | 618 parser.add_option('--color', callback=rebuild_cmdline, |
| 618 action="callback", | 619 action="callback", |
| 619 help="colorize tracebacks") | 620 help="colorize tracebacks") |
| 620 parser.add_option('-s', '--skip', | 621 parser.add_option('-s', '--skip', |
| 621 # XXX: I wish I could use the callback action but it | 622 # XXX: I wish I could use the callback action but it |
| 622 # doesn't seem to be able to get the value | 623 # doesn't seem to be able to get the value |
| 623 # associated to the option | 624 # associated to the option |
| 624 action="store", dest="skipped", default=None, | 625 action="store", dest="skipped", default=None, |
| 625 help="test names matching this name will be skipped " | 626 help="test names matching this name will be skipped " |
| 626 "to skip several patterns, use commas") | 627 "to skip several patterns, use commas") |
| 627 parser.add_option('-q', '--quiet', callback=rebuild_cmdline, | 628 parser.add_option('-q', '--quiet', callback=rebuild_cmdline, |
| 628 action="callback", help="Minimal output") | 629 action="callback", help="Minimal output") |
| 629 parser.add_option('-P', '--profile', default=None, dest='profile', | 630 parser.add_option('-P', '--profile', default=None, dest='profile', |
| 630 help="Profile execution and store data in the given file") | 631 help="Profile execution and store data in the given file") |
| 631 parser.add_option('-m', '--match', default=None, dest='tags_pattern', | 632 parser.add_option('-m', '--match', default=None, dest='tags_pattern', |
| 632 help="only execute test whose tag match the current patter
n") | 633 help="only execute test whose tag match the current patter
n") |
| 633 | 634 |
| 634 try: | |
| 635 from logilab.devtools.lib.coverage import Coverage | |
| 636 parser.add_option('--coverage', dest="coverage", default=False, | |
| 637 action="store_true", | |
| 638 help="run tests with pycoverage (conflicts with --pdb)
") | |
| 639 except ImportError: | |
| 640 pass | |
| 641 | |
| 642 if DJANGO_FOUND: | 635 if DJANGO_FOUND: |
| 643 parser.add_option('-J', '--django', dest='django', default=False, | 636 parser.add_option('-J', '--django', dest='django', default=False, |
| 644 action="store_true", | 637 action="store_true", |
| 645 help='use pytest for django test cases') | 638 help='use pytest for django test cases') |
| 646 return parser | 639 return parser |
| 647 | 640 |
| 648 | 641 |
| 649 def parseargs(parser): | 642 def parseargs(parser): |
| 650 """Parse the command line and return (options processed), (options to pass t
o | 643 """Parse the command line and return (options processed), (options to pass t
o |
| 651 unittest_main()), (explicitfile or None). | 644 unittest_main()), (explicitfile or None). |
| 652 """ | 645 """ |
| 653 # parse the command line | 646 # parse the command line |
| 654 options, args = parser.parse_args() | 647 options, args = parser.parse_args() |
| 655 if options.pdb and getattr(options, 'coverage', False): | |
| 656 parser.error("'pdb' and 'coverage' options are exclusive") | |
| 657 filenames = [arg for arg in args if arg.endswith('.py')] | 648 filenames = [arg for arg in args if arg.endswith('.py')] |
| 658 if filenames: | 649 if filenames: |
| 659 if len(filenames) > 1: | 650 if len(filenames) > 1: |
| 660 parser.error("only one filename is acceptable") | 651 parser.error("only one filename is acceptable") |
| 661 explicitfile = filenames[0] | 652 explicitfile = filenames[0] |
| 662 args.remove(explicitfile) | 653 args.remove(explicitfile) |
| 663 else: | 654 else: |
| 664 explicitfile = None | 655 explicitfile = None |
| 665 # someone wants DBC | 656 # someone wants DBC |
| 666 testlib.ENABLE_DBC = options.dbc | 657 testlib.ENABLE_DBC = options.dbc |
| 667 newargs = parser.newargs | 658 newargs = parser.newargs |
| 668 if options.skipped: | 659 if options.skipped: |
| 669 newargs.extend(['--skip', options.skipped]) | 660 newargs.extend(['--skip', options.skipped]) |
| 670 # restart implies exitfirst | 661 # restart implies exitfirst |
| 671 if options.restart: | 662 if options.restart: |
| 672 options.exitfirst = True | 663 options.exitfirst = True |
| 673 # append additional args to the new sys.argv and let unittest_main | 664 # append additional args to the new sys.argv and let unittest_main |
| 674 # do the rest | 665 # do the rest |
| 675 newargs += args | 666 newargs += args |
| 676 return options, explicitfile | 667 return options, explicitfile |
| 677 | 668 |
| 678 | 669 |
| 679 | 670 |
| 680 def run(): | 671 def run(): |
| 681 parser = make_parser() | 672 parser = make_parser() |
| 682 rootdir, testercls = project_root(parser) | 673 rootdir, testercls = project_root(parser) |
| 683 options, explicitfile = parseargs(parser) | 674 options, explicitfile = parseargs(parser) |
| 684 # mock a new command line | 675 # mock a new command line |
| 685 sys.argv[1:] = parser.newargs | 676 sys.argv[1:] = parser.newargs |
| 686 covermode = getattr(options, 'coverage', None) | |
| 687 cvg = None | 677 cvg = None |
| 688 if not '' in sys.path: | 678 if not '' in sys.path: |
| 689 sys.path.insert(0, '') | 679 sys.path.insert(0, '') |
| 690 if covermode: | |
| 691 # control_import_coverage(rootdir) | |
| 692 from logilab.devtools.lib.coverage import Coverage | |
| 693 cvg = Coverage([rootdir]) | |
| 694 cvg.erase() | |
| 695 cvg.start() | |
| 696 if DJANGO_FOUND and options.django: | 680 if DJANGO_FOUND and options.django: |
| 697 tester = DjangoTester(cvg, options) | 681 tester = DjangoTester(cvg, options) |
| 698 else: | 682 else: |
| 699 tester = testercls(cvg, options) | 683 tester = testercls(cvg, options) |
| 700 if explicitfile: | 684 if explicitfile: |
| 701 cmd, args = tester.testfile, (explicitfile,) | 685 cmd, args = tester.testfile, (explicitfile,) |
| 702 elif options.testdir: | 686 elif options.testdir: |
| 703 cmd, args = tester.testonedir, (options.testdir, options.exitfirst) | 687 cmd, args = tester.testonedir, (options.testdir, options.exitfirst) |
| 704 else: | 688 else: |
| 705 cmd, args = tester.testall, (options.exitfirst,) | 689 cmd, args = tester.testall, (options.exitfirst,) |
| 706 try: | 690 try: |
| 707 try: | 691 try: |
| 708 if options.profile: | 692 if options.profile: |
| 709 import hotshot | 693 import hotshot |
| 710 prof = hotshot.Profile(options.profile) | 694 prof = hotshot.Profile(options.profile) |
| 711 prof.runcall(cmd, *args) | 695 prof.runcall(cmd, *args) |
| 712 prof.close() | 696 prof.close() |
| 713 print 'profile data saved in', options.profile | 697 print('profile data saved in', options.profile) |
| 714 else: | 698 else: |
| 715 cmd(*args) | 699 cmd(*args) |
| 716 except SystemExit: | 700 except SystemExit: |
| 717 raise | 701 raise |
| 718 except: | 702 except: |
| 719 import traceback | 703 import traceback |
| 720 traceback.print_exc() | 704 traceback.print_exc() |
| 721 finally: | 705 finally: |
| 722 if covermode: | |
| 723 cvg.stop() | |
| 724 cvg.save() | |
| 725 tester.show_report() | 706 tester.show_report() |
| 726 if covermode: | |
| 727 print 'coverage information stored, use it with pycoverage -ra' | |
| 728 sys.exit(tester.errcode) | 707 sys.exit(tester.errcode) |
| 729 | 708 |
| 730 class SkipAwareTestProgram(unittest.TestProgram): | 709 class SkipAwareTestProgram(unittest.TestProgram): |
| 731 # XXX: don't try to stay close to unittest.py, use optparse | 710 # XXX: don't try to stay close to unittest.py, use optparse |
| 732 USAGE = """\ | 711 USAGE = """\ |
| 733 Usage: %(progName)s [options] [test] [...] | 712 Usage: %(progName)s [options] [test] [...] |
| 734 | 713 |
| 735 Options: | 714 Options: |
| 736 -h, --help Show this message | 715 -h, --help Show this message |
| 737 -v, --verbose Verbose output | 716 -v, --verbose Verbose output |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 809 self.test = self.module.suite() | 788 self.test = self.module.suite() |
| 810 else: | 789 else: |
| 811 self.test = self.testLoader.loadTestsFromModule(self.module) | 790 self.test = self.testLoader.loadTestsFromModule(self.module) |
| 812 return | 791 return |
| 813 if len(args) > 0: | 792 if len(args) > 0: |
| 814 self.test_pattern = args[0] | 793 self.test_pattern = args[0] |
| 815 self.testNames = args | 794 self.testNames = args |
| 816 else: | 795 else: |
| 817 self.testNames = (self.defaultTest, ) | 796 self.testNames = (self.defaultTest, ) |
| 818 self.createTests() | 797 self.createTests() |
| 819 except getopt.error, msg: | 798 except getopt.error as msg: |
| 820 self.usageExit(msg) | 799 self.usageExit(msg) |
| 821 | 800 |
| 822 def runTests(self): | 801 def runTests(self): |
| 823 if self.profile_name: | 802 if self.profile_name: |
| 824 import cProfile | 803 import cProfile |
| 825 cProfile.runctx('self._runTests()', globals(), locals(), self.profil
e_name ) | 804 cProfile.runctx('self._runTests()', globals(), locals(), self.profil
e_name ) |
| 826 else: | 805 else: |
| 827 return self._runTests() | 806 return self._runTests() |
| 828 | 807 |
| 829 def _runTests(self): | 808 def _runTests(self): |
| (...skipping 28 matching lines...) Expand all Loading... |
| 858 if getattr(self.options, 'restart', False): | 837 if getattr(self.options, 'restart', False): |
| 859 # retrieve succeeded tests from FILE_RESTART | 838 # retrieve succeeded tests from FILE_RESTART |
| 860 try: | 839 try: |
| 861 restartfile = open(FILE_RESTART, 'r') | 840 restartfile = open(FILE_RESTART, 'r') |
| 862 try: | 841 try: |
| 863 succeededtests = list(elem.rstrip('\n\r') for elem in | 842 succeededtests = list(elem.rstrip('\n\r') for elem in |
| 864 restartfile.readlines()) | 843 restartfile.readlines()) |
| 865 removeSucceededTests(self.test, succeededtests) | 844 removeSucceededTests(self.test, succeededtests) |
| 866 finally: | 845 finally: |
| 867 restartfile.close() | 846 restartfile.close() |
| 868 except Exception, ex: | 847 except Exception as ex: |
| 869 raise Exception("Error while reading succeeded tests into %s: %s
" | 848 raise Exception("Error while reading succeeded tests into %s: %s
" |
| 870 % (osp.join(os.getcwd(), FILE_RESTART), ex)) | 849 % (osp.join(os.getcwd(), FILE_RESTART), ex)) |
| 871 | 850 |
| 872 result = self.testRunner.run(self.test) | 851 result = self.testRunner.run(self.test) |
| 873 # help garbage collection: we want TestSuite, which hold refs to every | 852 # help garbage collection: we want TestSuite, which hold refs to every |
| 874 # executed TestCase, to be gc'ed | 853 # executed TestCase, to be gc'ed |
| 875 del self.test | 854 del self.test |
| 876 if getattr(result, "debuggers", None) and \ | 855 if getattr(result, "debuggers", None) and \ |
| 877 getattr(self, "pdbmode", None): | 856 getattr(self, "pdbmode", None): |
| 878 start_interactive_mode(result) | 857 start_interactive_mode(result) |
| (...skipping 21 matching lines...) Expand all Loading... |
| 900 | 879 |
| 901 def _this_is_skipped(self, testedname): | 880 def _this_is_skipped(self, testedname): |
| 902 return any([(pat in testedname) for pat in self.skipped_patterns]) | 881 return any([(pat in testedname) for pat in self.skipped_patterns]) |
| 903 | 882 |
| 904 def _runcondition(self, test, skipgenerator=True): | 883 def _runcondition(self, test, skipgenerator=True): |
| 905 if isinstance(test, testlib.InnerTest): | 884 if isinstance(test, testlib.InnerTest): |
| 906 testname = test.name | 885 testname = test.name |
| 907 else: | 886 else: |
| 908 if isinstance(test, testlib.TestCase): | 887 if isinstance(test, testlib.TestCase): |
| 909 meth = test._get_test_method() | 888 meth = test._get_test_method() |
| 910 func = meth.im_func | 889 testname = '%s.%s' % (test.__name__, meth.__name__) |
| 911 testname = '%s.%s' % (meth.im_class.__name__, func.__name__) | |
| 912 elif isinstance(test, types.FunctionType): | 890 elif isinstance(test, types.FunctionType): |
| 913 func = test | 891 func = test |
| 914 testname = func.__name__ | 892 testname = func.__name__ |
| 915 elif isinstance(test, types.MethodType): | 893 elif isinstance(test, types.MethodType): |
| 916 func = test.im_func | 894 cls = test.__self__.__class__ |
| 917 testname = '%s.%s' % (test.im_class.__name__, func.__name__) | 895 testname = '%s.%s' % (cls.__name__, test.__name__) |
| 918 else: | 896 else: |
| 919 return True # Not sure when this happens | 897 return True # Not sure when this happens |
| 920 if testlib.is_generator(test) and skipgenerator: | 898 if isgeneratorfunction(test) and skipgenerator: |
| 921 return self.does_match_tags(test) # Let inner tests decide at ru
n time | 899 return self.does_match_tags(test) # Let inner tests decide at ru
n time |
| 922 if self._this_is_skipped(testname): | 900 if self._this_is_skipped(testname): |
| 923 return False # this was explicitly skipped | 901 return False # this was explicitly skipped |
| 924 if self.test_pattern is not None: | 902 if self.test_pattern is not None: |
| 925 try: | 903 try: |
| 926 classpattern, testpattern = self.test_pattern.split('.') | 904 classpattern, testpattern = self.test_pattern.split('.') |
| 927 klass, name = testname.split('.') | 905 klass, name = testname.split('.') |
| 928 if classpattern not in klass or testpattern not in name: | 906 if classpattern not in klass or testpattern not in name: |
| 929 return False | 907 return False |
| 930 except ValueError: | 908 except ValueError: |
| (...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1018 # and to provide callable capability | 996 # and to provide callable capability |
| 1019 def loadTestsFromNames(self, names, module=None): | 997 def loadTestsFromNames(self, names, module=None): |
| 1020 suites = [] | 998 suites = [] |
| 1021 for name in names: | 999 for name in names: |
| 1022 suites.extend(self.loadTestsFromName(name, module)) | 1000 suites.extend(self.loadTestsFromName(name, module)) |
| 1023 return self.suiteClass(suites) | 1001 return self.suiteClass(suites) |
| 1024 | 1002 |
| 1025 def _collect_tests(self, module): | 1003 def _collect_tests(self, module): |
| 1026 tests = {} | 1004 tests = {} |
| 1027 for obj in vars(module).values(): | 1005 for obj in vars(module).values(): |
| 1028 if (issubclass(type(obj), (types.ClassType, type)) and | 1006 if isclass(obj) and issubclass(obj, unittest.TestCase): |
| 1029 issubclass(obj, unittest.TestCase)): | |
| 1030 classname = obj.__name__ | 1007 classname = obj.__name__ |
| 1031 if classname[0] == '_' or self._this_is_skipped(classname): | 1008 if classname[0] == '_' or self._this_is_skipped(classname): |
| 1032 continue | 1009 continue |
| 1033 methodnames = [] | 1010 methodnames = [] |
| 1034 # obj is a TestCase class | 1011 # obj is a TestCase class |
| 1035 for attrname in dir(obj): | 1012 for attrname in dir(obj): |
| 1036 if attrname.startswith(self.testMethodPrefix): | 1013 if attrname.startswith(self.testMethodPrefix): |
| 1037 attr = getattr(obj, attrname) | 1014 attr = getattr(obj, attrname) |
| 1038 if callable(attr): | 1015 if callable(attr): |
| 1039 methodnames.append(attrname) | 1016 methodnames.append(attrname) |
| (...skipping 128 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1168 | 1145 |
| 1169 if sys.version_info >= (2, 4): | 1146 if sys.version_info >= (2, 4): |
| 1170 doctest.DocTestCase.__bases__ = (testlib.TestCase,) | 1147 doctest.DocTestCase.__bases__ = (testlib.TestCase,) |
| 1171 # XXX check python2.6 compatibility | 1148 # XXX check python2.6 compatibility |
| 1172 #doctest.DocTestCase._cleanups = [] | 1149 #doctest.DocTestCase._cleanups = [] |
| 1173 #doctest.DocTestCase._out = [] | 1150 #doctest.DocTestCase._out = [] |
| 1174 else: | 1151 else: |
| 1175 unittest.FunctionTestCase.__bases__ = (testlib.TestCase,) | 1152 unittest.FunctionTestCase.__bases__ = (testlib.TestCase,) |
| 1176 unittest.TestSuite.run = _ts_run | 1153 unittest.TestSuite.run = _ts_run |
| 1177 unittest.TestSuite._wrapped_run = _ts_wrapped_run | 1154 unittest.TestSuite._wrapped_run = _ts_wrapped_run |
| OLD | NEW |