From 39fc21a124779a536e5157f32f1a969c7093424c Mon Sep 17 00:00:00 2001 From: Steven B <51370195+sdb9696@users.noreply.github.com> Date: Wed, 5 Jun 2024 20:13:10 +0300 Subject: [PATCH] Use freezegun for testing aes http client delays (#954) --- kasa/tests/test_aestransport.py | 34 +++++++++++++------- poetry.lock | 56 ++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 3 files changed, 79 insertions(+), 12 deletions(-) diff --git a/kasa/tests/test_aestransport.py b/kasa/tests/test_aestransport.py index 00bcb953..232546d5 100644 --- a/kasa/tests/test_aestransport.py +++ b/kasa/tests/test_aestransport.py @@ -15,6 +15,7 @@ import aiohttp import pytest from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import padding as asymmetric_padding +from freezegun.api import FrozenDateTimeFactory from yarl import URL from ..aestransport import AesEncyptionSession, AesTransport, TransportState @@ -287,12 +288,20 @@ async def test_port_override(): @pytest.mark.parametrize( - "request_delay, should_error, should_succeed", - [(0, False, True), (0.125, True, True), (0.3, True, True), (0.7, True, False)], - ids=["No error", "Error then succeed", "Two errors then succeed", "No succeed"], + "device_delay_required, should_error, should_succeed", + [ + pytest.param(0, False, True, id="No error"), + pytest.param(0.125, True, True, id="Error then succeed"), + pytest.param(0.3, True, True, id="Two errors then succeed"), + pytest.param(0.7, True, False, id="No succeed"), + ], ) async def test_device_closes_connection( - mocker, request_delay, should_error, should_succeed + mocker, + freezer: FrozenDateTimeFactory, + device_delay_required, + should_error, + should_succeed, ): """Test the delay logic in http client to deal with devices that close connections after each request. @@ -300,16 +309,19 @@ async def test_device_closes_connection( """ host = "127.0.0.1" - # Speed up the test by dividing all times by a factor. Doesn't seem to work on windows - # but leaving here as a TODO to manipulate system time for testing. - speed_up_factor = 1 - default_delay = HttpClient.WAIT_BETWEEN_REQUESTS_ON_OSERROR / speed_up_factor - request_delay = request_delay / speed_up_factor + default_delay = HttpClient.WAIT_BETWEEN_REQUESTS_ON_OSERROR + mock_aes_device = MockAesDevice( - host, 200, 0, 0, sequential_request_delay=request_delay + host, 200, 0, 0, sequential_request_delay=device_delay_required ) mocker.patch.object(aiohttp.ClientSession, "post", side_effect=mock_aes_device.post) + async def _asyncio_sleep_mock(delay, result=None): + freezer.tick(delay) + return result + + mocker.patch("asyncio.sleep", side_effect=_asyncio_sleep_mock) + config = DeviceConfig(host, credentials=Credentials("foo", "bar")) transport = AesTransport(config=config) transport._http_client.WAIT_BETWEEN_REQUESTS_ON_OSERROR = default_delay @@ -332,7 +344,7 @@ async def test_device_closes_connection( # If the device errors without a delay then it should error immedately ( + 1) # and then the number of times the default delay passes within the request delay window expected_error_count = ( - 0 if not should_error else int(request_delay / default_delay) + 1 + 0 if not should_error else int(device_delay_required / default_delay) + 1 ) for _ in range(3): try: diff --git a/poetry.lock b/poetry.lock index 90667c80..3d28de25 100644 --- a/poetry.lock +++ b/poetry.lock @@ -636,6 +636,20 @@ docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1 testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] typing = ["typing-extensions (>=4.8)"] +[[package]] +name = "freezegun" +version = "1.5.1" +description = "Let your Python tests travel through time" +optional = false +python-versions = ">=3.7" +files = [ + {file = "freezegun-1.5.1-py3-none-any.whl", hash = "sha256:bf111d7138a8abe55ab48a71755673dbaa4ab87f4cff5634a4442dfec34c15f1"}, + {file = "freezegun-1.5.1.tar.gz", hash = "sha256:b29dedfcda6d5e8e083ce71b2b542753ad48cfec44037b3fc79702e2980a89e9"}, +] + +[package.dependencies] +python-dateutil = ">=2.7" + [[package]] name = "frozenlist" version = "1.4.1" @@ -1505,6 +1519,21 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] +[[package]] +name = "pytest-freezer" +version = "0.4.8" +description = "Pytest plugin providing a fixture interface for spulec/freezegun" +optional = false +python-versions = ">= 3.6" +files = [ + {file = "pytest_freezer-0.4.8-py3-none-any.whl", hash = "sha256:644ce7ddb8ba52b92a1df0a80a699bad2b93514c55cf92e9f2517b68ebe74814"}, + {file = "pytest_freezer-0.4.8.tar.gz", hash = "sha256:8ee2f724b3ff3540523fa355958a22e6f4c1c819928b78a7a183ae4248ce6ee6"}, +] + +[package.dependencies] +freezegun = ">=1.0" +pytest = ">=3.6" + [[package]] name = "pytest-mock" version = "3.14.0" @@ -1555,6 +1584,20 @@ files = [ [package.dependencies] pytest = ">=7.0.0" +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + [[package]] name = "pytz" version = "2024.1" @@ -1681,6 +1724,17 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -2156,4 +2210,4 @@ speedups = ["kasa-crypt", "orjson"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "ba5c0da1e413e466834d0954528c7ace6dd9e01d9fb2e626f4c6b23044803aef" +content-hash = "871ef421fe7d48608bcea18b4c41d8bb368e84d74bf7b29db832dc97c5b980ae" diff --git a/pyproject.toml b/pyproject.toml index 08919e86..5f1fc354 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,6 +58,7 @@ codecov = "*" xdoctest = "*" coverage = {version = "*", extras = ["toml"]} pytest-timeout = "^2" +pytest-freezer = "^0.4" [tool.poetry.extras] docs = ["sphinx", "sphinx_rtd_theme", "sphinxcontrib-programoutput", "myst-parser", "docutils"]