# HG changeset patch # User Gregory Szorc # Date 2014-04-20 18:22:08 # Node ID 791bdd65acd31218cf30aee8e9074c565f333041 # Parent 242637139efbbdde8b070482acd9f1900d81d7ea run-tests: initial support for running tests with unittest The unittest package in Python's standard library provides an almost universal mechanism for defining and running tests. This patch starts the process of making run-tests.py talk to it. The main benefit of speaking unittest is that this will enable Mercurial's tests to be more easily consumed by other testing tools, like nose. This is useful for 3rd party extensions having their own test infrastructure, for example. Running tests in unittest mode will not result in completely sane behavior until the unittest mode is made the default execution mode. Expect things like double printing of output until support stabilizes. diff --git a/tests/run-tests.py b/tests/run-tests.py --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -57,6 +57,7 @@ import re import threading import killdaemons as killmod import Queue as queue +import unittest processlock = threading.Lock() @@ -187,6 +188,9 @@ def getparser(): parser.add_option("--tmpdir", type="string", help="run tests in the given temporary directory" " (implies --keep-tmpdir)") + parser.add_option("--unittest", action="store_true", + help="run tests with Python's unittest package" + " (this is an experimental feature)") parser.add_option("-v", "--verbose", action="store_true", help="output verbose messages") parser.add_option("--view", type="string", @@ -255,6 +259,13 @@ def parseargs(args, parser): if options.jobs < 1: parser.error('--jobs must be positive') + if options.unittest: + if options.jobs > 1: + sys.stderr.write( + 'warning: --jobs has no effect with --unittest') + if options.loop: + sys.stderr.write( + 'warning: --loop has no effect with --unittest') if options.interactive and options.debug: parser.error("-i/--interactive and -d/--debug are incompatible") if options.debug: @@ -1172,7 +1183,18 @@ class TestRunner(object): print "running all tests" tests = orig - self._executetests(tests) + if self.options.unittest: + suite = unittest.TestSuite() + for count, testpath in enumerate(tests): + suite.addTest(self._gettest(testpath, count, asunit=True)) + + verbosity = 1 + if self.options.verbose: + verbosity = 2 + runner = unittest.TextTestRunner(verbosity=verbosity) + runner.run(suite) + else: + self._executetests(tests) failed = len(self.results['!']) warned = len(self.results['~']) @@ -1207,7 +1229,7 @@ class TestRunner(object): if warned: return 80 - def _gettest(self, test, count): + def _gettest(self, test, count, asunit=False): """Obtain a Test by looking at its filename. Returns a Test instance. The Test may not be runnable if it doesn't @@ -1224,7 +1246,17 @@ class TestRunner(object): refpath = os.path.join(self.testdir, test + out) break - return testcls(self, test, count, refpath) + t = testcls(self, test, count, refpath) + + if not asunit: + return t + + # If we want a unittest compatible object, we wrap our Test. + class MercurialTest(unittest.TestCase): + def runTest(self): + t.run() + + return MercurialTest() def _cleanup(self): """Clean up state from this test invocation."""