From 732a74abf0e13193f3a48dcd9abdc7afb85dd5c7 Mon Sep 17 00:00:00 2001 From: Christophe Bornet Date: Tue, 17 Dec 2024 17:20:47 +0100 Subject: [PATCH] Use blockbuster to detect blocking calls --- poetry.lock | 28 +++++++++++++++++-- pyproject.toml | 1 + tests/conftest.py | 18 ++++++++++++ tests/idiomatic/integration/test_admin.py | 3 +- tests/idiomatic/integration/test_ddl_async.py | 14 +++++----- .../integration/test_timeout_async.py | 4 +-- 6 files changed, 56 insertions(+), 12 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0415a434..7c9a9fb8 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. [[package]] name = "anyio" @@ -22,6 +22,20 @@ doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.21.0b1)"] trio = ["trio (>=0.26.1)"] +[[package]] +name = "blockbuster" +version = "1.5.0" +description = "Utility to detect blocking calls in the async event loop" +optional = false +python-versions = ">=3.8" +files = [ + {file = "blockbuster-1.5.0-py3-none-any.whl", hash = "sha256:24c2923980a17fe4231b16942d45a117278b8939eeeba64dec3e7b92a91867c6"}, + {file = "blockbuster-1.5.0.tar.gz", hash = "sha256:f647638413f99fc6a6e47923ce3fa7ff3b9c078b458ed00e6131941db5540fac"}, +] + +[package.dependencies] +forbiddenfruit = ">=0.1.4" + [[package]] name = "certifi" version = "2024.8.30" @@ -353,6 +367,16 @@ docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2. testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] typing = ["typing-extensions (>=4.12.2)"] +[[package]] +name = "forbiddenfruit" +version = "0.1.4" +description = "Patch python built-in objects" +optional = false +python-versions = "*" +files = [ + {file = "forbiddenfruit-0.1.4.tar.gz", hash = "sha256:e3f7e66561a29ae129aac139a85d610dbf3dd896128187ed5454b6421f624253"}, +] + [[package]] name = "h11" version = "0.14.0" @@ -1263,4 +1287,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.8.0" -content-hash = "2b8f685671cd13aa78a1ea410b5a25a6f5284786cf55e3c802d4e2c11f8600d8" +content-hash = "b1a28b2db4719696afc20fa749ee5a8c9add1aa608f02b2d9caa1dcdb42d01a1" diff --git a/pyproject.toml b/pyproject.toml index 2e0e03b6..fe221edf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ pytest-httpserver = "~1.0.8" testcontainers = "~3.7.1" ruff = "^0.6.8" types-toml = "^0.10.8.7" +blockbuster = "~1.5.0" [build-system] requires = ["poetry-core>=1.0.0"] diff --git a/tests/conftest.py b/tests/conftest.py index 4fe30111..1d19fd58 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -20,9 +20,11 @@ import functools import warnings +from collections.abc import Iterator from typing import Any, Awaitable, Callable, TypedDict import pytest +from blockbuster import BlockBuster, blockbuster_ctx from deprecation import UnsupportedWarning from astrapy import DataAPIClient @@ -51,6 +53,22 @@ ) +@pytest.fixture(autouse=True) +def blockbuster() -> Iterator[BlockBuster]: + with blockbuster_ctx() as bb: + bb.functions["os.stat"].can_block_functions.extend( + [ + # TODO: follow discussion in https://github.com/encode/httpx/discussions/3456 + ("httpx/_client.py", {"_init_transport"}), + ("coverage/control.py", {"_should_trace"}), + ] + ) + bb.functions["os.path.abspath"].can_block_functions.append( + ("coverage/control.py", {"_should_trace"}), + ) + yield bb + + class DataAPICredentials(TypedDict): token: str | TokenProvider api_endpoint: str diff --git a/tests/idiomatic/integration/test_admin.py b/tests/idiomatic/integration/test_admin.py index dbb4f69b..1b4828c6 100644 --- a/tests/idiomatic/integration/test_admin.py +++ b/tests/idiomatic/integration/test_admin.py @@ -14,6 +14,7 @@ from __future__ import annotations +import asyncio import time from typing import Any, Awaitable, Callable @@ -73,7 +74,7 @@ async def await_until_true( while not (await acondition()): if time.time() - ini_time > max_seconds: raise ValueError("Timed out on condition.") - time.sleep(poll_interval) + await asyncio.sleep(poll_interval) @pytest.mark.skipif(not IS_ASTRA_DB, reason="Not supported outside of Astra DB") diff --git a/tests/idiomatic/integration/test_ddl_async.py b/tests/idiomatic/integration/test_ddl_async.py index 34f4728c..8bd9777a 100644 --- a/tests/idiomatic/integration/test_ddl_async.py +++ b/tests/idiomatic/integration/test_ddl_async.py @@ -14,7 +14,7 @@ from __future__ import annotations -import time +import asyncio import pytest @@ -146,7 +146,7 @@ async def test_collection_default_id_type_async( assert isinstance(doc["_id"], UUID) await acol.drop() - time.sleep(2) + await asyncio.sleep(2) acol = await async_database.create_collection( ID_TEST_COLLECTION_NAME_ROOT + DefaultIdType.UUIDV6, definition=CollectionDefinition( @@ -168,7 +168,7 @@ async def test_collection_default_id_type_async( assert doc["_id"].version == 6 await acol.drop() - time.sleep(2) + await asyncio.sleep(2) acol = await async_database.create_collection( ID_TEST_COLLECTION_NAME_ROOT + DefaultIdType.UUIDV7, definition=CollectionDefinition( @@ -190,7 +190,7 @@ async def test_collection_default_id_type_async( assert doc["_id"].version == 7 await acol.drop() - time.sleep(2) + await asyncio.sleep(2) acol = await async_database.create_collection( ID_TEST_COLLECTION_NAME_ROOT + DefaultIdType.DEFAULT, definition=CollectionDefinition( @@ -205,7 +205,7 @@ async def test_collection_default_id_type_async( assert acol_options.default_id.default_id_type == DefaultIdType.DEFAULT await acol.drop() - time.sleep(2) + await asyncio.sleep(2) acol = await async_database.create_collection( ID_TEST_COLLECTION_NAME_ROOT + DefaultIdType.OBJECTID, definition=CollectionDefinition( @@ -429,7 +429,7 @@ async def test_tokenless_client_async( @pytest.mark.describe( "test database-from-admin default keyspace per environment, async" ) - async def test_database_from_admin_default_keyspace_per_environment_async( + def test_async_database_from_admin_default_keyspace_per_environment( self, data_api_credentials_kwargs: DataAPICredentials, data_api_credentials_info: DataAPICredentialsInfo, @@ -448,7 +448,7 @@ async def test_database_from_admin_default_keyspace_per_environment_async( @pytest.mark.describe( "test database-from-astradbadmin default keyspace per environment, async" ) - async def test_database_from_astradbadmin_default_keyspace_per_environment_async( + def test_async_database_from_astradbadmin_default_keyspace_per_environment( self, data_api_credentials_kwargs: DataAPICredentials, data_api_credentials_info: DataAPICredentialsInfo, diff --git a/tests/idiomatic/integration/test_timeout_async.py b/tests/idiomatic/integration/test_timeout_async.py index d7ef64a2..d4fb59ec 100644 --- a/tests/idiomatic/integration/test_timeout_async.py +++ b/tests/idiomatic/integration/test_timeout_async.py @@ -80,11 +80,11 @@ async def test_cursor_overalltimeout_exceptions_async( acol = async_empty_collection await acol.insert_many([{"a": 1}] * 1000) - await acol.distinct("a", timeout_ms=20000) + await acol.distinct("a", timeout_ms=60000) with pytest.raises(DataAPITimeoutException): await acol.distinct("a", timeout_ms=1) - await acol.distinct("a", timeout_ms=20000) + await acol.distinct("a", timeout_ms=60000) with pytest.raises(DataAPITimeoutException): await acol.distinct("a", timeout_ms=1)