From b4aba36b73cd9610e6c9a1c8da0dc5a491fe0524 Mon Sep 17 00:00:00 2001 From: "Steven B." <51370195+sdb9696@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:20:25 +0100 Subject: [PATCH] Use pytest-socket to ensure no tests are performing io (#1133) --- kasa/tests/conftest.py | 37 ++++++++++++++++++++++++++++++++++-- kasa/tests/test_discovery.py | 14 ++++++++------ pyproject.toml | 2 ++ uv.lock | 14 ++++++++++++++ 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/kasa/tests/conftest.py b/kasa/tests/conftest.py index 9cefd7ab..8c6e7634 100644 --- a/kasa/tests/conftest.py +++ b/kasa/tests/conftest.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +import sys import warnings from unittest.mock import MagicMock, patch @@ -87,6 +88,11 @@ def pytest_addoption(parser): def pytest_collection_modifyitems(config, items): if not config.getoption("--ip"): print("Testing against fixtures.") + # pytest_socket doesn't work properly in windows with asyncio + # fine to disable as other platforms will pickup any issues. + if sys.platform == "win32": + for item in items: + item.add_marker(pytest.mark.enable_socket) else: print("Running against ip %s" % config.getoption("--ip")) requires_dummy = pytest.mark.skip( @@ -95,18 +101,45 @@ def pytest_collection_modifyitems(config, items): for item in items: if "requires_dummy" in item.keywords: item.add_marker(requires_dummy) + else: + item.add_marker(pytest.mark.enable_socket) @pytest.fixture(autouse=True, scope="session") -def asyncio_sleep_fixture(): # noqa: PT004 +def asyncio_sleep_fixture(request): # noqa: PT004 """Patch sleep to prevent tests actually waiting.""" orig_asyncio_sleep = asyncio.sleep async def _asyncio_sleep(*_, **__): await orig_asyncio_sleep(0) - with patch("asyncio.sleep", side_effect=_asyncio_sleep): + if request.config.getoption("--ip"): yield + else: + with patch("asyncio.sleep", side_effect=_asyncio_sleep): + yield + + +@pytest.fixture(autouse=True, scope="session") +def mock_datagram_endpoint(request): # noqa: PT004 + """Mock create_datagram_endpoint so it doesn't perform io.""" + + async def _create_datagram_endpoint(protocol_factory, *_, **__): + protocol = protocol_factory() + transport = MagicMock() + try: + return transport, protocol + finally: + protocol.connection_made(transport) + + if request.config.getoption("--ip"): + yield + else: + with patch( + "asyncio.BaseEventLoop.create_datagram_endpoint", + side_effect=_create_datagram_endpoint, + ): + yield # allow mocks to be awaited diff --git a/kasa/tests/test_discovery.py b/kasa/tests/test_discovery.py index 3c388e6a..15d4af9c 100644 --- a/kasa/tests/test_discovery.py +++ b/kasa/tests/test_discovery.py @@ -170,12 +170,13 @@ async def test_discover_single_hostname(discovery_mock, mocker): async def test_discover_credentials(mocker): """Make sure that discover gives credentials precedence over un and pw.""" host = "127.0.0.1" - mocker.patch("kasa.discover._DiscoverProtocol.wait_for_discovery_to_complete") - def mock_discover(self, *_, **__): + async def mock_discover(self, *_, **__): self.discovered_devices = {host: MagicMock()} + self.seen_hosts.add(host) + self._handle_discovered_event() - mocker.patch.object(_DiscoverProtocol, "do_discover", mock_discover) + mocker.patch.object(_DiscoverProtocol, "do_discover", new=mock_discover) dp = mocker.spy(_DiscoverProtocol, "__init__") # Only credentials passed @@ -197,12 +198,13 @@ async def test_discover_credentials(mocker): async def test_discover_single_credentials(mocker): """Make sure that discover_single gives credentials precedence over un and pw.""" host = "127.0.0.1" - mocker.patch("kasa.discover._DiscoverProtocol.wait_for_discovery_to_complete") - def mock_discover(self, *_, **__): + async def mock_discover(self, *_, **__): self.discovered_devices = {host: MagicMock()} + self.seen_hosts.add(host) + self._handle_discovered_event() - mocker.patch.object(_DiscoverProtocol, "do_discover", mock_discover) + mocker.patch.object(_DiscoverProtocol, "do_discover", new=mock_discover) dp = mocker.spy(_DiscoverProtocol, "__init__") # Only credentials passed diff --git a/pyproject.toml b/pyproject.toml index 4d217a87..51f05b5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,7 @@ dev-dependencies = [ "pytest-freezer~=0.4", "mypy~=1.0", "pytest-xdist>=3.6.1", + "pytest-socket>=0.7.0", ] @@ -108,6 +109,7 @@ markers = [ asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" timeout = 10 +addopts = "--disable-socket --allow-unix-socket" [tool.doc8] paths = ["docs"] diff --git a/uv.lock b/uv.lock index 5351999c..c9d4df2a 100644 --- a/uv.lock +++ b/uv.lock @@ -1277,6 +1277,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", size = 9863 }, ] +[[package]] +name = "pytest-socket" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/ff/90c7e1e746baf3d62ce864c479fd53410b534818b9437413903596f81580/pytest_socket-0.7.0.tar.gz", hash = "sha256:71ab048cbbcb085c15a4423b73b619a8b35d6a307f46f78ea46be51b1b7e11b3", size = 12389 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/58/5d14cb5cb59409e491ebe816c47bf81423cd03098ea92281336320ae5681/pytest_socket-0.7.0-py3-none-any.whl", hash = "sha256:7e0f4642177d55d317bbd58fc68c6bd9048d6eadb2d46a89307fa9221336ce45", size = 6754 }, +] + [[package]] name = "pytest-sugar" version = "1.0.0" @@ -1369,6 +1381,7 @@ dev = [ { name = "pytest-cov" }, { name = "pytest-freezer" }, { name = "pytest-mock" }, + { name = "pytest-socket" }, { name = "pytest-sugar" }, { name = "pytest-timeout" }, { name = "pytest-xdist" }, @@ -1407,6 +1420,7 @@ dev = [ { name = "pytest-cov" }, { name = "pytest-freezer", specifier = "~=0.4" }, { name = "pytest-mock" }, + { name = "pytest-socket", specifier = ">=0.7.0" }, { name = "pytest-sugar" }, { name = "pytest-timeout", specifier = "~=2.0" }, { name = "pytest-xdist", specifier = ">=3.6.1" },