diff options
| author | Robert Collins <robertc@robertcollins.net> | 2014-11-14 00:00:38 +1300 |
|---|---|---|
| committer | Robert Collins <robertc@robertcollins.net> | 2014-11-14 08:40:39 +1300 |
| commit | dfe62afa96970d059543a50c70047fa05b8c0bea (patch) | |
| tree | 3dd29fd5fcc9b310ab939c8170ecc3f01144c422 | |
| parent | 377ea8f7eedc0d539684e28c54b024c85f3f9c40 (diff) | |
| download | testtools-dfe62afa96970d059543a50c70047fa05b8c0bea.tar.gz | |
Use unittest2 TestProgram implementation.
This brings in many fixes made to discovery where previously we were only using
the discovery package or the version in the release of Python that the test
execution was occuring on. (Robert Collins, #1271133)
Change-Id: I6270c8e8949262c01cb3a4e40735077ad6fc1ef2
| -rw-r--r-- | NEWS | 5 | ||||
| -rwxr-xr-x | testtools/run.py | 225 | ||||
| -rw-r--r-- | testtools/tests/test_run.py | 10 |
3 files changed, 42 insertions, 198 deletions
@@ -10,6 +10,11 @@ NEXT Changes ------- +* Depends on unittest2 for discovery functionality and the ``TestProgram`` base + class. This brings in many fixes made to discovery where previously we were + only using the discovery package or the version in the release of Python + that the test execution was occuring on. (Robert Collins, #1271133) + * Fixed unit tests which were failing under pypy due to a change in the way pypy formats tracebacks. (Thomi Richards) diff --git a/testtools/run.py b/testtools/run.py index c8170a7..8f4b2a5 100755 --- a/testtools/run.py +++ b/testtools/run.py @@ -8,6 +8,7 @@ For instance, to run the testtools test suite. $ python -m testtools.run testtools.tests.test_suite """ +import argparse from functools import partial import os.path import unittest2 as unittest @@ -25,6 +26,11 @@ defaultTestLoaderCls = unittest.TestLoader have_discover = True discover_impl = unittest.loader +# Kept for API compatibility, but no longer used. +BUFFEROUTPUT = "" +CATCHBREAK = "" +FAILFAST = "" +USAGE_AS_MAIN = "" def list_test(test): """Return the test ids that would be run if test() was run. @@ -100,75 +106,23 @@ class TestToolsTestRunner(object): # Taken from python 2.7 and slightly modified for compatibility with # older versions. Delete when 2.7 is the oldest supported version. # Modifications: -# - Use have_discover to raise an error if the user tries to use -# discovery on an old version and doesn't have discover installed. -# - If --catch is given check that installHandler is available, as -# it won't be on old python versions. -# - print calls have been been made single-source python3 compatibile. -# - exception handling likewise. -# - The default help has been changed to USAGE_AS_MAIN and USAGE_FROM_MODULE -# removed. -# - A tweak has been added to detect 'python -m *.run' and use a -# better progName in that case. -# - self.module is more comprehensively set to None when being invoked from -# the commandline - __name__ is used as a sentinel value. +# - If --catch is given, check that installHandler is available, as +# it won't be on old python versions or python builds without signals. # - --list has been added which can list tests (should be upstreamed). # - --load-list has been added which can reduce the tests used (should be # upstreamed). -# - The limitation of using getopt is declared to the user. -# - http://bugs.python.org/issue16709 is worked around, by sorting tests when -# discover is used. -# - We monkey-patch the discover and unittest loaders to address -# http://bugs.python.org/issue16662 with the proposed upstream patch. - -FAILFAST = " -f, --failfast Stop on first failure\n" -CATCHBREAK = " -c, --catch Catch control-C and display results\n" -BUFFEROUTPUT = " -b, --buffer Buffer stdout and stderr during test runs\n" - -USAGE_AS_MAIN = """\ -Usage: %(progName)s [options] [tests] - -Options: - -h, --help Show this message - -v, --verbose Verbose output - -q, --quiet Minimal output - -l, --list List tests rather than executing them. - --load-list Specifies a file containing test ids, only tests matching - those ids are executed. -%(failfast)s%(catchbreak)s%(buffer)s -Examples: - %(progName)s test_module - run tests from test_module - %(progName)s module.TestClass - run tests from module.TestClass - %(progName)s module.Class.test_method - run specified test method - -All options must come before [tests]. [tests] can be a list of any number of -test modules, classes and test methods. - -Alternative Usage: %(progName)s discover [options] - -Options: - -v, --verbose Verbose output -%(failfast)s%(catchbreak)s%(buffer)s -s directory Directory to start discovery ('.' default) - -p pattern Pattern to match test files ('test*.py' default) - -t directory Top level directory of project (default to - start directory) - -l, --list List tests rather than executing them. - --load-list Specifies a file containing test ids, only tests matching - those ids are executed. - -For test discovery all test modules must be importable from the top -level directory of the project. -""" -class TestProgram(object): +class TestProgram(unittest.TestProgram): """A command-line program that runs a set of tests; this is primarily for making test modules conveniently executable. """ - USAGE = USAGE_AS_MAIN # defaults for testing + module=None + verbosity = 1 failfast = catchbreak = buffer = progName = None + _discovery_parser = None def __init__(self, module=__name__, defaultTest=None, argv=None, testRunner=None, testLoader=defaultTestLoader, @@ -194,6 +148,7 @@ class TestProgram(object): self.verbosity = verbosity self.buffer = buffer self.defaultTest = defaultTest + # XXX: Local edit (see http://bugs.python.org/issue22860) self.listtests = False self.load_list = None self.testRunner = testRunner @@ -206,6 +161,7 @@ class TestProgram(object): progName = os.path.basename(argv[0]) self.progName = progName self.parseArgs(argv) + # XXX: Local edit (see http://bugs.python.org/issue22860) if self.load_list: # TODO: preserve existing suites (like testresources does in # OptimisingTestSuite.add, but with a standard protocol). @@ -218,6 +174,7 @@ class TestProgram(object): source.close() test_ids = set(line.strip().decode('utf-8') for line in lines) self.test = filter_by_ids(self.test, test_ids) + # XXX: Local edit (see http://bugs.python.org/issue22860) if not self.listtests: self.runTests() else: @@ -228,144 +185,23 @@ class TestProgram(object): for test in iterate_tests(self.test): self.stdout.write('%s\n' % test.id()) - def usageExit(self, msg=None): - if msg: - print(msg) - usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '', - 'buffer': ''} - if self.failfast != False: - usage['failfast'] = FAILFAST - if self.catchbreak != False: - usage['catchbreak'] = CATCHBREAK - if self.buffer != False: - usage['buffer'] = BUFFEROUTPUT - print(self.USAGE % usage) - sys.exit(2) - - def parseArgs(self, argv): - if len(argv) > 1 and argv[1].lower() == 'discover': - self._do_discovery(argv[2:]) - return - - import getopt - long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer', - 'list', 'load-list='] - try: - options, args = getopt.getopt(argv[1:], 'hHvqfcbl', long_opts) - for opt, value in options: - if opt in ('-h','-H','--help'): - self.usageExit() - if opt in ('-q','--quiet'): - self.verbosity = 0 - if opt in ('-v','--verbose'): - self.verbosity = 2 - if opt in ('-f','--failfast'): - if self.failfast is None: - self.failfast = True - # Should this raise an exception if -f is not valid? - if opt in ('-c','--catch'): - if self.catchbreak is None: - self.catchbreak = True - # Should this raise an exception if -c is not valid? - if opt in ('-b','--buffer'): - if self.buffer is None: - self.buffer = True - # Should this raise an exception if -b is not valid? - if opt in ('-l', '--list'): - self.listtests = True - if opt == '--load-list': - self.load_list = value - if len(args) == 0 and self.defaultTest is None: - # createTests will load tests from self.module - self.testNames = None - elif len(args) > 0: - self.testNames = args - else: - self.testNames = (self.defaultTest,) - self.createTests() - except getopt.error: - self.usageExit(sys.exc_info()[1]) - - def createTests(self): - if self.testNames is None: - self.test = self.testLoader.loadTestsFromModule(self.module) - else: - self.test = self.testLoader.loadTestsFromNames(self.testNames, - self.module) - - def _do_discovery(self, argv, Loader=defaultTestLoaderCls): - # handle command line args for test discovery - if not have_discover: - raise AssertionError("Unable to use discovery, must use python 2.7 " - "or greater, or install the discover package.") - self.progName = '%s discover' % self.progName - import optparse - parser = optparse.OptionParser() - parser.prog = self.progName - parser.add_option('-v', '--verbose', dest='verbose', default=False, - help='Verbose output', action='store_true') - if self.failfast != False: - parser.add_option('-f', '--failfast', dest='failfast', default=False, - help='Stop on first fail or error', - action='store_true') - if self.catchbreak != False: - parser.add_option('-c', '--catch', dest='catchbreak', default=False, - help='Catch ctrl-C and display results so far', - action='store_true') - if self.buffer != False: - parser.add_option('-b', '--buffer', dest='buffer', default=False, - help='Buffer stdout and stderr during tests', - action='store_true') - parser.add_option('-s', '--start-directory', dest='start', default='.', - help="Directory to start discovery ('.' default)") - parser.add_option('-p', '--pattern', dest='pattern', default='test*.py', - help="Pattern to match tests ('test*.py' default)") - parser.add_option('-t', '--top-level-directory', dest='top', default=None, - help='Top level directory of project (defaults to start directory)') - parser.add_option('-l', '--list', dest='listtests', default=False, action="store_true", - help='List tests rather than running them.') - parser.add_option('--load-list', dest='load_list', default=None, - help='Specify a filename containing the test ids to use.') - - options, args = parser.parse_args(argv) - if len(args) > 3: - self.usageExit() - - for name, value in zip(('start', 'pattern', 'top'), args): - setattr(options, name, value) - - # only set options from the parsing here - # if they weren't set explicitly in the constructor - if self.failfast is None: - self.failfast = options.failfast - if self.catchbreak is None: - self.catchbreak = options.catchbreak - if self.buffer is None: - self.buffer = options.buffer - self.listtests = options.listtests - self.load_list = options.load_list - - if options.verbose: - self.verbosity = 2 - - start_dir = options.start - pattern = options.pattern - top_level_dir = options.top - - loader = Loader() - # See http://bugs.python.org/issue16709 - # While sorting here is intrusive, its better than being random. - # Rules for the sort: - # - standard suites are flattened, and the resulting tests sorted by - # id. - # - non-standard suites are preserved as-is, and sorted into position - # by the first test found by iterating the suite. - # We do this by a DSU process: flatten and grab a key, sort, strip the - # keys. - loaded = loader.discover(start_dir, pattern, top_level_dir) - self.test = sorted_tests(loaded) + def _getParentArgParser(self): + parser = super(TestProgram, self)._getParentArgParser() + # XXX: Local edit (see http://bugs.python.org/issue22860) + parser.add_argument('-l', '--list', dest='listtests', default=False, + action='store_true', help='List tests rather than executing them') + parser.add_argument('--load-list', dest='load_list', default=None, + help='Specifies a file containing test ids, only tests matching ' + 'those ids are executed') + return parser + + def _do_discovery(self, argv, Loader=None): + super(TestProgram, self)._do_discovery(argv, Loader=Loader) + # XXX: Local edit (see http://bugs.python.org/issue22860) + self.test = sorted_tests(self.test) def runTests(self): + # XXX: Local edit (see http://bugs.python.org/issue22860) if (self.catchbreak and getattr(unittest, 'installHandler', None) is not None): unittest.installHandler() @@ -375,6 +211,7 @@ class TestProgram(object): sys.exit(not self.result.wasSuccessful()) def _get_runner(self): + # XXX: Local edit (see http://bugs.python.org/issue22860) if self.testRunner is None: self.testRunner = TestToolsTestRunner try: diff --git a/testtools/tests/test_run.py b/testtools/tests/test_run.py index ac4b9dd..fd564c7 100644 --- a/testtools/tests/test_run.py +++ b/testtools/tests/test_run.py @@ -9,6 +9,7 @@ from textwrap import dedent from extras import try_import fixtures = try_import('fixtures') testresources = try_import('testresources') +import unittest2 import testtools from testtools import TestCase, run, skipUnless @@ -105,7 +106,7 @@ def test_suite(): testtools.__path__.append(self.package.base) -if fixtures and run.have_discover: +if fixtures: class SampleLoadTestsPackage(fixtures.Fixture): """Creates a test suite package using load_tests.""" @@ -168,10 +169,10 @@ testtools.runexample.TestFoo.test_quux """, out.getvalue()) def test_run_list_failed_import(self): - if not run.have_discover: - self.skipTest("Need discover") broken = self.useFixture(SampleTestFixture(broken=True)) out = StringIO() + # XXX: http://bugs.python.org/issue22811 + unittest2.defaultTestLoader._top_level_dir = None exc = self.assertRaises( SystemExit, run.main, ['prog', 'discover', '-l', broken.package.base, '*.py'], out) @@ -287,7 +288,6 @@ Ran 2 tests in \\d.\\d\\d\\ds OK """))) - @skipUnless(run.have_discover, "discovery not present") @skipUnless(fixtures, "fixtures not present") def test_issue_16662(self): # unittest's discover implementation didn't handle load_tests on @@ -296,6 +296,8 @@ OK # See http://bugs.python.org/issue16662 pkg = self.useFixture(SampleLoadTestsPackage()) out = StringIO() + # XXX: http://bugs.python.org/issue22811 + unittest2.defaultTestLoader._top_level_dir = None self.assertEqual(None, run.main( ['prog', 'discover', '-l', pkg.package.base], out)) self.assertEqual(dedent("""\ |
