comparison tests/run-tests.py @ 24330:799bc18e14d1

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.
author Augie Fackler <augie@google.com>
date Fri, 13 Mar 2015 12:47:16 -0400
parents e7ca4d4b10e1
children d3bdd8c7174f
comparison
equal deleted inserted replaced
24329:e7ca4d4b10e1 24330:799bc18e14d1
1289 class TestSuite(unittest.TestSuite): 1289 class TestSuite(unittest.TestSuite):
1290 """Custom unittest TestSuite that knows how to execute Mercurial tests.""" 1290 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1291 1291
1292 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None, 1292 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1293 retest=False, keywords=None, loop=False, runs_per_test=1, 1293 retest=False, keywords=None, loop=False, runs_per_test=1,
1294 loadtest=None,
1294 *args, **kwargs): 1295 *args, **kwargs):
1295 """Create a new instance that can run tests with a configuration. 1296 """Create a new instance that can run tests with a configuration.
1296 1297
1297 testdir specifies the directory where tests are executed from. This 1298 testdir specifies the directory where tests are executed from. This
1298 is typically the ``tests`` directory from Mercurial's source 1299 is typically the ``tests`` directory from Mercurial's source
1324 self._blacklist = blacklist 1325 self._blacklist = blacklist
1325 self._retest = retest 1326 self._retest = retest
1326 self._keywords = keywords 1327 self._keywords = keywords
1327 self._loop = loop 1328 self._loop = loop
1328 self._runs_per_test = runs_per_test 1329 self._runs_per_test = runs_per_test
1330 self._loadtest = loadtest
1329 1331
1330 def run(self, result): 1332 def run(self, result):
1331 # We have a number of filters that need to be applied. We do this 1333 # We have a number of filters that need to be applied. We do this
1332 # here instead of inside Test because it makes the running logic for 1334 # here instead of inside Test because it makes the running logic for
1333 # Test simpler. 1335 # Test simpler.
1334 tests = [] 1336 tests = []
1337 num_tests = [0]
1335 for test in self._tests: 1338 for test in self._tests:
1339 def get():
1340 num_tests[0] += 1
1341 if getattr(test, 'should_reload', False):
1342 return self._loadtest(test.name, num_tests[0])
1343 return test
1336 if not os.path.exists(test.path): 1344 if not os.path.exists(test.path):
1337 result.addSkip(test, "Doesn't exist") 1345 result.addSkip(test, "Doesn't exist")
1338 continue 1346 continue
1339 1347
1340 if not (self._whitelist and test.name in self._whitelist): 1348 if not (self._whitelist and test.name in self._whitelist):
1358 break 1366 break
1359 1367
1360 if ignored: 1368 if ignored:
1361 continue 1369 continue
1362 for _ in xrange(self._runs_per_test): 1370 for _ in xrange(self._runs_per_test):
1363 tests.append(test) 1371 tests.append(get())
1364 1372
1365 runtests = list(tests) 1373 runtests = list(tests)
1366 done = queue.Queue() 1374 done = queue.Queue()
1367 running = 0 1375 running = 0
1368 1376
1387 continue 1395 continue
1388 running -= 1 1396 running -= 1
1389 if tests and not running == self._jobs: 1397 if tests and not running == self._jobs:
1390 test = tests.pop(0) 1398 test = tests.pop(0)
1391 if self._loop: 1399 if self._loop:
1392 tests.append(test) 1400 if getattr(test, 'should_reload', False):
1401 num_tests[0] += 1
1402 tests.append(
1403 self._loadtest(test.name, num_tests[0]))
1404 else:
1405 tests.append(test)
1393 t = threading.Thread(target=job, name=test.name, 1406 t = threading.Thread(target=job, name=test.name,
1394 args=(test, result)) 1407 args=(test, result))
1395 t.start() 1408 t.start()
1396 running += 1 1409 running += 1
1397 except KeyboardInterrupt: 1410 except KeyboardInterrupt:
1731 blacklist=self.options.blacklist, 1744 blacklist=self.options.blacklist,
1732 retest=self.options.retest, 1745 retest=self.options.retest,
1733 keywords=self.options.keywords, 1746 keywords=self.options.keywords,
1734 loop=self.options.loop, 1747 loop=self.options.loop,
1735 runs_per_test=self.options.runs_per_test, 1748 runs_per_test=self.options.runs_per_test,
1736 tests=tests) 1749 tests=tests, loadtest=self._gettest)
1737 verbosity = 1 1750 verbosity = 1
1738 if self.options.verbose: 1751 if self.options.verbose:
1739 verbosity = 2 1752 verbosity = 2
1740 runner = TextTestRunner(self, verbosity=verbosity) 1753 runner = TextTestRunner(self, verbosity=verbosity)
1741 result = runner.run(suite) 1754 result = runner.run(suite)
1771 break 1784 break
1772 1785
1773 refpath = os.path.join(self._testdir, test) 1786 refpath = os.path.join(self._testdir, test)
1774 tmpdir = os.path.join(self._hgtmp, 'child%d' % count) 1787 tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
1775 1788
1776 return testcls(refpath, tmpdir, 1789 t = testcls(refpath, tmpdir,
1777 keeptmpdir=self.options.keep_tmpdir, 1790 keeptmpdir=self.options.keep_tmpdir,
1778 debug=self.options.debug, 1791 debug=self.options.debug,
1779 timeout=self.options.timeout, 1792 timeout=self.options.timeout,
1780 startport=self.options.port + count * 3, 1793 startport=self.options.port + count * 3,
1781 extraconfigopts=self.options.extra_config_opt, 1794 extraconfigopts=self.options.extra_config_opt,
1782 py3kwarnings=self.options.py3k_warnings, 1795 py3kwarnings=self.options.py3k_warnings,
1783 shell=self.options.shell) 1796 shell=self.options.shell)
1797 t.should_reload = True
1798 return t
1784 1799
1785 def _cleanup(self): 1800 def _cleanup(self):
1786 """Clean up state from this test invocation.""" 1801 """Clean up state from this test invocation."""
1787 1802
1788 if self.options.keep_tmpdir: 1803 if self.options.keep_tmpdir: