run-tests: initial support for running tests with unittest
authorGregory Szorc <gregory.szorc@gmail.com>
Sun, 20 Apr 2014 11:22:08 -0700
changeset 21426 791bdd65acd3
parent 21425 242637139efb
child 21427 60f944758ad4
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.
tests/run-tests.py
--- 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."""