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.
--- a/tests/run-tests.py Wed Apr 16 16:34:48 2014 -0500
+++ b/tests/run-tests.py Sun Apr 20 11:22:08 2014 -0700
@@ -57,6 +57,7 @@
import threading
import killdaemons as killmod
import Queue as queue
+import unittest
processlock = threading.Lock()
@@ -187,6 +188,9 @@
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 @@
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 @@
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 @@
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 @@
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."""