RAHHH
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Unit tests for the Trial unit-testing framework.
|
||||
"""
|
||||
|
||||
from hypothesis import HealthCheck, settings
|
||||
|
||||
|
||||
def _activateHypothesisProfile() -> None:
|
||||
"""
|
||||
Load a Hypothesis profile appropriate for a Twisted test suite.
|
||||
"""
|
||||
deterministic = settings(
|
||||
# Disable the deadline. It is too hard to guarantee that a particular
|
||||
# piece of Python code will always run in less than some fixed amount
|
||||
# of time. Hardware capabilities, the OS scheduler, the Python
|
||||
# garbage collector, and other factors all combine to make substantial
|
||||
# outliers possible. Such failures are a distraction from development
|
||||
# and a hassle on continuous integration environments.
|
||||
deadline=None,
|
||||
suppress_health_check=[
|
||||
# With the same reasoning as above, disable the Hypothesis time
|
||||
# limit on data generation by example search strategies.
|
||||
HealthCheck.too_slow,
|
||||
],
|
||||
# When a developer is working on one set of changes, or continuous
|
||||
# integration system is testing them, it is disruptive for Hypothesis
|
||||
# to discover a bug in pre-existing code. This is just what
|
||||
# Hypothesis will do by default, by exploring a pseudo-randomly
|
||||
# different set of examples each time. Such failures are a
|
||||
# distraction from development and a hassle in continuous integration
|
||||
# environments.
|
||||
derandomize=True,
|
||||
)
|
||||
|
||||
settings.register_profile("twisted_trial_test_profile_deterministic", deterministic)
|
||||
settings.load_profile("twisted_trial_test_profile_deterministic")
|
||||
|
||||
|
||||
_activateHypothesisProfile()
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,233 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for Deferred handling by L{twisted.trial.unittest.TestCase}.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from twisted.internet import defer, reactor, threads
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.python.util import runWithWarningsSuppressed
|
||||
from twisted.trial import unittest
|
||||
from twisted.trial.util import suppress as SUPPRESS
|
||||
|
||||
|
||||
class DeferredSetUpOK(unittest.TestCase):
|
||||
def setUp(self):
|
||||
d = defer.succeed("value")
|
||||
d.addCallback(self._cb_setUpCalled)
|
||||
return d
|
||||
|
||||
def _cb_setUpCalled(self, ignored):
|
||||
self._setUpCalled = True
|
||||
|
||||
def test_ok(self):
|
||||
self.assertTrue(self._setUpCalled)
|
||||
|
||||
|
||||
class DeferredSetUpFail(unittest.TestCase):
|
||||
testCalled = False
|
||||
|
||||
def setUp(self):
|
||||
return defer.fail(unittest.FailTest("i fail"))
|
||||
|
||||
def test_ok(self):
|
||||
DeferredSetUpFail.testCalled = True
|
||||
self.fail("I should not get called")
|
||||
|
||||
|
||||
class DeferredSetUpCallbackFail(unittest.TestCase):
|
||||
testCalled = False
|
||||
|
||||
def setUp(self):
|
||||
d = defer.succeed("value")
|
||||
d.addCallback(self._cb_setUpCalled)
|
||||
return d
|
||||
|
||||
def _cb_setUpCalled(self, ignored):
|
||||
self.fail("deliberate failure")
|
||||
|
||||
def test_ok(self):
|
||||
DeferredSetUpCallbackFail.testCalled = True
|
||||
|
||||
|
||||
class DeferredSetUpError(unittest.TestCase):
|
||||
testCalled = False
|
||||
|
||||
def setUp(self):
|
||||
return defer.fail(RuntimeError("deliberate error"))
|
||||
|
||||
def test_ok(self):
|
||||
DeferredSetUpError.testCalled = True
|
||||
|
||||
|
||||
class DeferredSetUpNeverFire(unittest.TestCase):
|
||||
testCalled = False
|
||||
|
||||
def setUp(self):
|
||||
return defer.Deferred()
|
||||
|
||||
def test_ok(self):
|
||||
DeferredSetUpNeverFire.testCalled = True
|
||||
|
||||
|
||||
class DeferredSetUpSkip(unittest.TestCase):
|
||||
testCalled = False
|
||||
|
||||
def setUp(self):
|
||||
d = defer.succeed("value")
|
||||
d.addCallback(self._cb1)
|
||||
return d
|
||||
|
||||
def _cb1(self, ignored):
|
||||
raise unittest.SkipTest("skip me")
|
||||
|
||||
def test_ok(self):
|
||||
DeferredSetUpSkip.testCalled = True
|
||||
|
||||
|
||||
class DeferredTests(unittest.TestCase):
|
||||
touched = False
|
||||
|
||||
def _cb_fail(self, reason):
|
||||
self.fail(reason)
|
||||
|
||||
def _cb_error(self, reason):
|
||||
raise RuntimeError(reason)
|
||||
|
||||
def _cb_skip(self, reason):
|
||||
raise unittest.SkipTest(reason)
|
||||
|
||||
def _touchClass(self, ignored):
|
||||
self.__class__.touched = True
|
||||
|
||||
def setUp(self):
|
||||
self.__class__.touched = False
|
||||
|
||||
def test_pass(self):
|
||||
return defer.succeed("success")
|
||||
|
||||
def test_passGenerated(self):
|
||||
self._touchClass(None)
|
||||
yield None
|
||||
|
||||
test_passGenerated = runWithWarningsSuppressed(
|
||||
[
|
||||
SUPPRESS(
|
||||
message="twisted.internet.defer.deferredGenerator was " "deprecated"
|
||||
)
|
||||
],
|
||||
defer.deferredGenerator,
|
||||
test_passGenerated,
|
||||
)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def test_passInlineCallbacks(self):
|
||||
"""
|
||||
Test case that is decorated with L{defer.inlineCallbacks}.
|
||||
"""
|
||||
self._touchClass(None)
|
||||
yield None
|
||||
|
||||
def test_fail(self):
|
||||
return defer.fail(self.failureException("I fail"))
|
||||
|
||||
def test_failureInCallback(self):
|
||||
d = defer.succeed("fail")
|
||||
d.addCallback(self._cb_fail)
|
||||
return d
|
||||
|
||||
def test_errorInCallback(self):
|
||||
d = defer.succeed("error")
|
||||
d.addCallback(self._cb_error)
|
||||
return d
|
||||
|
||||
def test_skip(self):
|
||||
d = defer.succeed("skip")
|
||||
d.addCallback(self._cb_skip)
|
||||
d.addCallback(self._touchClass)
|
||||
return d
|
||||
|
||||
def test_thread(self):
|
||||
return threads.deferToThread(lambda: None)
|
||||
|
||||
def test_expectedFailure(self):
|
||||
d = defer.succeed("todo")
|
||||
d.addCallback(self._cb_error)
|
||||
return d
|
||||
|
||||
test_expectedFailure.todo = "Expected failure" # type: ignore[attr-defined]
|
||||
|
||||
|
||||
class TimeoutTests(unittest.TestCase):
|
||||
timedOut: Failure | None = None
|
||||
|
||||
def test_pass(self):
|
||||
d = defer.Deferred()
|
||||
reactor.callLater(0, d.callback, "hoorj!")
|
||||
return d
|
||||
|
||||
test_pass.timeout = 2 # type: ignore[attr-defined]
|
||||
|
||||
def test_passDefault(self):
|
||||
# test default timeout
|
||||
d = defer.Deferred()
|
||||
reactor.callLater(0, d.callback, "hoorj!")
|
||||
return d
|
||||
|
||||
def test_timeout(self):
|
||||
return defer.Deferred()
|
||||
|
||||
test_timeout.timeout = 0.1 # type: ignore[attr-defined]
|
||||
|
||||
def test_timeoutZero(self):
|
||||
return defer.Deferred()
|
||||
|
||||
test_timeoutZero.timeout = 0 # type: ignore[attr-defined]
|
||||
|
||||
def test_expectedFailure(self):
|
||||
return defer.Deferred()
|
||||
|
||||
test_expectedFailure.timeout = 0.1 # type: ignore[attr-defined]
|
||||
test_expectedFailure.todo = "i will get it right, eventually" # type: ignore[attr-defined]
|
||||
|
||||
def test_skip(self):
|
||||
return defer.Deferred()
|
||||
|
||||
test_skip.timeout = 0.1 # type: ignore[attr-defined]
|
||||
test_skip.skip = "i will get it right, eventually" # type: ignore[attr-defined]
|
||||
|
||||
def test_errorPropagation(self):
|
||||
def timedOut(err):
|
||||
self.__class__.timedOut = err
|
||||
return err
|
||||
|
||||
d = defer.Deferred()
|
||||
d.addErrback(timedOut)
|
||||
return d
|
||||
|
||||
test_errorPropagation.timeout = 0.1 # type: ignore[attr-defined]
|
||||
|
||||
def test_calledButNeverCallback(self):
|
||||
d = defer.Deferred()
|
||||
|
||||
def neverFire(r):
|
||||
return defer.Deferred()
|
||||
|
||||
d.addCallback(neverFire)
|
||||
d.callback(1)
|
||||
return d
|
||||
|
||||
test_calledButNeverCallback.timeout = 0.1 # type: ignore[attr-defined]
|
||||
|
||||
|
||||
class TestClassTimeoutAttribute(unittest.TestCase):
|
||||
timeout = 0.2
|
||||
|
||||
def setUp(self):
|
||||
self.d = defer.Deferred()
|
||||
|
||||
def testMethod(self):
|
||||
self.methodCalled = True
|
||||
return self.d
|
||||
@@ -0,0 +1,261 @@
|
||||
# -*- test-case-name: twisted.trial.test.test_tests -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Definitions of test cases with various interesting error-related behaviors, to
|
||||
be used by test modules to exercise different features of trial's test runner.
|
||||
|
||||
See the L{twisted.trial.test.test_tests} module docstring for details about how
|
||||
this code is arranged.
|
||||
|
||||
Some of these tests are also used by L{twisted.trial._dist.test}.
|
||||
"""
|
||||
|
||||
|
||||
from unittest import skipIf
|
||||
|
||||
from twisted.internet import defer, protocol, reactor
|
||||
from twisted.internet.task import deferLater
|
||||
from twisted.trial import unittest, util
|
||||
|
||||
|
||||
class FoolishError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LargeError(Exception):
|
||||
"""
|
||||
An exception which has a string representation of at least a specified
|
||||
number of characters.
|
||||
"""
|
||||
|
||||
def __init__(self, minSize: int) -> None:
|
||||
Exception.__init__(self)
|
||||
self.minSize = minSize
|
||||
|
||||
def __str__(self):
|
||||
large = "x" * self.minSize
|
||||
return f"LargeError<I fail: {large}>"
|
||||
|
||||
|
||||
class FailureInSetUpMixin:
|
||||
def setUp(self):
|
||||
raise FoolishError("I am a broken setUp method")
|
||||
|
||||
def test_noop(self):
|
||||
pass
|
||||
|
||||
|
||||
class SynchronousTestFailureInSetUp(FailureInSetUpMixin, unittest.SynchronousTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousTestFailureInSetUp(FailureInSetUpMixin, unittest.TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class FailureInTearDownMixin:
|
||||
def tearDown(self):
|
||||
raise FoolishError("I am a broken tearDown method")
|
||||
|
||||
def test_noop(self):
|
||||
pass
|
||||
|
||||
|
||||
class SynchronousTestFailureInTearDown(
|
||||
FailureInTearDownMixin, unittest.SynchronousTestCase
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousTestFailureInTearDown(FailureInTearDownMixin, unittest.TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class FailureButTearDownRunsMixin:
|
||||
"""
|
||||
A test fails, but its L{tearDown} still runs.
|
||||
"""
|
||||
|
||||
tornDown = False
|
||||
|
||||
def tearDown(self):
|
||||
self.tornDown = True
|
||||
|
||||
def test_fails(self):
|
||||
"""
|
||||
A test that fails.
|
||||
"""
|
||||
raise FoolishError("I am a broken test")
|
||||
|
||||
|
||||
class SynchronousTestFailureButTearDownRuns(
|
||||
FailureButTearDownRunsMixin, unittest.SynchronousTestCase
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousTestFailureButTearDownRuns(
|
||||
FailureButTearDownRunsMixin, unittest.TestCase
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class TestRegularFail(unittest.SynchronousTestCase):
|
||||
def test_fail(self):
|
||||
self.fail("I fail")
|
||||
|
||||
def test_subfail(self):
|
||||
self.subroutine()
|
||||
|
||||
def subroutine(self):
|
||||
self.fail("I fail inside")
|
||||
|
||||
|
||||
class TestAsynchronousFail(unittest.TestCase):
|
||||
"""
|
||||
Test failures for L{unittest.TestCase} based classes.
|
||||
"""
|
||||
|
||||
text = "I fail"
|
||||
|
||||
def test_fail(self) -> defer.Deferred[None]:
|
||||
"""
|
||||
A test which fails in the callback of the returned L{defer.Deferred}.
|
||||
"""
|
||||
return deferLater(reactor, 0, self.fail, "I fail later") # type: ignore[arg-type]
|
||||
|
||||
def test_failGreaterThan64k(self) -> defer.Deferred[None]:
|
||||
"""
|
||||
A test which fails in the callback of the returned L{defer.Deferred}
|
||||
with a very long string.
|
||||
"""
|
||||
return deferLater(reactor, 0, self.fail, "I fail later: " + "x" * 2**16) # type: ignore[arg-type]
|
||||
|
||||
def test_exception(self) -> None:
|
||||
"""
|
||||
A test which raises an exception synchronously.
|
||||
"""
|
||||
raise Exception(self.text)
|
||||
|
||||
def test_exceptionGreaterThan64k(self) -> None:
|
||||
"""
|
||||
A test which raises an exception with a long string representation
|
||||
synchronously.
|
||||
"""
|
||||
raise LargeError(2**16)
|
||||
|
||||
def test_exceptionGreaterThan64kEncoded(self) -> None:
|
||||
"""
|
||||
A test which synchronously raises an exception with a long string
|
||||
representation including non-ascii content.
|
||||
"""
|
||||
# The exception text itself is not greater than 64k but SNOWMAN
|
||||
# encodes to 3 bytes with UTF-8 so the length of the UTF-8 encoding of
|
||||
# the string representation of this exception will be greater than 2
|
||||
# ** 16.
|
||||
raise Exception("\N{SNOWMAN}" * 2**15)
|
||||
|
||||
|
||||
class ErrorTest(unittest.SynchronousTestCase):
|
||||
"""
|
||||
A test case which has a L{test_foo} which will raise an error.
|
||||
|
||||
@ivar ran: boolean indicating whether L{test_foo} has been run.
|
||||
"""
|
||||
|
||||
ran = False
|
||||
|
||||
def test_foo(self):
|
||||
"""
|
||||
Set C{self.ran} to True and raise a C{ZeroDivisionError}
|
||||
"""
|
||||
self.ran = True
|
||||
1 / 0
|
||||
|
||||
|
||||
@skipIf(True, "skipping this test")
|
||||
class TestSkipTestCase(unittest.SynchronousTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class DelayedCall(unittest.TestCase):
|
||||
hiddenExceptionMsg = "something blew up"
|
||||
|
||||
def go(self):
|
||||
raise RuntimeError(self.hiddenExceptionMsg)
|
||||
|
||||
def testHiddenException(self):
|
||||
"""
|
||||
What happens if an error is raised in a DelayedCall and an error is
|
||||
also raised in the test?
|
||||
|
||||
L{test_reporter.ErrorReportingTests.testHiddenException} checks that
|
||||
both errors get reported.
|
||||
|
||||
Note that this behaviour is deprecated. A B{real} test would return a
|
||||
Deferred that got triggered by the callLater. This would guarantee the
|
||||
delayed call error gets reported.
|
||||
"""
|
||||
reactor.callLater(0, self.go)
|
||||
reactor.iterate(0.01)
|
||||
self.fail("Deliberate failure to mask the hidden exception")
|
||||
|
||||
testHiddenException.suppress = [ # type: ignore[attr-defined]
|
||||
util.suppress(
|
||||
message=r"reactor\.iterate cannot be used.*", category=DeprecationWarning
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class ReactorCleanupTests(unittest.TestCase):
|
||||
def test_leftoverPendingCalls(self):
|
||||
def _():
|
||||
print("foo!")
|
||||
|
||||
reactor.callLater(10000.0, _)
|
||||
|
||||
|
||||
class SocketOpenTest(unittest.TestCase):
|
||||
def test_socketsLeftOpen(self):
|
||||
f = protocol.Factory()
|
||||
f.protocol = protocol.Protocol
|
||||
reactor.listenTCP(0, f)
|
||||
|
||||
|
||||
class TimingOutDeferred(unittest.TestCase):
|
||||
def test_alpha(self):
|
||||
pass
|
||||
|
||||
def test_deferredThatNeverFires(self):
|
||||
self.methodCalled = True
|
||||
d = defer.Deferred()
|
||||
return d
|
||||
|
||||
def test_omega(self):
|
||||
pass
|
||||
|
||||
|
||||
def unexpectedException(self):
|
||||
"""i will raise an unexpected exception...
|
||||
... *CAUSE THAT'S THE KINDA GUY I AM*
|
||||
|
||||
>>> 1/0
|
||||
"""
|
||||
|
||||
|
||||
class EventuallyFailingTestCase(unittest.SynchronousTestCase):
|
||||
"""
|
||||
A test suite that fails after it is run a few times.
|
||||
"""
|
||||
|
||||
n: int = 0
|
||||
|
||||
def test_it(self):
|
||||
"""
|
||||
Run successfully a few times and then fail forever after.
|
||||
"""
|
||||
self.n += 1
|
||||
if self.n >= 5:
|
||||
self.fail("eventually failing")
|
||||
@@ -0,0 +1,95 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Hamcrest matchers useful throughout the test suite.
|
||||
"""
|
||||
|
||||
from typing import IO, Callable, Optional, TypeVar
|
||||
|
||||
from hamcrest.core.base_matcher import BaseMatcher
|
||||
from hamcrest.core.description import Description
|
||||
from hamcrest.core.matcher import Matcher
|
||||
|
||||
from twisted.python.filepath import IFilePath
|
||||
from twisted.python.reflect import fullyQualifiedName
|
||||
|
||||
_A = TypeVar("_A")
|
||||
_B = TypeVar("_B")
|
||||
|
||||
|
||||
class _MatchAfter(BaseMatcher[_A]):
|
||||
"""
|
||||
The implementation of L{after}.
|
||||
|
||||
@ivar f: The function to apply.
|
||||
@ivar m: The matcher to use on the result.
|
||||
|
||||
@ivar _e: After trying to apply the function fails with an exception, the
|
||||
exception that was raised. This can later be used by
|
||||
L{describe_mismatch}.
|
||||
"""
|
||||
|
||||
def __init__(self, f: Callable[[_A], _B], m: Matcher[_B]) -> None:
|
||||
self.f = f
|
||||
self.m = m
|
||||
self._e: Optional[Exception] = None
|
||||
|
||||
def _matches(self, item: _A) -> bool:
|
||||
"""
|
||||
Apply the function and delegate matching on the result.
|
||||
"""
|
||||
try:
|
||||
transformed = self.f(item)
|
||||
except Exception as e:
|
||||
self._e = e
|
||||
return False
|
||||
else:
|
||||
return self.m.matches(transformed)
|
||||
|
||||
def describe_mismatch(self, item: _A, mismatch_description: Description) -> None:
|
||||
"""
|
||||
Describe the mismatching item or the exception that occurred while
|
||||
pre-processing it.
|
||||
|
||||
@note: Since the exception reporting here depends on mutable state it
|
||||
will only work as long as PyHamcrest calls methods in the right
|
||||
order. The PyHamcrest Matcher interface doesn't seem to allow
|
||||
implementing this functionality in a more reliable way (see the
|
||||
implementation of L{assert_that}).
|
||||
"""
|
||||
if self._e is None:
|
||||
super().describe_mismatch(item, mismatch_description)
|
||||
else:
|
||||
mismatch_description.append_text(
|
||||
f"{fullyQualifiedName(self.f)}({item!r}) raised\n"
|
||||
f"{fullyQualifiedName(self._e.__class__)}: {self._e}"
|
||||
)
|
||||
|
||||
def describe_to(self, description: Description) -> None:
|
||||
"""
|
||||
Create a text description of the match requirement.
|
||||
"""
|
||||
description.append_text(f"[after {self.f}] ")
|
||||
self.m.describe_to(description)
|
||||
|
||||
|
||||
def after(f: Callable[[_A], _B], m: Matcher[_B]) -> Matcher[_A]:
|
||||
"""
|
||||
Create a matcher which calls C{f} and uses C{m} to match the result.
|
||||
"""
|
||||
return _MatchAfter(f, m)
|
||||
|
||||
|
||||
def fileContents(m: Matcher[str], encoding: str = "utf-8") -> Matcher[IFilePath]:
|
||||
"""
|
||||
Create a matcher which matches a L{FilePath} the contents of which are
|
||||
matched by L{m}.
|
||||
"""
|
||||
|
||||
def getContent(p: IFilePath) -> str:
|
||||
f: IO[bytes]
|
||||
with p.open() as f:
|
||||
return f.read().decode(encoding)
|
||||
|
||||
return after(getContent, m)
|
||||
@@ -0,0 +1,22 @@
|
||||
# Copyright (c) 2006 Twisted Matrix Laboratories. See LICENSE for details
|
||||
|
||||
"""
|
||||
Mock test module that contains a C{test_suite} method. L{runner.TestLoader}
|
||||
should load the tests from the C{test_suite}, not from the C{Foo} C{TestCase}.
|
||||
|
||||
See {twisted.trial.test.test_loader.LoaderTest.test_loadModuleWith_test_suite}.
|
||||
"""
|
||||
|
||||
|
||||
from twisted.trial import runner, unittest
|
||||
|
||||
|
||||
class Foo(unittest.SynchronousTestCase):
|
||||
def test_foo(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def test_suite():
|
||||
ts = runner.TestSuite()
|
||||
ts.name = "MyCustomSuite"
|
||||
return ts
|
||||
@@ -0,0 +1,22 @@
|
||||
# Copyright (c) 2006 Twisted Matrix Laboratories. See LICENSE for details
|
||||
|
||||
"""
|
||||
Mock test module that contains a C{testSuite} method. L{runner.TestLoader}
|
||||
should load the tests from the C{testSuite}, not from the C{Foo} C{TestCase}.
|
||||
|
||||
See L{twisted.trial.test.test_loader.LoaderTest.test_loadModuleWith_testSuite}.
|
||||
"""
|
||||
|
||||
|
||||
from twisted.trial import runner, unittest
|
||||
|
||||
|
||||
class Foo(unittest.SynchronousTestCase):
|
||||
def test_foo(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def testSuite():
|
||||
ts = runner.TestSuite()
|
||||
ts.name = "MyCustomSuite"
|
||||
return ts
|
||||
@@ -0,0 +1,29 @@
|
||||
# Copyright (c) 2006 Twisted Matrix Laboratories. See LICENSE for details
|
||||
|
||||
"""
|
||||
Mock test module that contains both a C{test_suite} and a C{testSuite} method.
|
||||
L{runner.TestLoader} should load the tests from the C{testSuite}, not from the
|
||||
C{Foo} C{TestCase} nor from the C{test_suite} method.
|
||||
|
||||
See {twisted.trial.test.test_loader.LoaderTest.test_loadModuleWithBothCustom}.
|
||||
"""
|
||||
|
||||
|
||||
from twisted.trial import runner, unittest
|
||||
|
||||
|
||||
class Foo(unittest.SynchronousTestCase):
|
||||
def test_foo(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def test_suite():
|
||||
ts = runner.TestSuite()
|
||||
ts.name = "test_suite"
|
||||
return ts
|
||||
|
||||
|
||||
def testSuite():
|
||||
ts = runner.TestSuite()
|
||||
ts.name = "testSuite"
|
||||
return ts
|
||||
@@ -0,0 +1,103 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
# this module is a trivial class with doctests to test trial's doctest
|
||||
# support.
|
||||
|
||||
|
||||
class Counter:
|
||||
"""a simple counter object for testing trial's doctest support
|
||||
|
||||
>>> c = Counter()
|
||||
>>> c.value()
|
||||
0
|
||||
>>> c += 3
|
||||
>>> c.value()
|
||||
3
|
||||
>>> c.incr()
|
||||
>>> c.value() == 4
|
||||
True
|
||||
>>> c == 4
|
||||
True
|
||||
>>> c != 9
|
||||
True
|
||||
|
||||
"""
|
||||
|
||||
_count = 0
|
||||
|
||||
def __init__(self, initialValue=0, maxval=None):
|
||||
self._count = initialValue
|
||||
self.maxval = maxval
|
||||
|
||||
def __iadd__(self, other):
|
||||
"""add other to my value and return self
|
||||
|
||||
>>> c = Counter(100)
|
||||
>>> c += 333
|
||||
>>> c == 433
|
||||
True
|
||||
"""
|
||||
if self.maxval is not None and ((self._count + other) > self.maxval):
|
||||
raise ValueError("sorry, counter got too big")
|
||||
else:
|
||||
self._count += other
|
||||
return self
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
"""equality operator, compare other to my value()
|
||||
|
||||
>>> c = Counter()
|
||||
>>> c == 0
|
||||
True
|
||||
>>> c += 10
|
||||
>>> c.incr()
|
||||
>>> c == 10 # fail this test on purpose
|
||||
True
|
||||
|
||||
"""
|
||||
return self._count == other
|
||||
|
||||
def __ne__(self, other: object) -> bool:
|
||||
"""inequality operator
|
||||
|
||||
>>> c = Counter()
|
||||
>>> c != 10
|
||||
True
|
||||
"""
|
||||
return not self.__eq__(other)
|
||||
|
||||
def incr(self):
|
||||
"""increment my value by 1
|
||||
|
||||
>>> from twisted.trial.test.mockdoctest import Counter
|
||||
>>> c = Counter(10, 11)
|
||||
>>> c.incr()
|
||||
>>> c.value() == 11
|
||||
True
|
||||
>>> c.incr()
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in ?
|
||||
File "twisted/trial/test/mockdoctest.py", line 51, in incr
|
||||
self.__iadd__(1)
|
||||
File "twisted/trial/test/mockdoctest.py", line 39, in __iadd__
|
||||
raise ValueError, "sorry, counter got too big"
|
||||
ValueError: sorry, counter got too big
|
||||
"""
|
||||
self.__iadd__(1)
|
||||
|
||||
def value(self):
|
||||
"""return this counter's value
|
||||
|
||||
>>> c = Counter(555)
|
||||
>>> c.value() == 555
|
||||
True
|
||||
"""
|
||||
return self._count
|
||||
|
||||
def unexpectedException(self):
|
||||
"""i will raise an unexpected exception...
|
||||
... *CAUSE THAT'S THE KINDA GUY I AM*
|
||||
|
||||
>>> 1/0
|
||||
"""
|
||||
@@ -0,0 +1,7 @@
|
||||
# -*- test-case-name: twisted.trial.test.moduleself -*-
|
||||
from twisted.trial import unittest
|
||||
|
||||
|
||||
class Foo(unittest.SynchronousTestCase):
|
||||
def testFoo(self) -> None:
|
||||
pass
|
||||
@@ -0,0 +1,11 @@
|
||||
# -*- test-case-name: twisted.trial.test.test_log -*-
|
||||
|
||||
# fodder for test_script, which parses files for emacs local variable
|
||||
# declarations. This one is supposed to have:
|
||||
# test-case-name: twisted.trial.test.test_log.
|
||||
# in the first line
|
||||
# The class declaration is irrelevant
|
||||
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
@@ -0,0 +1,7 @@
|
||||
# fodder for test_script, which parses files for emacs local variable
|
||||
# declarations. This one is supposed to have none.
|
||||
# The class declaration is irrelevant
|
||||
|
||||
|
||||
class Bar:
|
||||
pass
|
||||
@@ -0,0 +1,47 @@
|
||||
# -*- test-case-name: twisted.trial.test.test_script -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for handling of trial's --order option.
|
||||
"""
|
||||
|
||||
from twisted.trial import unittest
|
||||
|
||||
|
||||
class FooTest(unittest.TestCase):
|
||||
"""
|
||||
Used to make assertions about the order its tests will be run in.
|
||||
"""
|
||||
|
||||
def test_first(self) -> None:
|
||||
pass
|
||||
|
||||
def test_second(self) -> None:
|
||||
pass
|
||||
|
||||
def test_third(self) -> None:
|
||||
pass
|
||||
|
||||
def test_fourth(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class BazTest(unittest.TestCase):
|
||||
"""
|
||||
Used to make assertions about the order the test cases in this module are
|
||||
run in.
|
||||
"""
|
||||
|
||||
def test_baz(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class BarTest(unittest.TestCase):
|
||||
"""
|
||||
Used to make assertions about the order the test cases in this module are
|
||||
run in.
|
||||
"""
|
||||
|
||||
def test_bar(self) -> None:
|
||||
pass
|
||||
@@ -0,0 +1,178 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
#
|
||||
|
||||
"""
|
||||
Classes and functions used by L{twisted.trial.test.test_util}
|
||||
and L{twisted.trial.test.test_loader}.
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Python 3 has some funny import caching, which we don't want.
|
||||
# invalidate_caches clears it out for us.
|
||||
from importlib import invalidate_caches as invalidateImportCaches
|
||||
|
||||
from twisted.trial import unittest
|
||||
|
||||
testModule = """
|
||||
from twisted.trial import unittest
|
||||
|
||||
class FooTest(unittest.SynchronousTestCase):
|
||||
def testFoo(self):
|
||||
pass
|
||||
"""
|
||||
|
||||
dosModule = testModule.replace("\n", "\r\n")
|
||||
|
||||
|
||||
testSample = """
|
||||
'''This module is used by test_loader to test the Trial test loading
|
||||
functionality. Do NOT change the number of tests in this module.
|
||||
Do NOT change the names the tests in this module.
|
||||
'''
|
||||
|
||||
import unittest as pyunit
|
||||
from twisted.trial import unittest
|
||||
|
||||
class FooTest(unittest.SynchronousTestCase):
|
||||
def test_foo(self):
|
||||
pass
|
||||
|
||||
def test_bar(self):
|
||||
pass
|
||||
|
||||
|
||||
class PyunitTest(pyunit.TestCase):
|
||||
def test_foo(self):
|
||||
pass
|
||||
|
||||
def test_bar(self):
|
||||
pass
|
||||
|
||||
|
||||
class NotATest:
|
||||
def test_foo(self):
|
||||
pass
|
||||
|
||||
|
||||
class AlphabetTest(unittest.SynchronousTestCase):
|
||||
def test_a(self):
|
||||
pass
|
||||
|
||||
def test_b(self):
|
||||
pass
|
||||
|
||||
def test_c(self):
|
||||
pass
|
||||
"""
|
||||
|
||||
testInheritanceSample = """
|
||||
'''This module is used by test_loader to test the Trial test loading
|
||||
functionality. Do NOT change the number of tests in this module.
|
||||
Do NOT change the names the tests in this module.
|
||||
'''
|
||||
|
||||
from twisted.trial import unittest
|
||||
|
||||
class X:
|
||||
|
||||
def test_foo(self):
|
||||
pass
|
||||
|
||||
class A(unittest.SynchronousTestCase, X):
|
||||
pass
|
||||
|
||||
class B(unittest.SynchronousTestCase, X):
|
||||
pass
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class PackageTest(unittest.SynchronousTestCase):
|
||||
files = [
|
||||
("badpackage/__init__.py", "frotz\n"),
|
||||
("badpackage/test_module.py", ""),
|
||||
("unimportablepackage/__init__.py", ""),
|
||||
("unimportablepackage/test_module.py", "import notarealmoduleok\n"),
|
||||
("package2/__init__.py", ""),
|
||||
("package2/test_module.py", "import frotz\n"),
|
||||
("package/__init__.py", ""),
|
||||
("package/frotz.py", "frotz\n"),
|
||||
("package/test_bad_module.py", 'raise ZeroDivisionError("fake error")'),
|
||||
("package/test_dos_module.py", dosModule),
|
||||
("package/test_import_module.py", "import frotz"),
|
||||
("package/test_module.py", testModule),
|
||||
("goodpackage/__init__.py", ""),
|
||||
("goodpackage/test_sample.py", testSample),
|
||||
("goodpackage/sub/__init__.py", ""),
|
||||
("goodpackage/sub/test_sample.py", testSample),
|
||||
("inheritancepackage/__init__.py", ""),
|
||||
("inheritancepackage/test_x.py", testInheritanceSample),
|
||||
]
|
||||
|
||||
def _toModuleName(self, filename):
|
||||
name = os.path.splitext(filename)[0]
|
||||
segs = name.split("/")
|
||||
if segs[-1] == "__init__":
|
||||
segs = segs[:-1]
|
||||
return ".".join(segs)
|
||||
|
||||
def getModules(self):
|
||||
"""
|
||||
Return matching module names for files listed in C{self.files}.
|
||||
"""
|
||||
return [self._toModuleName(filename) for (filename, code) in self.files]
|
||||
|
||||
def cleanUpModules(self):
|
||||
modules = self.getModules()
|
||||
modules.sort()
|
||||
modules.reverse()
|
||||
for module in modules:
|
||||
try:
|
||||
del sys.modules[module]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def createFiles(self, files, parentDir="."):
|
||||
for filename, contents in self.files:
|
||||
filename = os.path.join(parentDir, filename)
|
||||
self._createDirectory(filename)
|
||||
with open(filename, "w") as fd:
|
||||
fd.write(contents)
|
||||
|
||||
def _createDirectory(self, filename):
|
||||
directory = os.path.dirname(filename)
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
def setUp(self, parentDir=None):
|
||||
invalidateImportCaches()
|
||||
if parentDir is None:
|
||||
parentDir = self.mktemp()
|
||||
self.parent = parentDir
|
||||
self.createFiles(self.files, parentDir)
|
||||
|
||||
def tearDown(self):
|
||||
self.cleanUpModules()
|
||||
|
||||
|
||||
class SysPathManglingTest(PackageTest):
|
||||
def setUp(self, parent=None):
|
||||
invalidateImportCaches()
|
||||
self.oldPath = sys.path[:]
|
||||
self.newPath = sys.path[:]
|
||||
if parent is None:
|
||||
parent = self.mktemp()
|
||||
PackageTest.setUp(self, parent)
|
||||
self.newPath.append(self.parent)
|
||||
self.mangleSysPath(self.newPath)
|
||||
|
||||
def tearDown(self):
|
||||
PackageTest.tearDown(self)
|
||||
self.mangleSysPath(self.oldPath)
|
||||
|
||||
def mangleSysPath(self, pathVar):
|
||||
sys.path[:] = pathVar
|
||||
@@ -0,0 +1,115 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Sample test cases defined using the standard library L{unittest.TestCase}
|
||||
class which are used as data by test cases which are actually part of the
|
||||
trial test suite to verify handling of handling of such cases.
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from sys import exc_info
|
||||
|
||||
from twisted.python.failure import Failure
|
||||
|
||||
|
||||
class PyUnitTest(unittest.TestCase):
|
||||
def test_pass(self):
|
||||
"""
|
||||
A passing test.
|
||||
"""
|
||||
|
||||
def test_error(self):
|
||||
"""
|
||||
A test which raises an exception to cause an error.
|
||||
"""
|
||||
raise Exception("pyunit error")
|
||||
|
||||
def test_fail(self):
|
||||
"""
|
||||
A test which uses L{unittest.TestCase.fail} to cause a failure.
|
||||
"""
|
||||
self.fail("pyunit failure")
|
||||
|
||||
@unittest.skip("pyunit skip")
|
||||
def test_skip(self):
|
||||
"""
|
||||
A test which uses the L{unittest.skip} decorator to cause a skip.
|
||||
"""
|
||||
|
||||
|
||||
class _NonStringId:
|
||||
"""
|
||||
A class that looks a little like a TestCase, but not enough so to
|
||||
actually be used as one. This helps L{BrokenRunInfrastructure} use some
|
||||
interfaces incorrectly to provoke certain failure conditions.
|
||||
"""
|
||||
|
||||
def id(self) -> object:
|
||||
return object()
|
||||
|
||||
|
||||
class BrokenRunInfrastructure(unittest.TestCase):
|
||||
"""
|
||||
A test suite that is broken at the level of integration between
|
||||
L{TestCase.run} and the results object.
|
||||
"""
|
||||
|
||||
def run(self, result):
|
||||
"""
|
||||
Override the normal C{run} behavior to pass the result object
|
||||
along to the test method. Each test method needs the result object so
|
||||
that it can implement its particular kind of brokenness.
|
||||
"""
|
||||
return getattr(self, self._testMethodName)(result)
|
||||
|
||||
def test_addSuccess(self, result):
|
||||
"""
|
||||
Violate the L{TestResult.addSuccess} interface.
|
||||
"""
|
||||
|
||||
result.addSuccess(_NonStringId())
|
||||
|
||||
def test_addError(self, result):
|
||||
"""
|
||||
Violate the L{TestResult.addError} interface.
|
||||
"""
|
||||
try:
|
||||
raise Exception("test_addError")
|
||||
except BaseException:
|
||||
err = exc_info()
|
||||
|
||||
result.addError(_NonStringId(), err)
|
||||
|
||||
def test_addFailure(self, result):
|
||||
"""
|
||||
Violate the L{TestResult.addFailure} interface.
|
||||
"""
|
||||
try:
|
||||
raise Exception("test_addFailure")
|
||||
except BaseException:
|
||||
err = exc_info()
|
||||
|
||||
result.addFailure(_NonStringId(), err)
|
||||
|
||||
def test_addSkip(self, result):
|
||||
"""
|
||||
Violate the L{TestResult.addSkip} interface.
|
||||
"""
|
||||
result.addSkip(_NonStringId(), "test_addSkip")
|
||||
|
||||
def test_addExpectedFailure(self, result):
|
||||
"""
|
||||
Violate the L{TestResult.addExpectedFailure} interface.
|
||||
"""
|
||||
try:
|
||||
raise Exception("test_addExpectedFailure")
|
||||
except BaseException:
|
||||
err = Failure()
|
||||
result.addExpectedFailure(_NonStringId(), err)
|
||||
|
||||
def test_addUnexpectedSuccess(self, result):
|
||||
"""
|
||||
Violate the L{TestResult.addUnexpectedSuccess} interface.
|
||||
"""
|
||||
result.addUnexpectedSuccess(_NonStringId())
|
||||
@@ -0,0 +1,95 @@
|
||||
"""This module is used by test_loader to test the Trial test loading
|
||||
functionality. Do NOT change the number of tests in this module. Do NOT change
|
||||
the names the tests in this module.
|
||||
"""
|
||||
|
||||
|
||||
import unittest as pyunit
|
||||
|
||||
from twisted.python.util import mergeFunctionMetadata
|
||||
from twisted.trial import unittest
|
||||
|
||||
|
||||
class FooTest(unittest.SynchronousTestCase):
|
||||
def test_foo(self) -> None:
|
||||
pass
|
||||
|
||||
def test_bar(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def badDecorator(fn):
|
||||
"""
|
||||
Decorate a function without preserving the name of the original function.
|
||||
Always return a function with the same name.
|
||||
"""
|
||||
|
||||
def nameCollision(*args, **kwargs):
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
return nameCollision
|
||||
|
||||
|
||||
def goodDecorator(fn):
|
||||
"""
|
||||
Decorate a function and preserve the original name.
|
||||
"""
|
||||
|
||||
def nameCollision(*args, **kwargs):
|
||||
return fn(*args, **kwargs)
|
||||
|
||||
return mergeFunctionMetadata(fn, nameCollision)
|
||||
|
||||
|
||||
class DecorationTest(unittest.SynchronousTestCase):
|
||||
def test_badDecorator(self) -> None:
|
||||
"""
|
||||
This test method is decorated in a way that gives it a confusing name
|
||||
that collides with another method.
|
||||
"""
|
||||
|
||||
test_badDecorator = badDecorator(test_badDecorator)
|
||||
|
||||
def test_goodDecorator(self) -> None:
|
||||
"""
|
||||
This test method is decorated in a way that preserves its name.
|
||||
"""
|
||||
|
||||
test_goodDecorator = goodDecorator(test_goodDecorator)
|
||||
|
||||
def renamedDecorator(self) -> None:
|
||||
"""
|
||||
This is secretly a test method and will be decorated and then renamed so
|
||||
test discovery can find it.
|
||||
"""
|
||||
|
||||
test_renamedDecorator = goodDecorator(renamedDecorator)
|
||||
|
||||
def nameCollision(self) -> None:
|
||||
"""
|
||||
This isn't a test, it's just here to collide with tests.
|
||||
"""
|
||||
|
||||
|
||||
class PyunitTest(pyunit.TestCase):
|
||||
def test_foo(self) -> None:
|
||||
pass
|
||||
|
||||
def test_bar(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class NotATest:
|
||||
def test_foo(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class AlphabetTest(unittest.SynchronousTestCase):
|
||||
def test_a(self) -> None:
|
||||
pass
|
||||
|
||||
def test_b(self) -> None:
|
||||
pass
|
||||
|
||||
def test_c(self) -> None:
|
||||
pass
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- test-case-name: twisted.trial.test.test_log,twisted.trial.test.test_runner -*-
|
||||
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
# fodder for test_script, which parses files for emacs local variable
|
||||
# declarations. This one is supposed to have:
|
||||
# test-case-name: twisted.trial.test.test_log,twisted.trial.test.test_runner
|
||||
# in the second line
|
||||
# The class declaration is irrelevant
|
||||
|
||||
|
||||
class Foo:
|
||||
pass
|
||||
@@ -0,0 +1,280 @@
|
||||
# -*- test-case-name: twisted.trial.test.test_tests -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Definitions of test cases with various interesting behaviors, to be used by
|
||||
L{twisted.trial.test.test_tests} and other test modules to exercise different
|
||||
features of trial's test runner.
|
||||
|
||||
See the L{twisted.trial.test.test_tests} module docstring for details about how
|
||||
this code is arranged.
|
||||
"""
|
||||
|
||||
|
||||
from twisted.trial.unittest import FailTest, SkipTest, SynchronousTestCase, TestCase
|
||||
|
||||
|
||||
class SkippingMixin:
|
||||
def test_skip1(self):
|
||||
raise SkipTest("skip1")
|
||||
|
||||
def test_skip2(self):
|
||||
raise RuntimeError("I should not get raised")
|
||||
|
||||
test_skip2.skip = "skip2" # type: ignore[attr-defined]
|
||||
|
||||
def test_skip3(self):
|
||||
self.fail("I should not fail")
|
||||
|
||||
test_skip3.skip = "skip3" # type: ignore[attr-defined]
|
||||
|
||||
|
||||
class SynchronousSkipping(SkippingMixin, SynchronousTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousSkipping(SkippingMixin, TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class SkippingSetUpMixin:
|
||||
def setUp(self):
|
||||
raise SkipTest("skipSetUp")
|
||||
|
||||
def test_1(self):
|
||||
pass
|
||||
|
||||
def test_2(self):
|
||||
pass
|
||||
|
||||
|
||||
class SynchronousSkippingSetUp(SkippingSetUpMixin, SynchronousTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousSkippingSetUp(SkippingSetUpMixin, TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class DeprecatedReasonlessSkipMixin:
|
||||
def test_1(self):
|
||||
raise SkipTest()
|
||||
|
||||
|
||||
class SynchronousDeprecatedReasonlessSkip(
|
||||
DeprecatedReasonlessSkipMixin, SynchronousTestCase
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousDeprecatedReasonlessSkip(DeprecatedReasonlessSkipMixin, TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class SkippedClassMixin:
|
||||
skip = "class"
|
||||
|
||||
def setUp(self):
|
||||
self.__class__._setUpRan = True
|
||||
|
||||
def test_skip1(self):
|
||||
raise SkipTest("skip1")
|
||||
|
||||
def test_skip2(self):
|
||||
raise RuntimeError("Ought to skip me")
|
||||
|
||||
test_skip2.skip = "skip2" # type: ignore
|
||||
|
||||
def test_skip3(self):
|
||||
pass
|
||||
|
||||
def test_skip4(self):
|
||||
raise RuntimeError("Skip me too")
|
||||
|
||||
|
||||
class SynchronousSkippedClass(SkippedClassMixin, SynchronousTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousSkippedClass(SkippedClassMixin, TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class TodoMixin:
|
||||
def test_todo1(self):
|
||||
self.fail("deliberate failure")
|
||||
|
||||
test_todo1.todo = "todo1" # type: ignore[attr-defined]
|
||||
|
||||
def test_todo2(self):
|
||||
raise RuntimeError("deliberate error")
|
||||
|
||||
test_todo2.todo = "todo2" # type: ignore[attr-defined]
|
||||
|
||||
def test_todo3(self):
|
||||
"""unexpected success"""
|
||||
|
||||
test_todo3.todo = "todo3" # type: ignore[attr-defined]
|
||||
|
||||
|
||||
class SynchronousTodo(TodoMixin, SynchronousTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousTodo(TodoMixin, TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class SetUpTodoMixin:
|
||||
def setUp(self):
|
||||
raise RuntimeError("deliberate error")
|
||||
|
||||
def test_todo1(self):
|
||||
pass
|
||||
|
||||
test_todo1.todo = "setUp todo1" # type: ignore[attr-defined]
|
||||
|
||||
|
||||
class SynchronousSetUpTodo(SetUpTodoMixin, SynchronousTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousSetUpTodo(SetUpTodoMixin, TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class TearDownTodoMixin:
|
||||
def tearDown(self):
|
||||
raise RuntimeError("deliberate error")
|
||||
|
||||
def test_todo1(self):
|
||||
pass
|
||||
|
||||
test_todo1.todo = "tearDown todo1" # type: ignore[attr-defined]
|
||||
|
||||
|
||||
class SynchronousTearDownTodo(TearDownTodoMixin, SynchronousTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousTearDownTodo(TearDownTodoMixin, TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class TodoClassMixin:
|
||||
todo = "class"
|
||||
|
||||
def test_todo1(self):
|
||||
pass
|
||||
|
||||
test_todo1.todo = "method" # type: ignore[attr-defined]
|
||||
|
||||
def test_todo2(self):
|
||||
pass
|
||||
|
||||
def test_todo3(self):
|
||||
self.fail("Deliberate Failure")
|
||||
|
||||
test_todo3.todo = "method" # type: ignore[attr-defined]
|
||||
|
||||
def test_todo4(self):
|
||||
self.fail("Deliberate Failure")
|
||||
|
||||
|
||||
class SynchronousTodoClass(TodoClassMixin, SynchronousTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousTodoClass(TodoClassMixin, TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class StrictTodoMixin:
|
||||
def test_todo1(self):
|
||||
raise RuntimeError("expected failure")
|
||||
|
||||
test_todo1.todo = (RuntimeError, "todo1") # type: ignore[attr-defined]
|
||||
|
||||
def test_todo2(self):
|
||||
raise RuntimeError("expected failure")
|
||||
|
||||
test_todo2.todo = ((RuntimeError, OSError), "todo2") # type: ignore[attr-defined]
|
||||
|
||||
def test_todo3(self):
|
||||
raise RuntimeError("we had no idea!")
|
||||
|
||||
test_todo3.todo = (OSError, "todo3") # type: ignore[attr-defined]
|
||||
|
||||
def test_todo4(self):
|
||||
raise RuntimeError("we had no idea!")
|
||||
|
||||
test_todo4.todo = ((OSError, SyntaxError), "todo4") # type: ignore[attr-defined]
|
||||
|
||||
def test_todo5(self):
|
||||
self.fail("deliberate failure")
|
||||
|
||||
test_todo5.todo = (FailTest, "todo5") # type: ignore[attr-defined]
|
||||
|
||||
def test_todo6(self):
|
||||
self.fail("deliberate failure")
|
||||
|
||||
test_todo6.todo = (RuntimeError, "todo6") # type: ignore[attr-defined]
|
||||
|
||||
def test_todo7(self):
|
||||
pass
|
||||
|
||||
test_todo7.todo = (RuntimeError, "todo7") # type: ignore[attr-defined]
|
||||
|
||||
|
||||
class SynchronousStrictTodo(StrictTodoMixin, SynchronousTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousStrictTodo(StrictTodoMixin, TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class AddCleanupMixin:
|
||||
def setUp(self):
|
||||
self.log = ["setUp"]
|
||||
|
||||
def brokenSetUp(self):
|
||||
self.log = ["setUp"]
|
||||
raise RuntimeError("Deliberate failure")
|
||||
|
||||
def skippingSetUp(self):
|
||||
self.log = ["setUp"]
|
||||
raise SkipTest("Don't do this")
|
||||
|
||||
def append(self, thing):
|
||||
self.log.append(thing)
|
||||
|
||||
def tearDown(self):
|
||||
self.log.append("tearDown")
|
||||
|
||||
def runTest(self):
|
||||
self.log.append("runTest")
|
||||
|
||||
|
||||
class SynchronousAddCleanup(AddCleanupMixin, SynchronousTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousAddCleanup(AddCleanupMixin, TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class ExpectedFailure(SynchronousTestCase):
|
||||
"""
|
||||
Hold a test that has an expected failure with an exception that has a
|
||||
large string representation.
|
||||
"""
|
||||
|
||||
def test_expectedFailureGreaterThan64k(self) -> None:
|
||||
"""
|
||||
Fail, but expectedly.
|
||||
"""
|
||||
raise RuntimeError("x" * (2**16 + 1))
|
||||
|
||||
test_expectedFailureGreaterThan64k.todo = "short todo string" # type: ignore[attr-defined]
|
||||
@@ -0,0 +1,118 @@
|
||||
# -*- test-case-name: twisted.trial.test.test_tests -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test cases used to make sure that warning suppression works at the module,
|
||||
method, and class levels.
|
||||
|
||||
See the L{twisted.trial.test.test_tests} module docstring for details about how
|
||||
this code is arranged.
|
||||
"""
|
||||
|
||||
|
||||
import warnings
|
||||
|
||||
from twisted.trial import unittest, util
|
||||
|
||||
METHOD_WARNING_MSG = "method warning message"
|
||||
CLASS_WARNING_MSG = "class warning message"
|
||||
MODULE_WARNING_MSG = "module warning message"
|
||||
|
||||
|
||||
class MethodWarning(Warning):
|
||||
pass
|
||||
|
||||
|
||||
class ClassWarning(Warning):
|
||||
pass
|
||||
|
||||
|
||||
class ModuleWarning(Warning):
|
||||
pass
|
||||
|
||||
|
||||
class EmitMixin:
|
||||
"""
|
||||
Mixin for emiting a variety of warnings.
|
||||
"""
|
||||
|
||||
def _emit(self):
|
||||
warnings.warn(METHOD_WARNING_MSG, MethodWarning)
|
||||
warnings.warn(CLASS_WARNING_MSG, ClassWarning)
|
||||
warnings.warn(MODULE_WARNING_MSG, ModuleWarning)
|
||||
|
||||
|
||||
class SuppressionMixin(EmitMixin):
|
||||
suppress = [util.suppress(message=CLASS_WARNING_MSG)]
|
||||
|
||||
def testSuppressMethod(self):
|
||||
self._emit()
|
||||
|
||||
testSuppressMethod.suppress = [util.suppress(message=METHOD_WARNING_MSG)] # type: ignore[attr-defined]
|
||||
|
||||
def testSuppressClass(self):
|
||||
self._emit()
|
||||
|
||||
def testOverrideSuppressClass(self):
|
||||
self._emit()
|
||||
|
||||
testOverrideSuppressClass.suppress = [] # type: ignore[attr-defined]
|
||||
|
||||
|
||||
class SetUpSuppressionMixin:
|
||||
def setUp(self):
|
||||
self._emit()
|
||||
|
||||
|
||||
class TearDownSuppressionMixin:
|
||||
def tearDown(self):
|
||||
self._emit()
|
||||
|
||||
|
||||
class TestSuppression2Mixin(EmitMixin):
|
||||
def testSuppressModule(self):
|
||||
self._emit()
|
||||
|
||||
|
||||
suppress = [util.suppress(message=MODULE_WARNING_MSG)]
|
||||
|
||||
|
||||
class SynchronousTestSuppression(SuppressionMixin, unittest.SynchronousTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class SynchronousTestSetUpSuppression(
|
||||
SetUpSuppressionMixin, SynchronousTestSuppression
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class SynchronousTestTearDownSuppression(
|
||||
TearDownSuppressionMixin, SynchronousTestSuppression
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class SynchronousTestSuppression2(TestSuppression2Mixin, unittest.SynchronousTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousTestSuppression(SuppressionMixin, unittest.TestCase):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousTestSetUpSuppression(
|
||||
SetUpSuppressionMixin, AsynchronousTestSuppression
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousTestTearDownSuppression(
|
||||
TearDownSuppressionMixin, AsynchronousTestSuppression
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
class AsynchronousTestSuppression2(TestSuppression2Mixin, unittest.TestCase):
|
||||
pass
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,84 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for async assertions provided by C{twisted.trial.unittest.TestCase}.
|
||||
"""
|
||||
|
||||
|
||||
import unittest as pyunit
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.python import failure
|
||||
from twisted.trial import unittest
|
||||
|
||||
|
||||
class AsynchronousAssertionsTests(unittest.TestCase):
|
||||
"""
|
||||
Tests for L{TestCase}'s asynchronous extensions to L{SynchronousTestCase}.
|
||||
That is, assertFailure.
|
||||
"""
|
||||
|
||||
def test_assertFailure(self):
|
||||
d = defer.maybeDeferred(lambda: 1 / 0)
|
||||
return self.assertFailure(d, ZeroDivisionError)
|
||||
|
||||
def test_assertFailure_wrongException(self):
|
||||
d = defer.maybeDeferred(lambda: 1 / 0)
|
||||
self.assertFailure(d, OverflowError)
|
||||
d.addCallbacks(
|
||||
lambda x: self.fail("Should have failed"),
|
||||
lambda x: x.trap(self.failureException),
|
||||
)
|
||||
return d
|
||||
|
||||
def test_assertFailure_noException(self):
|
||||
d = defer.succeed(None)
|
||||
self.assertFailure(d, ZeroDivisionError)
|
||||
d.addCallbacks(
|
||||
lambda x: self.fail("Should have failed"),
|
||||
lambda x: x.trap(self.failureException),
|
||||
)
|
||||
return d
|
||||
|
||||
def test_assertFailure_moreInfo(self):
|
||||
"""
|
||||
In the case of assertFailure failing, check that we get lots of
|
||||
information about the exception that was raised.
|
||||
"""
|
||||
try:
|
||||
1 / 0
|
||||
except ZeroDivisionError:
|
||||
f = failure.Failure()
|
||||
d = defer.fail(f)
|
||||
d = self.assertFailure(d, RuntimeError)
|
||||
d.addErrback(self._checkInfo, f)
|
||||
return d
|
||||
|
||||
def _checkInfo(self, assertionFailure, f):
|
||||
assert assertionFailure.check(self.failureException)
|
||||
output = assertionFailure.getErrorMessage()
|
||||
self.assertIn(f.getErrorMessage(), output)
|
||||
self.assertIn(f.getBriefTraceback(), output)
|
||||
|
||||
def test_assertFailure_masked(self):
|
||||
"""
|
||||
A single wrong assertFailure should fail the whole test.
|
||||
"""
|
||||
|
||||
class ExampleFailure(Exception):
|
||||
pass
|
||||
|
||||
class TC(unittest.TestCase):
|
||||
failureException = ExampleFailure
|
||||
|
||||
def test_assertFailure(self):
|
||||
d = defer.maybeDeferred(lambda: 1 / 0)
|
||||
self.assertFailure(d, OverflowError)
|
||||
self.assertFailure(d, ZeroDivisionError)
|
||||
return d
|
||||
|
||||
test = TC("test_assertFailure")
|
||||
result = pyunit.TestResult()
|
||||
test.run(result)
|
||||
self.assertEqual(1, len(result.failures))
|
||||
@@ -0,0 +1,258 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for returning Deferreds from a TestCase.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import unittest as pyunit
|
||||
|
||||
from twisted.internet import defer
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.trial import reporter, unittest, util
|
||||
from twisted.trial.test import detests
|
||||
|
||||
|
||||
class SetUpTests(unittest.TestCase):
|
||||
def _loadSuite(
|
||||
self, klass: type[pyunit.TestCase]
|
||||
) -> tuple[reporter.TestResult, pyunit.TestSuite]:
|
||||
loader = pyunit.TestLoader()
|
||||
r = reporter.TestResult()
|
||||
s = loader.loadTestsFromTestCase(klass)
|
||||
return r, s
|
||||
|
||||
def test_success(self) -> None:
|
||||
result, suite = self._loadSuite(detests.DeferredSetUpOK)
|
||||
suite(result)
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
|
||||
def test_fail(self) -> None:
|
||||
self.assertFalse(detests.DeferredSetUpFail.testCalled)
|
||||
result, suite = self._loadSuite(detests.DeferredSetUpFail)
|
||||
suite(result)
|
||||
self.assertFalse(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(len(result.failures), 0)
|
||||
self.assertEqual(len(result.errors), 1)
|
||||
self.assertFalse(detests.DeferredSetUpFail.testCalled)
|
||||
|
||||
def test_callbackFail(self) -> None:
|
||||
self.assertFalse(detests.DeferredSetUpCallbackFail.testCalled)
|
||||
result, suite = self._loadSuite(detests.DeferredSetUpCallbackFail)
|
||||
suite(result)
|
||||
self.assertFalse(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(len(result.failures), 0)
|
||||
self.assertEqual(len(result.errors), 1)
|
||||
self.assertFalse(detests.DeferredSetUpCallbackFail.testCalled)
|
||||
|
||||
def test_error(self) -> None:
|
||||
self.assertFalse(detests.DeferredSetUpError.testCalled)
|
||||
result, suite = self._loadSuite(detests.DeferredSetUpError)
|
||||
suite(result)
|
||||
self.assertFalse(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(len(result.failures), 0)
|
||||
self.assertEqual(len(result.errors), 1)
|
||||
self.assertFalse(detests.DeferredSetUpError.testCalled)
|
||||
|
||||
def test_skip(self) -> None:
|
||||
self.assertFalse(detests.DeferredSetUpSkip.testCalled)
|
||||
result, suite = self._loadSuite(detests.DeferredSetUpSkip)
|
||||
suite(result)
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(len(result.failures), 0)
|
||||
self.assertEqual(len(result.errors), 0)
|
||||
self.assertEqual(len(result.skips), 1)
|
||||
self.assertFalse(detests.DeferredSetUpSkip.testCalled)
|
||||
|
||||
|
||||
class NeverFireTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self._oldTimeout = util.DEFAULT_TIMEOUT_DURATION
|
||||
util.DEFAULT_TIMEOUT_DURATION = 0.1
|
||||
|
||||
def tearDown(self) -> None:
|
||||
util.DEFAULT_TIMEOUT_DURATION = self._oldTimeout
|
||||
|
||||
def _loadSuite(
|
||||
self, klass: type[pyunit.TestCase]
|
||||
) -> tuple[reporter.TestResult, pyunit.TestSuite]:
|
||||
loader = pyunit.TestLoader()
|
||||
r = reporter.TestResult()
|
||||
s = loader.loadTestsFromTestCase(klass)
|
||||
return r, s
|
||||
|
||||
def test_setUp(self) -> None:
|
||||
self.assertFalse(detests.DeferredSetUpNeverFire.testCalled)
|
||||
result, suite = self._loadSuite(detests.DeferredSetUpNeverFire)
|
||||
suite(result)
|
||||
self.assertFalse(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(len(result.failures), 0)
|
||||
self.assertEqual(len(result.errors), 1)
|
||||
self.assertFalse(detests.DeferredSetUpNeverFire.testCalled)
|
||||
assert isinstance(result.errors[0][1], Failure)
|
||||
self.assertTrue(result.errors[0][1].check(defer.TimeoutError))
|
||||
|
||||
|
||||
class TestTester(unittest.TestCase):
|
||||
def getTest(self, name: str) -> pyunit.TestCase:
|
||||
raise NotImplementedError("must override me")
|
||||
|
||||
def runTest(self, name: str) -> reporter.TestResult: # type: ignore[override]
|
||||
result = reporter.TestResult()
|
||||
self.getTest(name).run(result)
|
||||
return result
|
||||
|
||||
|
||||
class DeferredTests(TestTester):
|
||||
def getTest(self, name: str) -> detests.DeferredTests:
|
||||
return detests.DeferredTests(name)
|
||||
|
||||
def test_pass(self) -> None:
|
||||
result = self.runTest("test_pass")
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
|
||||
def test_passGenerated(self) -> None:
|
||||
result = self.runTest("test_passGenerated")
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertTrue(detests.DeferredTests.touched)
|
||||
|
||||
test_passGenerated.supress = [ # type: ignore[attr-defined]
|
||||
util.suppress(message="twisted.internet.defer.deferredGenerator is deprecated")
|
||||
]
|
||||
|
||||
def test_passInlineCallbacks(self) -> None:
|
||||
"""
|
||||
The body of a L{defer.inlineCallbacks} decorated test gets run.
|
||||
"""
|
||||
result = self.runTest("test_passInlineCallbacks")
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertTrue(detests.DeferredTests.touched)
|
||||
|
||||
def test_fail(self) -> None:
|
||||
result = self.runTest("test_fail")
|
||||
self.assertFalse(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(len(result.failures), 1)
|
||||
|
||||
def test_failureInCallback(self) -> None:
|
||||
result = self.runTest("test_failureInCallback")
|
||||
self.assertFalse(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(len(result.failures), 1)
|
||||
|
||||
def test_errorInCallback(self) -> None:
|
||||
result = self.runTest("test_errorInCallback")
|
||||
self.assertFalse(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(len(result.errors), 1)
|
||||
|
||||
def test_skip(self) -> None:
|
||||
result = self.runTest("test_skip")
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(len(result.skips), 1)
|
||||
self.assertFalse(detests.DeferredTests.touched)
|
||||
|
||||
def test_todo(self) -> None:
|
||||
result = self.runTest("test_expectedFailure")
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(len(result.errors), 0)
|
||||
self.assertEqual(len(result.failures), 0)
|
||||
self.assertEqual(len(result.expectedFailures), 1)
|
||||
|
||||
def test_thread(self) -> None:
|
||||
result = self.runTest("test_thread")
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertTrue(result.wasSuccessful(), result.errors)
|
||||
|
||||
|
||||
class TimeoutTests(TestTester):
|
||||
def getTest(self, name: str) -> detests.TimeoutTests:
|
||||
return detests.TimeoutTests(name)
|
||||
|
||||
def _wasTimeout(self, error: Failure) -> None:
|
||||
self.assertEqual(error.check(defer.TimeoutError), defer.TimeoutError)
|
||||
|
||||
def test_pass(self) -> None:
|
||||
result = self.runTest("test_pass")
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
|
||||
def test_passDefault(self) -> None:
|
||||
result = self.runTest("test_passDefault")
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
|
||||
def test_timeout(self) -> None:
|
||||
result = self.runTest("test_timeout")
|
||||
self.assertFalse(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(len(result.errors), 1)
|
||||
assert isinstance(result.errors[0][1], Failure)
|
||||
self._wasTimeout(result.errors[0][1])
|
||||
|
||||
def test_timeoutZero(self) -> None:
|
||||
result = self.runTest("test_timeoutZero")
|
||||
self.assertFalse(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(len(result.errors), 1)
|
||||
assert isinstance(result.errors[0][1], Failure)
|
||||
self._wasTimeout(result.errors[0][1])
|
||||
|
||||
def test_skip(self) -> None:
|
||||
result = self.runTest("test_skip")
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(len(result.skips), 1)
|
||||
|
||||
def test_todo(self) -> None:
|
||||
result = self.runTest("test_expectedFailure")
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(len(result.expectedFailures), 1)
|
||||
assert isinstance(result.expectedFailures[0][1], Failure)
|
||||
self._wasTimeout(result.expectedFailures[0][1])
|
||||
|
||||
def test_errorPropagation(self) -> None:
|
||||
result = self.runTest("test_errorPropagation")
|
||||
self.assertFalse(result.wasSuccessful())
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
assert detests.TimeoutTests.timedOut is not None
|
||||
self._wasTimeout(detests.TimeoutTests.timedOut)
|
||||
|
||||
def test_classTimeout(self) -> None:
|
||||
loader = pyunit.TestLoader()
|
||||
suite = loader.loadTestsFromTestCase(detests.TestClassTimeoutAttribute)
|
||||
result = reporter.TestResult()
|
||||
suite.run(result)
|
||||
self.assertEqual(len(result.errors), 1)
|
||||
assert isinstance(result.errors[0][1], Failure)
|
||||
self._wasTimeout(result.errors[0][1])
|
||||
|
||||
def test_callbackReturnsNonCallingDeferred(self) -> None:
|
||||
# hacky timeout
|
||||
# raises KeyboardInterrupt because Trial sucks
|
||||
from twisted.internet import reactor
|
||||
|
||||
call = reactor.callLater(2, reactor.crash) # type: ignore[attr-defined]
|
||||
result = self.runTest("test_calledButNeverCallback")
|
||||
if call.active():
|
||||
call.cancel()
|
||||
self.assertFalse(result.wasSuccessful())
|
||||
assert isinstance(result.errors[0][1], Failure)
|
||||
self._wasTimeout(result.errors[0][1])
|
||||
|
||||
|
||||
# The test loader erroneously attempts to run this:
|
||||
del TestTester
|
||||
@@ -0,0 +1,59 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test Twisted's doctest support.
|
||||
"""
|
||||
import unittest as pyunit
|
||||
|
||||
from twisted.trial import itrial, reporter, runner, unittest
|
||||
from twisted.trial.test import mockdoctest
|
||||
|
||||
|
||||
class RunnersTests(unittest.SynchronousTestCase):
|
||||
"""
|
||||
Tests for Twisted's doctest support.
|
||||
"""
|
||||
|
||||
def test_id(self) -> None:
|
||||
"""
|
||||
Check that the id() of the doctests' case object contains the FQPN of
|
||||
the actual tests.
|
||||
"""
|
||||
loader = runner.TestLoader()
|
||||
suite = loader.loadDoctests(mockdoctest)
|
||||
idPrefix = "twisted.trial.test.mockdoctest.Counter"
|
||||
for test in suite._tests:
|
||||
self.assertIn(idPrefix, itrial.ITestCase(test).id())
|
||||
|
||||
def test_basicTrialIntegration(self) -> None:
|
||||
"""
|
||||
L{loadDoctests} loads all of the doctests in the given module.
|
||||
"""
|
||||
loader = runner.TestLoader()
|
||||
suite = loader.loadDoctests(mockdoctest)
|
||||
self.assertEqual(7, suite.countTestCases())
|
||||
|
||||
def _testRun(self, suite: pyunit.TestSuite) -> None:
|
||||
"""
|
||||
Run C{suite} and check the result.
|
||||
"""
|
||||
result = reporter.TestResult()
|
||||
suite.run(result)
|
||||
self.assertEqual(5, result.successes)
|
||||
self.assertEqual(2, len(result.failures))
|
||||
|
||||
def test_expectedResults(self, count: int = 1) -> None:
|
||||
"""
|
||||
Trial can correctly run doctests with its xUnit test APIs.
|
||||
"""
|
||||
suite = runner.TestLoader().loadDoctests(mockdoctest)
|
||||
self._testRun(suite)
|
||||
|
||||
def test_repeatable(self) -> None:
|
||||
"""
|
||||
Doctests should be runnable repeatably.
|
||||
"""
|
||||
suite = runner.TestLoader().loadDoctests(mockdoctest)
|
||||
self._testRun(suite)
|
||||
self._testRun(suite)
|
||||
@@ -0,0 +1,120 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for interrupting tests with Control-C.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from io import StringIO
|
||||
|
||||
from twisted.trial import reporter, runner, unittest
|
||||
|
||||
|
||||
class TrialTest(unittest.SynchronousTestCase):
|
||||
def setUp(self) -> None:
|
||||
self.output = StringIO()
|
||||
self.reporter = reporter.TestResult()
|
||||
self.loader = runner.TestLoader()
|
||||
|
||||
|
||||
class InterruptInTestTests(TrialTest):
|
||||
test_03_doNothing_run: bool | None
|
||||
|
||||
class InterruptedTest(unittest.TestCase):
|
||||
def test_02_raiseInterrupt(self) -> None:
|
||||
raise KeyboardInterrupt
|
||||
|
||||
def test_01_doNothing(self) -> None:
|
||||
pass
|
||||
|
||||
def test_03_doNothing(self) -> None:
|
||||
InterruptInTestTests.test_03_doNothing_run = True
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.suite = self.loader.loadClass(InterruptInTestTests.InterruptedTest)
|
||||
InterruptInTestTests.test_03_doNothing_run = None
|
||||
|
||||
def test_setUpOK(self) -> None:
|
||||
self.assertEqual(3, self.suite.countTestCases())
|
||||
self.assertEqual(0, self.reporter.testsRun)
|
||||
self.assertFalse(self.reporter.shouldStop)
|
||||
|
||||
def test_interruptInTest(self) -> None:
|
||||
runner.TrialSuite([self.suite]).run(self.reporter)
|
||||
self.assertTrue(self.reporter.shouldStop)
|
||||
self.assertEqual(2, self.reporter.testsRun)
|
||||
self.assertFalse(
|
||||
InterruptInTestTests.test_03_doNothing_run, "test_03_doNothing ran."
|
||||
)
|
||||
|
||||
|
||||
class InterruptInSetUpTests(TrialTest):
|
||||
testsRun = 0
|
||||
test_02_run: bool
|
||||
|
||||
class InterruptedTest(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
if InterruptInSetUpTests.testsRun > 0:
|
||||
raise KeyboardInterrupt
|
||||
|
||||
def test_01(self) -> None:
|
||||
InterruptInSetUpTests.testsRun += 1
|
||||
|
||||
def test_02(self) -> None:
|
||||
InterruptInSetUpTests.testsRun += 1
|
||||
InterruptInSetUpTests.test_02_run = True
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.suite = self.loader.loadClass(InterruptInSetUpTests.InterruptedTest)
|
||||
InterruptInSetUpTests.test_02_run = False
|
||||
InterruptInSetUpTests.testsRun = 0
|
||||
|
||||
def test_setUpOK(self) -> None:
|
||||
self.assertEqual(0, InterruptInSetUpTests.testsRun)
|
||||
self.assertEqual(2, self.suite.countTestCases())
|
||||
self.assertEqual(0, self.reporter.testsRun)
|
||||
self.assertFalse(self.reporter.shouldStop)
|
||||
|
||||
def test_interruptInSetUp(self) -> None:
|
||||
runner.TrialSuite([self.suite]).run(self.reporter)
|
||||
self.assertTrue(self.reporter.shouldStop)
|
||||
self.assertEqual(2, self.reporter.testsRun)
|
||||
self.assertFalse(InterruptInSetUpTests.test_02_run, "test_02 ran")
|
||||
|
||||
|
||||
class InterruptInTearDownTests(TrialTest):
|
||||
testsRun = 0
|
||||
test_02_run: bool
|
||||
|
||||
class InterruptedTest(unittest.TestCase):
|
||||
def tearDown(self) -> None:
|
||||
if InterruptInTearDownTests.testsRun > 0:
|
||||
raise KeyboardInterrupt
|
||||
|
||||
def test_01(self) -> None:
|
||||
InterruptInTearDownTests.testsRun += 1
|
||||
|
||||
def test_02(self) -> None:
|
||||
InterruptInTearDownTests.testsRun += 1
|
||||
InterruptInTearDownTests.test_02_run = True
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.suite = self.loader.loadClass(InterruptInTearDownTests.InterruptedTest)
|
||||
InterruptInTearDownTests.testsRun = 0
|
||||
InterruptInTearDownTests.test_02_run = False
|
||||
|
||||
def test_setUpOK(self) -> None:
|
||||
self.assertEqual(0, InterruptInTearDownTests.testsRun)
|
||||
self.assertEqual(2, self.suite.countTestCases())
|
||||
self.assertEqual(0, self.reporter.testsRun)
|
||||
self.assertFalse(self.reporter.shouldStop)
|
||||
|
||||
def test_interruptInTearDown(self) -> None:
|
||||
runner.TrialSuite([self.suite]).run(self.reporter)
|
||||
self.assertEqual(1, self.reporter.testsRun)
|
||||
self.assertTrue(self.reporter.shouldStop)
|
||||
self.assertFalse(InterruptInTearDownTests.test_02_run, "test_02 ran")
|
||||
@@ -0,0 +1,649 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for loading tests by name.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest as pyunit
|
||||
from hashlib import md5
|
||||
from operator import attrgetter
|
||||
from types import ModuleType
|
||||
from typing import TYPE_CHECKING, Callable, Generator
|
||||
|
||||
from hamcrest import assert_that, equal_to, has_properties
|
||||
from hamcrest.core.matcher import Matcher
|
||||
|
||||
from twisted.python import filepath, util
|
||||
from twisted.python.modules import PythonAttribute, PythonModule, getModule
|
||||
from twisted.python.reflect import ModuleNotFound
|
||||
from twisted.trial import reporter, runner, unittest
|
||||
from twisted.trial._asyncrunner import _iterateTests
|
||||
from twisted.trial.itrial import ITestCase
|
||||
from twisted.trial.test import packages
|
||||
from .matchers import after
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from _typeshed import SupportsRichComparison
|
||||
|
||||
|
||||
def testNames(tests: pyunit.TestCase | pyunit.TestSuite) -> list[str]:
|
||||
"""
|
||||
Return the id of each test within the given test suite or case.
|
||||
"""
|
||||
names = []
|
||||
for test in _iterateTests(tests):
|
||||
names.append(test.id())
|
||||
return names
|
||||
|
||||
|
||||
class FinderPy3Tests(packages.SysPathManglingTest):
|
||||
def setUp(self) -> None: # type: ignore[override]
|
||||
super().setUp()
|
||||
self.loader = runner.TestLoader()
|
||||
|
||||
def test_findNonModule(self) -> None:
|
||||
"""
|
||||
findByName, if given something findable up until the last entry, will
|
||||
raise AttributeError (as it cannot tell if 'nonexistent' here is
|
||||
supposed to be a module or a class).
|
||||
"""
|
||||
self.assertRaises(
|
||||
AttributeError, self.loader.findByName, "twisted.trial.test.nonexistent"
|
||||
)
|
||||
|
||||
def test_findNonPackage(self) -> None:
|
||||
self.assertRaises(ModuleNotFound, self.loader.findByName, "nonextant")
|
||||
|
||||
def test_findNonFile(self) -> None:
|
||||
"""
|
||||
findByName, given a file path that doesn't exist, will raise a
|
||||
ValueError saying that it is not a Python file.
|
||||
"""
|
||||
path = util.sibpath(__file__, "nonexistent.py")
|
||||
self.assertRaises(ValueError, self.loader.findByName, path)
|
||||
|
||||
def test_findFileWithImportError(self) -> None:
|
||||
"""
|
||||
findByName will re-raise ImportErrors inside modules that it has found
|
||||
and imported.
|
||||
"""
|
||||
self.assertRaises(
|
||||
ImportError, self.loader.findByName, "unimportablepackage.test_module"
|
||||
)
|
||||
|
||||
|
||||
def looselyResembles(module: ModuleType) -> Matcher[ModuleType]:
|
||||
"""
|
||||
Match a module with a L{ModuleSpec} like that of the given module.
|
||||
|
||||
@return: A matcher for a module spec that has the same name and origin as
|
||||
the given module spec, though the origin may be structurally inequal
|
||||
as long as it is semantically equal.
|
||||
"""
|
||||
expected = module.__spec__
|
||||
# Technically possible but not expected in any of the tests written so
|
||||
# far.
|
||||
assert expected is not None
|
||||
match_spec = has_properties(
|
||||
{
|
||||
"name": equal_to(expected.name),
|
||||
"origin": after(
|
||||
filepath.FilePath,
|
||||
equal_to(filepath.FilePath(expected.origin)),
|
||||
),
|
||||
}
|
||||
)
|
||||
return after(attrgetter("__spec__"), match_spec)
|
||||
|
||||
|
||||
class FileTests(packages.SysPathManglingTest):
|
||||
"""
|
||||
Tests for L{runner.filenameToModule}.
|
||||
"""
|
||||
|
||||
def test_notFile(self) -> None:
|
||||
"""
|
||||
L{runner.filenameToModule} raises a C{ValueError} when a non-existing
|
||||
file is passed.
|
||||
"""
|
||||
err = self.assertRaises(ValueError, runner.filenameToModule, "it")
|
||||
self.assertEqual(str(err), "'it' doesn't exist")
|
||||
|
||||
def test_moduleInPath(self) -> None:
|
||||
"""
|
||||
If the file in question is a module on the Python path, then it should
|
||||
properly import and return that module.
|
||||
"""
|
||||
sample1 = runner.filenameToModule(util.sibpath(__file__, "sample.py"))
|
||||
from twisted.trial.test import sample as sample2
|
||||
|
||||
self.assertEqual(sample2, sample1)
|
||||
|
||||
def test_moduleNotInPath(self) -> None:
|
||||
"""
|
||||
If passed the path to a file containing the implementation of a
|
||||
module within a package which is not on the import path,
|
||||
L{runner.filenameToModule} returns a module object loosely
|
||||
resembling the module defined by that file anyway.
|
||||
"""
|
||||
|
||||
self.mangleSysPath(self.oldPath)
|
||||
sample1 = runner.filenameToModule(
|
||||
os.path.join(self.parent, "goodpackage", "test_sample.py")
|
||||
)
|
||||
self.assertEqual(sample1.__name__, "goodpackage.test_sample")
|
||||
|
||||
self.cleanUpModules()
|
||||
self.mangleSysPath(self.newPath)
|
||||
from goodpackage import test_sample as sample2 # type: ignore[import-not-found]
|
||||
|
||||
self.assertIsNot(sample1, sample2)
|
||||
assert_that(sample1, looselyResembles(sample2))
|
||||
|
||||
def test_packageInPath(self) -> None:
|
||||
"""
|
||||
If the file in question is a package on the Python path, then it should
|
||||
properly import and return that package.
|
||||
"""
|
||||
package1 = runner.filenameToModule(os.path.join(self.parent, "goodpackage"))
|
||||
|
||||
self.assertIs(package1, sys.modules["goodpackage"])
|
||||
|
||||
def test_packageNotInPath(self) -> None:
|
||||
"""
|
||||
If passed the path to a directory which represents a package which
|
||||
is not on the import path, L{runner.filenameToModule} returns a
|
||||
module object loosely resembling the package defined by that
|
||||
directory anyway.
|
||||
"""
|
||||
self.mangleSysPath(self.oldPath)
|
||||
package1 = runner.filenameToModule(os.path.join(self.parent, "goodpackage"))
|
||||
self.assertEqual(package1.__name__, "goodpackage")
|
||||
|
||||
self.cleanUpModules()
|
||||
self.mangleSysPath(self.newPath)
|
||||
import goodpackage
|
||||
|
||||
self.assertIsNot(package1, goodpackage)
|
||||
assert_that(package1, looselyResembles(goodpackage))
|
||||
|
||||
def test_directoryNotPackage(self) -> None:
|
||||
"""
|
||||
L{runner.filenameToModule} raises a C{ValueError} when the name of an
|
||||
empty directory is passed that isn't considered a valid Python package
|
||||
because it doesn't contain a C{__init__.py} file.
|
||||
"""
|
||||
emptyDir = filepath.FilePath(self.parent).child("emptyDirectory")
|
||||
emptyDir.createDirectory()
|
||||
|
||||
err = self.assertRaises(ValueError, runner.filenameToModule, emptyDir.path)
|
||||
self.assertEqual(str(err), f"{emptyDir.path!r} is not a package directory")
|
||||
|
||||
def test_filenameNotPython(self) -> None:
|
||||
"""
|
||||
L{runner.filenameToModule} raises a C{SyntaxError} when a non-Python
|
||||
file is passed.
|
||||
"""
|
||||
filename = filepath.FilePath(self.parent).child("notpython")
|
||||
filename.setContent(b"This isn't python")
|
||||
self.assertRaises(SyntaxError, runner.filenameToModule, filename.path)
|
||||
|
||||
def test_filenameMatchesPackage(self) -> None:
|
||||
"""
|
||||
The C{__file__} attribute of the module should match the package name.
|
||||
"""
|
||||
filename = filepath.FilePath(self.parent).child("goodpackage.py")
|
||||
filename.setContent(packages.testModule.encode("utf8"))
|
||||
|
||||
try:
|
||||
module = runner.filenameToModule(filename.path)
|
||||
self.assertEqual(filename.path, module.__file__)
|
||||
finally:
|
||||
filename.remove()
|
||||
|
||||
def test_directory(self) -> None:
|
||||
"""
|
||||
Test loader against a filesystem directory containing an empty
|
||||
C{__init__.py} file. It should handle 'path' and 'path/' the same way.
|
||||
"""
|
||||
goodDir = filepath.FilePath(self.parent).child("goodDirectory")
|
||||
goodDir.createDirectory()
|
||||
goodDir.child("__init__.py").setContent(b"")
|
||||
|
||||
try:
|
||||
module = runner.filenameToModule(goodDir.path)
|
||||
self.assertTrue(module.__name__.endswith("goodDirectory"))
|
||||
module = runner.filenameToModule(goodDir.path + os.path.sep)
|
||||
self.assertTrue(module.__name__.endswith("goodDirectory"))
|
||||
finally:
|
||||
goodDir.remove()
|
||||
|
||||
|
||||
class LoaderTests(packages.SysPathManglingTest):
|
||||
"""
|
||||
Tests for L{trial.TestLoader}.
|
||||
"""
|
||||
|
||||
def setUp(self) -> None: # type: ignore[override]
|
||||
self.loader = runner.TestLoader()
|
||||
packages.SysPathManglingTest.setUp(self)
|
||||
|
||||
def test_sortCases(self) -> None:
|
||||
from twisted.trial.test import sample
|
||||
|
||||
suite = self.loader.loadClass(sample.AlphabetTest)
|
||||
self.assertEqual(
|
||||
["test_a", "test_b", "test_c"],
|
||||
[test._testMethodName for test in suite._tests],
|
||||
)
|
||||
newOrder = ["test_b", "test_c", "test_a"]
|
||||
sortDict = dict(zip(newOrder, range(3)))
|
||||
self.loader.sorter = lambda x: sortDict.get(x.shortDescription(), -1) # type: ignore[arg-type, union-attr, call-arg]
|
||||
suite = self.loader.loadClass(sample.AlphabetTest)
|
||||
self.assertEqual(newOrder, [test._testMethodName for test in suite._tests])
|
||||
|
||||
def test_loadFailure(self) -> None:
|
||||
"""
|
||||
Loading a test that fails and getting the result of it ends up with one
|
||||
test ran and one failure.
|
||||
"""
|
||||
suite = self.loader.loadByName(
|
||||
"twisted.trial.test.erroneous.TestRegularFail.test_fail"
|
||||
)
|
||||
result = reporter.TestResult()
|
||||
suite.run(result)
|
||||
self.assertEqual(result.testsRun, 1)
|
||||
self.assertEqual(len(result.failures), 1)
|
||||
|
||||
def test_loadBadDecorator(self) -> None:
|
||||
"""
|
||||
A decorated test method for which the decorator has failed to set the
|
||||
method's __name__ correctly is loaded and its name in the class scope
|
||||
discovered.
|
||||
"""
|
||||
from twisted.trial.test import sample
|
||||
|
||||
suite = self.loader.loadAnything(
|
||||
sample.DecorationTest.test_badDecorator,
|
||||
parent=sample.DecorationTest,
|
||||
qualName=["sample", "DecorationTest", "test_badDecorator"],
|
||||
)
|
||||
self.assertEqual(1, suite.countTestCases())
|
||||
self.assertEqual("test_badDecorator", suite._testMethodName)
|
||||
|
||||
def test_loadGoodDecorator(self) -> None:
|
||||
"""
|
||||
A decorated test method for which the decorator has set the method's
|
||||
__name__ correctly is loaded and the only name by which it goes is used.
|
||||
"""
|
||||
from twisted.trial.test import sample
|
||||
|
||||
suite = self.loader.loadAnything(
|
||||
sample.DecorationTest.test_goodDecorator,
|
||||
parent=sample.DecorationTest,
|
||||
qualName=["sample", "DecorationTest", "test_goodDecorator"],
|
||||
)
|
||||
self.assertEqual(1, suite.countTestCases())
|
||||
self.assertEqual("test_goodDecorator", suite._testMethodName)
|
||||
|
||||
def test_loadRenamedDecorator(self) -> None:
|
||||
"""
|
||||
Load a decorated method which has been copied to a new name inside the
|
||||
class. Thus its __name__ and its key in the class's __dict__ no
|
||||
longer match.
|
||||
"""
|
||||
from twisted.trial.test import sample
|
||||
|
||||
suite = self.loader.loadAnything(
|
||||
sample.DecorationTest.test_renamedDecorator,
|
||||
parent=sample.DecorationTest,
|
||||
qualName=["sample", "DecorationTest", "test_renamedDecorator"],
|
||||
)
|
||||
self.assertEqual(1, suite.countTestCases())
|
||||
self.assertEqual("test_renamedDecorator", suite._testMethodName)
|
||||
|
||||
def test_loadClass(self) -> None:
|
||||
from twisted.trial.test import sample
|
||||
|
||||
suite = self.loader.loadClass(sample.FooTest)
|
||||
self.assertEqual(2, suite.countTestCases())
|
||||
self.assertEqual(
|
||||
["test_bar", "test_foo"], [test._testMethodName for test in suite._tests]
|
||||
)
|
||||
|
||||
def test_loadNonClass(self) -> None:
|
||||
from twisted.trial.test import sample
|
||||
|
||||
self.assertRaises(TypeError, self.loader.loadClass, sample)
|
||||
self.assertRaises(TypeError, self.loader.loadClass, sample.FooTest.test_foo)
|
||||
self.assertRaises(TypeError, self.loader.loadClass, "string")
|
||||
self.assertRaises(TypeError, self.loader.loadClass, ("foo", "bar"))
|
||||
|
||||
def test_loadNonTestCase(self) -> None:
|
||||
from twisted.trial.test import sample
|
||||
|
||||
self.assertRaises(ValueError, self.loader.loadClass, sample.NotATest)
|
||||
|
||||
def test_loadModule(self) -> None:
|
||||
from twisted.trial.test import sample
|
||||
|
||||
suite = self.loader.loadModule(sample)
|
||||
self.assertEqual(10, suite.countTestCases())
|
||||
|
||||
def test_loadNonModule(self) -> None:
|
||||
from twisted.trial.test import sample
|
||||
|
||||
self.assertRaises(TypeError, self.loader.loadModule, sample.FooTest)
|
||||
self.assertRaises(TypeError, self.loader.loadModule, sample.FooTest.test_foo)
|
||||
self.assertRaises(TypeError, self.loader.loadModule, "string")
|
||||
self.assertRaises(TypeError, self.loader.loadModule, ("foo", "bar"))
|
||||
|
||||
def test_loadPackage(self) -> None:
|
||||
import goodpackage
|
||||
|
||||
suite = self.loader.loadPackage(goodpackage)
|
||||
self.assertEqual(7, suite.countTestCases())
|
||||
|
||||
def test_loadNonPackage(self) -> None:
|
||||
from twisted.trial.test import sample
|
||||
|
||||
self.assertRaises(TypeError, self.loader.loadPackage, sample.FooTest)
|
||||
self.assertRaises(TypeError, self.loader.loadPackage, sample.FooTest.test_foo)
|
||||
self.assertRaises(TypeError, self.loader.loadPackage, "string")
|
||||
self.assertRaises(TypeError, self.loader.loadPackage, ("foo", "bar"))
|
||||
|
||||
def test_loadModuleAsPackage(self) -> None:
|
||||
from twisted.trial.test import sample
|
||||
|
||||
## XXX -- should this instead raise a ValueError? -- jml
|
||||
self.assertRaises(TypeError, self.loader.loadPackage, sample)
|
||||
|
||||
def test_loadPackageRecursive(self) -> None:
|
||||
import goodpackage
|
||||
|
||||
suite = self.loader.loadPackage(goodpackage, recurse=True)
|
||||
self.assertEqual(14, suite.countTestCases())
|
||||
|
||||
def test_loadAnythingOnModule(self) -> None:
|
||||
from twisted.trial.test import sample
|
||||
|
||||
suite = self.loader.loadAnything(sample)
|
||||
self.assertEqual(
|
||||
sample.__name__, suite._tests[0]._tests[0].__class__.__module__
|
||||
)
|
||||
|
||||
def test_loadAnythingOnClass(self) -> None:
|
||||
from twisted.trial.test import sample
|
||||
|
||||
suite = self.loader.loadAnything(sample.FooTest)
|
||||
self.assertEqual(2, suite.countTestCases())
|
||||
|
||||
def test_loadAnythingOnPackage(self) -> None:
|
||||
import goodpackage
|
||||
|
||||
suite = self.loader.loadAnything(goodpackage)
|
||||
self.assertTrue(isinstance(suite, self.loader.suiteFactory))
|
||||
self.assertEqual(7, suite.countTestCases())
|
||||
|
||||
def test_loadAnythingOnPackageRecursive(self) -> None:
|
||||
import goodpackage
|
||||
|
||||
suite = self.loader.loadAnything(goodpackage, recurse=True)
|
||||
self.assertTrue(isinstance(suite, self.loader.suiteFactory))
|
||||
self.assertEqual(14, suite.countTestCases())
|
||||
|
||||
def test_loadAnythingOnString(self) -> None:
|
||||
# the important thing about this test is not the string-iness
|
||||
# but the non-handledness.
|
||||
self.assertRaises(TypeError, self.loader.loadAnything, "goodpackage")
|
||||
|
||||
def test_importErrors(self) -> None:
|
||||
import package # type: ignore[import-not-found]
|
||||
|
||||
suite = self.loader.loadPackage(package, recurse=True)
|
||||
result = reporter.Reporter()
|
||||
suite.run(result)
|
||||
self.assertEqual(False, result.wasSuccessful())
|
||||
self.assertEqual(2, len(result.errors))
|
||||
errors = [test.id() for test, error in result.errors]
|
||||
errors.sort()
|
||||
self.assertEqual(
|
||||
errors, ["package.test_bad_module", "package.test_import_module"]
|
||||
)
|
||||
|
||||
def test_differentInstances(self) -> None:
|
||||
"""
|
||||
L{TestLoader.loadClass} returns a suite with each test method
|
||||
represented by a different instances of the L{TestCase} they are
|
||||
defined on.
|
||||
"""
|
||||
|
||||
class DistinctInstances(pyunit.TestCase):
|
||||
def test_1(self) -> None:
|
||||
self.first = "test1Run"
|
||||
|
||||
def test_2(self) -> None:
|
||||
self.assertFalse(hasattr(self, "first"))
|
||||
|
||||
suite = self.loader.loadClass(DistinctInstances)
|
||||
result = reporter.Reporter()
|
||||
suite.run(result)
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
|
||||
def test_loadModuleWith_test_suite(self) -> None:
|
||||
"""
|
||||
Check that C{test_suite} is used when present and other L{TestCase}s are
|
||||
not included.
|
||||
"""
|
||||
from twisted.trial.test import mockcustomsuite
|
||||
|
||||
suite = self.loader.loadModule(mockcustomsuite)
|
||||
self.assertEqual(0, suite.countTestCases())
|
||||
self.assertEqual("MyCustomSuite", getattr(suite, "name", None))
|
||||
|
||||
def test_loadModuleWith_testSuite(self) -> None:
|
||||
"""
|
||||
Check that C{testSuite} is used when present and other L{TestCase}s are
|
||||
not included.
|
||||
"""
|
||||
from twisted.trial.test import mockcustomsuite2
|
||||
|
||||
suite = self.loader.loadModule(mockcustomsuite2)
|
||||
self.assertEqual(0, suite.countTestCases())
|
||||
self.assertEqual("MyCustomSuite", getattr(suite, "name", None))
|
||||
|
||||
def test_loadModuleWithBothCustom(self) -> None:
|
||||
"""
|
||||
Check that if C{testSuite} and C{test_suite} are both present in a
|
||||
module then C{testSuite} gets priority.
|
||||
"""
|
||||
from twisted.trial.test import mockcustomsuite3
|
||||
|
||||
suite = self.loader.loadModule(mockcustomsuite3)
|
||||
self.assertEqual("testSuite", getattr(suite, "name", None))
|
||||
|
||||
def test_customLoadRaisesAttributeError(self) -> None:
|
||||
"""
|
||||
Make sure that any C{AttributeError}s raised by C{testSuite} are not
|
||||
swallowed by L{TestLoader}.
|
||||
"""
|
||||
|
||||
def testSuite() -> None:
|
||||
raise AttributeError("should be reraised")
|
||||
|
||||
from twisted.trial.test import mockcustomsuite2
|
||||
|
||||
mockcustomsuite2.testSuite, original = (testSuite, mockcustomsuite2.testSuite)
|
||||
try:
|
||||
self.assertRaises(AttributeError, self.loader.loadModule, mockcustomsuite2)
|
||||
finally:
|
||||
mockcustomsuite2.testSuite = original
|
||||
|
||||
# XXX - duplicated and modified from test_script
|
||||
def assertSuitesEqual(
|
||||
self,
|
||||
test1: pyunit.TestCase | pyunit.TestSuite,
|
||||
test2: pyunit.TestCase | pyunit.TestSuite,
|
||||
) -> None:
|
||||
names1 = testNames(test1)
|
||||
names2 = testNames(test2)
|
||||
names1.sort()
|
||||
names2.sort()
|
||||
self.assertEqual(names1, names2)
|
||||
|
||||
def test_loadByNamesDuplicate(self) -> None:
|
||||
"""
|
||||
Check that loadByNames ignores duplicate names
|
||||
"""
|
||||
module = "twisted.trial.test.test_log"
|
||||
suite1 = self.loader.loadByNames([module, module], True)
|
||||
suite2 = self.loader.loadByName(module, True)
|
||||
self.assertSuitesEqual(suite1, suite2)
|
||||
|
||||
def test_loadByNamesPreservesOrder(self) -> None:
|
||||
"""
|
||||
L{TestLoader.loadByNames} preserves the order of tests provided to it.
|
||||
"""
|
||||
modules = [
|
||||
"inheritancepackage.test_x.A.test_foo",
|
||||
"twisted.trial.test.sample",
|
||||
"goodpackage",
|
||||
"twisted.trial.test.test_log",
|
||||
"twisted.trial.test.sample.FooTest",
|
||||
"package.test_module",
|
||||
]
|
||||
suite1 = self.loader.loadByNames(modules)
|
||||
suite2 = runner.TestSuite(map(self.loader.loadByName, modules))
|
||||
self.assertEqual(testNames(suite1), testNames(suite2))
|
||||
|
||||
def test_loadDifferentNames(self) -> None:
|
||||
"""
|
||||
Check that loadByNames loads all the names that it is given
|
||||
"""
|
||||
modules = ["goodpackage", "package.test_module"]
|
||||
suite1 = self.loader.loadByNames(modules)
|
||||
suite2 = runner.TestSuite(map(self.loader.loadByName, modules))
|
||||
self.assertSuitesEqual(suite1, suite2)
|
||||
|
||||
def test_loadInheritedMethods(self) -> None:
|
||||
"""
|
||||
Check that test methods names which are inherited from are all
|
||||
loaded rather than just one.
|
||||
"""
|
||||
methods = [
|
||||
"inheritancepackage.test_x.A.test_foo",
|
||||
"inheritancepackage.test_x.B.test_foo",
|
||||
]
|
||||
suite1 = self.loader.loadByNames(methods)
|
||||
suite2 = runner.TestSuite(map(self.loader.loadByName, methods))
|
||||
self.assertSuitesEqual(suite1, suite2)
|
||||
|
||||
|
||||
class ZipLoadingTests(LoaderTests):
|
||||
def setUp(self) -> None: # type: ignore[override]
|
||||
from twisted.python.test.test_zippath import zipit
|
||||
|
||||
LoaderTests.setUp(self)
|
||||
zipit(self.parent, self.parent + ".zip")
|
||||
self.parent += ".zip"
|
||||
self.mangleSysPath(self.oldPath + [self.parent])
|
||||
|
||||
|
||||
class PackageOrderingTests(packages.SysPathManglingTest):
|
||||
def setUp(self) -> None: # type: ignore[override]
|
||||
self.loader = runner.TestLoader()
|
||||
self.topDir = self.mktemp()
|
||||
parent = os.path.join(self.topDir, "uberpackage")
|
||||
os.makedirs(parent)
|
||||
open(os.path.join(parent, "__init__.py"), "wb").close()
|
||||
packages.SysPathManglingTest.setUp(self, parent)
|
||||
self.mangleSysPath(self.oldPath + [self.topDir])
|
||||
|
||||
def _trialSortAlgorithm(
|
||||
self, sorter: Callable[[PythonModule | PythonAttribute], SupportsRichComparison]
|
||||
) -> Generator[PythonModule | PythonAttribute, None, None]:
|
||||
"""
|
||||
Right now, halfway by accident, trial sorts like this:
|
||||
|
||||
1. all modules are grouped together in one list and sorted.
|
||||
|
||||
2. within each module, the classes are grouped together in one list
|
||||
and sorted.
|
||||
|
||||
3. finally within each class, each test method is grouped together
|
||||
in a list and sorted.
|
||||
|
||||
This attempts to return a sorted list of testable thingies following
|
||||
those rules, so that we can compare the behavior of loadPackage.
|
||||
|
||||
The things that show as 'cases' are errors from modules which failed to
|
||||
import, and test methods. Let's gather all those together.
|
||||
"""
|
||||
pkg = getModule("uberpackage")
|
||||
testModules = []
|
||||
for testModule in pkg.walkModules():
|
||||
if testModule.name.split(".")[-1].startswith("test_"):
|
||||
testModules.append(testModule)
|
||||
sortedModules = sorted(testModules, key=sorter) # ONE
|
||||
for modinfo in sortedModules:
|
||||
# Now let's find all the classes.
|
||||
module = modinfo.load(None)
|
||||
if module is None:
|
||||
yield modinfo
|
||||
else:
|
||||
testClasses = []
|
||||
for attrib in modinfo.iterAttributes():
|
||||
if runner.isTestCase(attrib.load()):
|
||||
testClasses.append(attrib)
|
||||
sortedClasses = sorted(testClasses, key=sorter) # TWO
|
||||
for clsinfo in sortedClasses:
|
||||
testMethods = []
|
||||
for attr in clsinfo.iterAttributes():
|
||||
if attr.name.split(".")[-1].startswith("test"):
|
||||
testMethods.append(attr)
|
||||
sortedMethods = sorted(testMethods, key=sorter) # THREE
|
||||
yield from sortedMethods
|
||||
|
||||
def loadSortedPackages(
|
||||
self, sorter: Callable[[runner._Loadable], SupportsRichComparison] = runner.name
|
||||
) -> None:
|
||||
"""
|
||||
Verify that packages are loaded in the correct order.
|
||||
"""
|
||||
import uberpackage # type: ignore[import-not-found]
|
||||
|
||||
self.loader.sorter = sorter
|
||||
suite = self.loader.loadPackage(uberpackage, recurse=True)
|
||||
# XXX: Work around strange, unexplained Zope crap.
|
||||
# jml, 2007-11-15.
|
||||
suite = unittest.decorate(suite, ITestCase)
|
||||
resultingTests = list(_iterateTests(suite))
|
||||
manifest = list(self._trialSortAlgorithm(sorter))
|
||||
for number, (manifestTest, actualTest) in enumerate(
|
||||
zip(manifest, resultingTests)
|
||||
):
|
||||
self.assertEqual(
|
||||
manifestTest.name,
|
||||
actualTest.id(),
|
||||
"#%d: %s != %s" % (number, manifestTest.name, actualTest.id()),
|
||||
)
|
||||
self.assertEqual(len(manifest), len(resultingTests))
|
||||
|
||||
def test_sortPackagesDefaultOrder(self) -> None:
|
||||
self.loadSortedPackages()
|
||||
|
||||
def test_sortPackagesSillyOrder(self) -> None:
|
||||
def sillySorter(s: runner._Loadable) -> str:
|
||||
# This has to work on fully-qualified class names and class
|
||||
# objects, which is silly, but it's the "spec", such as it is.
|
||||
# if isinstance(s, type):
|
||||
# return s.__module__+'.'+s.__name__
|
||||
n = runner.name(s)
|
||||
d = md5(n.encode("utf8")).hexdigest()
|
||||
return d
|
||||
|
||||
self.loadSortedPackages(sillySorter)
|
||||
@@ -0,0 +1,270 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test the interaction between trial and errors logged during test run.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from twisted.internet import reactor, task
|
||||
from twisted.python import failure, log
|
||||
from twisted.trial import _synctest, reporter, unittest
|
||||
|
||||
|
||||
def makeFailure():
|
||||
"""
|
||||
Return a new, realistic failure.
|
||||
"""
|
||||
try:
|
||||
1 / 0
|
||||
except ZeroDivisionError:
|
||||
f = failure.Failure()
|
||||
return f
|
||||
|
||||
|
||||
class Mask:
|
||||
"""
|
||||
Hide C{MockTest}s from Trial's automatic test finder.
|
||||
"""
|
||||
|
||||
class FailureLoggingMixin:
|
||||
def test_silent(self):
|
||||
"""
|
||||
Don't log any errors.
|
||||
"""
|
||||
|
||||
def test_single(self):
|
||||
"""
|
||||
Log a single error.
|
||||
"""
|
||||
log.err(makeFailure())
|
||||
|
||||
def test_double(self):
|
||||
"""
|
||||
Log two errors.
|
||||
"""
|
||||
log.err(makeFailure())
|
||||
log.err(makeFailure())
|
||||
|
||||
def test_singleThenFail(self):
|
||||
"""
|
||||
Log a single error, then fail.
|
||||
"""
|
||||
log.err(makeFailure())
|
||||
1 + None
|
||||
|
||||
class SynchronousFailureLogging(FailureLoggingMixin, unittest.SynchronousTestCase):
|
||||
pass
|
||||
|
||||
class AsynchronousFailureLogging(FailureLoggingMixin, unittest.TestCase):
|
||||
def test_inCallback(self):
|
||||
"""
|
||||
Log an error in an asynchronous callback.
|
||||
"""
|
||||
return task.deferLater(reactor, 0, lambda: log.err(makeFailure()))
|
||||
|
||||
|
||||
class ObserverTests(unittest.SynchronousTestCase):
|
||||
"""
|
||||
Tests for L{_synctest._LogObserver}, a helper for the implementation of
|
||||
L{SynchronousTestCase.flushLoggedErrors}.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.result = reporter.TestResult()
|
||||
self.observer = _synctest._LogObserver()
|
||||
|
||||
def test_msg(self):
|
||||
"""
|
||||
Test that a standard log message doesn't go anywhere near the result.
|
||||
"""
|
||||
self.observer.gotEvent(
|
||||
{
|
||||
"message": ("some message",),
|
||||
"time": time.time(),
|
||||
"isError": 0,
|
||||
"system": "-",
|
||||
}
|
||||
)
|
||||
self.assertEqual(self.observer.getErrors(), [])
|
||||
|
||||
def test_error(self):
|
||||
"""
|
||||
Test that an observed error gets added to the result
|
||||
"""
|
||||
f = makeFailure()
|
||||
self.observer.gotEvent(
|
||||
{
|
||||
"message": (),
|
||||
"time": time.time(),
|
||||
"isError": 1,
|
||||
"system": "-",
|
||||
"failure": f,
|
||||
"why": None,
|
||||
}
|
||||
)
|
||||
self.assertEqual(self.observer.getErrors(), [f])
|
||||
|
||||
def test_flush(self):
|
||||
"""
|
||||
Check that flushing the observer with no args removes all errors.
|
||||
"""
|
||||
self.test_error()
|
||||
flushed = self.observer.flushErrors()
|
||||
self.assertEqual(self.observer.getErrors(), [])
|
||||
self.assertEqual(len(flushed), 1)
|
||||
self.assertTrue(flushed[0].check(ZeroDivisionError))
|
||||
|
||||
def _makeRuntimeFailure(self):
|
||||
return failure.Failure(RuntimeError("test error"))
|
||||
|
||||
def test_flushByType(self):
|
||||
"""
|
||||
Check that flushing the observer remove all failures of the given type.
|
||||
"""
|
||||
self.test_error() # log a ZeroDivisionError to the observer
|
||||
f = self._makeRuntimeFailure()
|
||||
self.observer.gotEvent(
|
||||
dict(
|
||||
message=(), time=time.time(), isError=1, system="-", failure=f, why=None
|
||||
)
|
||||
)
|
||||
flushed = self.observer.flushErrors(ZeroDivisionError)
|
||||
self.assertEqual(self.observer.getErrors(), [f])
|
||||
self.assertEqual(len(flushed), 1)
|
||||
self.assertTrue(flushed[0].check(ZeroDivisionError))
|
||||
|
||||
def test_ignoreErrors(self):
|
||||
"""
|
||||
Check that C{_ignoreErrors} actually causes errors to be ignored.
|
||||
"""
|
||||
self.observer._ignoreErrors(ZeroDivisionError)
|
||||
f = makeFailure()
|
||||
self.observer.gotEvent(
|
||||
{
|
||||
"message": (),
|
||||
"time": time.time(),
|
||||
"isError": 1,
|
||||
"system": "-",
|
||||
"failure": f,
|
||||
"why": None,
|
||||
}
|
||||
)
|
||||
self.assertEqual(self.observer.getErrors(), [])
|
||||
|
||||
def test_clearIgnores(self):
|
||||
"""
|
||||
Check that C{_clearIgnores} ensures that previously ignored errors
|
||||
get captured.
|
||||
"""
|
||||
self.observer._ignoreErrors(ZeroDivisionError)
|
||||
self.observer._clearIgnores()
|
||||
f = makeFailure()
|
||||
self.observer.gotEvent(
|
||||
{
|
||||
"message": (),
|
||||
"time": time.time(),
|
||||
"isError": 1,
|
||||
"system": "-",
|
||||
"failure": f,
|
||||
"why": None,
|
||||
}
|
||||
)
|
||||
self.assertEqual(self.observer.getErrors(), [f])
|
||||
|
||||
|
||||
class LogErrorsMixin:
|
||||
"""
|
||||
High-level tests demonstrating the expected behaviour of logged errors
|
||||
during tests.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.result = reporter.TestResult()
|
||||
|
||||
def tearDown(self):
|
||||
self.flushLoggedErrors(ZeroDivisionError)
|
||||
|
||||
def test_singleError(self):
|
||||
"""
|
||||
Test that a logged error gets reported as a test error.
|
||||
"""
|
||||
test = self.MockTest("test_single")
|
||||
test(self.result)
|
||||
self.assertEqual(len(self.result.errors), 1)
|
||||
self.assertTrue(
|
||||
self.result.errors[0][1].check(ZeroDivisionError), self.result.errors[0][1]
|
||||
)
|
||||
self.assertEqual(0, self.result.successes)
|
||||
|
||||
def test_twoErrors(self):
|
||||
"""
|
||||
Test that when two errors get logged, they both get reported as test
|
||||
errors.
|
||||
"""
|
||||
test = self.MockTest("test_double")
|
||||
test(self.result)
|
||||
self.assertEqual(len(self.result.errors), 2)
|
||||
self.assertEqual(0, self.result.successes)
|
||||
|
||||
def test_errorsIsolated(self):
|
||||
"""
|
||||
Check that an error logged in one test doesn't fail the next test.
|
||||
"""
|
||||
t1 = self.MockTest("test_single")
|
||||
t2 = self.MockTest("test_silent")
|
||||
t1(self.result)
|
||||
t2(self.result)
|
||||
self.assertEqual(len(self.result.errors), 1)
|
||||
self.assertEqual(self.result.errors[0][0], t1)
|
||||
self.assertEqual(1, self.result.successes)
|
||||
|
||||
def test_errorsIsolatedWhenTestFails(self):
|
||||
"""
|
||||
An error logged in a failed test doesn't fail the next test.
|
||||
"""
|
||||
t1 = self.MockTest("test_singleThenFail")
|
||||
t2 = self.MockTest("test_silent")
|
||||
t1(self.result)
|
||||
t2(self.result)
|
||||
|
||||
self.assertEqual(len(self.result.errors), 2)
|
||||
self.assertEqual(self.result.errors[0][0], t1)
|
||||
self.result.errors[0][1].trap(TypeError)
|
||||
|
||||
self.assertEqual(self.result.errors[1][0], t1)
|
||||
self.result.errors[1][1].trap(ZeroDivisionError)
|
||||
|
||||
self.assertEqual(1, self.result.successes)
|
||||
|
||||
def test_boundedObservers(self):
|
||||
"""
|
||||
There are no extra log observers after a test runs.
|
||||
"""
|
||||
# XXX trial is *all about* global log state. It should really be fixed.
|
||||
observer = _synctest._LogObserver()
|
||||
self.patch(_synctest, "_logObserver", observer)
|
||||
observers = log.theLogPublisher.observers[:]
|
||||
test = self.MockTest()
|
||||
test(self.result)
|
||||
self.assertEqual(observers, log.theLogPublisher.observers)
|
||||
|
||||
|
||||
class SynchronousLogErrorsTests(LogErrorsMixin, unittest.SynchronousTestCase):
|
||||
MockTest = Mask.SynchronousFailureLogging
|
||||
|
||||
|
||||
class AsynchronousLogErrorsTests(LogErrorsMixin, unittest.TestCase):
|
||||
MockTest = Mask.AsynchronousFailureLogging
|
||||
|
||||
def test_inCallback(self):
|
||||
"""
|
||||
Test that errors logged in callbacks get reported as test errors.
|
||||
"""
|
||||
test = self.MockTest("test_inCallback")
|
||||
test(self.result)
|
||||
self.assertEqual(len(self.result.errors), 1)
|
||||
self.assertTrue(
|
||||
self.result.errors[0][1].check(ZeroDivisionError), self.result.errors[0][1]
|
||||
)
|
||||
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
Tests for L{twisted.trial.test.matchers}.
|
||||
"""
|
||||
from hamcrest import anything, assert_that, contains_string, equal_to, not_
|
||||
from hamcrest.core.core.allof import AllOf
|
||||
from hamcrest.core.string_description import StringDescription
|
||||
from hypothesis import given
|
||||
from hypothesis.strategies import just, sampled_from, text
|
||||
|
||||
from twisted.python.filepath import FilePath
|
||||
from twisted.trial.unittest import SynchronousTestCase
|
||||
from .matchers import fileContents
|
||||
|
||||
|
||||
class FileContentsTests(SynchronousTestCase):
|
||||
"""
|
||||
Tests for L{fileContents}.
|
||||
"""
|
||||
|
||||
@given(text(), just("utf-8"))
|
||||
def test_matches(self, contents: str, encoding: str) -> None:
|
||||
"""
|
||||
L{fileContents} matches a L{IFilePath} that refers to a file that
|
||||
contains a string that is matched by the parameterized matcher.
|
||||
|
||||
:param contents: The text string to place in the file and match
|
||||
against.
|
||||
|
||||
:param encoding: The text encoding to use to encode C{contents} when
|
||||
writing to the file.
|
||||
"""
|
||||
p = FilePath(self.mktemp())
|
||||
p.setContent(contents.encode(encoding))
|
||||
|
||||
description = StringDescription()
|
||||
assert_that(
|
||||
fileContents(equal_to(contents)).matches(p, description), equal_to(True)
|
||||
)
|
||||
assert_that(str(description), equal_to(""))
|
||||
|
||||
@given(
|
||||
just("some text, it doesn't matter what"),
|
||||
sampled_from(["ascii", "latin-1", "utf-8"]),
|
||||
)
|
||||
def test_mismatches(self, contents: str, encoding: str) -> None:
|
||||
"""
|
||||
L{fileContents} does not match an L{IFilePath} that refers to a
|
||||
file that contains a string that is not matched by the parameterized
|
||||
matcher.
|
||||
|
||||
:param contents: The text string to place in the file and match
|
||||
against.
|
||||
|
||||
:param encoding: The text encoding to use to encode C{contents} when
|
||||
writing to the file.
|
||||
"""
|
||||
p = FilePath(self.mktemp())
|
||||
p.setContent(contents.encode(encoding))
|
||||
|
||||
description = StringDescription()
|
||||
assert_that(
|
||||
fileContents(not_(anything())).matches(p, description), equal_to(False)
|
||||
)
|
||||
assert_that(str(description), equal_to(f"was <{p}>"))
|
||||
|
||||
def test_ioerror(self) -> None:
|
||||
"""
|
||||
L{fileContents} reports details of any I/O error encountered while
|
||||
attempting to match.
|
||||
"""
|
||||
p = FilePath(self.mktemp())
|
||||
|
||||
description = StringDescription()
|
||||
assert_that(fileContents(anything()).matches(p, description), equal_to(False))
|
||||
assert_that(
|
||||
str(description),
|
||||
# It must contain at least ...
|
||||
AllOf(
|
||||
# the name of the matcher.
|
||||
contains_string("fileContents"),
|
||||
# the name of the exception raised.
|
||||
contains_string("FileNotFoundError"),
|
||||
# the repr (so weird values are escaped) of the path being
|
||||
# matched against.
|
||||
contains_string(repr(p.path)),
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,173 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for the output generated by trial.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from io import StringIO
|
||||
from typing import TypeVar
|
||||
|
||||
from twisted.scripts import trial
|
||||
from twisted.trial import runner
|
||||
from twisted.trial.test import packages
|
||||
|
||||
_T = TypeVar("_T")
|
||||
|
||||
_noModuleError = "No module named 'frotz'"
|
||||
|
||||
|
||||
def runTrial(*args: str) -> str:
|
||||
from twisted.trial import reporter
|
||||
|
||||
config = trial.Options()
|
||||
config.parseOptions(args)
|
||||
output = StringIO()
|
||||
myRunner = runner.TrialRunner(
|
||||
reporter.VerboseTextReporter,
|
||||
stream=output,
|
||||
workingDirectory=config["temp-directory"],
|
||||
)
|
||||
suite = trial._getSuite(config)
|
||||
myRunner.run(suite)
|
||||
return output.getvalue()
|
||||
|
||||
|
||||
class ImportErrorsTests(packages.SysPathManglingTest):
|
||||
"""Actually run trial as if on the command line and check that the output
|
||||
is what we expect.
|
||||
"""
|
||||
|
||||
def debug(self) -> None:
|
||||
pass
|
||||
|
||||
parent = "_testImportErrors"
|
||||
|
||||
def runTrial(self, *args: str) -> str:
|
||||
return runTrial("--temp-directory", self.mktemp(), *args)
|
||||
|
||||
def _print(self, stuff: _T) -> _T:
|
||||
print(stuff)
|
||||
return stuff
|
||||
|
||||
def assertIn( # type: ignore[override]
|
||||
self, container: str, containee: str, *args: object, **kwargs: object
|
||||
) -> str:
|
||||
# redefined to be useful in callbacks
|
||||
super().assertIn(containee, container, *args, **kwargs)
|
||||
return container
|
||||
|
||||
def assertNotIn( # type: ignore[override]
|
||||
self, container: str, containee: str, *args: object, **kwargs: object
|
||||
) -> str:
|
||||
# redefined to be useful in callbacks
|
||||
super().assertNotIn(containee, container, *args, **kwargs)
|
||||
return container
|
||||
|
||||
def test_trialRun(self) -> None:
|
||||
self.runTrial()
|
||||
|
||||
def test_nonexistentModule(self) -> str:
|
||||
d = self.runTrial("twisted.doesntexist")
|
||||
self.assertIn(d, "[ERROR]")
|
||||
self.assertIn(d, "twisted.doesntexist")
|
||||
return d
|
||||
|
||||
def test_nonexistentPackage(self) -> str:
|
||||
d = self.runTrial("doesntexist")
|
||||
self.assertIn(d, "doesntexist")
|
||||
self.assertIn(d, "ModuleNotFound")
|
||||
self.assertIn(d, "[ERROR]")
|
||||
return d
|
||||
|
||||
def test_nonexistentPackageWithModule(self) -> str:
|
||||
d = self.runTrial("doesntexist.barney")
|
||||
self.assertIn(d, "doesntexist.barney")
|
||||
self.assertIn(d, "ObjectNotFound")
|
||||
self.assertIn(d, "[ERROR]")
|
||||
return d
|
||||
|
||||
def test_badpackage(self) -> str:
|
||||
d = self.runTrial("badpackage")
|
||||
self.assertIn(d, "[ERROR]")
|
||||
self.assertIn(d, "badpackage")
|
||||
self.assertNotIn(d, "IOError")
|
||||
return d
|
||||
|
||||
def test_moduleInBadpackage(self) -> str:
|
||||
d = self.runTrial("badpackage.test_module")
|
||||
self.assertIn(d, "[ERROR]")
|
||||
self.assertIn(d, "badpackage.test_module")
|
||||
self.assertNotIn(d, "IOError")
|
||||
return d
|
||||
|
||||
def test_badmodule(self) -> str:
|
||||
d = self.runTrial("package.test_bad_module")
|
||||
self.assertIn(d, "[ERROR]")
|
||||
self.assertIn(d, "package.test_bad_module")
|
||||
self.assertNotIn(d, "IOError")
|
||||
self.assertNotIn(d, "<module ")
|
||||
return d
|
||||
|
||||
def test_badimport(self) -> str:
|
||||
d = self.runTrial("package.test_import_module")
|
||||
self.assertIn(d, "[ERROR]")
|
||||
self.assertIn(d, "package.test_import_module")
|
||||
self.assertNotIn(d, "IOError")
|
||||
self.assertNotIn(d, "<module ")
|
||||
return d
|
||||
|
||||
def test_recurseImport(self) -> str:
|
||||
d = self.runTrial("package")
|
||||
self.assertIn(d, "[ERROR]")
|
||||
self.assertIn(d, "test_bad_module")
|
||||
self.assertIn(d, "test_import_module")
|
||||
self.assertNotIn(d, "<module ")
|
||||
self.assertNotIn(d, "IOError")
|
||||
return d
|
||||
|
||||
def test_recurseImportErrors(self) -> str:
|
||||
d = self.runTrial("package2")
|
||||
self.assertIn(d, "[ERROR]")
|
||||
self.assertIn(d, "package2")
|
||||
self.assertIn(d, "test_module")
|
||||
self.assertIn(d, _noModuleError)
|
||||
self.assertNotIn(d, "<module ")
|
||||
self.assertNotIn(d, "IOError")
|
||||
return d
|
||||
|
||||
def test_nonRecurseImportErrors(self) -> str:
|
||||
d = self.runTrial("-N", "package2")
|
||||
self.assertIn(d, "[ERROR]")
|
||||
self.assertIn(d, _noModuleError)
|
||||
self.assertNotIn(d, "<module ")
|
||||
return d
|
||||
|
||||
def test_regularRun(self) -> str:
|
||||
d = self.runTrial("package.test_module")
|
||||
self.assertNotIn(d, "[ERROR]")
|
||||
self.assertNotIn(d, "IOError")
|
||||
self.assertIn(d, "OK")
|
||||
self.assertIn(d, "PASSED (successes=1)")
|
||||
return d
|
||||
|
||||
def test_filename(self) -> str:
|
||||
self.mangleSysPath(self.oldPath)
|
||||
d = self.runTrial(os.path.join(self.parent, "package", "test_module.py"))
|
||||
self.assertNotIn(d, "[ERROR]")
|
||||
self.assertNotIn(d, "IOError")
|
||||
self.assertIn(d, "OK")
|
||||
self.assertIn(d, "PASSED (successes=1)")
|
||||
return d
|
||||
|
||||
def test_dosFile(self) -> str:
|
||||
## XXX -- not really an output test, more of a script test
|
||||
self.mangleSysPath(self.oldPath)
|
||||
d = self.runTrial(os.path.join(self.parent, "package", "test_dos_module.py"))
|
||||
self.assertNotIn(d, "[ERROR]")
|
||||
self.assertNotIn(d, "IOError")
|
||||
self.assertIn(d, "OK")
|
||||
self.assertIn(d, "PASSED (successes=1)")
|
||||
return d
|
||||
@@ -0,0 +1,46 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
#
|
||||
# Maintainer: Jonathan Lange
|
||||
|
||||
"""
|
||||
Tests for L{twisted.plugins.twisted_trial}.
|
||||
"""
|
||||
|
||||
from twisted.plugin import getPlugins
|
||||
from twisted.trial import unittest
|
||||
from twisted.trial.itrial import IReporter
|
||||
|
||||
|
||||
class PluginsTests(unittest.SynchronousTestCase):
|
||||
"""
|
||||
Tests for Trial's reporter plugins.
|
||||
"""
|
||||
|
||||
def getPluginsByLongOption(self, longOption):
|
||||
"""
|
||||
Return the Trial reporter plugin with the given long option.
|
||||
|
||||
If more than one is found, raise ValueError. If none are found, raise
|
||||
IndexError.
|
||||
"""
|
||||
plugins = [
|
||||
plugin for plugin in getPlugins(IReporter) if plugin.longOpt == longOption
|
||||
]
|
||||
if len(plugins) > 1:
|
||||
raise ValueError(
|
||||
"More than one plugin found with long option %r: %r"
|
||||
% (longOption, plugins)
|
||||
)
|
||||
return plugins[0]
|
||||
|
||||
def test_subunitPlugin(self) -> None:
|
||||
"""
|
||||
One of the reporter plugins is the subunit reporter plugin.
|
||||
"""
|
||||
subunitPlugin = self.getPluginsByLongOption("subunit")
|
||||
self.assertEqual("Subunit Reporter", subunitPlugin.name)
|
||||
self.assertEqual("twisted.trial.reporter", subunitPlugin.module)
|
||||
self.assertEqual("subunit", subunitPlugin.longOpt)
|
||||
self.assertIdentical(None, subunitPlugin.shortOpt)
|
||||
self.assertEqual("SubunitReporter", subunitPlugin.klass)
|
||||
@@ -0,0 +1,257 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import traceback
|
||||
import unittest as pyunit
|
||||
from unittest import skipIf
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.trial.itrial import IReporter, ITestCase
|
||||
from twisted.trial.test import pyunitcases
|
||||
from twisted.trial.unittest import PyUnitResultAdapter, SynchronousTestCase
|
||||
|
||||
|
||||
class PyUnitTestTests(SynchronousTestCase):
|
||||
def setUp(self) -> None:
|
||||
self.original = pyunitcases.PyUnitTest("test_pass")
|
||||
self.test = ITestCase(self.original)
|
||||
|
||||
def test_callable(self) -> None:
|
||||
"""
|
||||
Tests must be callable in order to be used with Python's unittest.py.
|
||||
"""
|
||||
self.assertTrue(callable(self.test), f"{self.test!r} is not callable.")
|
||||
|
||||
|
||||
class PyUnitResultTests(SynchronousTestCase):
|
||||
"""
|
||||
Tests to show that PyUnitResultAdapter wraps TestResult objects from the
|
||||
standard library 'unittest' module in such a way as to make them usable and
|
||||
useful from Trial.
|
||||
"""
|
||||
|
||||
# Once erroneous is ported to Python 3 this can be replaced with
|
||||
# erroneous.ErrorTest:
|
||||
class ErrorTest(SynchronousTestCase):
|
||||
"""
|
||||
A test case which has a L{test_foo} which will raise an error.
|
||||
|
||||
@ivar ran: boolean indicating whether L{test_foo} has been run.
|
||||
"""
|
||||
|
||||
ran = False
|
||||
|
||||
def test_foo(self) -> None:
|
||||
"""
|
||||
Set C{self.ran} to True and raise a C{ZeroDivisionError}
|
||||
"""
|
||||
self.ran = True
|
||||
1 / 0
|
||||
|
||||
def test_dontUseAdapterWhenReporterProvidesIReporter(self) -> None:
|
||||
"""
|
||||
The L{PyUnitResultAdapter} is only used when the result passed to
|
||||
C{run} does *not* provide L{IReporter}.
|
||||
"""
|
||||
|
||||
@implementer(IReporter)
|
||||
class StubReporter: # type: ignore[misc]
|
||||
"""
|
||||
A reporter which records data about calls made to it.
|
||||
|
||||
@ivar errors: Errors passed to L{addError}.
|
||||
@ivar failures: Failures passed to L{addFailure}.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.errors: list[Failure] = []
|
||||
self.failures: list[None] = []
|
||||
|
||||
def startTest(self, test: object) -> None:
|
||||
"""
|
||||
Do nothing.
|
||||
"""
|
||||
|
||||
def stopTest(self, test: object) -> None:
|
||||
"""
|
||||
Do nothing.
|
||||
"""
|
||||
|
||||
def addError(self, test: object, error: Failure) -> None:
|
||||
"""
|
||||
Record the error.
|
||||
"""
|
||||
self.errors.append(error)
|
||||
|
||||
test = self.ErrorTest("test_foo")
|
||||
result = StubReporter()
|
||||
test.run(result)
|
||||
self.assertIsInstance(result.errors[0], Failure)
|
||||
|
||||
def test_success(self) -> None:
|
||||
class SuccessTest(SynchronousTestCase):
|
||||
ran = False
|
||||
|
||||
def test_foo(s) -> None:
|
||||
s.ran = True
|
||||
|
||||
test = SuccessTest("test_foo")
|
||||
result = pyunit.TestResult()
|
||||
test.run(result)
|
||||
|
||||
self.assertTrue(test.ran)
|
||||
self.assertEqual(1, result.testsRun)
|
||||
self.assertTrue(result.wasSuccessful())
|
||||
|
||||
def test_failure(self) -> None:
|
||||
class FailureTest(SynchronousTestCase):
|
||||
ran = False
|
||||
|
||||
def test_foo(s) -> None:
|
||||
s.ran = True
|
||||
s.fail("boom!")
|
||||
|
||||
test = FailureTest("test_foo")
|
||||
result = pyunit.TestResult()
|
||||
test.run(result)
|
||||
|
||||
self.assertTrue(test.ran)
|
||||
self.assertEqual(1, result.testsRun)
|
||||
self.assertEqual(1, len(result.failures))
|
||||
self.assertFalse(result.wasSuccessful())
|
||||
|
||||
def test_error(self) -> None:
|
||||
test = self.ErrorTest("test_foo")
|
||||
result = pyunit.TestResult()
|
||||
test.run(result)
|
||||
|
||||
self.assertTrue(test.ran)
|
||||
self.assertEqual(1, result.testsRun)
|
||||
self.assertEqual(1, len(result.errors))
|
||||
self.assertFalse(result.wasSuccessful())
|
||||
|
||||
def test_setUpError(self) -> None:
|
||||
class ErrorTest(SynchronousTestCase):
|
||||
ran = False
|
||||
|
||||
def setUp(self) -> None:
|
||||
1 / 0
|
||||
|
||||
def test_foo(s) -> None:
|
||||
s.ran = True
|
||||
|
||||
test = ErrorTest("test_foo")
|
||||
result = pyunit.TestResult()
|
||||
test.run(result)
|
||||
|
||||
self.assertFalse(test.ran)
|
||||
self.assertEqual(1, result.testsRun)
|
||||
self.assertEqual(1, len(result.errors))
|
||||
self.assertFalse(result.wasSuccessful())
|
||||
|
||||
def test_tracebackFromFailure(self) -> None:
|
||||
"""
|
||||
Errors added through the L{PyUnitResultAdapter} have the same traceback
|
||||
information as if there were no adapter at all.
|
||||
"""
|
||||
try:
|
||||
1 / 0
|
||||
except ZeroDivisionError:
|
||||
exc_info = sys.exc_info()
|
||||
f = Failure()
|
||||
pyresult = pyunit.TestResult()
|
||||
result = PyUnitResultAdapter(pyresult)
|
||||
result.addError(self, f)
|
||||
self.assertEqual(
|
||||
pyresult.errors[0][1], "".join(traceback.format_exception(*exc_info))
|
||||
)
|
||||
|
||||
def test_traceback(self) -> None:
|
||||
"""
|
||||
As test_tracebackFromFailure, but covering more code.
|
||||
"""
|
||||
|
||||
class ErrorTest(SynchronousTestCase):
|
||||
exc_info = None
|
||||
|
||||
def test_foo(self) -> None:
|
||||
try:
|
||||
1 / 0
|
||||
except ZeroDivisionError:
|
||||
self.exc_info = sys.exc_info()
|
||||
raise
|
||||
|
||||
test = ErrorTest("test_foo")
|
||||
result = pyunit.TestResult()
|
||||
test.run(result)
|
||||
|
||||
# We can't test that the tracebacks are equal, because Trial's
|
||||
# machinery inserts a few extra frames on the top and we don't really
|
||||
# want to trim them off without an extremely good reason.
|
||||
#
|
||||
# So, we just test that the result's stack ends with the
|
||||
# exception's stack.
|
||||
assert test.exc_info is not None
|
||||
expected_stack = "".join(traceback.format_tb(test.exc_info[2]))
|
||||
observed_stack = "\n".join(result.errors[0][1].splitlines()[:-1])
|
||||
|
||||
self.assertEqual(
|
||||
expected_stack.strip(), observed_stack[-len(expected_stack) :].strip()
|
||||
)
|
||||
|
||||
def test_tracebackFromCleanFailure(self) -> None:
|
||||
"""
|
||||
Errors added through the L{PyUnitResultAdapter} have the same
|
||||
traceback information as if there were no adapter at all, even
|
||||
if the Failure that held the information has been cleaned.
|
||||
"""
|
||||
try:
|
||||
1 / 0
|
||||
except ZeroDivisionError:
|
||||
exc_info = sys.exc_info()
|
||||
f = Failure()
|
||||
f.cleanFailure()
|
||||
pyresult = pyunit.TestResult()
|
||||
result = PyUnitResultAdapter(pyresult)
|
||||
result.addError(self, f)
|
||||
tback = "".join(traceback.format_exception(*exc_info))
|
||||
self.assertEqual(
|
||||
pyresult.errors[0][1].endswith("ZeroDivisionError: division by zero\n"),
|
||||
tback.endswith("ZeroDivisionError: division by zero\n"),
|
||||
)
|
||||
|
||||
def test_trialSkip(self) -> None:
|
||||
"""
|
||||
Skips using trial's skipping functionality are reported as skips in
|
||||
the L{pyunit.TestResult}.
|
||||
"""
|
||||
|
||||
class SkipTest(SynchronousTestCase):
|
||||
@skipIf(True, "Let's skip!")
|
||||
def test_skip(self) -> None:
|
||||
1 / 0
|
||||
|
||||
test = SkipTest("test_skip")
|
||||
result = pyunit.TestResult()
|
||||
test.run(result)
|
||||
self.assertEqual(result.skipped, [(test, "Let's skip!")])
|
||||
|
||||
def test_pyunitSkip(self) -> None:
|
||||
"""
|
||||
Skips using pyunit's skipping functionality are reported as skips in
|
||||
the L{pyunit.TestResult}.
|
||||
"""
|
||||
|
||||
class SkipTest(SynchronousTestCase):
|
||||
@pyunit.skip("skippy")
|
||||
def test_skip(self) -> None:
|
||||
1 / 0
|
||||
|
||||
test = SkipTest("test_skip")
|
||||
result = pyunit.TestResult()
|
||||
test.run(result)
|
||||
self.assertEqual(result.skipped, [(test, "skippy")])
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,93 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
#
|
||||
|
||||
"""
|
||||
Tests for L{twisted.trial.util}
|
||||
"""
|
||||
|
||||
from unittest import skipIf
|
||||
|
||||
from twisted.trial.unittest import TestCase
|
||||
|
||||
|
||||
@skipIf(True, "Skip all tests when @skipIf is used on a class")
|
||||
class SkipDecoratorUsedOnClass(TestCase):
|
||||
"""
|
||||
All tests should be skipped because @skipIf decorator is used on
|
||||
this class.
|
||||
"""
|
||||
|
||||
def test_shouldNeverRun_1(self) -> None:
|
||||
raise Exception("Test should skip and never reach here")
|
||||
|
||||
def test_shouldNeverRun_2(self) -> None:
|
||||
raise Exception("Test should skip and never reach here")
|
||||
|
||||
|
||||
@skipIf(True, "")
|
||||
class SkipDecoratorUsedOnClassWithEmptyReason(TestCase):
|
||||
"""
|
||||
All tests should be skipped because @skipIf decorator is used on
|
||||
this class, even if the reason is an empty string
|
||||
"""
|
||||
|
||||
def test_shouldNeverRun_1(self) -> None:
|
||||
raise Exception("Test should skip and never reach here")
|
||||
|
||||
def test_shouldNeverRun_2(self) -> None:
|
||||
raise Exception("Test should skip and never reach here")
|
||||
|
||||
|
||||
class SkipDecoratorUsedOnMethods(TestCase):
|
||||
"""
|
||||
Only methods where @skipIf decorator is used should be skipped.
|
||||
"""
|
||||
|
||||
@skipIf(True, "skipIf decorator used so skip test")
|
||||
def test_shouldNeverRun(self) -> None:
|
||||
raise Exception("Test should skip and never reach here")
|
||||
|
||||
@skipIf(True, "")
|
||||
def test_shouldNeverRunWithEmptyReason(self) -> None:
|
||||
raise Exception("Test should skip and never reach here")
|
||||
|
||||
def test_shouldShouldRun(self) -> None:
|
||||
self.assertTrue(True, "Test should run and not be skipped")
|
||||
|
||||
@skipIf(False, "should not skip")
|
||||
def test_shouldShouldRunWithSkipIfFalse(self) -> None:
|
||||
self.assertTrue(True, "Test should run and not be skipped")
|
||||
|
||||
@skipIf(False, "")
|
||||
def test_shouldShouldRunWithSkipIfFalseEmptyReason(self) -> None:
|
||||
self.assertTrue(True, "Test should run and not be skipped")
|
||||
|
||||
|
||||
class SkipAttributeOnClass(TestCase):
|
||||
"""
|
||||
All tests should be skipped because skip attribute is set on
|
||||
this class.
|
||||
"""
|
||||
|
||||
skip = "'skip' attribute set on this class, so skip all tests"
|
||||
|
||||
def test_one(self) -> None:
|
||||
raise Exception("Test should skip and never reach here")
|
||||
|
||||
def test_two(self) -> None:
|
||||
raise Exception("Test should skip and never reach here")
|
||||
|
||||
|
||||
class SkipAttributeOnMethods(TestCase):
|
||||
"""
|
||||
Only methods where @skipIf decorator is used should be skipped.
|
||||
"""
|
||||
|
||||
def test_one(self) -> None:
|
||||
raise Exception("Should never reach here")
|
||||
|
||||
test_one.skip = "skip test, skip attribute set on method" # type: ignore[attr-defined]
|
||||
|
||||
def test_shouldNotSkip(self) -> None:
|
||||
self.assertTrue(True, "Test should run and not be skipped")
|
||||
@@ -0,0 +1,152 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for warning suppression features of Trial.
|
||||
"""
|
||||
|
||||
|
||||
import unittest as pyunit
|
||||
|
||||
from twisted.python.reflect import namedAny
|
||||
from twisted.trial import unittest
|
||||
from twisted.trial.test import suppression
|
||||
|
||||
|
||||
class SuppressionMixin:
|
||||
"""
|
||||
Tests for the warning suppression features of
|
||||
L{twisted.trial.unittest.SynchronousTestCase}.
|
||||
"""
|
||||
|
||||
def runTests(self, suite):
|
||||
suite.run(pyunit.TestResult())
|
||||
|
||||
def _load(self, cls, methodName):
|
||||
"""
|
||||
Return a new L{unittest.TestSuite} with a single test method in it.
|
||||
|
||||
@param cls: A L{TestCase} subclass defining a test method.
|
||||
|
||||
@param methodName: The name of the test method from C{cls}.
|
||||
"""
|
||||
return pyunit.TestSuite([cls(methodName)])
|
||||
|
||||
def _assertWarnings(self, warnings, which):
|
||||
"""
|
||||
Assert that a certain number of warnings with certain messages were
|
||||
emitted in a certain order.
|
||||
|
||||
@param warnings: A list of emitted warnings, as returned by
|
||||
C{flushWarnings}.
|
||||
|
||||
@param which: A list of strings giving warning messages that should
|
||||
appear in C{warnings}.
|
||||
|
||||
@raise self.failureException: If the warning messages given by C{which}
|
||||
do not match the messages in the warning information in C{warnings},
|
||||
or if they do not appear in the same order.
|
||||
"""
|
||||
self.assertEqual([warning["message"] for warning in warnings], which)
|
||||
|
||||
def test_setUpSuppression(self):
|
||||
"""
|
||||
Suppressions defined by the test method being run are applied to any
|
||||
warnings emitted while running the C{setUp} fixture.
|
||||
"""
|
||||
self.runTests(self._load(self.TestSetUpSuppression, "testSuppressMethod"))
|
||||
warningsShown = self.flushWarnings([self.TestSetUpSuppression._emit])
|
||||
self._assertWarnings(
|
||||
warningsShown,
|
||||
[
|
||||
suppression.CLASS_WARNING_MSG,
|
||||
suppression.MODULE_WARNING_MSG,
|
||||
suppression.CLASS_WARNING_MSG,
|
||||
suppression.MODULE_WARNING_MSG,
|
||||
],
|
||||
)
|
||||
|
||||
def test_tearDownSuppression(self):
|
||||
"""
|
||||
Suppressions defined by the test method being run are applied to any
|
||||
warnings emitted while running the C{tearDown} fixture.
|
||||
"""
|
||||
self.runTests(self._load(self.TestTearDownSuppression, "testSuppressMethod"))
|
||||
warningsShown = self.flushWarnings([self.TestTearDownSuppression._emit])
|
||||
self._assertWarnings(
|
||||
warningsShown,
|
||||
[
|
||||
suppression.CLASS_WARNING_MSG,
|
||||
suppression.MODULE_WARNING_MSG,
|
||||
suppression.CLASS_WARNING_MSG,
|
||||
suppression.MODULE_WARNING_MSG,
|
||||
],
|
||||
)
|
||||
|
||||
def test_suppressMethod(self):
|
||||
"""
|
||||
A suppression set on a test method prevents warnings emitted by that
|
||||
test method which the suppression matches from being emitted.
|
||||
"""
|
||||
self.runTests(self._load(self.TestSuppression, "testSuppressMethod"))
|
||||
warningsShown = self.flushWarnings([self.TestSuppression._emit])
|
||||
self._assertWarnings(
|
||||
warningsShown,
|
||||
[suppression.CLASS_WARNING_MSG, suppression.MODULE_WARNING_MSG],
|
||||
)
|
||||
|
||||
def test_suppressClass(self):
|
||||
"""
|
||||
A suppression set on a L{SynchronousTestCase} subclass prevents warnings
|
||||
emitted by any test methods defined on that class which match the
|
||||
suppression from being emitted.
|
||||
"""
|
||||
self.runTests(self._load(self.TestSuppression, "testSuppressClass"))
|
||||
warningsShown = self.flushWarnings([self.TestSuppression._emit])
|
||||
self.assertEqual(warningsShown[0]["message"], suppression.METHOD_WARNING_MSG)
|
||||
self.assertEqual(warningsShown[1]["message"], suppression.MODULE_WARNING_MSG)
|
||||
self.assertEqual(len(warningsShown), 2)
|
||||
|
||||
def test_suppressModule(self):
|
||||
"""
|
||||
A suppression set on a module prevents warnings emitted by any test
|
||||
mewthods defined in that module which match the suppression from being
|
||||
emitted.
|
||||
"""
|
||||
self.runTests(self._load(self.TestSuppression2, "testSuppressModule"))
|
||||
warningsShown = self.flushWarnings([self.TestSuppression._emit])
|
||||
self.assertEqual(warningsShown[0]["message"], suppression.METHOD_WARNING_MSG)
|
||||
self.assertEqual(warningsShown[1]["message"], suppression.CLASS_WARNING_MSG)
|
||||
self.assertEqual(len(warningsShown), 2)
|
||||
|
||||
def test_overrideSuppressClass(self):
|
||||
"""
|
||||
The suppression set on a test method completely overrides a suppression
|
||||
with wider scope; if it does not match a warning emitted by that test
|
||||
method, the warning is emitted, even if a wider suppression matches.
|
||||
"""
|
||||
self.runTests(self._load(self.TestSuppression, "testOverrideSuppressClass"))
|
||||
warningsShown = self.flushWarnings([self.TestSuppression._emit])
|
||||
self.assertEqual(warningsShown[0]["message"], suppression.METHOD_WARNING_MSG)
|
||||
self.assertEqual(warningsShown[1]["message"], suppression.CLASS_WARNING_MSG)
|
||||
self.assertEqual(warningsShown[2]["message"], suppression.MODULE_WARNING_MSG)
|
||||
self.assertEqual(len(warningsShown), 3)
|
||||
|
||||
|
||||
class SynchronousSuppressionTests(SuppressionMixin, unittest.SynchronousTestCase):
|
||||
"""
|
||||
@see: L{twisted.trial.test.test_tests}
|
||||
"""
|
||||
|
||||
TestSetUpSuppression = namedAny(
|
||||
"twisted.trial.test.suppression.SynchronousTestSetUpSuppression"
|
||||
)
|
||||
TestTearDownSuppression = namedAny(
|
||||
"twisted.trial.test.suppression.SynchronousTestTearDownSuppression"
|
||||
)
|
||||
TestSuppression = namedAny(
|
||||
"twisted.trial.test.suppression.SynchronousTestSuppression"
|
||||
)
|
||||
TestSuppression2 = namedAny(
|
||||
"twisted.trial.test.suppression.SynchronousTestSuppression2"
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Direct unit tests for L{twisted.trial.unittest.SynchronousTestCase} and
|
||||
L{twisted.trial.unittest.TestCase}.
|
||||
"""
|
||||
|
||||
|
||||
from twisted.trial.unittest import SynchronousTestCase, TestCase
|
||||
|
||||
|
||||
class TestCaseMixin:
|
||||
"""
|
||||
L{TestCase} tests.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Create a couple instances of C{MyTestCase}, each for the same test
|
||||
method, to be used in the test methods of this class.
|
||||
"""
|
||||
self.first = self.MyTestCase("test_1")
|
||||
self.second = self.MyTestCase("test_1")
|
||||
|
||||
def test_equality(self):
|
||||
"""
|
||||
In order for one test method to be runnable twice, two TestCase
|
||||
instances with the same test method name must not compare as equal.
|
||||
"""
|
||||
self.assertTrue(self.first == self.first)
|
||||
self.assertTrue(self.first != self.second)
|
||||
self.assertFalse(self.first == self.second)
|
||||
|
||||
def test_hashability(self):
|
||||
"""
|
||||
In order for one test method to be runnable twice, two TestCase
|
||||
instances with the same test method name should not have the same
|
||||
hash value.
|
||||
"""
|
||||
container = {}
|
||||
container[self.first] = None
|
||||
container[self.second] = None
|
||||
self.assertEqual(len(container), 2)
|
||||
|
||||
|
||||
class SynchronousTestCaseTests(TestCaseMixin, SynchronousTestCase):
|
||||
class MyTestCase(SynchronousTestCase):
|
||||
"""
|
||||
Some test methods which can be used to test behaviors of
|
||||
L{SynchronousTestCase}.
|
||||
"""
|
||||
|
||||
def test_1(self):
|
||||
pass
|
||||
|
||||
|
||||
# Yes, subclass SynchronousTestCase again. There are no interesting behaviors
|
||||
# of self being tested below, only of self.MyTestCase.
|
||||
class AsynchronousTestCaseTests(TestCaseMixin, SynchronousTestCase):
|
||||
class MyTestCase(TestCase):
|
||||
"""
|
||||
Some test methods which can be used to test behaviors of
|
||||
L{TestCase}.
|
||||
"""
|
||||
|
||||
def test_1(self):
|
||||
pass
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,652 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
#
|
||||
|
||||
"""
|
||||
Tests for L{twisted.trial.util}
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import locale
|
||||
import os
|
||||
import sys
|
||||
from io import StringIO
|
||||
from typing import Generator
|
||||
|
||||
from zope.interface import implementer
|
||||
|
||||
from hamcrest import assert_that, equal_to
|
||||
|
||||
from twisted.internet.base import DelayedCall
|
||||
from twisted.internet.interfaces import IProcessTransport
|
||||
from twisted.python import filepath
|
||||
from twisted.python.failure import Failure
|
||||
from twisted.trial import util
|
||||
from twisted.trial.unittest import SynchronousTestCase
|
||||
from twisted.trial.util import (
|
||||
DirtyReactorAggregateError,
|
||||
_Janitor,
|
||||
acquireAttribute,
|
||||
excInfoOrFailureToExcInfo,
|
||||
openTestLog,
|
||||
)
|
||||
|
||||
|
||||
class MktempTests(SynchronousTestCase):
|
||||
"""
|
||||
Tests for L{TestCase.mktemp}, a helper function for creating temporary file
|
||||
or directory names.
|
||||
"""
|
||||
|
||||
def test_name(self) -> None:
|
||||
"""
|
||||
The path name returned by C{mktemp} is directly beneath a directory
|
||||
which identifies the test method which created the name.
|
||||
"""
|
||||
name = self.mktemp()
|
||||
dirs = os.path.dirname(name).split(os.sep)[:-1]
|
||||
self.assertEqual(
|
||||
dirs, ["twisted.trial.test.test_util", "MktempTests", "test_name"]
|
||||
)
|
||||
|
||||
def test_unique(self) -> None:
|
||||
"""
|
||||
Repeated calls to C{mktemp} return different values.
|
||||
"""
|
||||
name = self.mktemp()
|
||||
self.assertNotEqual(name, self.mktemp())
|
||||
|
||||
def test_created(self) -> None:
|
||||
"""
|
||||
The directory part of the path name returned by C{mktemp} exists.
|
||||
"""
|
||||
name = self.mktemp()
|
||||
dirname = os.path.dirname(name)
|
||||
self.assertTrue(os.path.exists(dirname))
|
||||
self.assertFalse(os.path.exists(name))
|
||||
|
||||
def test_location(self) -> None:
|
||||
"""
|
||||
The path returned by C{mktemp} is beneath the current working directory.
|
||||
"""
|
||||
path = os.path.abspath(self.mktemp())
|
||||
self.assertTrue(path.startswith(os.getcwd()))
|
||||
|
||||
|
||||
class DirtyReactorAggregateErrorTests(SynchronousTestCase):
|
||||
"""
|
||||
Tests for the L{DirtyReactorAggregateError}.
|
||||
"""
|
||||
|
||||
def test_formatDelayedCall(self) -> None:
|
||||
"""
|
||||
Delayed calls are formatted nicely.
|
||||
"""
|
||||
error = DirtyReactorAggregateError(["Foo", "bar"])
|
||||
self.assertEqual(
|
||||
str(error),
|
||||
"""\
|
||||
Reactor was unclean.
|
||||
DelayedCalls: (set twisted.internet.base.DelayedCall.debug = True to debug)
|
||||
Foo
|
||||
bar""",
|
||||
)
|
||||
|
||||
def test_formatSelectables(self) -> None:
|
||||
"""
|
||||
Selectables are formatted nicely.
|
||||
"""
|
||||
error = DirtyReactorAggregateError([], ["selectable 1", "selectable 2"])
|
||||
self.assertEqual(
|
||||
str(error),
|
||||
"""\
|
||||
Reactor was unclean.
|
||||
Selectables:
|
||||
selectable 1
|
||||
selectable 2""",
|
||||
)
|
||||
|
||||
def test_formatDelayedCallsAndSelectables(self) -> None:
|
||||
"""
|
||||
Both delayed calls and selectables can appear in the same error.
|
||||
"""
|
||||
error = DirtyReactorAggregateError(["bleck", "Boozo"], ["Sel1", "Sel2"])
|
||||
self.assertEqual(
|
||||
str(error),
|
||||
"""\
|
||||
Reactor was unclean.
|
||||
DelayedCalls: (set twisted.internet.base.DelayedCall.debug = True to debug)
|
||||
bleck
|
||||
Boozo
|
||||
Selectables:
|
||||
Sel1
|
||||
Sel2""",
|
||||
)
|
||||
|
||||
|
||||
class StubReactor:
|
||||
"""
|
||||
A reactor stub which contains enough functionality to be used with the
|
||||
L{_Janitor}.
|
||||
|
||||
@ivar iterations: A list of the arguments passed to L{iterate}.
|
||||
@ivar removeAllCalled: Number of times that L{removeAll} was called.
|
||||
@ivar selectables: The value that will be returned from L{removeAll}.
|
||||
@ivar delayedCalls: The value to return from L{getDelayedCalls}.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, delayedCalls: list[DelayedCall], selectables: list[object] | None = None
|
||||
) -> None:
|
||||
"""
|
||||
@param delayedCalls: See L{StubReactor.delayedCalls}.
|
||||
@param selectables: See L{StubReactor.selectables}.
|
||||
"""
|
||||
self.delayedCalls = delayedCalls
|
||||
self.iterations: list[float | None] = []
|
||||
self.removeAllCalled = 0
|
||||
if not selectables:
|
||||
selectables = []
|
||||
self.selectables = selectables
|
||||
|
||||
def iterate(self, timeout: float | None = None) -> None:
|
||||
"""
|
||||
Increment C{self.iterations}.
|
||||
"""
|
||||
self.iterations.append(timeout)
|
||||
|
||||
def getDelayedCalls(self) -> list[DelayedCall]:
|
||||
"""
|
||||
Return C{self.delayedCalls}.
|
||||
"""
|
||||
return self.delayedCalls
|
||||
|
||||
def removeAll(self) -> list[object]:
|
||||
"""
|
||||
Increment C{self.removeAllCalled} and return C{self.selectables}.
|
||||
"""
|
||||
self.removeAllCalled += 1
|
||||
return self.selectables
|
||||
|
||||
|
||||
class StubErrorReporter:
|
||||
"""
|
||||
A subset of L{twisted.trial.itrial.IReporter} which records L{addError}
|
||||
calls.
|
||||
|
||||
@ivar errors: List of two-tuples of (test, error) which were passed to
|
||||
L{addError}.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.errors: list[tuple[object, Failure]] = []
|
||||
|
||||
def addError(self, test: object, error: Failure) -> None:
|
||||
"""
|
||||
Record parameters in C{self.errors}.
|
||||
"""
|
||||
self.errors.append((test, error))
|
||||
|
||||
|
||||
class JanitorTests(SynchronousTestCase):
|
||||
"""
|
||||
Tests for L{_Janitor}!
|
||||
"""
|
||||
|
||||
def test_cleanPendingSpinsReactor(self) -> None:
|
||||
"""
|
||||
During pending-call cleanup, the reactor will be spun twice with an
|
||||
instant timeout. This is not a requirement, it is only a test for
|
||||
current behavior. Hopefully Trial will eventually not do this kind of
|
||||
reactor stuff.
|
||||
"""
|
||||
reactor = StubReactor([])
|
||||
jan = _Janitor(None, None, reactor=reactor)
|
||||
jan._cleanPending()
|
||||
self.assertEqual(reactor.iterations, [0, 0])
|
||||
|
||||
def test_cleanPendingCancelsCalls(self) -> None:
|
||||
"""
|
||||
During pending-call cleanup, the janitor cancels pending timed calls.
|
||||
"""
|
||||
|
||||
def func() -> str:
|
||||
return "Lulz"
|
||||
|
||||
cancelled: list[DelayedCall] = []
|
||||
delayedCall = DelayedCall(300, func, (), {}, cancelled.append, lambda x: None)
|
||||
reactor = StubReactor([delayedCall])
|
||||
jan = _Janitor(None, None, reactor=reactor)
|
||||
jan._cleanPending()
|
||||
self.assertEqual(cancelled, [delayedCall])
|
||||
|
||||
def test_cleanPendingReturnsDelayedCallStrings(self) -> None:
|
||||
"""
|
||||
The Janitor produces string representations of delayed calls from the
|
||||
delayed call cleanup method. It gets the string representations
|
||||
*before* cancelling the calls; this is important because cancelling the
|
||||
call removes critical debugging information from the string
|
||||
representation.
|
||||
"""
|
||||
delayedCall = DelayedCall(
|
||||
300, lambda: None, (), {}, lambda x: None, lambda x: None, seconds=lambda: 0
|
||||
)
|
||||
delayedCallString = str(delayedCall)
|
||||
reactor = StubReactor([delayedCall])
|
||||
jan = _Janitor(None, None, reactor=reactor)
|
||||
strings = jan._cleanPending()
|
||||
self.assertEqual(strings, [delayedCallString])
|
||||
|
||||
def test_cleanReactorRemovesSelectables(self) -> None:
|
||||
"""
|
||||
The Janitor will remove selectables during reactor cleanup.
|
||||
"""
|
||||
reactor = StubReactor([])
|
||||
jan = _Janitor(None, None, reactor=reactor)
|
||||
jan._cleanReactor()
|
||||
self.assertEqual(reactor.removeAllCalled, 1)
|
||||
|
||||
def test_cleanReactorKillsProcesses(self) -> None:
|
||||
"""
|
||||
The Janitor will kill processes during reactor cleanup.
|
||||
"""
|
||||
|
||||
@implementer(IProcessTransport)
|
||||
class StubProcessTransport: # type: ignore[misc]
|
||||
"""
|
||||
A stub L{IProcessTransport} provider which records signals.
|
||||
@ivar signals: The signals passed to L{signalProcess}.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.signals: list[str | int] = []
|
||||
|
||||
def signalProcess(self, signal: str | int) -> None:
|
||||
"""
|
||||
Append C{signal} to C{self.signals}.
|
||||
"""
|
||||
self.signals.append(signal)
|
||||
|
||||
pt = StubProcessTransport()
|
||||
reactor = StubReactor([], [pt])
|
||||
jan = _Janitor(None, None, reactor=reactor)
|
||||
jan._cleanReactor()
|
||||
self.assertEqual(pt.signals, ["KILL"])
|
||||
|
||||
def test_cleanReactorReturnsSelectableStrings(self) -> None:
|
||||
"""
|
||||
The Janitor returns string representations of the selectables that it
|
||||
cleaned up from the reactor cleanup method.
|
||||
"""
|
||||
|
||||
class Selectable:
|
||||
"""
|
||||
A stub Selectable which only has an interesting string
|
||||
representation.
|
||||
"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "(SELECTABLE!)"
|
||||
|
||||
reactor = StubReactor([], [Selectable()])
|
||||
jan = _Janitor(None, None, reactor=reactor)
|
||||
self.assertEqual(jan._cleanReactor(), ["(SELECTABLE!)"])
|
||||
|
||||
def test_postCaseCleanupNoErrors(self) -> None:
|
||||
"""
|
||||
The post-case cleanup method will return True and not call C{addError}
|
||||
on the result if there are no pending calls.
|
||||
"""
|
||||
reactor = StubReactor([])
|
||||
test = object()
|
||||
reporter = StubErrorReporter()
|
||||
jan = _Janitor(test, reporter, reactor=reactor)
|
||||
self.assertTrue(jan.postCaseCleanup())
|
||||
self.assertEqual(reporter.errors, [])
|
||||
|
||||
def test_postCaseCleanupWithErrors(self) -> None:
|
||||
"""
|
||||
The post-case cleanup method will return False and call C{addError} on
|
||||
the result with a L{DirtyReactorAggregateError} Failure if there are
|
||||
pending calls.
|
||||
"""
|
||||
delayedCall = DelayedCall(
|
||||
300, lambda: None, (), {}, lambda x: None, lambda x: None, seconds=lambda: 0
|
||||
)
|
||||
delayedCallString = str(delayedCall)
|
||||
reactor = StubReactor([delayedCall], [])
|
||||
test = object()
|
||||
reporter = StubErrorReporter()
|
||||
jan = _Janitor(test, reporter, reactor=reactor)
|
||||
self.assertFalse(jan.postCaseCleanup())
|
||||
self.assertEqual(len(reporter.errors), 1)
|
||||
self.assertEqual(reporter.errors[0][1].value.delayedCalls, [delayedCallString])
|
||||
|
||||
def test_postClassCleanupNoErrors(self) -> None:
|
||||
"""
|
||||
The post-class cleanup method will not call C{addError} on the result
|
||||
if there are no pending calls or selectables.
|
||||
"""
|
||||
reactor = StubReactor([])
|
||||
test = object()
|
||||
reporter = StubErrorReporter()
|
||||
jan = _Janitor(test, reporter, reactor=reactor)
|
||||
jan.postClassCleanup()
|
||||
self.assertEqual(reporter.errors, [])
|
||||
|
||||
def test_postClassCleanupWithPendingCallErrors(self) -> None:
|
||||
"""
|
||||
The post-class cleanup method call C{addError} on the result with a
|
||||
L{DirtyReactorAggregateError} Failure if there are pending calls.
|
||||
"""
|
||||
delayedCall = DelayedCall(
|
||||
300, lambda: None, (), {}, lambda x: None, lambda x: None, seconds=lambda: 0
|
||||
)
|
||||
delayedCallString = str(delayedCall)
|
||||
reactor = StubReactor([delayedCall], [])
|
||||
test = object()
|
||||
reporter = StubErrorReporter()
|
||||
jan = _Janitor(test, reporter, reactor=reactor)
|
||||
jan.postClassCleanup()
|
||||
self.assertEqual(len(reporter.errors), 1)
|
||||
self.assertEqual(reporter.errors[0][1].value.delayedCalls, [delayedCallString])
|
||||
|
||||
def test_postClassCleanupWithSelectableErrors(self) -> None:
|
||||
"""
|
||||
The post-class cleanup method call C{addError} on the result with a
|
||||
L{DirtyReactorAggregateError} Failure if there are selectables.
|
||||
"""
|
||||
selectable = "SELECTABLE HERE"
|
||||
reactor = StubReactor([], [selectable])
|
||||
test = object()
|
||||
reporter = StubErrorReporter()
|
||||
jan = _Janitor(test, reporter, reactor=reactor)
|
||||
jan.postClassCleanup()
|
||||
self.assertEqual(len(reporter.errors), 1)
|
||||
self.assertEqual(reporter.errors[0][1].value.selectables, [repr(selectable)])
|
||||
|
||||
|
||||
class RemoveSafelyTests(SynchronousTestCase):
|
||||
"""
|
||||
Tests for L{util._removeSafely}.
|
||||
"""
|
||||
|
||||
def test_removeSafelyNoTrialMarker(self) -> None:
|
||||
"""
|
||||
If a path doesn't contain a node named C{"_trial_marker"}, that path is
|
||||
not removed by L{util._removeSafely} and a L{util._NoTrialMarker}
|
||||
exception is raised instead.
|
||||
"""
|
||||
directory = self.mktemp().encode("utf-8")
|
||||
os.mkdir(directory)
|
||||
dirPath = filepath.FilePath(directory)
|
||||
self.assertRaises(util._NoTrialMarker, util._removeSafely, dirPath)
|
||||
|
||||
def test_removeSafelyRemoveFailsMoveSucceeds(self) -> None:
|
||||
"""
|
||||
If an L{OSError} is raised while removing a path in
|
||||
L{util._removeSafely}, an attempt is made to move the path to a new
|
||||
name.
|
||||
"""
|
||||
|
||||
def dummyRemove() -> None:
|
||||
"""
|
||||
Raise an C{OSError} to emulate the branch of L{util._removeSafely}
|
||||
in which path removal fails.
|
||||
"""
|
||||
raise OSError()
|
||||
|
||||
# Patch stdout so we can check the print statements in _removeSafely
|
||||
out = StringIO()
|
||||
self.patch(sys, "stdout", out)
|
||||
|
||||
# Set up a trial directory with a _trial_marker
|
||||
directory = self.mktemp().encode("utf-8")
|
||||
os.mkdir(directory)
|
||||
dirPath = filepath.FilePath(directory)
|
||||
dirPath.child(b"_trial_marker").touch()
|
||||
# Ensure that path.remove() raises an OSError
|
||||
dirPath.remove = dummyRemove # type: ignore[method-assign]
|
||||
|
||||
util._removeSafely(dirPath)
|
||||
self.assertIn("could not remove FilePath", out.getvalue())
|
||||
|
||||
def test_removeSafelyRemoveFailsMoveFails(self) -> None:
|
||||
"""
|
||||
If an L{OSError} is raised while removing a path in
|
||||
L{util._removeSafely}, an attempt is made to move the path to a new
|
||||
name. If that attempt fails, the L{OSError} is re-raised.
|
||||
"""
|
||||
|
||||
def dummyRemove() -> None:
|
||||
"""
|
||||
Raise an C{OSError} to emulate the branch of L{util._removeSafely}
|
||||
in which path removal fails.
|
||||
"""
|
||||
raise OSError("path removal failed")
|
||||
|
||||
def dummyMoveTo(destination: object, followLinks: bool = True) -> None:
|
||||
"""
|
||||
Raise an C{OSError} to emulate the branch of L{util._removeSafely}
|
||||
in which path movement fails.
|
||||
"""
|
||||
raise OSError("path movement failed")
|
||||
|
||||
# Patch stdout so we can check the print statements in _removeSafely
|
||||
out = StringIO()
|
||||
self.patch(sys, "stdout", out)
|
||||
|
||||
# Set up a trial directory with a _trial_marker
|
||||
directory = self.mktemp().encode("utf-8")
|
||||
os.mkdir(directory)
|
||||
dirPath = filepath.FilePath(directory)
|
||||
dirPath.child(b"_trial_marker").touch()
|
||||
|
||||
# Ensure that path.remove() and path.moveTo() both raise OSErrors
|
||||
dirPath.remove = dummyRemove # type: ignore[method-assign]
|
||||
dirPath.moveTo = dummyMoveTo # type: ignore[method-assign]
|
||||
|
||||
error = self.assertRaises(OSError, util._removeSafely, dirPath)
|
||||
self.assertEqual(str(error), "path movement failed")
|
||||
self.assertIn("could not remove FilePath", out.getvalue())
|
||||
|
||||
|
||||
class ExcInfoTests(SynchronousTestCase):
|
||||
"""
|
||||
Tests for L{excInfoOrFailureToExcInfo}.
|
||||
"""
|
||||
|
||||
def test_excInfo(self) -> None:
|
||||
"""
|
||||
L{excInfoOrFailureToExcInfo} returns exactly what it is passed, if it is
|
||||
passed a tuple like the one returned by L{sys.exc_info}.
|
||||
"""
|
||||
info = (ValueError, ValueError("foo"), None)
|
||||
self.assertTrue(info is excInfoOrFailureToExcInfo(info))
|
||||
|
||||
def test_failure(self) -> None:
|
||||
"""
|
||||
When called with a L{Failure} instance, L{excInfoOrFailureToExcInfo}
|
||||
returns a tuple like the one returned by L{sys.exc_info}, with the
|
||||
elements taken from the type, value, and traceback of the failure.
|
||||
"""
|
||||
try:
|
||||
1 / 0
|
||||
except BaseException:
|
||||
f = Failure()
|
||||
self.assertEqual((f.type, f.value, f.tb), excInfoOrFailureToExcInfo(f))
|
||||
|
||||
|
||||
class AcquireAttributeTests(SynchronousTestCase):
|
||||
"""
|
||||
Tests for L{acquireAttribute}.
|
||||
"""
|
||||
|
||||
def test_foundOnEarlierObject(self) -> None:
|
||||
"""
|
||||
The value returned by L{acquireAttribute} is the value of the requested
|
||||
attribute on the first object in the list passed in which has that
|
||||
attribute.
|
||||
"""
|
||||
self.value = value = object()
|
||||
self.assertTrue(value is acquireAttribute([self, object()], "value"))
|
||||
|
||||
def test_foundOnLaterObject(self) -> None:
|
||||
"""
|
||||
The same as L{test_foundOnEarlierObject}, but for the case where the 2nd
|
||||
element in the object list has the attribute and the first does not.
|
||||
"""
|
||||
self.value = value = object()
|
||||
self.assertTrue(value is acquireAttribute([object(), self], "value"))
|
||||
|
||||
def test_notFoundException(self) -> None:
|
||||
"""
|
||||
If none of the objects passed in the list to L{acquireAttribute} have
|
||||
the requested attribute, L{AttributeError} is raised.
|
||||
"""
|
||||
self.assertRaises(AttributeError, acquireAttribute, [object()], "foo")
|
||||
|
||||
def test_notFoundDefault(self) -> None:
|
||||
"""
|
||||
If none of the objects passed in the list to L{acquireAttribute} have
|
||||
the requested attribute and a default value is given, the default value
|
||||
is returned.
|
||||
"""
|
||||
default = object()
|
||||
self.assertTrue(default is acquireAttribute([object()], "foo", default))
|
||||
|
||||
|
||||
class ListToPhraseTests(SynchronousTestCase):
|
||||
"""
|
||||
Input is transformed into a string representation of the list,
|
||||
with each item separated by delimiter (defaulting to a comma) and the final
|
||||
two being separated by a final delimiter.
|
||||
"""
|
||||
|
||||
def test_empty(self) -> None:
|
||||
"""
|
||||
If things is empty, an empty string is returned.
|
||||
"""
|
||||
sample: list[None] = []
|
||||
expected = ""
|
||||
result = util._listToPhrase(sample, "and")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_oneWord(self) -> None:
|
||||
"""
|
||||
With a single item, the item is returned.
|
||||
"""
|
||||
sample = ["One"]
|
||||
expected = "One"
|
||||
result = util._listToPhrase(sample, "and")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_twoWords(self) -> None:
|
||||
"""
|
||||
Two words are separated by the final delimiter.
|
||||
"""
|
||||
sample = ["One", "Two"]
|
||||
expected = "One and Two"
|
||||
result = util._listToPhrase(sample, "and")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_threeWords(self) -> None:
|
||||
"""
|
||||
With more than two words, the first two are separated by the delimiter.
|
||||
"""
|
||||
sample = ["One", "Two", "Three"]
|
||||
expected = "One, Two, and Three"
|
||||
result = util._listToPhrase(sample, "and")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_fourWords(self) -> None:
|
||||
"""
|
||||
If a delimiter is specified, it is used instead of the default comma.
|
||||
"""
|
||||
sample = ["One", "Two", "Three", "Four"]
|
||||
expected = "One; Two; Three; or Four"
|
||||
result = util._listToPhrase(sample, "or", delimiter="; ")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_notString(self) -> None:
|
||||
"""
|
||||
If something in things is not a string, it is converted into one.
|
||||
"""
|
||||
sample = [1, 2, "three"]
|
||||
expected = "1, 2, and three"
|
||||
result = util._listToPhrase(sample, "and")
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_stringTypeError(self) -> None:
|
||||
"""
|
||||
If things is a string, a TypeError is raised.
|
||||
"""
|
||||
sample = "One, two, three"
|
||||
error = self.assertRaises(TypeError, util._listToPhrase, sample, "and")
|
||||
self.assertEqual(str(error), "Things must be a list or a tuple")
|
||||
|
||||
def test_iteratorTypeError(self) -> None:
|
||||
"""
|
||||
If things is an iterator, a TypeError is raised.
|
||||
"""
|
||||
sample = iter([1, 2, 3])
|
||||
error = self.assertRaises(TypeError, util._listToPhrase, sample, "and")
|
||||
self.assertEqual(str(error), "Things must be a list or a tuple")
|
||||
|
||||
def test_generatorTypeError(self) -> None:
|
||||
"""
|
||||
If things is a generator, a TypeError is raised.
|
||||
"""
|
||||
|
||||
def sample() -> Generator[int, None, None]:
|
||||
yield from range(2)
|
||||
|
||||
error = self.assertRaises(TypeError, util._listToPhrase, sample, "and")
|
||||
self.assertEqual(str(error), "Things must be a list or a tuple")
|
||||
|
||||
|
||||
class OpenTestLogTests(SynchronousTestCase):
|
||||
"""
|
||||
Tests for C{openTestLog}.
|
||||
"""
|
||||
|
||||
def test_utf8(self) -> None:
|
||||
"""
|
||||
The log file is opened in text mode and uses UTF-8 for encoding.
|
||||
"""
|
||||
# Modern OSes are running default locale in UTF-8 and this is what is
|
||||
# used by Python at startup. For this test, we force an ASCII default
|
||||
# encoding so that we can see that UTF-8 is used even if it isn't the
|
||||
# platform default.
|
||||
currentLocale = locale.getlocale()
|
||||
self.addCleanup(locale.setlocale, locale.LC_ALL, currentLocale)
|
||||
locale.setlocale(locale.LC_ALL, ("C", "ascii"))
|
||||
|
||||
text = "Here comes the \N{SUN}"
|
||||
p = filepath.FilePath(self.mktemp())
|
||||
with openTestLog(p) as f:
|
||||
f.write(text)
|
||||
|
||||
with open(p.path, "rb") as f:
|
||||
written = f.read()
|
||||
|
||||
assert_that(text.encode("utf-8"), equal_to(written))
|
||||
|
||||
def test_append(self) -> None:
|
||||
"""
|
||||
The log file is opened in append mode so if runner configuration specifies
|
||||
an existing log file its contents are not wiped out.
|
||||
"""
|
||||
existingText = "Hello, world.\n "
|
||||
newText = "Goodbye, world.\n"
|
||||
expected = f"Hello, world.{os.linesep} Goodbye, world.{os.linesep}"
|
||||
p = filepath.FilePath(self.mktemp())
|
||||
with openTestLog(p) as f:
|
||||
f.write(existingText)
|
||||
with openTestLog(p) as f:
|
||||
f.write(newText)
|
||||
|
||||
assert_that(
|
||||
p.getContent().decode("utf-8"),
|
||||
equal_to(expected),
|
||||
)
|
||||
@@ -0,0 +1,539 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for Trial's interaction with the Python warning system.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import warnings
|
||||
from io import StringIO
|
||||
from typing import Mapping, Sequence, TypeVar
|
||||
from unittest import TestResult
|
||||
|
||||
from twisted.python.filepath import FilePath
|
||||
from twisted.trial._synctest import (
|
||||
_collectWarnings,
|
||||
_setWarningRegistryToNone,
|
||||
_Warning,
|
||||
)
|
||||
from twisted.trial.unittest import SynchronousTestCase
|
||||
|
||||
|
||||
class Mask:
|
||||
"""
|
||||
Hide a test case definition from trial's automatic discovery mechanism.
|
||||
"""
|
||||
|
||||
class MockTests(SynchronousTestCase):
|
||||
"""
|
||||
A test case which is used by L{FlushWarningsTests} to verify behavior
|
||||
which cannot be verified by code inside a single test method.
|
||||
"""
|
||||
|
||||
message = "some warning text"
|
||||
category: type[Warning] = UserWarning
|
||||
|
||||
def test_unflushed(self) -> None:
|
||||
"""
|
||||
Generate a warning and don't flush it.
|
||||
"""
|
||||
warnings.warn(self.message, self.category)
|
||||
|
||||
def test_flushed(self) -> None:
|
||||
"""
|
||||
Generate a warning and flush it.
|
||||
"""
|
||||
warnings.warn(self.message, self.category)
|
||||
self.assertEqual(len(self.flushWarnings()), 1)
|
||||
|
||||
|
||||
_K = TypeVar("_K")
|
||||
_V = TypeVar("_V")
|
||||
|
||||
|
||||
class FlushWarningsTests(SynchronousTestCase):
|
||||
"""
|
||||
Tests for C{flushWarnings}, an API for examining the warnings
|
||||
emitted so far in a test.
|
||||
"""
|
||||
|
||||
def assertDictSubset(self, set: Mapping[_K, _V], subset: Mapping[_K, _V]) -> None:
|
||||
"""
|
||||
Assert that all the keys present in C{subset} are also present in
|
||||
C{set} and that the corresponding values are equal.
|
||||
"""
|
||||
for k, v in subset.items():
|
||||
self.assertEqual(set[k], v)
|
||||
|
||||
def assertDictSubsets(
|
||||
self, sets: Sequence[Mapping[_K, _V]], subsets: Sequence[Mapping[_K, _V]]
|
||||
) -> None:
|
||||
"""
|
||||
For each pair of corresponding elements in C{sets} and C{subsets},
|
||||
assert that the element from C{subsets} is a subset of the element from
|
||||
C{sets}.
|
||||
"""
|
||||
self.assertEqual(len(sets), len(subsets))
|
||||
for a, b in zip(sets, subsets):
|
||||
self.assertDictSubset(a, b)
|
||||
|
||||
def test_none(self) -> None:
|
||||
"""
|
||||
If no warnings are emitted by a test, C{flushWarnings} returns an empty
|
||||
list.
|
||||
"""
|
||||
self.assertEqual(self.flushWarnings(), [])
|
||||
|
||||
def test_several(self) -> None:
|
||||
"""
|
||||
If several warnings are emitted by a test, C{flushWarnings} returns a
|
||||
list containing all of them.
|
||||
"""
|
||||
firstMessage = "first warning message"
|
||||
firstCategory = UserWarning
|
||||
warnings.warn(message=firstMessage, category=firstCategory)
|
||||
|
||||
secondMessage = "second warning message"
|
||||
secondCategory = RuntimeWarning
|
||||
warnings.warn(message=secondMessage, category=secondCategory)
|
||||
|
||||
self.assertDictSubsets(
|
||||
self.flushWarnings(),
|
||||
[
|
||||
{"category": firstCategory, "message": firstMessage},
|
||||
{"category": secondCategory, "message": secondMessage},
|
||||
],
|
||||
)
|
||||
|
||||
def test_repeated(self) -> None:
|
||||
"""
|
||||
The same warning triggered twice from the same place is included twice
|
||||
in the list returned by C{flushWarnings}.
|
||||
"""
|
||||
message = "the message"
|
||||
category = RuntimeWarning
|
||||
for i in range(2):
|
||||
warnings.warn(message=message, category=category)
|
||||
|
||||
self.assertDictSubsets(
|
||||
self.flushWarnings(), [{"category": category, "message": message}] * 2
|
||||
)
|
||||
|
||||
def test_cleared(self) -> None:
|
||||
"""
|
||||
After a particular warning event has been returned by C{flushWarnings},
|
||||
it is not returned by subsequent calls.
|
||||
"""
|
||||
message = "the message"
|
||||
category = RuntimeWarning
|
||||
warnings.warn(message=message, category=category)
|
||||
self.assertDictSubsets(
|
||||
self.flushWarnings(), [{"category": category, "message": message}]
|
||||
)
|
||||
self.assertEqual(self.flushWarnings(), [])
|
||||
|
||||
def test_unflushed(self) -> None:
|
||||
"""
|
||||
Any warnings emitted by a test which are not flushed are emitted to the
|
||||
Python warning system.
|
||||
"""
|
||||
result = TestResult()
|
||||
case = Mask.MockTests("test_unflushed")
|
||||
case.run(result)
|
||||
warningsShown = self.flushWarnings([Mask.MockTests.test_unflushed])
|
||||
self.assertEqual(warningsShown[0]["message"], "some warning text")
|
||||
self.assertIdentical(warningsShown[0]["category"], UserWarning)
|
||||
|
||||
where = type(case).test_unflushed.__code__
|
||||
filename = where.co_filename
|
||||
# If someone edits MockTests.test_unflushed, the value added to
|
||||
# firstlineno might need to change.
|
||||
lineno = where.co_firstlineno + 4
|
||||
|
||||
self.assertEqual(warningsShown[0]["filename"], filename)
|
||||
self.assertEqual(warningsShown[0]["lineno"], lineno)
|
||||
|
||||
self.assertEqual(len(warningsShown), 1)
|
||||
|
||||
def test_flushed(self) -> None:
|
||||
"""
|
||||
Any warnings emitted by a test which are flushed are not emitted to the
|
||||
Python warning system.
|
||||
"""
|
||||
result = TestResult()
|
||||
case = Mask.MockTests("test_flushed")
|
||||
output = StringIO()
|
||||
monkey = self.patch(sys, "stdout", output)
|
||||
case.run(result)
|
||||
monkey.restore()
|
||||
self.assertEqual(output.getvalue(), "")
|
||||
|
||||
def test_warningsConfiguredAsErrors(self) -> None:
|
||||
"""
|
||||
If a warnings filter has been installed which turns warnings into
|
||||
exceptions, tests have an error added to the reporter for them for each
|
||||
unflushed warning.
|
||||
"""
|
||||
|
||||
class CustomWarning(Warning):
|
||||
pass
|
||||
|
||||
result = TestResult()
|
||||
case = Mask.MockTests("test_unflushed")
|
||||
case.category = CustomWarning
|
||||
|
||||
originalWarnings = warnings.filters[:]
|
||||
try:
|
||||
warnings.simplefilter("error")
|
||||
case.run(result)
|
||||
self.assertEqual(len(result.errors), 1)
|
||||
self.assertIdentical(result.errors[0][0], case)
|
||||
self.assertTrue(
|
||||
# Different python versions differ in whether they report the
|
||||
# fully qualified class name or just the class name.
|
||||
result.errors[0][1]
|
||||
.splitlines()[-1]
|
||||
.endswith("CustomWarning: some warning text")
|
||||
)
|
||||
finally:
|
||||
warnings.filters[:] = originalWarnings # type: ignore[index]
|
||||
|
||||
def test_flushedWarningsConfiguredAsErrors(self) -> None:
|
||||
"""
|
||||
If a warnings filter has been installed which turns warnings into
|
||||
exceptions, tests which emit those warnings but flush them do not have
|
||||
an error added to the reporter.
|
||||
"""
|
||||
|
||||
class CustomWarning(Warning):
|
||||
pass
|
||||
|
||||
result = TestResult()
|
||||
case = Mask.MockTests("test_flushed")
|
||||
case.category = CustomWarning
|
||||
|
||||
originalWarnings = warnings.filters[:]
|
||||
try:
|
||||
warnings.simplefilter("error")
|
||||
case.run(result)
|
||||
self.assertEqual(result.errors, [])
|
||||
finally:
|
||||
warnings.filters[:] = originalWarnings # type: ignore[index]
|
||||
|
||||
def test_multipleFlushes(self) -> None:
|
||||
"""
|
||||
Any warnings emitted after a call to C{flushWarnings} can be flushed by
|
||||
another call to C{flushWarnings}.
|
||||
"""
|
||||
warnings.warn("first message")
|
||||
self.assertEqual(len(self.flushWarnings()), 1)
|
||||
warnings.warn("second message")
|
||||
self.assertEqual(len(self.flushWarnings()), 1)
|
||||
|
||||
def test_filterOnOffendingFunction(self) -> None:
|
||||
"""
|
||||
The list returned by C{flushWarnings} includes only those
|
||||
warnings which refer to the source of the function passed as the value
|
||||
for C{offendingFunction}, if a value is passed for that parameter.
|
||||
"""
|
||||
firstMessage = "first warning text"
|
||||
firstCategory = UserWarning
|
||||
|
||||
def one() -> None:
|
||||
warnings.warn(firstMessage, firstCategory, stacklevel=1)
|
||||
|
||||
secondMessage = "some text"
|
||||
secondCategory = RuntimeWarning
|
||||
|
||||
def two() -> None:
|
||||
warnings.warn(secondMessage, secondCategory, stacklevel=1)
|
||||
|
||||
one()
|
||||
two()
|
||||
|
||||
self.assertDictSubsets(
|
||||
self.flushWarnings(offendingFunctions=[one]),
|
||||
[{"category": firstCategory, "message": firstMessage}],
|
||||
)
|
||||
self.assertDictSubsets(
|
||||
self.flushWarnings(offendingFunctions=[two]),
|
||||
[{"category": secondCategory, "message": secondMessage}],
|
||||
)
|
||||
|
||||
def test_functionBoundaries(self) -> None:
|
||||
"""
|
||||
Verify that warnings emitted at the very edges of a function are still
|
||||
determined to be emitted from that function.
|
||||
"""
|
||||
|
||||
def warner() -> None:
|
||||
warnings.warn("first line warning")
|
||||
warnings.warn("internal line warning")
|
||||
warnings.warn("last line warning")
|
||||
|
||||
warner()
|
||||
self.assertEqual(len(self.flushWarnings(offendingFunctions=[warner])), 3)
|
||||
|
||||
def test_invalidFilter(self) -> None:
|
||||
"""
|
||||
If an object which is neither a function nor a method is included in the
|
||||
C{offendingFunctions} list, C{flushWarnings} raises L{ValueError}. Such
|
||||
a call flushes no warnings.
|
||||
"""
|
||||
warnings.warn("oh no")
|
||||
self.assertRaises(ValueError, self.flushWarnings, [None])
|
||||
self.assertEqual(len(self.flushWarnings()), 1)
|
||||
|
||||
def test_missingSource(self) -> None:
|
||||
"""
|
||||
Warnings emitted by a function the source code of which is not
|
||||
available can still be flushed.
|
||||
"""
|
||||
package = FilePath(self.mktemp().encode("utf-8")).child(
|
||||
b"twisted_private_helper"
|
||||
)
|
||||
package.makedirs()
|
||||
package.child(b"__init__.py").setContent(b"")
|
||||
package.child(b"missingsourcefile.py").setContent(
|
||||
b"""
|
||||
import warnings
|
||||
def foo():
|
||||
warnings.warn("oh no")
|
||||
"""
|
||||
)
|
||||
pathEntry = package.parent().path.decode("utf-8")
|
||||
sys.path.insert(0, pathEntry)
|
||||
self.addCleanup(sys.path.remove, pathEntry)
|
||||
|
||||
# since import is a synthetic thing that we made up just for this test,
|
||||
# it's a local type ignore rather than being present in the mypy config
|
||||
# file like everything else
|
||||
from twisted_private_helper import ( # type: ignore[import-not-found]
|
||||
missingsourcefile,
|
||||
)
|
||||
|
||||
self.addCleanup(sys.modules.pop, "twisted_private_helper")
|
||||
self.addCleanup(sys.modules.pop, missingsourcefile.__name__)
|
||||
package.child(b"missingsourcefile.py").remove()
|
||||
|
||||
missingsourcefile.foo()
|
||||
self.assertEqual(len(self.flushWarnings([missingsourcefile.foo])), 1)
|
||||
|
||||
def test_renamedSource(self) -> None:
|
||||
"""
|
||||
Warnings emitted by a function defined in a file which has been renamed
|
||||
since it was initially compiled can still be flushed.
|
||||
|
||||
This is testing the code which specifically supports working around the
|
||||
unfortunate behavior of CPython to write a .py source file name into
|
||||
the .pyc files it generates and then trust that it is correct in
|
||||
various places. If source files are renamed, .pyc files may not be
|
||||
regenerated, but they will contain incorrect filenames.
|
||||
"""
|
||||
package = FilePath(self.mktemp().encode("utf-8")).child(
|
||||
b"twisted_private_helper"
|
||||
)
|
||||
package.makedirs()
|
||||
package.child(b"__init__.py").setContent(b"")
|
||||
package.child(b"module.py").setContent(
|
||||
b"""
|
||||
import warnings
|
||||
def foo():
|
||||
warnings.warn("oh no")
|
||||
"""
|
||||
)
|
||||
pathEntry = package.parent().path.decode("utf-8")
|
||||
sys.path.insert(0, pathEntry)
|
||||
self.addCleanup(sys.path.remove, pathEntry)
|
||||
|
||||
# Import it to cause pycs to be generated
|
||||
from twisted_private_helper import module
|
||||
|
||||
# Clean up the state resulting from that import; we're not going to use
|
||||
# this module, so it should go away.
|
||||
del sys.modules["twisted_private_helper"]
|
||||
del sys.modules[module.__name__]
|
||||
|
||||
# Some Python versions have extra state related to the just
|
||||
# imported/renamed package. Clean it up too. See also
|
||||
# http://bugs.python.org/issue15912
|
||||
try:
|
||||
from importlib import invalidate_caches
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
invalidate_caches()
|
||||
|
||||
# Rename the source directory
|
||||
package.moveTo(package.sibling(b"twisted_renamed_helper"))
|
||||
|
||||
# Import the newly renamed version
|
||||
from twisted_renamed_helper import module # type: ignore[import-not-found]
|
||||
|
||||
self.addCleanup(sys.modules.pop, "twisted_renamed_helper")
|
||||
self.addCleanup(sys.modules.pop, module.__name__)
|
||||
|
||||
# Generate the warning
|
||||
module.foo()
|
||||
|
||||
# Flush it
|
||||
self.assertEqual(len(self.flushWarnings([module.foo])), 1)
|
||||
|
||||
def test_offendingFunctions_deep_branch(self) -> None:
|
||||
"""
|
||||
In Python 3.6 the dis.findlinestarts documented behaviour
|
||||
was changed such that the reported lines might not be sorted ascending.
|
||||
In Python 3.10 PEP 626 introduced byte-code change such that the last
|
||||
line of a function wasn't always associated with the last byte-code.
|
||||
In the past flushWarning was not detecting that such a function was
|
||||
associated with any warnings.
|
||||
"""
|
||||
|
||||
def foo(a: int = 1, b: int = 1) -> None:
|
||||
if a:
|
||||
if b:
|
||||
warnings.warn("oh no")
|
||||
else:
|
||||
pass
|
||||
|
||||
# Generate the warning
|
||||
foo()
|
||||
|
||||
# Flush it
|
||||
self.assertEqual(len(self.flushWarnings([foo])), 1)
|
||||
|
||||
|
||||
class FakeWarning(Warning):
|
||||
pass
|
||||
|
||||
|
||||
class CollectWarningsTests(SynchronousTestCase):
|
||||
"""
|
||||
Tests for L{_collectWarnings}.
|
||||
"""
|
||||
|
||||
def test_callsObserver(self) -> None:
|
||||
"""
|
||||
L{_collectWarnings} calls the observer with each emitted warning.
|
||||
"""
|
||||
firstMessage = "dummy calls observer warning"
|
||||
secondMessage = firstMessage[::-1]
|
||||
thirdMessage = Warning(1, 2, 3)
|
||||
events: list[str | _Warning] = []
|
||||
|
||||
def f() -> None:
|
||||
events.append("call")
|
||||
warnings.warn(firstMessage)
|
||||
warnings.warn(secondMessage)
|
||||
warnings.warn(thirdMessage)
|
||||
events.append("returning")
|
||||
|
||||
_collectWarnings(events.append, f)
|
||||
|
||||
self.assertEqual(events[0], "call")
|
||||
assert isinstance(events[1], _Warning)
|
||||
self.assertEqual(events[1].message, firstMessage)
|
||||
assert isinstance(events[2], _Warning)
|
||||
self.assertEqual(events[2].message, secondMessage)
|
||||
assert isinstance(events[3], _Warning)
|
||||
self.assertEqual(events[3].message, str(thirdMessage))
|
||||
self.assertEqual(events[4], "returning")
|
||||
self.assertEqual(len(events), 5)
|
||||
|
||||
def test_suppresses(self) -> None:
|
||||
"""
|
||||
Any warnings emitted by a call to a function passed to
|
||||
L{_collectWarnings} are not actually emitted to the warning system.
|
||||
"""
|
||||
output = StringIO()
|
||||
self.patch(sys, "stdout", output)
|
||||
_collectWarnings(lambda x: None, warnings.warn, "text")
|
||||
self.assertEqual(output.getvalue(), "")
|
||||
|
||||
def test_callsFunction(self) -> None:
|
||||
"""
|
||||
L{_collectWarnings} returns the result of calling the callable passed to
|
||||
it with the parameters given.
|
||||
"""
|
||||
arguments = []
|
||||
value = object()
|
||||
|
||||
def f(*args: object, **kwargs: object) -> object:
|
||||
arguments.append((args, kwargs))
|
||||
return value
|
||||
|
||||
result = _collectWarnings(lambda x: None, f, 1, "a", b=2, c="d")
|
||||
self.assertEqual(arguments, [((1, "a"), {"b": 2, "c": "d"})])
|
||||
self.assertIdentical(result, value)
|
||||
|
||||
def test_duplicateWarningCollected(self) -> None:
|
||||
"""
|
||||
Subsequent emissions of a warning from a particular source site can be
|
||||
collected by L{_collectWarnings}. In particular, the per-module
|
||||
emitted-warning cache should be bypassed (I{__warningregistry__}).
|
||||
"""
|
||||
# Make sure the worst case is tested: if __warningregistry__ isn't in a
|
||||
# module's globals, then the warning system will add it and start using
|
||||
# it to avoid emitting duplicate warnings. Delete __warningregistry__
|
||||
# to ensure that even modules which are first imported as a test is
|
||||
# running still interact properly with the warning system.
|
||||
global __warningregistry__
|
||||
del __warningregistry__ # type: ignore[name-defined]
|
||||
|
||||
def f() -> None:
|
||||
warnings.warn("foo")
|
||||
|
||||
warnings.simplefilter("default")
|
||||
f()
|
||||
events: list[_Warning] = []
|
||||
_collectWarnings(events.append, f)
|
||||
self.assertEqual(len(events), 1)
|
||||
self.assertEqual(events[0].message, "foo")
|
||||
self.assertEqual(len(self.flushWarnings()), 1)
|
||||
|
||||
def test_immutableObject(self) -> None:
|
||||
"""
|
||||
L{_collectWarnings}'s behavior is not altered by the presence of an
|
||||
object which cannot have attributes set on it as a value in
|
||||
C{sys.modules}.
|
||||
"""
|
||||
key = object()
|
||||
sys.modules[key] = key # type: ignore[index, assignment]
|
||||
self.addCleanup(sys.modules.pop, key) # type: ignore[arg-type]
|
||||
self.test_duplicateWarningCollected()
|
||||
|
||||
def test_setWarningRegistryChangeWhileIterating(self) -> None:
|
||||
"""
|
||||
If the dictionary passed to L{_setWarningRegistryToNone} changes size
|
||||
partway through the process, C{_setWarningRegistryToNone} continues to
|
||||
set C{__warningregistry__} to L{None} on the rest of the values anyway.
|
||||
|
||||
|
||||
This might be caused by C{sys.modules} containing something that's not
|
||||
really a module and imports things on setattr. py.test does this, as
|
||||
does L{twisted.python.deprecate.deprecatedModuleAttribute}.
|
||||
"""
|
||||
d: dict[object, A | None] = {}
|
||||
|
||||
class A:
|
||||
def __init__(self, key: object) -> None:
|
||||
self.__dict__["_key"] = key
|
||||
|
||||
def __setattr__(self, value: object, item: object) -> None:
|
||||
d[self._key] = None # type: ignore[attr-defined]
|
||||
|
||||
key1 = object()
|
||||
key2 = object()
|
||||
d[key1] = A(key2)
|
||||
|
||||
key3 = object()
|
||||
key4 = object()
|
||||
d[key3] = A(key4)
|
||||
|
||||
_setWarningRegistryToNone(d)
|
||||
|
||||
# If both key2 and key4 were added, then both A instanced were
|
||||
# processed.
|
||||
self.assertEqual({key1, key2, key3, key4}, set(d.keys()))
|
||||
@@ -0,0 +1,23 @@
|
||||
import unittest
|
||||
|
||||
from twisted.internet import defer
|
||||
|
||||
# Used in test_tests.UnhandledDeferredTests
|
||||
|
||||
|
||||
class TestBleeding(unittest.TestCase):
|
||||
"""This test creates an unhandled Deferred and leaves it in a cycle.
|
||||
|
||||
The Deferred is left in a cycle so that the garbage collector won't pick it
|
||||
up immediately. We were having some problems where unhandled Deferreds in
|
||||
one test were failing random other tests. (See #1507, #1213)
|
||||
"""
|
||||
|
||||
def test_unhandledDeferred(self):
|
||||
try:
|
||||
1 / 0
|
||||
except ZeroDivisionError:
|
||||
f = defer.fail()
|
||||
# these two lines create the cycle. don't remove them
|
||||
l = [f]
|
||||
l.append(l)
|
||||
Reference in New Issue
Block a user