run-tests: avoid running the same test instance concurrently
authorAugie Fackler <augie@google.com>
Fri, 13 Mar 2015 12:47:16 -0400
changeset 24330 799bc18e14d1
parent 24329 e7ca4d4b10e1
child 24331 d3bdd8c7174f
run-tests: avoid running the same test instance concurrently There's a fair amount of mutable state stored on test case instances. That causes many weird failures if you try to do something like `run-tests.py -j16 --loop test-help.t`. The quick fix is this slightly weird test-reloading dance, which ensures that every time a test is executed it runs on a fresh instance of the TestCase subclass.
tests/run-tests.py
--- a/tests/run-tests.py	Fri Mar 13 11:21:53 2015 -0400
+++ b/tests/run-tests.py	Fri Mar 13 12:47:16 2015 -0400
@@ -1291,6 +1291,7 @@
 
     def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
                  retest=False, keywords=None, loop=False, runs_per_test=1,
+                 loadtest=None,
                  *args, **kwargs):
         """Create a new instance that can run tests with a configuration.
 
@@ -1326,13 +1327,20 @@
         self._keywords = keywords
         self._loop = loop
         self._runs_per_test = runs_per_test
+        self._loadtest = loadtest
 
     def run(self, result):
         # We have a number of filters that need to be applied. We do this
         # here instead of inside Test because it makes the running logic for
         # Test simpler.
         tests = []
+        num_tests = [0]
         for test in self._tests:
+            def get():
+                num_tests[0] += 1
+                if getattr(test, 'should_reload', False):
+                    return self._loadtest(test.name, num_tests[0])
+                return test
             if not os.path.exists(test.path):
                 result.addSkip(test, "Doesn't exist")
                 continue
@@ -1360,7 +1368,7 @@
                     if ignored:
                         continue
             for _ in xrange(self._runs_per_test):
-                tests.append(test)
+                tests.append(get())
 
         runtests = list(tests)
         done = queue.Queue()
@@ -1389,7 +1397,12 @@
                 if tests and not running == self._jobs:
                     test = tests.pop(0)
                     if self._loop:
-                        tests.append(test)
+                        if getattr(test, 'should_reload', False):
+                            num_tests[0] += 1
+                            tests.append(
+                                self._loadtest(test.name, num_tests[0]))
+                        else:
+                            tests.append(test)
                     t = threading.Thread(target=job, name=test.name,
                                          args=(test, result))
                     t.start()
@@ -1733,7 +1746,7 @@
                               keywords=self.options.keywords,
                               loop=self.options.loop,
                               runs_per_test=self.options.runs_per_test,
-                              tests=tests)
+                              tests=tests, loadtest=self._gettest)
             verbosity = 1
             if self.options.verbose:
                 verbosity = 2
@@ -1773,14 +1786,16 @@
         refpath = os.path.join(self._testdir, test)
         tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
 
-        return testcls(refpath, tmpdir,
-                       keeptmpdir=self.options.keep_tmpdir,
-                       debug=self.options.debug,
-                       timeout=self.options.timeout,
-                       startport=self.options.port + count * 3,
-                       extraconfigopts=self.options.extra_config_opt,
-                       py3kwarnings=self.options.py3k_warnings,
-                       shell=self.options.shell)
+        t = testcls(refpath, tmpdir,
+                    keeptmpdir=self.options.keep_tmpdir,
+                    debug=self.options.debug,
+                    timeout=self.options.timeout,
+                    startport=self.options.port + count * 3,
+                    extraconfigopts=self.options.extra_config_opt,
+                    py3kwarnings=self.options.py3k_warnings,
+                    shell=self.options.shell)
+        t.should_reload = True
+        return t
 
     def _cleanup(self):
         """Clean up state from this test invocation."""