diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 66d1a3970..b84b02b29 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -182,7 +182,7 @@ jobs: - name: run tests run: | - python -m pytest --maxfail 2 --cov zmq -m "not wheel and not new_console" -v zmq/tests + pytest --maxfail 2 --cov zmq -m "not wheel and not new_console" -v - name: upload coverage run: codecov diff --git a/pyproject.toml b/pyproject.toml index 075639dde..9118c8e93 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,6 +67,9 @@ exclude = [ ] quote-style = "preserve" +[tool.ruff.isort] +known-first-party = ["zmq", "zmq_test_utils"] + [tool.ruff.lint] select = [ "E", @@ -85,7 +88,7 @@ exclude = ["buildutils/templates/*"] [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F4", "E4"] "__init__.pyi" = ["F4", "E4"] -"zmq/tests/*" = ["E4", "F4"] +"tests/*" = ["E4", "F4"] "docs/source/conf.py" = ["E4"] "zmq/eventloop/*" = ["E402"] "zmq/ssh/forward.py" = ["E"] diff --git a/pytest.ini b/pytest.ini index 1d19f8419..ce7fc0793 100644 --- a/pytest.ini +++ b/pytest.ini @@ -5,7 +5,6 @@ markers = new_console: these tests create a new console wheel: these tests are for installs from a wheel, not dev-installs testpaths = - zmq/tests - + tests # automatically run coroutine tests with asyncio asyncio_mode = auto diff --git a/zmq/tests/conftest.py b/tests/conftest.py similarity index 100% rename from zmq/tests/conftest.py rename to tests/conftest.py diff --git a/zmq/tests/cython_ext.pyx b/tests/cython_ext.pyx similarity index 100% rename from zmq/tests/cython_ext.pyx rename to tests/cython_ext.pyx diff --git a/zmq/tests/test_asyncio.py b/tests/test_asyncio.py similarity index 100% rename from zmq/tests/test_asyncio.py rename to tests/test_asyncio.py diff --git a/zmq/tests/test_auth.py b/tests/test_auth.py similarity index 99% rename from zmq/tests/test_auth.py rename to tests/test_auth.py index 211851abe..cf6e8a0c4 100644 --- a/zmq/tests/test_auth.py +++ b/tests/test_auth.py @@ -14,7 +14,7 @@ import zmq import zmq.asyncio import zmq.auth -from zmq.tests import SkipTest, skip_pypy +from zmq_test_utils import SkipTest, skip_pypy try: import tornado diff --git a/zmq/tests/test_cffi_backend.py b/tests/test_cffi_backend.py similarity index 99% rename from zmq/tests/test_cffi_backend.py rename to tests/test_cffi_backend.py index c7ca60525..c35d88048 100644 --- a/zmq/tests/test_cffi_backend.py +++ b/tests/test_cffi_backend.py @@ -1,7 +1,7 @@ import time from unittest import TestCase -from zmq.tests import SkipTest +from zmq_test_utils import SkipTest try: from zmq.backend.cffi import ( # type: ignore diff --git a/zmq/tests/test_constants.py b/tests/test_constants.py similarity index 100% rename from zmq/tests/test_constants.py rename to tests/test_constants.py diff --git a/zmq/tests/test_context.py b/tests/test_context.py similarity index 99% rename from zmq/tests/test_context.py rename to tests/test_context.py index 718add3f3..d4ec5da0e 100644 --- a/zmq/tests/test_context.py +++ b/tests/test_context.py @@ -14,7 +14,7 @@ from pytest import mark import zmq -from zmq.tests import PYPY, BaseZMQTestCase, GreenTest, SkipTest +from zmq_test_utils import PYPY, BaseZMQTestCase, GreenTest, SkipTest class KwargTestSocket(zmq.Socket): diff --git a/zmq/tests/test_cython.py b/tests/test_cython.py similarity index 75% rename from zmq/tests/test_cython.py rename to tests/test_cython.py index a86a657b3..7b958bc57 100644 --- a/zmq/tests/test_cython.py +++ b/tests/test_cython.py @@ -26,24 +26,19 @@ ) @pytest.mark.parametrize('language_level', [3, 2]) def test_cython(language_level, request, tmpdir): - assert 'zmq.tests.cython_ext' not in sys.modules - - importers = pyximport.install( + hook = pyximport.install( setup_args=dict(include_dirs=zmq.get_includes()), language_level=language_level, build_dir=str(tmpdir), ) - - cython_ext = None - - def unimport(): - pyximport.uninstall(*importers) - sys.modules.pop('zmq.tests.cython_ext', None) - - request.addfinalizer(unimport) - - # this import tests the compilation - from . import cython_ext + # don't actually need the hook, just the finder + pyximport.uninstall(*hook) + finder = hook[1] + + # loading the module tests the compilation + spec = finder.find_spec("cython_ext", [HERE]) + cython_ext = spec.loader.create_module(spec) + spec.loader.exec_module(cython_ext) assert hasattr(cython_ext, 'send_recv_test') diff --git a/zmq/tests/test_decorators.py b/tests/test_decorators.py similarity index 99% rename from zmq/tests/test_decorators.py rename to tests/test_decorators.py index 762200b58..6f8bbc183 100644 --- a/zmq/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -4,7 +4,7 @@ import zmq from zmq.decorators import context, socket -from zmq.tests import BaseZMQTestCase, term_context +from zmq_test_utils import BaseZMQTestCase, term_context ############################################## # Test cases for @context diff --git a/zmq/tests/test_device.py b/tests/test_device.py similarity index 98% rename from zmq/tests/test_device.py rename to tests/test_device.py index bc2dbf79e..9b4b858e4 100644 --- a/zmq/tests/test_device.py +++ b/tests/test_device.py @@ -5,7 +5,7 @@ import zmq from zmq import devices -from zmq.tests import PYPY, BaseZMQTestCase, GreenTest, SkipTest, have_gevent +from zmq_test_utils import PYPY, BaseZMQTestCase, GreenTest, SkipTest, have_gevent if PYPY: # cleanup of shared Context doesn't work on PyPy diff --git a/zmq/tests/test_draft.py b/tests/test_draft.py similarity index 96% rename from zmq/tests/test_draft.py rename to tests/test_draft.py index 4802a2a0a..88bb7b5a3 100644 --- a/zmq/tests/test_draft.py +++ b/tests/test_draft.py @@ -6,7 +6,7 @@ import pytest import zmq -from zmq.tests import BaseZMQTestCase +from zmq_test_utils import BaseZMQTestCase class TestDraftSockets(BaseZMQTestCase): diff --git a/zmq/tests/test_error.py b/tests/test_error.py similarity index 96% rename from zmq/tests/test_error.py rename to tests/test_error.py index 1ed5ba427..c5599b2f7 100644 --- a/zmq/tests/test_error.py +++ b/tests/test_error.py @@ -5,7 +5,7 @@ import zmq from zmq import Again, ContextTerminated, ZMQError, strerror -from zmq.tests import BaseZMQTestCase +from zmq_test_utils import BaseZMQTestCase class TestZMQError(BaseZMQTestCase): diff --git a/zmq/tests/test_etc.py b/tests/test_etc.py similarity index 100% rename from zmq/tests/test_etc.py rename to tests/test_etc.py diff --git a/zmq/tests/test_ext.py b/tests/test_ext.py similarity index 100% rename from zmq/tests/test_ext.py rename to tests/test_ext.py diff --git a/zmq/tests/test_future.py b/tests/test_future.py similarity index 99% rename from zmq/tests/test_future.py rename to tests/test_future.py index f51258401..c6b5be219 100644 --- a/zmq/tests/test_future.py +++ b/tests/test_future.py @@ -14,7 +14,7 @@ import zmq from zmq.eventloop import future -from zmq.tests import BaseZMQTestCase +from zmq_test_utils import BaseZMQTestCase class TestFutureSocket(BaseZMQTestCase): diff --git a/zmq/tests/test_imports.py b/tests/test_imports.py similarity index 100% rename from zmq/tests/test_imports.py rename to tests/test_imports.py diff --git a/zmq/tests/test_includes.py b/tests/test_includes.py similarity index 100% rename from zmq/tests/test_includes.py rename to tests/test_includes.py diff --git a/zmq/tests/test_ioloop.py b/tests/test_ioloop.py similarity index 100% rename from zmq/tests/test_ioloop.py rename to tests/test_ioloop.py diff --git a/zmq/tests/test_log.py b/tests/test_log.py similarity index 99% rename from zmq/tests/test_log.py rename to tests/test_log.py index 855c17d9c..a9cd40e49 100644 --- a/zmq/tests/test_log.py +++ b/tests/test_log.py @@ -7,7 +7,7 @@ import zmq from zmq.log import handlers -from zmq.tests import BaseZMQTestCase +from zmq_test_utils import BaseZMQTestCase class TestPubLog(BaseZMQTestCase): diff --git a/zmq/tests/test_message.py b/tests/test_message.py similarity index 99% rename from zmq/tests/test_message.py rename to tests/test_message.py index 6020c87e1..77de12cb0 100644 --- a/zmq/tests/test_message.py +++ b/tests/test_message.py @@ -16,7 +16,7 @@ import time import zmq -from zmq.tests import PYPY, BaseZMQTestCase, SkipTest, skip_pypy +from zmq_test_utils import PYPY, BaseZMQTestCase, SkipTest, skip_pypy # some useful constants: diff --git a/zmq/tests/test_monitor.py b/tests/test_monitor.py similarity index 98% rename from zmq/tests/test_monitor.py rename to tests/test_monitor.py index df4e85063..9da02707d 100644 --- a/zmq/tests/test_monitor.py +++ b/tests/test_monitor.py @@ -3,8 +3,8 @@ import zmq import zmq.asyncio -from zmq.tests import require_zmq_4 from zmq.utils.monitor import recv_monitor_message +from zmq_test_utils import require_zmq_4 pytestmark = require_zmq_4 import pytest diff --git a/zmq/tests/test_monqueue.py b/tests/test_monqueue.py similarity index 99% rename from zmq/tests/test_monqueue.py rename to tests/test_monqueue.py index 194142314..af320f63d 100644 --- a/zmq/tests/test_monqueue.py +++ b/tests/test_monqueue.py @@ -6,7 +6,7 @@ import zmq from zmq import devices -from zmq.tests import PYPY, BaseZMQTestCase +from zmq_test_utils import PYPY, BaseZMQTestCase if PYPY or zmq.zmq_version_info() >= (4, 1): # cleanup of shared Context doesn't work on PyPy diff --git a/zmq/tests/test_multipart.py b/tests/test_multipart.py similarity index 92% rename from zmq/tests/test_multipart.py rename to tests/test_multipart.py index b1fa6be04..2e0730ac9 100644 --- a/zmq/tests/test_multipart.py +++ b/tests/test_multipart.py @@ -3,7 +3,7 @@ import zmq -from zmq.tests import BaseZMQTestCase, GreenTest, have_gevent +from zmq_test_utils import BaseZMQTestCase, GreenTest, have_gevent class TestMultipart(BaseZMQTestCase): diff --git a/tests/test_mypy.py b/tests/test_mypy.py new file mode 100644 index 000000000..1cfa0063c --- /dev/null +++ b/tests/test_mypy.py @@ -0,0 +1,50 @@ +""" +Test our typing with mypy +""" + +import os +import sys +from pathlib import Path +from subprocess import PIPE, STDOUT, Popen + +import pytest + +pytest.importorskip("mypy") + +repo_root = Path(__file__).parents[1] + + +print(repo_root) +examples_dir = repo_root / "examples" +mypy_dir = repo_root / "mypy_tests" + + +def run_mypy(*mypy_args): + """Run mypy for a path + + Captures output and reports it on errors + """ + p = Popen( + [sys.executable, "-m", "mypy"] + list(mypy_args), stdout=PIPE, stderr=STDOUT + ) + o, _ = p.communicate() + out = o.decode("utf8", "replace") + print(out) + assert p.returncode == 0, out + + +examples = [path.name for path in examples_dir.glob("*") if path.is_dir()] + + +@pytest.mark.parametrize("example", examples) +def test_mypy_example(example): + example_dir = examples_dir / example + run_mypy("--disallow-untyped-calls", str(example_dir)) + + +mypy_tests = [p.name for p in mypy_dir.glob("*.py")] + + +@pytest.mark.parametrize("filename", mypy_tests) +def test_mypy(filename): + run_mypy("--disallow-untyped-calls", str(mypy_dir / filename)) diff --git a/zmq/tests/test_pair.py b/tests/test_pair.py similarity index 94% rename from zmq/tests/test_pair.py rename to tests/test_pair.py index 22d740784..db74bc87a 100644 --- a/zmq/tests/test_pair.py +++ b/tests/test_pair.py @@ -3,7 +3,7 @@ import zmq -from zmq.tests import BaseZMQTestCase, GreenTest, have_gevent +from zmq_test_utils import BaseZMQTestCase, GreenTest, have_gevent x = b' ' diff --git a/zmq/tests/test_poll.py b/tests/test_poll.py similarity index 99% rename from zmq/tests/test_poll.py rename to tests/test_poll.py index 44a345df1..274b736ea 100644 --- a/zmq/tests/test_poll.py +++ b/tests/test_poll.py @@ -8,7 +8,7 @@ from pytest import mark import zmq -from zmq.tests import GreenTest, PollZMQTestCase, have_gevent +from zmq_test_utils import GreenTest, PollZMQTestCase, have_gevent def wait(): diff --git a/zmq/tests/test_proxy_steerable.py b/tests/test_proxy_steerable.py similarity index 98% rename from zmq/tests/test_proxy_steerable.py rename to tests/test_proxy_steerable.py index 831267064..c7dc5aeae 100644 --- a/zmq/tests/test_proxy_steerable.py +++ b/tests/test_proxy_steerable.py @@ -6,7 +6,7 @@ import zmq from zmq import devices -from zmq.tests import PYPY, BaseZMQTestCase, SkipTest +from zmq_test_utils import PYPY, BaseZMQTestCase, SkipTest if PYPY: # cleanup of shared Context doesn't work on PyPy diff --git a/zmq/tests/test_pubsub.py b/tests/test_pubsub.py similarity index 93% rename from zmq/tests/test_pubsub.py rename to tests/test_pubsub.py index 6cd4dbbaa..24c68587f 100644 --- a/zmq/tests/test_pubsub.py +++ b/tests/test_pubsub.py @@ -5,7 +5,7 @@ import time import zmq -from zmq.tests import BaseZMQTestCase, GreenTest, have_gevent +from zmq_test_utils import BaseZMQTestCase, GreenTest, have_gevent class TestPubSub(BaseZMQTestCase): diff --git a/zmq/tests/test_reqrep.py b/tests/test_reqrep.py similarity index 96% rename from zmq/tests/test_reqrep.py rename to tests/test_reqrep.py index f1d59f9fd..b26886639 100644 --- a/zmq/tests/test_reqrep.py +++ b/tests/test_reqrep.py @@ -3,7 +3,7 @@ import zmq -from zmq.tests import BaseZMQTestCase, GreenTest, have_gevent +from zmq_test_utils import BaseZMQTestCase, GreenTest, have_gevent class TestReqRep(BaseZMQTestCase): diff --git a/zmq/tests/test_retry_eintr.py b/tests/test_retry_eintr.py similarity index 98% rename from zmq/tests/test_retry_eintr.py rename to tests/test_retry_eintr.py index 9493091dc..105f2e467 100644 --- a/zmq/tests/test_retry_eintr.py +++ b/tests/test_retry_eintr.py @@ -8,7 +8,7 @@ from pytest import mark import zmq -from zmq.tests import BaseZMQTestCase, SkipTest +from zmq_test_utils import BaseZMQTestCase, SkipTest # Partially based on EINTRBaseTest from CPython 3.5 eintr_tester diff --git a/zmq/tests/test_security.py b/tests/test_security.py similarity index 99% rename from zmq/tests/test_security.py rename to tests/test_security.py index 684451c00..93145b9f6 100644 --- a/zmq/tests/test_security.py +++ b/tests/test_security.py @@ -9,8 +9,8 @@ from threading import Thread import zmq -from zmq.tests import PYPY, BaseZMQTestCase, SkipTest from zmq.utils import z85 +from zmq_test_utils import PYPY, BaseZMQTestCase, SkipTest USER = b"admin" PASS = b"password" diff --git a/zmq/tests/test_socket.py b/tests/test_socket.py similarity index 99% rename from zmq/tests/test_socket.py rename to tests/test_socket.py index 3c1beef35..71d07432e 100644 --- a/zmq/tests/test_socket.py +++ b/tests/test_socket.py @@ -16,7 +16,7 @@ from pytest import mark import zmq -from zmq.tests import BaseZMQTestCase, GreenTest, SkipTest, have_gevent, skip_pypy +from zmq_test_utils import BaseZMQTestCase, GreenTest, SkipTest, have_gevent, skip_pypy pypy = platform.python_implementation().lower() == 'pypy' windows = platform.platform().lower().startswith('windows') diff --git a/zmq/tests/test_ssh.py b/tests/test_ssh.py similarity index 100% rename from zmq/tests/test_ssh.py rename to tests/test_ssh.py diff --git a/zmq/tests/test_version.py b/tests/test_version.py similarity index 100% rename from zmq/tests/test_version.py rename to tests/test_version.py diff --git a/zmq/tests/test_win32_shim.py b/tests/test_win32_shim.py similarity index 97% rename from zmq/tests/test_win32_shim.py rename to tests/test_win32_shim.py index 449b1b093..ae3c3357a 100644 --- a/zmq/tests/test_win32_shim.py +++ b/tests/test_win32_shim.py @@ -4,8 +4,8 @@ from pytest import mark -from zmq.tests import BaseZMQTestCase from zmq.utils.win32 import allow_interrupt +from zmq_test_utils import BaseZMQTestCase def count_calls(f): diff --git a/zmq/tests/test_z85.py b/tests/test_z85.py similarity index 100% rename from zmq/tests/test_z85.py rename to tests/test_z85.py diff --git a/zmq/tests/test_zmqstream.py b/tests/test_zmqstream.py similarity index 100% rename from zmq/tests/test_zmqstream.py rename to tests/test_zmqstream.py diff --git a/tests/zmq_test_utils.py b/tests/zmq_test_utils.py new file mode 100644 index 000000000..eceaf3ef4 --- /dev/null +++ b/tests/zmq_test_utils.py @@ -0,0 +1,251 @@ +# Copyright (c) PyZMQ Developers. +# Distributed under the terms of the Modified BSD License. + +import os +import platform +import signal +import sys +import time +import warnings +from functools import partial +from threading import Thread +from typing import List +from unittest import SkipTest, TestCase + +from pytest import mark + +import zmq +from zmq.utils import jsonapi + +try: + import gevent + + from zmq import green as gzmq + + have_gevent = True +except ImportError: + have_gevent = False + + +PYPY = platform.python_implementation() == 'PyPy' + +# ----------------------------------------------------------------------------- +# skip decorators (directly from unittest) +# ----------------------------------------------------------------------------- + + +def _id(x): + return x + + +skip_pypy = mark.skipif(PYPY, reason="Doesn't work on PyPy") +require_zmq_4 = mark.skipif(zmq.zmq_version_info() < (4,), reason="requires zmq >= 4") + +# ----------------------------------------------------------------------------- +# Base test class +# ----------------------------------------------------------------------------- + + +def term_context(ctx, timeout): + """Terminate a context with a timeout""" + t = Thread(target=ctx.term) + t.daemon = True + t.start() + t.join(timeout=timeout) + if t.is_alive(): + # reset Context.instance, so the failure to term doesn't corrupt subsequent tests + zmq.sugar.context.Context._instance = None + raise RuntimeError( + "context could not terminate, open sockets likely remain in test" + ) + + +class BaseZMQTestCase(TestCase): + green = False + teardown_timeout = 10 + test_timeout_seconds = int(os.environ.get("ZMQ_TEST_TIMEOUT") or 60) + sockets: List[zmq.Socket] + + @property + def _should_test_timeout(self): + return hasattr(signal, 'SIGALRM') and self.test_timeout_seconds + + @property + def Context(self): + if self.green: + return gzmq.Context + else: + return zmq.Context + + def socket(self, socket_type): + s = self.context.socket(socket_type) + self.sockets.append(s) + return s + + def _alarm_timeout(self, timeout, *args): + raise TimeoutError(f"Test did not complete in {timeout} seconds") + + def setUp(self): + super().setUp() + if self.green and not have_gevent: + raise SkipTest("requires gevent") + + self.context = self.Context.instance() + self.sockets = [] + if self._should_test_timeout: + # use SIGALRM to avoid test hangs + signal.signal( + signal.SIGALRM, partial(self._alarm_timeout, self.test_timeout_seconds) + ) + signal.alarm(self.test_timeout_seconds) + + def tearDown(self): + if self._should_test_timeout: + # cancel the timeout alarm, if there was one + signal.alarm(0) + contexts = {self.context} + while self.sockets: + sock = self.sockets.pop() + contexts.add(sock.context) # in case additional contexts are created + sock.close(0) + for ctx in contexts: + try: + term_context(ctx, self.teardown_timeout) + except Exception: + # reset Context.instance, so the failure to term doesn't corrupt subsequent tests + zmq.sugar.context.Context._instance = None + raise + + super().tearDown() + + def create_bound_pair( + self, type1=zmq.PAIR, type2=zmq.PAIR, interface='tcp://127.0.0.1' + ): + """Create a bound socket pair using a random port.""" + s1 = self.context.socket(type1) + s1.setsockopt(zmq.LINGER, 0) + port = s1.bind_to_random_port(interface) + s2 = self.context.socket(type2) + s2.setsockopt(zmq.LINGER, 0) + s2.connect(f'{interface}:{port}') + self.sockets.extend([s1, s2]) + return s1, s2 + + def ping_pong(self, s1, s2, msg): + s1.send(msg) + msg2 = s2.recv() + s2.send(msg2) + msg3 = s1.recv() + return msg3 + + def ping_pong_json(self, s1, s2, o): + if jsonapi.jsonmod is None: + raise SkipTest("No json library") + s1.send_json(o) + o2 = s2.recv_json() + s2.send_json(o2) + o3 = s1.recv_json() + return o3 + + def ping_pong_pyobj(self, s1, s2, o): + s1.send_pyobj(o) + o2 = s2.recv_pyobj() + s2.send_pyobj(o2) + o3 = s1.recv_pyobj() + return o3 + + def assertRaisesErrno(self, errno, func, *args, **kwargs): + try: + func(*args, **kwargs) + except zmq.ZMQError as e: + self.assertEqual( + e.errno, + errno, + f"wrong error raised, expected '{zmq.ZMQError(errno)}' \ +got '{zmq.ZMQError(e.errno)}'", + ) + else: + self.fail("Function did not raise any error") + + def _select_recv(self, multipart, socket, **kwargs): + """call recv[_multipart] in a way that raises if there is nothing to receive""" + if zmq.zmq_version_info() >= (3, 1, 0): + # zmq 3.1 has a bug, where poll can return false positives, + # so we wait a little bit just in case + # See LIBZMQ-280 on JIRA + time.sleep(0.1) + + r, w, x = zmq.select([socket], [], [], timeout=kwargs.pop('timeout', 5)) + assert len(r) > 0, "Should have received a message" + kwargs['flags'] = zmq.DONTWAIT | kwargs.get('flags', 0) + + recv = socket.recv_multipart if multipart else socket.recv + return recv(**kwargs) + + def recv(self, socket, **kwargs): + """call recv in a way that raises if there is nothing to receive""" + return self._select_recv(False, socket, **kwargs) + + def recv_multipart(self, socket, **kwargs): + """call recv_multipart in a way that raises if there is nothing to receive""" + return self._select_recv(True, socket, **kwargs) + + +class PollZMQTestCase(BaseZMQTestCase): + pass + + +class GreenTest: + """Mixin for making green versions of test classes""" + + green = True + teardown_timeout = 10 + + def assertRaisesErrno(self, errno, func, *args, **kwargs): + if errno == zmq.EAGAIN: + raise SkipTest("Skipping because we're green.") + try: + func(*args, **kwargs) + except zmq.ZMQError: + e = sys.exc_info()[1] + self.assertEqual( + e.errno, + errno, + f"wrong error raised, expected '{zmq.ZMQError(errno)}' \ +got '{zmq.ZMQError(e.errno)}'", + ) + else: + self.fail("Function did not raise any error") + + def tearDown(self): + if self._should_test_timeout: + # cancel the timeout alarm, if there was one + signal.alarm(0) + contexts = {self.context} + while self.sockets: + sock = self.sockets.pop() + contexts.add(sock.context) # in case additional contexts are created + sock.close() + try: + gevent.joinall( + [gevent.spawn(ctx.term) for ctx in contexts], + timeout=self.teardown_timeout, + raise_error=True, + ) + except gevent.Timeout: + raise RuntimeError( + "context could not terminate, open sockets likely remain in test" + ) + + def skip_green(self): + raise SkipTest("Skipping because we are green") + + +def skip_green(f): + def skipping_test(self, *args, **kwargs): + if self.green: + raise SkipTest("Skipping because we are green") + else: + return f(self, *args, **kwargs) + + return skipping_test diff --git a/zmq/tests/__init__.py b/zmq/tests/__init__.py index fbe4d718f..6fc832334 100644 --- a/zmq/tests/__init__.py +++ b/zmq/tests/__init__.py @@ -32,6 +32,10 @@ # ----------------------------------------------------------------------------- # skip decorators (directly from unittest) # ----------------------------------------------------------------------------- +warnings.warn( + "zmq.tests is deprecated in pyzmq 25, we recommend managing your own contexts and sockets.", + DeprecationWarning, +) def _id(x): @@ -95,12 +99,6 @@ def _alarm_timeout(self, timeout, *args): def setUp(self): super().setUp() - if not self._is_pyzmq_test: - warnings.warn( - "zmq.tests.BaseZMQTestCase is deprecated in pyzmq 25, we recommend managing your own contexts and sockets.", - DeprecationWarning, - stacklevel=3, - ) if self.green and not have_gevent: raise SkipTest("requires gevent") diff --git a/zmq/tests/test_mypy.py b/zmq/tests/test_mypy.py deleted file mode 100644 index 48e57ba07..000000000 --- a/zmq/tests/test_mypy.py +++ /dev/null @@ -1,74 +0,0 @@ -""" -Test our typing with mypy -""" - -import os -import sys -from subprocess import PIPE, STDOUT, Popen - -import pytest - -import zmq - -pytest.importorskip("mypy") - -zmq_dir = os.path.dirname(zmq.__file__) - - -def resolve_repo_dir(path): - """Resolve a dir in the repo - - Resolved relative to zmq dir - - fallback on CWD (e.g. test run from repo, zmq installed, not -e) - """ - resolved_path = os.path.join(os.path.dirname(zmq_dir), path) - - # fallback on CWD - if not os.path.exists(resolved_path): - resolved_path = path - return resolved_path - - -examples_dir = resolve_repo_dir("examples") -mypy_dir = resolve_repo_dir("mypy_tests") - - -def run_mypy(*mypy_args): - """Run mypy for a path - - Captures output and reports it on errors - """ - p = Popen( - [sys.executable, "-m", "mypy"] + list(mypy_args), stdout=PIPE, stderr=STDOUT - ) - o, _ = p.communicate() - out = o.decode("utf8", "replace") - print(out) - assert p.returncode == 0, out - - -if os.path.exists(examples_dir): - examples = [ - d - for d in os.listdir(examples_dir) - if os.path.isdir(os.path.join(examples_dir, d)) - ] - - -@pytest.mark.skipif( - not os.path.exists(examples_dir), reason="only test from examples directory" -) -@pytest.mark.parametrize("example", examples) -def test_mypy_example(example): - example_dir = os.path.join(examples_dir, example) - run_mypy("--disallow-untyped-calls", example_dir) - - -if os.path.exists(mypy_dir): - mypy_tests = [p for p in os.listdir(mypy_dir) if p.endswith(".py")] - - -@pytest.mark.parametrize("filename", mypy_tests) -def test_mypy(filename): - run_mypy("--disallow-untyped-calls", os.path.join(mypy_dir, filename))