Rev 4206: Refactor tests.run_suite to be more data driven, making it shorter and able to be extended more easily. in http://people.ubuntu.com/~robertc/baz2.0/pending/tests.parallel
Robert Collins
robertc at robertcollins.net
Thu Mar 26 12:15:51 GMT 2009
At http://people.ubuntu.com/~robertc/baz2.0/pending/tests.parallel
------------------------------------------------------------
revno: 4206
revision-id: robertc at robertcollins.net-20090326121545-t30g0qejzx7tknxl
parent: pqm at pqm.ubuntu.com-20090326001427-mnhqpak56tlqa5e7
committer: Robert Collins <robertc at robertcollins.net>
branch nick: tests.parallel
timestamp: Thu 2009-03-26 23:15:45 +1100
message:
Refactor tests.run_suite to be more data driven, making it shorter and able to be extended more easily.
=== modified file 'NEWS'
--- a/NEWS 2009-03-25 22:04:40 +0000
+++ b/NEWS 2009-03-26 12:15:45 +0000
@@ -235,6 +235,11 @@
building a working tree (file.writelines(str) is very inefficient). This
can have a large effect on ``bzr checkout`` times. (John Arbash Meinel)
+* ``tests.run_suite`` has a new parameter ``suite_decorators``, a list of
+ callables to use to decorate the test suite. Such decorators can add or
+ remove tests, or even remote the test suite to another machine if
+ desired. (Robert Collins)
+
* The smart server verb ``Repository.get_parent_map`` can now include
information about ghosts when the special revision ``include-missing:``
is in the requested parents map list. With this flag, ghosts are
=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py 2009-03-24 12:15:01 +0000
+++ b/bzrlib/tests/__init__.py 2009-03-26 12:15:45 +0000
@@ -589,7 +589,7 @@
if isinstance(suite, unittest.TestCase):
yield suite
elif isinstance(suite, unittest.TestSuite):
- for item in suite._tests:
+ for item in suite:
for r in iter_suite_tests(item):
yield r
else:
@@ -2628,7 +2628,8 @@
random_seed=None,
exclude_pattern=None,
strict=False,
- runner_class=None):
+ runner_class=None,
+ suite_decorators=None):
"""Run a test suite for bzr selftest.
:param runner_class: The class of runner to use. Must support the
@@ -2650,42 +2651,189 @@
list_only=list_only,
)
runner.stop_on_failure=stop_on_failure
- # Initialise the random number generator and display the seed used.
- # We convert the seed to a long to make it reuseable across invocations.
- random_order = False
- if random_seed is not None:
- random_order = True
- if random_seed == "now":
- random_seed = long(time.time())
+ # built in decorator factories:
+ decorators = [
+ random_order(random_seed, runner),
+ exclude_tests(exclude_pattern),
+ ]
+ if matching_tests_first:
+ decorators.append(tests_first(pattern))
+ else:
+ decorators.append(filter_tests(pattern))
+ if suite_decorators:
+ decorators.extend(suite_decorators)
+ for decorator in decorators:
+ suite = decorator(suite)
+ result = runner.run(suite)
+ if strict:
+ return result.wasStrictlySuccessful()
+ else:
+ return result.wasSuccessful()
+
+
+def exclude_tests(exclude_pattern):
+ """Return a test suite decorator that excludes tests."""
+ if exclude_pattern is None:
+ return identity_decorator
+ def decorator(suite):
+ return ExcludeDecorator(suite, exclude_pattern)
+ return decorator
+
+
+def filter_tests(pattern):
+ if pattern == '.*':
+ return identity_decorator
+ def decorator(suite):
+ return FilterTestsDecorator(suite, pattern)
+ return decorator
+
+
+def random_order(random_seed, runner):
+ """Return a test suite decorator factory for randomising tests order.
+
+ :param random_seed: now, a string which casts to a long, or a long.
+ :param runner: A test runner with a stream attribute to report on.
+ """
+ if random_seed is None:
+ return identity_decorator
+ def decorator(suite):
+ return RandomDecorator(suite, random_seed, runner.stream)
+ return decorator
+
+
+def tests_first(pattern):
+ if pattern == '.*':
+ return identity_decorator
+ def decorator(suite):
+ return TestFirstDecorator(suite, pattern)
+ return decorator
+
+
+def identity_decorator(suite):
+ """Return suite."""
+ return suite
+
+
+class TestDecorator(TestSuite):
+ """A decorator for TestCase/TestSuite objects.
+
+ Usually, subclasses should override __iter__(used when flattening test
+ suites), which we do to filter, reorder, parallelise and so on, run() and
+ debug().
+ """
+
+ def __init__(self, suite):
+ TestSuite.__init__(self)
+ self.addTest(suite)
+
+ def countTestCases(self):
+ cases = 0
+ for test in self:
+ cases += test.countTestCases()
+ return cases
+
+ def debug(self):
+ for test in self:
+ test.debug()
+
+ def run(self, result):
+ # Use iteration on self, not self._tests, to allow subclasses to hook
+ # into __iter__.
+ for test in self:
+ if result.shouldStop:
+ break
+ test.run(result)
+ return result
+
+
+class ExcludeDecorator(TestDecorator):
+ """A decorator which excludes test matching an exclude pattern."""
+
+ def __init__(self, suite, exclude_pattern):
+ TestDecorator.__init__(self, suite)
+ self.exclude_pattern = exclude_pattern
+ self.excluded = False
+
+ def __iter__(self):
+ if self.excluded:
+ return iter(self._tests)
+ self.excluded = True
+ suite = exclude_tests_by_re(self, self.exclude_pattern)
+ del self._tests[:]
+ self.addTests(suite)
+ return iter(self._tests)
+
+
+class FilterTestsDecorator(TestDecorator):
+ """A decorator which filters tests to those matching a pattern."""
+
+ def __init__(self, suite, pattern):
+ TestDecorator.__init__(self, suite)
+ self.pattern = pattern
+ self.filtered = False
+
+ def __iter__(self):
+ if self.filtered:
+ return iter(self._tests)
+ self.filtered = True
+ suite = filter_suite_by_re(self, self.pattern)
+ del self._tests[:]
+ self.addTests(suite)
+ return iter(self._tests)
+
+
+class RandomDecorator(TestDecorator):
+ """A decorator which randomises the order of its tests."""
+
+ def __init__(self, suite, random_seed, stream):
+ TestDecorator.__init__(self, suite)
+ self.random_seed = random_seed
+ self.randomised = False
+ self.stream = stream
+
+ def __iter__(self):
+ if self.randomised:
+ return iter(self._tests)
+ self.randomised = True
+ self.stream.writeln("Randomizing test order using seed %s\n" %
+ (self.actual_seed()))
+ # Initialise the random number generator.
+ random.seed(self.actual_seed())
+ suite = randomize_suite(self)
+ del self._tests[:]
+ self.addTests(suite)
+ return iter(self._tests)
+
+ def actual_seed(self):
+ if self.random_seed == "now":
+ # We convert the seed to a long to make it reuseable across
+ # invocations (because the user can reenter it).
+ self.random_seed = long(time.time())
else:
# Convert the seed to a long if we can
try:
- random_seed = long(random_seed)
+ self.random_seed = long(self.random_seed)
except:
pass
- runner.stream.writeln("Randomizing test order using seed %s\n" %
- (random_seed))
- random.seed(random_seed)
- # Customise the list of tests if requested
- if exclude_pattern is not None:
- suite = exclude_tests_by_re(suite, exclude_pattern)
- if random_order:
- order_changer = randomize_suite
- else:
- order_changer = preserve_input
- if pattern != '.*' or random_order:
- if matching_tests_first:
- suites = map(order_changer, split_suite_by_re(suite, pattern))
- suite = TestUtil.TestSuite(suites)
- else:
- suite = order_changer(filter_suite_by_re(suite, pattern))
-
- result = runner.run(suite)
-
- if strict:
- return result.wasStrictlySuccessful()
-
- return result.wasSuccessful()
+ return self.random_seed
+
+
+class TestFirstDecorator(TestDecorator):
+ """A decorator which moves named tests to the front."""
+
+ def __init__(self, suite, pattern):
+ TestDecorator.__init__(self, suite)
+ self.pattern = pattern
+ self.filtered = False
+
+ def __iter__(self):
+ if self.filtered:
+ return iter(self._tests)
+ self.filtered = True
+ suites = split_suite_by_re(self, self.pattern)
+ del self._tests[:]
+ self.addTests(suites)
+ return iter(self._tests)
# Controlled by "bzr selftest -E=..." option
More information about the bazaar-commits
mailing list