Ticket #1518: exitfirst.patch

File exitfirst.patch, 11.0 KB (added by Julian, 4 years ago)
  • doc/core/man/trial.1

    diff --git a/doc/core/man/trial.1 b/doc/core/man/trial.1
    index 1bbdd58..086b401 100644
    a b Set Python's recursion limit. See sys.setrecursionlimit(). 
    154154Select the reporter to use for trial's output.  Use the --help-reporters
    155155option to see a list of valid reporters.
    156156.TP
     157\fB-x\fR, \fB--exitfirst\fR
     158Stop the test run after the first test which does not succeed. This includes
     159failures, errors, or unexpected successes.
     160.TP
    157161\fB--spew\fR
    158162Print an insanely verbose log of everything that happens. Useful when
    159163debugging freezes or locks in complex code.
  • twisted/scripts/trial.py

    diff --git a/twisted/scripts/trial.py b/twisted/scripts/trial.py
    index 46859d5..9a3c31c 100644
    a b class _BasicOptions(object): 
    114114                 "Turn dirty reactor errors into warnings"],
    115115                ["force-gc", None, "Have Trial run gc.collect() before and "
    116116                 "after each test case."],
     117                ["exitfirst", "x",
     118                 "Exit after the first non-successful result."],
    117119                ]
    118120
    119121    optParameters = [
    class Options(_BasicOptions, usage.Options, app.ReactorSelectionMixin): 
    375377    def postOptions(self):
    376378        _BasicOptions.postOptions(self)
    377379        if self['jobs']:
    378             for option in ['debug', 'profile', 'debug-stacktraces']:
     380            conflicts = ['debug', 'profile', 'debug-stacktraces', 'exitfirst']
     381            for option in conflicts:
    379382                if self[option]:
    380383                    raise usage.UsageError(
    381384                        "You can't specify --%s when using --jobs" % option)
    def _makeRunner(config): 
    485488            else:
    486489                args['debugger'] = _wrappedPdb()
    487490
     491        args['exitFirst'] = config['exitfirst']
    488492        args['profile'] = config['profile']
    489493        args['forceGarbageCollection'] = config['force-gc']
    490494
  • new file twisted/topfiles/1518.feature

    diff --git a/twisted/topfiles/1518.feature b/twisted/topfiles/1518.feature
    new file mode 100644
    index 0000000..0628b66
    - +  
     1trial now has --stop and --stop-failed flags which stop the test run after the first non-success or first failure respectively.
  • twisted/trial/reporter.py

    diff --git a/twisted/trial/reporter.py b/twisted/trial/reporter.py
    index fe5a832..c57cb86 100644
    a b class UncleanWarningsReporterWrapper(TestResultDecorator): 
    234234
    235235
    236236
     237@implementer(itrial.IReporter)
     238class _ExitWrapper(TestResultDecorator):
     239    """
     240    A wrapper for a reporter that causes the reporter to stop after
     241    unsuccessful tests.
     242    """
     243
     244    def addError(self, *args, **kwargs):
     245        """
     246        See L{itrial.IReporter}.
     247        """
     248        self.shouldStop = True
     249        return self._originalReporter.addError(*args, **kwargs)
     250
     251
     252    def addFailure(self, *args, **kwargs):
     253        """
     254        See L{itrial.IReporter}.
     255        """
     256        self.shouldStop = True
     257        return self._originalReporter.addFailure(*args, **kwargs)
     258
     259
     260    def addUnexpectedSuccess(self, *args, **kwargs):
     261        """
     262        See L{itrial.IReporter}.
     263        """
     264        self.shouldStop = True
     265        return self._originalReporter.addUnexpectedSuccess(*args, **kwargs)
     266
     267
     268
    237269class _AdaptedReporter(TestResultDecorator):
    238270    """
    239271    TestResult decorator that makes sure that addError only gets tests that
  • twisted/trial/runner.py

    diff --git a/twisted/trial/runner.py b/twisted/trial/runner.py
    index be06d5a..37c5f2b 100644
    a b from twisted.python.versions import Version 
    2828from twisted.internet import defer
    2929from twisted.trial import util, unittest
    3030from twisted.trial.itrial import ITestCase
    31 from twisted.trial.reporter import UncleanWarningsReporterWrapper
     31from twisted.trial.reporter import _ExitWrapper, UncleanWarningsReporterWrapper
    3232
    3333# These are imported so that they remain in the public API for t.trial.runner
    3434from twisted.trial.unittest import TestSuite
    class TrialRunner(object): 
    669669    def _makeResult(self):
    670670        reporter = self.reporterFactory(self.stream, self.tbformat,
    671671                                        self.rterrors, self._log)
     672        if self._exitFirst:
     673            reporter = _ExitWrapper(reporter)
    672674        if self.uncleanWarnings:
    673675            reporter = UncleanWarningsReporterWrapper(reporter)
    674676        return reporter
    class TrialRunner(object): 
    683685                 uncleanWarnings=False,
    684686                 workingDirectory=None,
    685687                 forceGarbageCollection=False,
    686                  debugger=None):
     688                 debugger=None,
     689                 exitFirst=False):
    687690        self.reporterFactory = reporterFactory
    688691        self.logfile = logfile
    689692        self.mode = mode
    class TrialRunner(object): 
    697700        self._logFileObject = None
    698701        self._forceGarbageCollection = forceGarbageCollection
    699702        self.debugger = debugger
     703        self._exitFirst = exitFirst
    700704        if profile:
    701705            self.run = util.profiled(self.run, 'profile.data')
    702706
  • twisted/trial/test/test_reporter.py

    diff --git a/twisted/trial/test/test_reporter.py b/twisted/trial/test/test_reporter.py
    index c9b33e8..cc0fa06 100644
    a b from twisted.internet.utils import suppressWarnings 
    1515from twisted.python import log
    1616from twisted.python.failure import Failure
    1717from twisted.trial import itrial, unittest, runner, reporter, util
    18 from twisted.trial.reporter import UncleanWarningsReporterWrapper
     18from twisted.trial.reporter import _ExitWrapper, UncleanWarningsReporterWrapper
    1919from twisted.trial.test import erroneous
    2020from twisted.trial.unittest import makeTodo, SkipTest, Todo
    2121from twisted.trial.test import sample
    class AnsiColorizerTests(unittest.SynchronousTestCase): 
    16741674        sys.modules['curses'] = fakecurses()
    16751675        self.assertFalse(reporter._AnsiColorizer.supported(FakeStream()))
    16761676        self.assertEqual(sys.modules['curses'].setUp, 1)
     1677
     1678
     1679
     1680class ExitWrapperTests(unittest.SynchronousTestCase):
     1681    """
     1682    Tests for L{reporter._ExitWrapper}.
     1683    """
     1684
     1685    def setUp(self):
     1686        self.failure = Failure(Exception("I am a Failure"))
     1687        self.test = sample.FooTest('test_foo')
     1688        self.result = reporter.TestResult()
     1689        self.wrapped = _ExitWrapper(self.result)
     1690        self.assertFalse(self.wrapped.shouldStop)
     1691
     1692
     1693    def test_stopOnFailure(self):
     1694        """
     1695        L{reporter._ExitWrapper} causes a wrapped reporter to stop after its
     1696        first failure.
     1697        """
     1698
     1699        self.wrapped.addFailure(self.test, self.failure)
     1700        self.assertTrue(self.wrapped.shouldStop)
     1701        self.assertEqual(self.result.failures, [(self.test, self.failure)])
     1702
     1703
     1704    def test_stopOnError(self):
     1705        """
     1706        L{reporter._ExitWrapper} causes a wrapped reporter to stop after its
     1707        first error.
     1708        """
     1709
     1710        self.wrapped.addError(self.test, self.failure)
     1711        self.assertTrue(self.wrapped.shouldStop)
     1712        self.assertEqual(self.result.errors, [(self.test, self.failure)])
     1713
     1714
     1715    def test_stopOnUnexpectedSuccess(self):
     1716        """
     1717        L{reporter._StopWrapper} causes a wrapped reporter to stop after an
     1718        unexpected success if C{onlyAfterFailure} is C{False}.
     1719        """
     1720
     1721        self.wrapped.addUnexpectedSuccess(self.test, self.failure)
     1722        self.assertTrue(self.wrapped.shouldStop)
     1723        self.assertEqual(
     1724            self.result.unexpectedSuccesses, [(self.test, self.failure)])
  • twisted/trial/test/test_runner.py

    diff --git a/twisted/trial/test/test_runner.py b/twisted/trial/test/test_runner.py
    index f0a2fef..985e7b1 100644
    a b class TestRunner(unittest.SynchronousTestCase): 
    562562        self.assertEqual(['runcall'], my_runner.debugger._calls)
    563563
    564564
     565    def test_exitfirst(self):
     566        """
     567        If trial was passed the C{--exitfirst} option, the constructed test
     568        result object is wrapped with L{reporter._ExitWrapper}.
     569        """
     570
     571        self.parseOptions(["--exitfirst"])
     572        runner = self.getRunner()
     573        result = runner._makeResult()
     574        self.assertIsInstance(result, reporter._ExitWrapper)
     575
     576
    565577
    566578class TestTrialSuite(unittest.SynchronousTestCase):
    567579
  • twisted/trial/test/test_script.py

    diff --git a/twisted/trial/test/test_script.py b/twisted/trial/test/test_script.py
    index 45a254b..60bdb9e 100644
    a b class OptionsTestCase(unittest.TestCase): 
    518518            str(error))
    519519
    520520
     521    def test_jobsConflictWithExitFirst(self):
     522        """
     523        C{parseOptions} raises a C{UsageError} when C{--exitfirst} is passed
     524        along C{--jobs} as it's not supported yet.
     525
     526        @see: U{http://twistedmatrix.com/trac/ticket/6436}
     527        """
     528        error = self.assertRaises(
     529            UsageError, self.options.parseOptions,
     530            ["--jobs", "4", "--exitfirst"])
     531        self.assertEqual(
     532            "You can't specify --exitfirst when using --jobs",
     533            str(error))
     534
     535
    521536
    522537class MakeRunnerTestCase(unittest.TestCase):
    523538    """
    524539    Tests for the L{_makeRunner} helper.
    525540    """
    526541
     542    def setUp(self):
     543        self.options = trial.Options()
     544
    527545    def test_jobs(self):
    528546        """
    529547        L{_makeRunner} returns a L{DistTrialRunner} instance when the C{--jobs}
    530548        option is passed, and passes the C{workerNumber} and C{workerArguments}
    531549        parameters to it.
    532550        """
    533         options = trial.Options()
    534         options.parseOptions(["--jobs", "4", "--force-gc"])
    535         runner = trial._makeRunner(options)
     551        self.options.parseOptions(["--jobs", "4", "--force-gc"])
     552        runner = trial._makeRunner(self.options)
    536553        self.assertIsInstance(runner, DistTrialRunner)
    537554        self.assertEqual(4, runner._workerNumber)
    538555        self.assertEqual(["--force-gc"], runner._workerArguments)
    class MakeRunnerTestCase(unittest.TestCase): 
    543560        L{_makeRunner} returns a L{TrialRunner} instance in C{DRY_RUN} mode
    544561        when the C{--dry-run} option is passed, even if C{--jobs} is set.
    545562        """
    546         options = trial.Options()
    547         options.parseOptions(["--jobs", "4", "--dry-run"])
    548         runner = trial._makeRunner(options)
     563        self.options.parseOptions(["--jobs", "4", "--dry-run"])
     564        runner = trial._makeRunner(self.options)
    549565        self.assertIsInstance(runner, TrialRunner)
    550566        self.assertEqual(TrialRunner.DRY_RUN, runner.mode)
    551567
    class MakeRunnerTestCase(unittest.TestCase): 
    566582        self.assertRaises(trial._DebuggerNotFound, trial._makeRunner, options)
    567583
    568584
     585    def test_exitfirst(self):
     586        """
     587        Passing C{--exitfirst} wraps the reporter with a
     588        L{reporter._ExitWrapper} that stops on any non-success.
     589        """
     590        self.options.parseOptions(["--exitfirst"])
     591        runner = trial._makeRunner(self.options)
     592        self.assertTrue(runner._exitFirst)
     593
     594
    569595class TestRun(unittest.TestCase):
    570596    """
    571597    Tests for the L{run} function.