Merge lp:~lifeless/bzr/test-speed into lp:~bzr/bzr/trunk-old

Proposed by Robert Collins
Status: Merged
Merged at revision: not available
Proposed branch: lp:~lifeless/bzr/test-speed
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 374 lines
To merge this branch: bzr merge lp:~lifeless/bzr/test-speed
Reviewer Review Type Date Requested Status
Vincent Ladeuil Approve
Review via email: mp+10775@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

Selftest was a bit tangled up. This makes it clearer without any
functional changes:

* Use startTestRun and stopTestRun, as upstream python does
* lift test listing code out of 'TestRunner' into the DWIM layer
* push all test result outputting down into ExtendedTestResult rather
  than being in the runner.

This means that the runner knows how to run, the DWIM layer knows when
to run, and Results know how to report on results.

Code-reviews will show the wrong diff though. Sorry - latency on trunk etc.

-Rob

Revision history for this message
Robert Collins (lifeless) wrote :
Download full text (14.1 KiB)

=== modified file 'NEWS'
--- NEWS 2009-08-26 07:02:45 +0000
+++ NEWS 2009-08-27 03:51:02 +0000
@@ -79,6 +79,9 @@
   classes changed to manage lock lifetime of the trees they open in a way
   consistent with reader-exclusive locks. (Robert Collins, #305006)

+* ``bzrlib.tests`` now uses ``stopTestRun`` for its ``TestResult``
+ subclasses - the same as python's unittest module. (Robert Collins)
+
 Internals
 *********

=== modified file 'bzrlib/tests/__init__.py'
--- bzrlib/tests/__init__.py 2009-08-26 07:02:45 +0000
+++ bzrlib/tests/__init__.py 2009-08-27 03:51:02 +0000
@@ -175,17 +175,47 @@
         self._overall_start_time = time.time()
         self._strict = strict

- def done(self):
- # nb: called stopTestRun in the version of this that Python merged
- # upstream, according to lifeless 20090803
+ def stopTestRun(self):
+ run = self.testsRun
+ actionTaken = "Ran"
+ stopTime = time.time()
+ timeTaken = stopTime - self.startTime
+ self.printErrors()
+ self.stream.writeln(self.separator2)
+ self.stream.writeln("%s %d test%s in %.3fs" % (actionTaken,
+ run, run != 1 and "s" or "", timeTaken))
+ self.stream.writeln()
+ if not self.wasSuccessful():
+ self.stream.write("FAILED (")
+ failed, errored = map(len, (self.failures, self.errors))
+ if failed:
+ self.stream.write("failures=%d" % failed)
+ if errored:
+ if failed: self.stream.write(", ")
+ self.stream.write("errors=%d" % errored)
+ if self.known_failure_count:
+ if failed or errored: self.stream.write(", ")
+ self.stream.write("known_failure_count=%d" %
+ self.known_failure_count)
+ self.stream.writeln(")")
+ else:
+ if self.known_failure_count:
+ self.stream.writeln("OK (known_failures=%d)" %
+ self.known_failure_count)
+ else:
+ self.stream.writeln("OK")
+ if self.skip_count > 0:
+ skipped = self.skip_count
+ self.stream.writeln('%d test%s skipped' %
+ (skipped, skipped != 1 and "s" or ""))
+ if self.unsupported:
+ for feature, count in sorted(self.unsupported.items()):
+ self.stream.writeln("Missing feature '%s' skipped %d tests." %
+ (feature, count))
         if self._strict:
             ok = self.wasStrictlySuccessful()
         else:
             ok = self.wasSuccessful()
- if ok:
- self.stream.write('tests passed\n')
- else:
- self.stream.write('tests failed\n')
         if TestCase._first_thread_leaker_id:
             self.stream.write(
                 '%s is leaking threads among %d leaking tests.\n' % (
@@ -383,12 +413,12 @@
         else:
             raise errors.BzrError("Unknown whence %r" % whence)

- def finished(self):
- pass
-
     def report_cleaning_up(self):
         pass

+ def startTestRun(self):
+ self.startTime = time.time()
+
     def report_succe...

Revision history for this message
Vincent Ladeuil (vila) wrote :

This far better that what we had.

You give us a far cleaner TestRunner, so whatever is left appears bigger now:
- the loop on result_decorators to propagate a boolean looks highly suspicious,
- the injection of BZRTransformingResult is even more ugly than before, couldn't
  that be a result decorator now ?
- I still don't like that self.verbosity handling here, again, can't that
  be turned into a decorator ?

Given that this patch is a clear improvement, please land, the above can come later
(if you agree on them).

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'NEWS'
--- NEWS 2009-08-27 00:53:27 +0000
+++ NEWS 2009-08-27 07:35:12 +0000
@@ -90,6 +90,9 @@
90 classes changed to manage lock lifetime of the trees they open in a way90 classes changed to manage lock lifetime of the trees they open in a way
91 consistent with reader-exclusive locks. (Robert Collins, #305006)91 consistent with reader-exclusive locks. (Robert Collins, #305006)
9292
93* ``bzrlib.tests`` now uses ``stopTestRun`` for its ``TestResult``
94 subclasses - the same as python's unittest module. (Robert Collins)
95
93Internals96Internals
94*********97*********
9598
9699
=== modified file 'bzrlib/tests/__init__.py'
--- bzrlib/tests/__init__.py 2009-08-26 07:02:45 +0000
+++ bzrlib/tests/__init__.py 2009-08-27 07:35:12 +0000
@@ -175,17 +175,47 @@
175 self._overall_start_time = time.time()175 self._overall_start_time = time.time()
176 self._strict = strict176 self._strict = strict
177177
178 def done(self):178 def stopTestRun(self):
179 # nb: called stopTestRun in the version of this that Python merged179 run = self.testsRun
180 # upstream, according to lifeless 20090803180 actionTaken = "Ran"
181 stopTime = time.time()
182 timeTaken = stopTime - self.startTime
183 self.printErrors()
184 self.stream.writeln(self.separator2)
185 self.stream.writeln("%s %d test%s in %.3fs" % (actionTaken,
186 run, run != 1 and "s" or "", timeTaken))
187 self.stream.writeln()
188 if not self.wasSuccessful():
189 self.stream.write("FAILED (")
190 failed, errored = map(len, (self.failures, self.errors))
191 if failed:
192 self.stream.write("failures=%d" % failed)
193 if errored:
194 if failed: self.stream.write(", ")
195 self.stream.write("errors=%d" % errored)
196 if self.known_failure_count:
197 if failed or errored: self.stream.write(", ")
198 self.stream.write("known_failure_count=%d" %
199 self.known_failure_count)
200 self.stream.writeln(")")
201 else:
202 if self.known_failure_count:
203 self.stream.writeln("OK (known_failures=%d)" %
204 self.known_failure_count)
205 else:
206 self.stream.writeln("OK")
207 if self.skip_count > 0:
208 skipped = self.skip_count
209 self.stream.writeln('%d test%s skipped' %
210 (skipped, skipped != 1 and "s" or ""))
211 if self.unsupported:
212 for feature, count in sorted(self.unsupported.items()):
213 self.stream.writeln("Missing feature '%s' skipped %d tests." %
214 (feature, count))
181 if self._strict:215 if self._strict:
182 ok = self.wasStrictlySuccessful()216 ok = self.wasStrictlySuccessful()
183 else:217 else:
184 ok = self.wasSuccessful()218 ok = self.wasSuccessful()
185 if ok:
186 self.stream.write('tests passed\n')
187 else:
188 self.stream.write('tests failed\n')
189 if TestCase._first_thread_leaker_id:219 if TestCase._first_thread_leaker_id:
190 self.stream.write(220 self.stream.write(
191 '%s is leaking threads among %d leaking tests.\n' % (221 '%s is leaking threads among %d leaking tests.\n' % (
@@ -383,12 +413,12 @@
383 else:413 else:
384 raise errors.BzrError("Unknown whence %r" % whence)414 raise errors.BzrError("Unknown whence %r" % whence)
385415
386 def finished(self):
387 pass
388
389 def report_cleaning_up(self):416 def report_cleaning_up(self):
390 pass417 pass
391418
419 def startTestRun(self):
420 self.startTime = time.time()
421
392 def report_success(self, test):422 def report_success(self, test):
393 pass423 pass
394424
@@ -421,15 +451,14 @@
421 self.pb.update_latency = 0451 self.pb.update_latency = 0
422 self.pb.show_transport_activity = False452 self.pb.show_transport_activity = False
423453
424 def done(self):454 def stopTestRun(self):
425 # called when the tests that are going to run have run455 # called when the tests that are going to run have run
426 self.pb.clear()456 self.pb.clear()
427 super(TextTestResult, self).done()
428
429 def finished(self):
430 self.pb.finished()457 self.pb.finished()
458 super(TextTestResult, self).stopTestRun()
431459
432 def report_starting(self):460 def startTestRun(self):
461 super(TextTestResult, self).startTestRun()
433 self.pb.update('[test 0/%d] Starting' % (self.num_tests))462 self.pb.update('[test 0/%d] Starting' % (self.num_tests))
434463
435 def printErrors(self):464 def printErrors(self):
@@ -514,7 +543,8 @@
514 result = a_string543 result = a_string
515 return result.ljust(final_width)544 return result.ljust(final_width)
516545
517 def report_starting(self):546 def startTestRun(self):
547 super(VerboseTestResult, self).startTestRun()
518 self.stream.write('running %d tests...\n' % self.num_tests)548 self.stream.write('running %d tests...\n' % self.num_tests)
519549
520 def report_test_start(self, test):550 def report_test_start(self, test):
@@ -578,7 +608,6 @@
578 descriptions=0,608 descriptions=0,
579 verbosity=1,609 verbosity=1,
580 bench_history=None,610 bench_history=None,
581 list_only=False,
582 strict=False,611 strict=False,
583 result_decorators=None,612 result_decorators=None,
584 ):613 ):
@@ -593,85 +622,43 @@
593 self.descriptions = descriptions622 self.descriptions = descriptions
594 self.verbosity = verbosity623 self.verbosity = verbosity
595 self._bench_history = bench_history624 self._bench_history = bench_history
596 self.list_only = list_only
597 self._strict = strict625 self._strict = strict
598 self._result_decorators = result_decorators or []626 self._result_decorators = result_decorators or []
599627
600 def run(self, test):628 def run(self, test):
601 "Run the given test case or test suite."629 "Run the given test case or test suite."
602 startTime = time.time()
603 if self.verbosity == 1:630 if self.verbosity == 1:
604 result_class = TextTestResult631 result_class = TextTestResult
605 elif self.verbosity >= 2:632 elif self.verbosity >= 2:
606 result_class = VerboseTestResult633 result_class = VerboseTestResult
607 result = result_class(self.stream,634 original_result = result_class(self.stream,
608 self.descriptions,635 self.descriptions,
609 self.verbosity,636 self.verbosity,
610 bench_history=self._bench_history,637 bench_history=self._bench_history,
611 strict=self._strict,638 strict=self._strict,
612 )639 )
613 run_result = result640 # Signal to result objects that look at stop early policy to stop,
641 original_result.stop_early = self.stop_on_failure
642 result = original_result
614 for decorator in self._result_decorators:643 for decorator in self._result_decorators:
615 run_result = decorator(run_result)644 result = decorator(result)
616 result.stop_early = self.stop_on_failure645 result.stop_early = self.stop_on_failure
617 result.report_starting()646 try:
618 if self.list_only:647 import testtools
619 if self.verbosity >= 2:648 except ImportError:
620 self.stream.writeln("Listing tests only ...\n")649 pass
621 run = 0650 else:
622 for t in iter_suite_tests(test):651 if isinstance(test, testtools.ConcurrentTestSuite):
623 self.stream.writeln("%s" % (t.id()))652 # We need to catch bzr specific behaviors
624 run += 1653 result = BZRTransformingResult(result)
625 return None654 result.startTestRun()
626 else:655 try:
627 try:656 test.run(result)
628 import testtools657 finally:
629 except ImportError:658 result.stopTestRun()
630 test.run(run_result)659 # higher level code uses our extended protocol to determine
631 else:660 # what exit code to give.
632 if isinstance(test, testtools.ConcurrentTestSuite):661 return original_result
633 # We need to catch bzr specific behaviors
634 test.run(BZRTransformingResult(run_result))
635 else:
636 test.run(run_result)
637 run = result.testsRun
638 actionTaken = "Ran"
639 stopTime = time.time()
640 timeTaken = stopTime - startTime
641 result.printErrors()
642 self.stream.writeln(result.separator2)
643 self.stream.writeln("%s %d test%s in %.3fs" % (actionTaken,
644 run, run != 1 and "s" or "", timeTaken))
645 self.stream.writeln()
646 if not result.wasSuccessful():
647 self.stream.write("FAILED (")
648 failed, errored = map(len, (result.failures, result.errors))
649 if failed:
650 self.stream.write("failures=%d" % failed)
651 if errored:
652 if failed: self.stream.write(", ")
653 self.stream.write("errors=%d" % errored)
654 if result.known_failure_count:
655 if failed or errored: self.stream.write(", ")
656 self.stream.write("known_failure_count=%d" %
657 result.known_failure_count)
658 self.stream.writeln(")")
659 else:
660 if result.known_failure_count:
661 self.stream.writeln("OK (known_failures=%d)" %
662 result.known_failure_count)
663 else:
664 self.stream.writeln("OK")
665 if result.skip_count > 0:
666 skipped = result.skip_count
667 self.stream.writeln('%d test%s skipped' %
668 (skipped, skipped != 1 and "s" or ""))
669 if result.unsupported:
670 for feature, count in sorted(result.unsupported.items()):
671 self.stream.writeln("Missing feature '%s' skipped %d tests." %
672 (feature, count))
673 result.finished()
674 return result
675662
676663
677def iter_suite_tests(suite):664def iter_suite_tests(suite):
@@ -2807,7 +2794,6 @@
2807 descriptions=0,2794 descriptions=0,
2808 verbosity=verbosity,2795 verbosity=verbosity,
2809 bench_history=bench_history,2796 bench_history=bench_history,
2810 list_only=list_only,
2811 strict=strict,2797 strict=strict,
2812 result_decorators=result_decorators,2798 result_decorators=result_decorators,
2813 )2799 )
@@ -2830,10 +2816,15 @@
2830 decorators.append(CountingDecorator)2816 decorators.append(CountingDecorator)
2831 for decorator in decorators:2817 for decorator in decorators:
2832 suite = decorator(suite)2818 suite = decorator(suite)
2833 result = runner.run(suite)
2834 if list_only:2819 if list_only:
2820 # Done after test suite decoration to allow randomisation etc
2821 # to take effect, though that is of marginal benefit.
2822 if verbosity >= 2:
2823 stream.write("Listing tests only ...\n")
2824 for t in iter_suite_tests(suite):
2825 stream.write("%s\n" % (t.id()))
2835 return True2826 return True
2836 result.done()2827 result = runner.run(suite)
2837 if strict:2828 if strict:
2838 return result.wasStrictlySuccessful()2829 return result.wasStrictlySuccessful()
2839 else:2830 else:
@@ -3168,6 +3159,12 @@
3168 def stopTest(self, test):3159 def stopTest(self, test):
3169 self.result.stopTest(test)3160 self.result.stopTest(test)
31703161
3162 def startTestRun(self):
3163 self.result.startTestRun()
3164
3165 def stopTestRun(self):
3166 self.result.stopTestRun()
3167
3171 def addSkip(self, test, reason):3168 def addSkip(self, test, reason):
3172 self.result.addSkip(test, reason)3169 self.result.addSkip(test, reason)
31733170
31743171
=== modified file 'bzrlib/tests/test_selftest.py'
--- bzrlib/tests/test_selftest.py 2009-08-26 06:33:13 +0000
+++ bzrlib/tests/test_selftest.py 2009-08-27 07:35:12 +0000
@@ -820,7 +820,7 @@
820 def test_known_failure(self):820 def test_known_failure(self):
821 """A KnownFailure being raised should trigger several result actions."""821 """A KnownFailure being raised should trigger several result actions."""
822 class InstrumentedTestResult(tests.ExtendedTestResult):822 class InstrumentedTestResult(tests.ExtendedTestResult):
823 def done(self): pass823 def stopTestRun(self): pass
824 def startTests(self): pass824 def startTests(self): pass
825 def report_test_start(self, test): pass825 def report_test_start(self, test): pass
826 def report_known_failure(self, test, err):826 def report_known_failure(self, test, err):
@@ -874,7 +874,7 @@
874 def test_add_not_supported(self):874 def test_add_not_supported(self):
875 """Test the behaviour of invoking addNotSupported."""875 """Test the behaviour of invoking addNotSupported."""
876 class InstrumentedTestResult(tests.ExtendedTestResult):876 class InstrumentedTestResult(tests.ExtendedTestResult):
877 def done(self): pass877 def stopTestRun(self): pass
878 def startTests(self): pass878 def startTests(self): pass
879 def report_test_start(self, test): pass879 def report_test_start(self, test): pass
880 def report_unsupported(self, test, feature):880 def report_unsupported(self, test, feature):
@@ -918,7 +918,7 @@
918 def test_unavailable_exception(self):918 def test_unavailable_exception(self):
919 """An UnavailableFeature being raised should invoke addNotSupported."""919 """An UnavailableFeature being raised should invoke addNotSupported."""
920 class InstrumentedTestResult(tests.ExtendedTestResult):920 class InstrumentedTestResult(tests.ExtendedTestResult):
921 def done(self): pass921 def stopTestRun(self): pass
922 def startTests(self): pass922 def startTests(self): pass
923 def report_test_start(self, test): pass923 def report_test_start(self, test): pass
924 def addNotSupported(self, test, feature):924 def addNotSupported(self, test, feature):
@@ -1001,11 +1001,14 @@
1001 because of our use of global state.1001 because of our use of global state.
1002 """1002 """
1003 old_root = tests.TestCaseInTempDir.TEST_ROOT1003 old_root = tests.TestCaseInTempDir.TEST_ROOT
1004 old_leak = tests.TestCase._first_thread_leaker_id
1004 try:1005 try:
1005 tests.TestCaseInTempDir.TEST_ROOT = None1006 tests.TestCaseInTempDir.TEST_ROOT = None
1007 tests.TestCase._first_thread_leaker_id = None
1006 return testrunner.run(test)1008 return testrunner.run(test)
1007 finally:1009 finally:
1008 tests.TestCaseInTempDir.TEST_ROOT = old_root1010 tests.TestCaseInTempDir.TEST_ROOT = old_root
1011 tests.TestCase._first_thread_leaker_id = old_leak
10091012
1010 def test_known_failure_failed_run(self):1013 def test_known_failure_failed_run(self):
1011 # run a test that generates a known failure which should be printed in1014 # run a test that generates a known failure which should be printed in
@@ -1291,6 +1294,34 @@
1291 self.assertContainsRe(log, 'this will be kept')1294 self.assertContainsRe(log, 'this will be kept')
1292 self.assertEqual(log, test._log_contents)1295 self.assertEqual(log, test._log_contents)
12931296
1297 def test_startTestRun(self):
1298 """run should call result.startTestRun()"""
1299 calls = []
1300 class LoggingDecorator(tests.ForwardingResult):
1301 def startTestRun(self):
1302 tests.ForwardingResult.startTestRun(self)
1303 calls.append('startTestRun')
1304 test = unittest.FunctionTestCase(lambda:None)
1305 stream = StringIO()
1306 runner = tests.TextTestRunner(stream=stream,
1307 result_decorators=[LoggingDecorator])
1308 result = self.run_test_runner(runner, test)
1309 self.assertLength(1, calls)
1310
1311 def test_stopTestRun(self):
1312 """run should call result.stopTestRun()"""
1313 calls = []
1314 class LoggingDecorator(tests.ForwardingResult):
1315 def stopTestRun(self):
1316 tests.ForwardingResult.stopTestRun(self)
1317 calls.append('stopTestRun')
1318 test = unittest.FunctionTestCase(lambda:None)
1319 stream = StringIO()
1320 runner = tests.TextTestRunner(stream=stream,
1321 result_decorators=[LoggingDecorator])
1322 result = self.run_test_runner(runner, test)
1323 self.assertLength(1, calls)
1324
12941325
1295class SampleTestCase(tests.TestCase):1326class SampleTestCase(tests.TestCase):
12961327
@@ -2934,19 +2965,3 @@
2934 self.verbosity)2965 self.verbosity)
2935 tests.run_suite(suite, runner_class=MyRunner, stream=StringIO())2966 tests.run_suite(suite, runner_class=MyRunner, stream=StringIO())
2936 self.assertLength(1, calls)2967 self.assertLength(1, calls)
2937
2938 def test_done(self):
2939 """run_suite should call result.done()"""
2940 self.calls = 0
2941 def one_more_call(): self.calls += 1
2942 def test_function():
2943 pass
2944 test = unittest.FunctionTestCase(test_function)
2945 class InstrumentedTestResult(tests.ExtendedTestResult):
2946 def done(self): one_more_call()
2947 class MyRunner(tests.TextTestRunner):
2948 def run(self, test):
2949 return InstrumentedTestResult(self.stream, self.descriptions,
2950 self.verbosity)
2951 tests.run_suite(test, runner_class=MyRunner, stream=StringIO())
2952 self.assertEquals(1, self.calls)