diff -r 2a18f6b85da2 Doc/library/unittest.rst --- a/Doc/library/unittest.rst Sun Apr 12 18:47:56 2015 -0400 +++ b/Doc/library/unittest.rst Mon Apr 13 11:55:36 2015 -0400 @@ -565,6 +565,14 @@ Skipped classes will not have :meth:`~TestCase.setUpClass` or :meth:`~TestCase.tearDownClass` run. Skipped modules will not have :func:`setUpModule` or :func:`tearDownModule` run. +.. exception:: StopTestRun(report=True) + + This exception is raised to stop all further tests from running. + + If report==False, all output will be suppressed. This can be useful to + prevent automated build systems from logging the results of the stopped + test run. + .. _subtests: diff -r 2a18f6b85da2 Doc/whatsnew/3.5.rst --- a/Doc/whatsnew/3.5.rst Sun Apr 12 18:47:56 2015 -0400 +++ b/Doc/whatsnew/3.5.rst Mon Apr 13 11:55:36 2015 -0400 @@ -478,6 +478,12 @@ * The :func:`time.monotonic` function is now always available. (Contributed by Victor Stinner in :issue:`22043`.) +untitest +-------- + +* The StopTestRun exception has been added. It allows a test method to halt + the test. (Contributed by Pamela McA'Nulty in :issue:`10612`.) + urllib ------ diff -r 2a18f6b85da2 Lib/unittest/__init__.py --- a/Lib/unittest/__init__.py Sun Apr 12 18:47:56 2015 -0400 +++ b/Lib/unittest/__init__.py Mon Apr 13 11:55:36 2015 -0400 @@ -46,7 +46,8 @@ __all__ = ['TestResult', 'TestCase', 'TestSuite', 'TextTestRunner', 'TestLoader', 'FunctionTestCase', 'main', - 'defaultTestLoader', 'SkipTest', 'skip', 'skipIf', 'skipUnless', + 'defaultTestLoader', 'SkipTest', 'StopTestRun', + 'skip', 'skipIf', 'skipUnless', 'expectedFailure', 'TextTestResult', 'installHandler', 'registerResult', 'removeResult', 'removeHandler'] @@ -56,8 +57,8 @@ __unittest = True from .result import TestResult -from .case import (TestCase, FunctionTestCase, SkipTest, skip, skipIf, - skipUnless, expectedFailure) +from .case import (TestCase, FunctionTestCase, SkipTest, StopTestRun, + skip, skipIf, skipUnless, expectedFailure) from .suite import BaseTestSuite, TestSuite from .loader import (TestLoader, defaultTestLoader, makeSuite, getTestCaseNames, findTestCases) diff -r 2a18f6b85da2 Lib/unittest/case.py --- a/Lib/unittest/case.py Sun Apr 12 18:47:56 2015 -0400 +++ b/Lib/unittest/case.py Mon Apr 13 11:55:36 2015 -0400 @@ -29,6 +29,15 @@ instead of raising this directly. """ +class StopTestRun(Exception): + """ + The test run should stop. + TODO: document the report param + TODO: make __str__ real + """ + def __init__(self, report=True): + self.report = report + class _ShouldStop(Exception): """ The test should stop. @@ -49,6 +58,8 @@ self.skipped = [] self.expectedFailure = None self.errors = [] + self.stop_run = False + self.stop_run_hard = False @contextlib.contextmanager def testPartExecutor(self, test_case, isTest=False): @@ -61,6 +72,10 @@ except SkipTest as e: self.success = False self.skipped.append((test_case, str(e))) + except StopTestRun as e: + self.success = False + self.stop_run = True + self.stop_run_hard = e.report==False except _ShouldStop: pass except: @@ -582,15 +597,23 @@ self.doCleanups() for test, reason in outcome.skipped: self._addSkip(result, test, reason) - self._feedErrorsToResult(result, outcome.errors) - if outcome.success: - if expecting_failure: - if outcome.expectedFailure: - self._addExpectedFailure(result, outcome.expectedFailure) + + if outcome.stop_run: + result.stop() + + if outcome.stop_run_hard: + result.haltTestRun = True + else: + self._feedErrorsToResult(result, outcome.errors) + if outcome.success: + if expecting_failure: + if outcome.expectedFailure: + self._addExpectedFailure(result, outcome.expectedFailure) + else: + self._addUnexpectedSuccess(result) else: - self._addUnexpectedSuccess(result) - else: - result.addSuccess(self) + result.addSuccess(self) + return result finally: result.stopTest(self) diff -r 2a18f6b85da2 Lib/unittest/runner.py --- a/Lib/unittest/runner.py Sun Apr 12 18:47:56 2015 -0400 +++ b/Lib/unittest/runner.py Mon Apr 13 11:55:36 2015 -0400 @@ -180,6 +180,9 @@ stopTestRun() stopTime = time.time() timeTaken = stopTime - startTime + if getattr(result, 'haltTestRun', False): + return result + result.printErrors() if hasattr(result, 'separator2'): self.stream.writeln(result.separator2) diff -r 2a18f6b85da2 Lib/unittest/test/support.py --- a/Lib/unittest/test/support.py Sun Apr 12 18:47:56 2015 -0400 +++ b/Lib/unittest/test/support.py Mon Apr 13 11:55:36 2015 -0400 @@ -86,6 +86,10 @@ self._events.append('addUnexpectedSuccess') super().addUnexpectedSuccess(*args) + def printErrors(self): + self._events.append('printErrors') + super().printErrors() + class LegacyLoggingResult(_BaseLoggingResult): """ diff -r 2a18f6b85da2 Lib/unittest/test/test_runner.py --- a/Lib/unittest/test/test_runner.py Sun Apr 12 18:47:56 2015 -0400 +++ b/Lib/unittest/test/test_runner.py Mon Apr 13 11:55:36 2015 -0400 @@ -250,6 +250,52 @@ expected = ['startTestRun', 'stopTestRun'] self.assertEqual(events, expected) + def _run_stopTestRun_suite(self, report): + events = [] + + class Foo(unittest.TestCase): + def __init__(self, arg): + super(Foo, self).__init__(arg) + self.events = events + + def setUp(self): + self.events.append('setUp') + + def tearDown(self): + self.events.append('tearDown') + + def test_a(self): + self.events.append('test_a') + raise unittest.StopTestRun(report=report) + + def test_b(self): + self.events.append('test_b') + + class LoggingRunner(unittest.TextTestRunner): + def __init__(self, events): + super(LoggingRunner, self).__init__(io.StringIO()) + self._events = events + + def _makeResult(self): + return LoggingResult(self._events) + + runner = LoggingRunner(events) + suite = unittest.makeSuite(Foo) + runner.run(suite) + return events + + #Issue 10612 : StopTestRun exception to halt test run, no reporting + def test_StopTestRun_exception_no_report(self): + events = self._run_stopTestRun_suite(False) + expected = ['startTestRun', 'startTest', 'setUp', 'test_a', 'tearDown', 'stopTest', 'stopTestRun'] + self.assertEqual(events, expected) + + #Issue 10612 : StopTestRun exception to halt test run, with reporting + def test_StopTestRun_exception_with_report(self): + events = self._run_stopTestRun_suite(True) + expected = ['startTestRun', 'startTest', 'setUp', 'test_a', 'tearDown', 'stopTest', 'stopTestRun', 'printErrors'] + self.assertEqual(events, expected) + def test_pickle_unpickle(self): # Issue #7197: a TextTestRunner should be (un)pickleable. This is # required by test_multiprocessing under Windows (in verbose mode).