ExpectLog,
gen_test,
)
-from tornado.test.util import skipOnTravis
+from tornado.test.util import skipOnTravis, abstract_base_test
from tornado.web import Application, RequestHandler, stream_request_body
from contextlib import closing
self.assertEqual(self.fetch_json("/")["protocol"], "https")
+@abstract_base_test
class UnixSocketTest(AsyncTestCase):
"""HTTPServers can listen on Unix sockets too.
address = ""
def setUp(self):
- if type(self) is UnixSocketTest:
- raise unittest.SkipTest("abstract base class")
super().setUp()
app = Application([("/hello", HelloWorldRequestHandler)])
self.server = HTTPServer(app)
from tornado.httpserver import HTTPServer
from tornado.netutil import Resolver
from tornado.options import define, add_parse_callback, options
+from tornado.test.util import ABT_SKIP_MESSAGE
TEST_MODULES = [
def test_runner_factory(stderr):
+
+ class TornadoTextTestResult(unittest.TextTestResult):
+ def addSkip(self, test, reason):
+ if reason == ABT_SKIP_MESSAGE:
+ # Don't report abstract base tests as skips in our own tooling.
+ #
+ # See tornado.test.util.abstract_base_test.
+ return
+ super().addSkip(test, reason)
+
class TornadoTextTestRunner(unittest.TextTestRunner):
def __init__(self, *args, **kwargs):
kwargs["stream"] = stderr
+ kwargs["resultclass"] = TornadoTextTestResult
super().__init__(*args, **kwargs)
def run(self, test):
import socket
import sys
import textwrap
-import typing # noqa: F401
+import typing
import unittest
import warnings
from tornado.testing import bind_unused_port
+_TestCaseType = typing.TypeVar("_TestCaseType", bound=typing.Type[unittest.TestCase])
+
skipIfNonUnix = unittest.skipIf(
os.name != "posix" or sys.platform == "cygwin", "non-unix platform"
)
with warnings.catch_warnings():
warnings.simplefilter("ignore", DeprecationWarning)
yield
+
+
+ABT_SKIP_MESSAGE = "abstract base class"
+
+
+def abstract_base_test(cls: _TestCaseType) -> _TestCaseType:
+ """Decorator to mark a test class as an "abstract" base class.
+
+ This is different from a regular abstract base class because
+ we do not limit instantiation of the class. (If we did, it would
+ interfere with test discovery). Instead, we prevent the tests from
+ being run.
+
+ Subclasses of an abstract base test are run as normal. There is
+ no support for the ``@abstractmethod`` decorator so there is no runtime
+ check that all such methods are implemented.
+
+ Note that while it is semantically cleaner to modify the test loader
+ to exclude abstract base tests, this is more complicated and would
+ interfere with third-party test runners. This approach degrades
+ gracefully to other tools such as editor-integrated testing.
+ """
+
+ # Type-checking fails due to https://github.com/python/mypy/issues/14458
+ # @functools.wraps(cls)
+ class AbstractBaseWrapper(cls): # type: ignore
+ @classmethod
+ def setUpClass(cls):
+ if cls is AbstractBaseWrapper:
+ raise unittest.SkipTest(ABT_SKIP_MESSAGE)
+ super(AbstractBaseWrapper, cls).setUpClass()
+
+ return AbstractBaseWrapper # type: ignore