codekingpro/portable-devtools
114k
1import contextlib
2import os
3import platform
4import socket
5import sys
6import sysconfig
7import textwrap
8import typing
9import unittest
10import warnings
11
12from tornado.testing import bind_unused_port
13
14_TestCaseType = typing.TypeVar("_TestCaseType", bound=typing.Type[unittest.TestCase])
15
16skipIfNonUnix = unittest.skipIf(
17 os.name != "posix" or sys.platform == "cygwin", "non-unix platform"
18)
19
20# Set the environment variable NO_NETWORK=1 to disable any tests that
21# depend on an external network.
22skipIfNoNetwork = unittest.skipIf("NO_NETWORK" in os.environ, "network access disabled")
23
24skipNotCPython = unittest.skipIf(
25 # "CPython" here essentially refers to the traditional synchronous refcounting GC,
26 # so we skip these tests in free-threading builds of cpython too.
27 platform.python_implementation() != "CPython"
28 or sysconfig.get_config_var("Py_GIL_DISABLED"),
29 "Not CPython implementation",
30)
31
32
33def _detect_ipv6():
34 if not socket.has_ipv6:
35 # socket.has_ipv6 check reports whether ipv6 was present at compile
36 # time. It's usually true even when ipv6 doesn't work for other reasons.
37 return False
38 sock = None
39 try:
40 sock = socket.socket(socket.AF_INET6)
41 sock.bind(("::1", 0))
42 except OSError:
43 return False
44 finally:
45 if sock is not None:
46 sock.close()
47 return True
48
49
50skipIfNoIPv6 = unittest.skipIf(not _detect_ipv6(), "ipv6 support not present")
51
52
53def refusing_port():
54 """Returns a local port number that will refuse all connections.
55
56 Return value is (cleanup_func, port); the cleanup function
57 must be called to free the port to be reused.
58 """
59 # On travis-ci port numbers are reassigned frequently. To avoid
60 # collisions with other tests, we use an open client-side socket's
61 # ephemeral port number to ensure that nothing can listen on that
62 # port.
63 server_socket, port = bind_unused_port()
64 server_socket.setblocking(True)
65 client_socket = socket.socket()
66 client_socket.connect(("127.0.0.1", port))
67 conn, client_addr = server_socket.accept()
68 conn.close()
69 server_socket.close()
70 return (client_socket.close, client_addr[1])
71
72
73def exec_test(caller_globals, caller_locals, s):
74 """Execute ``s`` in a given context and return the result namespace.
75
76 Used to define functions for tests in particular python
77 versions that would be syntax errors in older versions.
78 """
79 # Flatten the real global and local namespace into our fake
80 # globals: it's all global from the perspective of code defined
81 # in s.
82 global_namespace = dict(caller_globals, **caller_locals) # type: ignore
83 local_namespace = {} # type: typing.Dict[str, typing.Any]
84 exec(textwrap.dedent(s), global_namespace, local_namespace)
85 return local_namespace
86
87
88@contextlib.contextmanager
89def ignore_deprecation():
90 """Context manager to ignore deprecation warnings."""
91 with warnings.catch_warnings():
92 warnings.simplefilter("ignore", DeprecationWarning)
93 yield
94
95
96ABT_SKIP_MESSAGE = "abstract base class"
97
98
99def abstract_base_test(cls: _TestCaseType) -> _TestCaseType:
100 """Decorator to mark a test class as an "abstract" base class.
101
102 This is different from a regular abstract base class because
103 we do not limit instantiation of the class. (If we did, it would
104 interfere with test discovery). Instead, we prevent the tests from
105 being run.
106
107 Subclasses of an abstract base test are run as normal. There is
108 no support for the ``@abstractmethod`` decorator so there is no runtime
109 check that all such methods are implemented.
110
111 Note that while it is semantically cleaner to modify the test loader
112 to exclude abstract base tests, this is more complicated and would
113 interfere with third-party test runners. This approach degrades
114 gracefully to other tools such as editor-integrated testing.
115 """
116
117 # Type-checking fails due to https://github.com/python/mypy/issues/14458
118 # @functools.wraps(cls)
119 class AbstractBaseWrapper(cls): # type: ignore
120 @classmethod
121 def setUpClass(cls):
122 if cls is AbstractBaseWrapper:
123 raise unittest.SkipTest(ABT_SKIP_MESSAGE)
124 super(AbstractBaseWrapper, cls).setUpClass()
125
126 return AbstractBaseWrapper # type: ignore
127 