From 54273fa88169ccbaf588c1d615601ecff26c135d Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Mon, 15 Jul 2024 13:16:29 -0700 Subject: [PATCH 01/15] Removed ptyest-asyncio restriction --- requirements_test.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements_test.txt b/requirements_test.txt index ea6d086a5..f489cfb2e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,5 +1,5 @@ -pytest < 8.0.0 -pytest-asyncio <= 0.16 +pytest < 9.0.0 +pytest-asyncio pytest-mock pytest-testdox>=1.1.1 pytest-cov From cf689f107d3d5f594a436b516a32ea49e09a8e84 Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Mon, 15 Jul 2024 13:29:05 -0700 Subject: [PATCH 02/15] removed unnecessary manual pytestmark --- pytest.ini | 1 + tests/unit/common/test_async_adapter.py | 1 - tests/unit/iothub/aio/test_async_clients.py | 1 - tests/unit/iothub/aio/test_async_handler_manager.py | 1 - .../provisioning/aio/test_async_provisioning_device_client.py | 1 - 5 files changed, 1 insertion(+), 4 deletions(-) diff --git a/pytest.ini b/pytest.ini index 07ac113a3..23108d61e 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,5 @@ [pytest] +asyncio_mode=auto testdox_format = plaintext addopts = --testdox --timeout 20 --ignore e2e --ignore tests/e2e norecursedirs=__pycache__, *.egg-info diff --git a/tests/unit/common/test_async_adapter.py b/tests/unit/common/test_async_adapter.py index 800b85d42..d90a895e4 100644 --- a/tests/unit/common/test_async_adapter.py +++ b/tests/unit/common/test_async_adapter.py @@ -11,7 +11,6 @@ import azure.iot.device.common.async_adapter as async_adapter logging.basicConfig(level=logging.DEBUG) -pytestmark = pytest.mark.asyncio @pytest.fixture diff --git a/tests/unit/iothub/aio/test_async_clients.py b/tests/unit/iothub/aio/test_async_clients.py index b22d1774e..3796f494a 100644 --- a/tests/unit/iothub/aio/test_async_clients.py +++ b/tests/unit/iothub/aio/test_async_clients.py @@ -44,7 +44,6 @@ SharedIoTHubModuleClientCreateFromEdgeEnvironmentWithDebugEnvTests, ) -pytestmark = pytest.mark.asyncio logging.basicConfig(level=logging.DEBUG) diff --git a/tests/unit/iothub/aio/test_async_handler_manager.py b/tests/unit/iothub/aio/test_async_handler_manager.py index b3dc5c056..781b902ab 100644 --- a/tests/unit/iothub/aio/test_async_handler_manager.py +++ b/tests/unit/iothub/aio/test_async_handler_manager.py @@ -16,7 +16,6 @@ from azure.iot.device.iothub.inbox_manager import InboxManager from azure.iot.device.iothub.aio.async_inbox import AsyncClientInbox -pytestmark = pytest.mark.asyncio logging.basicConfig(level=logging.DEBUG) # NOTE ON TEST IMPLEMENTATION: diff --git a/tests/unit/provisioning/aio/test_async_provisioning_device_client.py b/tests/unit/provisioning/aio/test_async_provisioning_device_client.py index 2b1106a52..75ebc5107 100644 --- a/tests/unit/provisioning/aio/test_async_provisioning_device_client.py +++ b/tests/unit/provisioning/aio/test_async_provisioning_device_client.py @@ -20,7 +20,6 @@ logging.basicConfig(level=logging.DEBUG) -pytestmark = pytest.mark.asyncio async def create_completed_future(result=None): From 8cfd2bc4617dbafab19f6c23254eb04af9a1a1c0 Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Mon, 15 Jul 2024 13:30:38 -0700 Subject: [PATCH 03/15] Removed unnecessary manual pytestmark from E2E --- tests/e2e/iothub_e2e/aio/test_c2d.py | 1 - tests/e2e/iothub_e2e/aio/test_connect_disconnect.py | 2 -- tests/e2e/iothub_e2e/aio/test_connect_disconnect_stress.py | 2 -- tests/e2e/iothub_e2e/aio/test_infrastructure.py | 2 -- tests/e2e/iothub_e2e/aio/test_methods.py | 2 -- tests/e2e/iothub_e2e/aio/test_sas_renewal.py | 2 -- tests/e2e/iothub_e2e/aio/test_send_message.py | 2 -- tests/e2e/iothub_e2e/aio/test_send_message_stress.py | 1 - tests/e2e/iothub_e2e/aio/test_twin.py | 2 -- tests/e2e/iothub_e2e/aio/test_twin_stress.py | 2 -- .../tests/test_async_certificate_enrollments.py | 1 - .../provisioning_e2e/tests/test_async_symmetric_enrollments.py | 2 +- 12 files changed, 1 insertion(+), 20 deletions(-) diff --git a/tests/e2e/iothub_e2e/aio/test_c2d.py b/tests/e2e/iothub_e2e/aio/test_c2d.py index 980ccc583..07905e135 100644 --- a/tests/e2e/iothub_e2e/aio/test_c2d.py +++ b/tests/e2e/iothub_e2e/aio/test_c2d.py @@ -10,7 +10,6 @@ logger = logging.getLogger(__name__) logger.setLevel(level=logging.INFO) -pytestmark = pytest.mark.asyncio # TODO: add tests for various application properties # TODO: is there a way to call send_c2d so it arrives as an object rather than a JSON string? diff --git a/tests/e2e/iothub_e2e/aio/test_connect_disconnect.py b/tests/e2e/iothub_e2e/aio/test_connect_disconnect.py index 89d3d267b..01df331de 100644 --- a/tests/e2e/iothub_e2e/aio/test_connect_disconnect.py +++ b/tests/e2e/iothub_e2e/aio/test_connect_disconnect.py @@ -9,8 +9,6 @@ logger = logging.getLogger(__name__) logger.setLevel(level=logging.INFO) -pytestmark = pytest.mark.asyncio - @pytest.mark.describe("Client object") class TestConnectDisconnect(object): diff --git a/tests/e2e/iothub_e2e/aio/test_connect_disconnect_stress.py b/tests/e2e/iothub_e2e/aio/test_connect_disconnect_stress.py index c4d1f5caf..0e4e1d2fd 100644 --- a/tests/e2e/iothub_e2e/aio/test_connect_disconnect_stress.py +++ b/tests/e2e/iothub_e2e/aio/test_connect_disconnect_stress.py @@ -10,8 +10,6 @@ logger = logging.getLogger(__name__) logger.setLevel(level=logging.INFO) -pytestmark = pytest.mark.asyncio - @pytest.mark.stress @pytest.mark.describe("Client object connect/disconnect stress") diff --git a/tests/e2e/iothub_e2e/aio/test_infrastructure.py b/tests/e2e/iothub_e2e/aio/test_infrastructure.py index 5ace0aebf..1587d5c71 100644 --- a/tests/e2e/iothub_e2e/aio/test_infrastructure.py +++ b/tests/e2e/iothub_e2e/aio/test_infrastructure.py @@ -4,8 +4,6 @@ import pytest import uuid -pytestmark = pytest.mark.asyncio - @pytest.mark.describe("ServiceHelper object") class TestServiceHelper(object): diff --git a/tests/e2e/iothub_e2e/aio/test_methods.py b/tests/e2e/iothub_e2e/aio/test_methods.py index 50e7f487c..4ca196dab 100644 --- a/tests/e2e/iothub_e2e/aio/test_methods.py +++ b/tests/e2e/iothub_e2e/aio/test_methods.py @@ -11,8 +11,6 @@ logger = logging.getLogger(__name__) logger.setLevel(level=logging.INFO) -pytestmark = pytest.mark.asyncio - @pytest.fixture def method_name(): diff --git a/tests/e2e/iothub_e2e/aio/test_sas_renewal.py b/tests/e2e/iothub_e2e/aio/test_sas_renewal.py index e7942b150..faabe343a 100644 --- a/tests/e2e/iothub_e2e/aio/test_sas_renewal.py +++ b/tests/e2e/iothub_e2e/aio/test_sas_renewal.py @@ -11,8 +11,6 @@ logger = logging.getLogger(__name__) logger.setLevel(level=logging.INFO) -pytestmark = pytest.mark.asyncio - @pytest.mark.skipif( test_config.config.auth not in test_config.AUTH_WITH_RENEWING_TOKEN, diff --git a/tests/e2e/iothub_e2e/aio/test_send_message.py b/tests/e2e/iothub_e2e/aio/test_send_message.py index 40bb2daa2..bc609e9c9 100644 --- a/tests/e2e/iothub_e2e/aio/test_send_message.py +++ b/tests/e2e/iothub_e2e/aio/test_send_message.py @@ -11,8 +11,6 @@ logger = logging.getLogger(__name__) logger.setLevel(level=logging.INFO) -pytestmark = pytest.mark.asyncio - @pytest.mark.describe("Client send_message method") class TestSendMessage(object): diff --git a/tests/e2e/iothub_e2e/aio/test_send_message_stress.py b/tests/e2e/iothub_e2e/aio/test_send_message_stress.py index 81d7aacb9..64beb7025 100644 --- a/tests/e2e/iothub_e2e/aio/test_send_message_stress.py +++ b/tests/e2e/iothub_e2e/aio/test_send_message_stress.py @@ -15,7 +15,6 @@ logger = logging.getLogger(__name__) logger.setLevel(level=logging.INFO) -pytestmark = pytest.mark.asyncio # Settings that apply to all tests in this module TELEMETRY_PAYLOAD_SIZE = 16 * 1024 diff --git a/tests/e2e/iothub_e2e/aio/test_twin.py b/tests/e2e/iothub_e2e/aio/test_twin.py index 92b798910..a16e9b136 100644 --- a/tests/e2e/iothub_e2e/aio/test_twin.py +++ b/tests/e2e/iothub_e2e/aio/test_twin.py @@ -11,8 +11,6 @@ logger = logging.getLogger(__name__) logger.setLevel(level=logging.INFO) -pytestmark = pytest.mark.asyncio - # TODO: tests with drop_incoming and reject_incoming diff --git a/tests/e2e/iothub_e2e/aio/test_twin_stress.py b/tests/e2e/iothub_e2e/aio/test_twin_stress.py index b873becf2..db0858203 100644 --- a/tests/e2e/iothub_e2e/aio/test_twin_stress.py +++ b/tests/e2e/iothub_e2e/aio/test_twin_stress.py @@ -12,8 +12,6 @@ logger = logging.getLogger(__name__) logger.setLevel(level=logging.INFO) -pytestmark = pytest.mark.asyncio - @pytest.fixture def toxic(): diff --git a/tests/e2e/provisioning_e2e/tests/test_async_certificate_enrollments.py b/tests/e2e/provisioning_e2e/tests/test_async_certificate_enrollments.py index 1b8ea4ab3..0219af14e 100644 --- a/tests/e2e/provisioning_e2e/tests/test_async_certificate_enrollments.py +++ b/tests/e2e/provisioning_e2e/tests/test_async_certificate_enrollments.py @@ -29,7 +29,6 @@ ) -pytestmark = pytest.mark.asyncio logging.basicConfig(level=logging.DEBUG) diff --git a/tests/e2e/provisioning_e2e/tests/test_async_symmetric_enrollments.py b/tests/e2e/provisioning_e2e/tests/test_async_symmetric_enrollments.py index 3657db15b..914a158d5 100644 --- a/tests/e2e/provisioning_e2e/tests/test_async_symmetric_enrollments.py +++ b/tests/e2e/provisioning_e2e/tests/test_async_symmetric_enrollments.py @@ -13,7 +13,7 @@ import os import uuid -pytestmark = pytest.mark.asyncio + logging.basicConfig(level=logging.DEBUG) From 39d94f65938a5a3a1aadd8ee5867d91f3b35e9b9 Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Mon, 15 Jul 2024 13:52:06 -0700 Subject: [PATCH 04/15] Removed use of deprecated APIs/constants --- azure-iot-device/azure/iot/device/common/http_transport.py | 6 ++---- azure-iot-device/azure/iot/device/common/mqtt_transport.py | 6 ++---- tests/e2e/iothub_e2e/sync/conftest.py | 2 +- tests/unit/common/test_http_transport.py | 7 +++---- tests/unit/common/test_mqtt_transport.py | 6 +++--- 5 files changed, 11 insertions(+), 16 deletions(-) diff --git a/azure-iot-device/azure/iot/device/common/http_transport.py b/azure-iot-device/azure/iot/device/common/http_transport.py index d09629f0e..a4d3cebc1 100644 --- a/azure-iot-device/azure/iot/device/common/http_transport.py +++ b/azure-iot-device/azure/iot/device/common/http_transport.py @@ -69,7 +69,8 @@ def _create_ssl_context(self): This method creates the SSLContext object used to authenticate the connection. The generated context is used by the http_client and is necessary when authenticating using a self-signed X509 cert or trusted X509 cert """ logger.debug("creating a SSL context") - ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLSv1_2) + # Note that PROTOCOL_TLS_CLIENT implies ssl.CERT_REQUIRED and check_hostname == true + ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT) if self._server_verification_cert: ssl_context.load_verify_locations(cadata=self._server_verification_cert) @@ -91,9 +92,6 @@ def _create_ssl_context(self): self._x509_cert.pass_phrase, ) - ssl_context.verify_mode = ssl.CERT_REQUIRED - ssl_context.check_hostname = True - return ssl_context @pipeline_thread.invoke_on_http_thread_nowait diff --git a/azure-iot-device/azure/iot/device/common/mqtt_transport.py b/azure-iot-device/azure/iot/device/common/mqtt_transport.py index cf54a0416..0d3b4dd76 100644 --- a/azure-iot-device/azure/iot/device/common/mqtt_transport.py +++ b/azure-iot-device/azure/iot/device/common/mqtt_transport.py @@ -321,7 +321,8 @@ def _create_ssl_context(self): This method creates the SSLContext object used by Paho to authenticate the connection. """ logger.debug("creating a SSL context") - ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLSv1_2) + # Note that PROTOCOL_TLS_CLIENT implies ssl.CERT_REQUIRED and check_hostname == true + ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT) if self._server_verification_cert: logger.debug("configuring SSL context with custom server verification cert") @@ -346,9 +347,6 @@ def _create_ssl_context(self): self._x509_cert.pass_phrase, ) - ssl_context.verify_mode = ssl.CERT_REQUIRED - ssl_context.check_hostname = True - return ssl_context def shutdown(self): diff --git a/tests/e2e/iothub_e2e/sync/conftest.py b/tests/e2e/iothub_e2e/sync/conftest.py index d04d9c5cd..b271d8aea 100644 --- a/tests/e2e/iothub_e2e/sync/conftest.py +++ b/tests/e2e/iothub_e2e/sync/conftest.py @@ -20,7 +20,7 @@ def brand_new_client(device_identity, client_kwargs, service_helper, device_id, # Keep this here. It is useful to see this info inside the inside devops pipeline test failures. logger.info( "Connecting device_id={}, module_id={}, to hub={} at {} (UTC)".format( - device_id, module_id, test_env.IOTHUB_HOSTNAME, datetime.datetime.utcnow() + device_id, module_id, test_env.IOTHUB_HOSTNAME, datetime.datetime.now(datetime.UTC) ) ) diff --git a/tests/unit/common/test_http_transport.py b/tests/unit/common/test_http_transport.py index 9f89d14b9..1dbee2d8b 100644 --- a/tests/unit/common/test_http_transport.py +++ b/tests/unit/common/test_http_transport.py @@ -100,14 +100,13 @@ def test_proxy_format(self, proxy_options): ) def test_configures_tls_context(self, mocker): mock_ssl_context_constructor = mocker.patch.object(ssl, "SSLContext") - mock_ssl_context = mock_ssl_context_constructor.return_value HTTPTransport(hostname=fake_hostname) # Verify correctness of TLS/SSL Context assert mock_ssl_context_constructor.call_count == 1 - assert mock_ssl_context_constructor.call_args == mocker.call(protocol=ssl.PROTOCOL_TLSv1_2) - assert mock_ssl_context.check_hostname is True - assert mock_ssl_context.verify_mode == ssl.CERT_REQUIRED + assert mock_ssl_context_constructor.call_args == mocker.call( + protocol=ssl.PROTOCOL_TLS_CLIENT + ) @pytest.mark.it( "Configures TLS/SSL context using default certificates if protocol wrapper not instantiated with a server verification certificate" diff --git a/tests/unit/common/test_mqtt_transport.py b/tests/unit/common/test_mqtt_transport.py index ee547d03a..12aea49ac 100644 --- a/tests/unit/common/test_mqtt_transport.py +++ b/tests/unit/common/test_mqtt_transport.py @@ -267,9 +267,9 @@ def test_configures_tls_context(self, mocker): # Verify correctness of TLS/SSL Context assert mock_ssl_context_constructor.call_count == 1 - assert mock_ssl_context_constructor.call_args == mocker.call(protocol=ssl.PROTOCOL_TLSv1_2) - assert mock_ssl_context.check_hostname is True - assert mock_ssl_context.verify_mode == ssl.CERT_REQUIRED + assert mock_ssl_context_constructor.call_args == mocker.call( + protocol=ssl.PROTOCOL_TLS_CLIENT + ) # Verify context has been set assert mock_mqtt_client.tls_set_context.call_count == 1 From a658a99293e98dd1e8c1add3b6a90dffa4a43d81 Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Wed, 17 Jul 2024 08:10:04 -0700 Subject: [PATCH 05/15] reverted some changes --- azure-iot-device/azure/iot/device/common/http_transport.py | 6 ++++-- azure-iot-device/azure/iot/device/common/mqtt_transport.py | 6 ++++-- tests/e2e/iothub_e2e/sync/conftest.py | 2 +- tests/unit/common/test_http_transport.py | 7 ++++--- tests/unit/common/test_mqtt_transport.py | 6 +++--- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/azure-iot-device/azure/iot/device/common/http_transport.py b/azure-iot-device/azure/iot/device/common/http_transport.py index a4d3cebc1..d09629f0e 100644 --- a/azure-iot-device/azure/iot/device/common/http_transport.py +++ b/azure-iot-device/azure/iot/device/common/http_transport.py @@ -69,8 +69,7 @@ def _create_ssl_context(self): This method creates the SSLContext object used to authenticate the connection. The generated context is used by the http_client and is necessary when authenticating using a self-signed X509 cert or trusted X509 cert """ logger.debug("creating a SSL context") - # Note that PROTOCOL_TLS_CLIENT implies ssl.CERT_REQUIRED and check_hostname == true - ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT) + ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLSv1_2) if self._server_verification_cert: ssl_context.load_verify_locations(cadata=self._server_verification_cert) @@ -92,6 +91,9 @@ def _create_ssl_context(self): self._x509_cert.pass_phrase, ) + ssl_context.verify_mode = ssl.CERT_REQUIRED + ssl_context.check_hostname = True + return ssl_context @pipeline_thread.invoke_on_http_thread_nowait diff --git a/azure-iot-device/azure/iot/device/common/mqtt_transport.py b/azure-iot-device/azure/iot/device/common/mqtt_transport.py index 0d3b4dd76..cf54a0416 100644 --- a/azure-iot-device/azure/iot/device/common/mqtt_transport.py +++ b/azure-iot-device/azure/iot/device/common/mqtt_transport.py @@ -321,8 +321,7 @@ def _create_ssl_context(self): This method creates the SSLContext object used by Paho to authenticate the connection. """ logger.debug("creating a SSL context") - # Note that PROTOCOL_TLS_CLIENT implies ssl.CERT_REQUIRED and check_hostname == true - ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_CLIENT) + ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLSv1_2) if self._server_verification_cert: logger.debug("configuring SSL context with custom server verification cert") @@ -347,6 +346,9 @@ def _create_ssl_context(self): self._x509_cert.pass_phrase, ) + ssl_context.verify_mode = ssl.CERT_REQUIRED + ssl_context.check_hostname = True + return ssl_context def shutdown(self): diff --git a/tests/e2e/iothub_e2e/sync/conftest.py b/tests/e2e/iothub_e2e/sync/conftest.py index b271d8aea..d04d9c5cd 100644 --- a/tests/e2e/iothub_e2e/sync/conftest.py +++ b/tests/e2e/iothub_e2e/sync/conftest.py @@ -20,7 +20,7 @@ def brand_new_client(device_identity, client_kwargs, service_helper, device_id, # Keep this here. It is useful to see this info inside the inside devops pipeline test failures. logger.info( "Connecting device_id={}, module_id={}, to hub={} at {} (UTC)".format( - device_id, module_id, test_env.IOTHUB_HOSTNAME, datetime.datetime.now(datetime.UTC) + device_id, module_id, test_env.IOTHUB_HOSTNAME, datetime.datetime.utcnow() ) ) diff --git a/tests/unit/common/test_http_transport.py b/tests/unit/common/test_http_transport.py index 1dbee2d8b..9f89d14b9 100644 --- a/tests/unit/common/test_http_transport.py +++ b/tests/unit/common/test_http_transport.py @@ -100,13 +100,14 @@ def test_proxy_format(self, proxy_options): ) def test_configures_tls_context(self, mocker): mock_ssl_context_constructor = mocker.patch.object(ssl, "SSLContext") + mock_ssl_context = mock_ssl_context_constructor.return_value HTTPTransport(hostname=fake_hostname) # Verify correctness of TLS/SSL Context assert mock_ssl_context_constructor.call_count == 1 - assert mock_ssl_context_constructor.call_args == mocker.call( - protocol=ssl.PROTOCOL_TLS_CLIENT - ) + assert mock_ssl_context_constructor.call_args == mocker.call(protocol=ssl.PROTOCOL_TLSv1_2) + assert mock_ssl_context.check_hostname is True + assert mock_ssl_context.verify_mode == ssl.CERT_REQUIRED @pytest.mark.it( "Configures TLS/SSL context using default certificates if protocol wrapper not instantiated with a server verification certificate" diff --git a/tests/unit/common/test_mqtt_transport.py b/tests/unit/common/test_mqtt_transport.py index 12aea49ac..ee547d03a 100644 --- a/tests/unit/common/test_mqtt_transport.py +++ b/tests/unit/common/test_mqtt_transport.py @@ -267,9 +267,9 @@ def test_configures_tls_context(self, mocker): # Verify correctness of TLS/SSL Context assert mock_ssl_context_constructor.call_count == 1 - assert mock_ssl_context_constructor.call_args == mocker.call( - protocol=ssl.PROTOCOL_TLS_CLIENT - ) + assert mock_ssl_context_constructor.call_args == mocker.call(protocol=ssl.PROTOCOL_TLSv1_2) + assert mock_ssl_context.check_hostname is True + assert mock_ssl_context.verify_mode == ssl.CERT_REQUIRED # Verify context has been set assert mock_mqtt_client.tls_set_context.call_count == 1 From a5bfc482d22993910269e3fc12b3c484ab5f6e77 Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Fri, 16 Jan 2026 16:57:31 -0800 Subject: [PATCH 06/15] some updates --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 87ad78425..d825d4651 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,5 +1,5 @@ pytest < 9.0.0 -pytest-asyncio +pytest-asyncio <= 1.2.0 pytest-mock pytest-testdox>=1.1.1 pytest-cov From 9aa271d37eb88050e105e9827b73ed7e211047bd Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Tue, 20 Jan 2026 09:01:17 -0800 Subject: [PATCH 07/15] dep updates --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index d825d4651..5067d3545 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,5 +1,5 @@ pytest < 9.0.0 -pytest-asyncio <= 1.2.0 +pytest-asyncio < 1.3.0 pytest-mock pytest-testdox>=1.1.1 pytest-cov From 43a46190ff7fafa1bb53ee3bf2fa08a454ea4361 Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Tue, 20 Jan 2026 09:15:03 -0800 Subject: [PATCH 08/15] service helper changes test --- dev_utils/dev_utils/service_helper.py | 35 +++++++++++++++------------ 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/dev_utils/dev_utils/service_helper.py b/dev_utils/dev_utils/service_helper.py index 066dfdf4c..d9eb379a8 100644 --- a/dev_utils/dev_utils/service_helper.py +++ b/dev_utils/dev_utils/service_helper.py @@ -15,21 +15,29 @@ def __init__( event_loop=None, executor=None, ): - self._event_loop = event_loop or asyncio.get_event_loop() + # Keep the passed-in loop as a fallback, but prefer the currently running loop when + # scheduling work so we never attach futures to a different loop than the caller. + self._event_loop_override = event_loop self._executor = executor or concurrent.futures.ThreadPoolExecutor() self._inner_object = ServiceHelperSync( iothub_connection_string, eventhub_connection_string, eventhub_consumer_group ) + def _get_event_loop(self): + try: + return asyncio.get_running_loop() + except RuntimeError: + return self._event_loop_override or asyncio.get_event_loop() + + async def _run_in_executor(self, func, *args): + loop = self._get_event_loop() + return await loop.run_in_executor(self._executor, func, *args) + def set_identity(self, device_id, module_id): return self._inner_object.set_identity(device_id, module_id) async def set_desired_properties(self, desired_props): - return await self._event_loop.run_in_executor( - self._executor, - self._inner_object.set_desired_properties, - desired_props, - ) + return await self._run_in_executor(self._inner_object.set_desired_properties, desired_props) async def invoke_method( self, @@ -38,8 +46,7 @@ async def invoke_method( connect_timeout_in_seconds=None, response_timeout_in_seconds=None, ): - return await self._event_loop.run_in_executor( - self._executor, + return await self._run_in_executor( self._inner_object.invoke_method, method_name, payload, @@ -52,25 +59,21 @@ async def send_c2d( payload, properties, ): - return await self._event_loop.run_in_executor( - self._executor, self._inner_object.send_c2d, payload, properties - ) + return await self._run_in_executor(self._inner_object.send_c2d, payload, properties) async def wait_for_eventhub_arrival(self, message_id, timeout=60): - return await self._event_loop.run_in_executor( - self._executor, + return await self._run_in_executor( self._inner_object.wait_for_eventhub_arrival, message_id, timeout, ) async def get_next_reported_patch_arrival(self, block=True, timeout=240): - return await self._event_loop.run_in_executor( - self._executor, + return await self._run_in_executor( self._inner_object.get_next_reported_patch_arrival, block, timeout, ) async def shutdown(self): - return await self._event_loop.run_in_executor(self._executor, self._inner_object.shutdown) + return await self._run_in_executor(self._inner_object.shutdown) From f2374da7e5e3b0efd643b887ec89b78b0d857ce3 Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Tue, 20 Jan 2026 09:58:01 -0800 Subject: [PATCH 09/15] removed custom event loop fixture --- requirements_test.txt | 4 ++-- tests/e2e/iothub_e2e/aio/conftest.py | 15 +++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/requirements_test.txt b/requirements_test.txt index fb22b0dbb..fd98c948b 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,5 +1,5 @@ -pytest < 9.0.0 -pytest-asyncio < 1.3.0 +pytest~=8, +pytest-asyncio~=1.2 pytest-mock pytest-testdox>=1.1.1 pytest-cov diff --git a/tests/e2e/iothub_e2e/aio/conftest.py b/tests/e2e/iothub_e2e/aio/conftest.py index e6ff8f52c..e2eccd4f2 100644 --- a/tests/e2e/iothub_e2e/aio/conftest.py +++ b/tests/e2e/iothub_e2e/aio/conftest.py @@ -2,7 +2,6 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. import pytest -import asyncio from dev_utils import test_env, ServiceHelper import logging import datetime @@ -56,11 +55,11 @@ def pytest_sessionfinish(session, exitstatus): print("-----------------------------------") -@pytest.fixture(scope="session") -def event_loop(): - loop = asyncio.get_event_loop() - yield loop - loop.close() +# @pytest.fixture(scope="session") +# def event_loop(): +# loop = asyncio.get_event_loop() +# yield loop +# loop.close() @pytest.fixture(scope="function") @@ -104,12 +103,12 @@ async def client(brand_new_client): @pytest.fixture(scope="session") -async def service_helper(event_loop, executor): +async def service_helper(executor): service_helper = ServiceHelper( iothub_connection_string=test_env.IOTHUB_CONNECTION_STRING, eventhub_connection_string=test_env.EVENTHUB_CONNECTION_STRING, eventhub_consumer_group=test_env.EVENTHUB_CONSUMER_GROUP, - event_loop=event_loop, + # event_loop=event_loop, executor=executor, ) yield service_helper From ca34a7780ffb64880739a8a16138a792b09c5b05 Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Tue, 20 Jan 2026 10:02:06 -0800 Subject: [PATCH 10/15] fixed requirements --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index fd98c948b..72f6d2f08 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pytest~=8, +pytest~=8 pytest-asyncio~=1.2 pytest-mock pytest-testdox>=1.1.1 From 39f48b2b19823d88f2af5823f7a708f85d52c307 Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Tue, 20 Jan 2026 10:05:04 -0800 Subject: [PATCH 11/15] updated requirements again --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index 72f6d2f08..26789d3a1 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -pytest~=8 +pytest>=8,<9 pytest-asyncio~=1.2 pytest-mock pytest-testdox>=1.1.1 From 8d9a36980f93174581c1d3f170579cc186d156d1 Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Tue, 20 Jan 2026 10:33:13 -0800 Subject: [PATCH 12/15] enabled legacy mode + enabled async for dps tests --- tests/e2e/iothub_e2e/pytest.ini | 2 +- tests/e2e/provisioning_e2e/pytest.ini | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/e2e/iothub_e2e/pytest.ini b/tests/e2e/iothub_e2e/pytest.ini index 0094ca0ed..85a244538 100644 --- a/tests/e2e/iothub_e2e/pytest.ini +++ b/tests/e2e/iothub_e2e/pytest.ini @@ -5,7 +5,7 @@ junit_logging=all junit_family=xunit2 junit_log_passing_tests=True asyncio_mode=auto -# --force-testdox to always use testdox format, even when redirecting to file +asyncio_legacy_mode=true addopts= --testdox --force-testdox diff --git a/tests/e2e/provisioning_e2e/pytest.ini b/tests/e2e/provisioning_e2e/pytest.ini index 2f799d7d7..eff4df9b4 100644 --- a/tests/e2e/provisioning_e2e/pytest.ini +++ b/tests/e2e/provisioning_e2e/pytest.ini @@ -1,2 +1,7 @@ [pytest] -addopts = --timeout 30 \ No newline at end of file +timeout=30 +asyncio_mode=auto +asyncio_legacy_mode=true +addopts= + --testdox + --force-testdox \ No newline at end of file From 35e2387b7030badca0b7de383edddd8ee560e916 Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Tue, 20 Jan 2026 11:00:12 -0800 Subject: [PATCH 13/15] more legacy config --- tests/e2e/iothub_e2e/aio/conftest.py | 8 -------- tests/e2e/iothub_e2e/pytest.ini | 1 + tests/e2e/provisioning_e2e/pytest.ini | 2 ++ 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/e2e/iothub_e2e/aio/conftest.py b/tests/e2e/iothub_e2e/aio/conftest.py index e2eccd4f2..5c76eb570 100644 --- a/tests/e2e/iothub_e2e/aio/conftest.py +++ b/tests/e2e/iothub_e2e/aio/conftest.py @@ -55,13 +55,6 @@ def pytest_sessionfinish(session, exitstatus): print("-----------------------------------") -# @pytest.fixture(scope="session") -# def event_loop(): -# loop = asyncio.get_event_loop() -# yield loop -# loop.close() - - @pytest.fixture(scope="function") async def brand_new_client(device_identity, client_kwargs, service_helper, device_id, module_id): service_helper.set_identity(device_id, module_id) @@ -108,7 +101,6 @@ async def service_helper(executor): iothub_connection_string=test_env.IOTHUB_CONNECTION_STRING, eventhub_connection_string=test_env.EVENTHUB_CONNECTION_STRING, eventhub_consumer_group=test_env.EVENTHUB_CONSUMER_GROUP, - # event_loop=event_loop, executor=executor, ) yield service_helper diff --git a/tests/e2e/iothub_e2e/pytest.ini b/tests/e2e/iothub_e2e/pytest.ini index 85a244538..7c59e7388 100644 --- a/tests/e2e/iothub_e2e/pytest.ini +++ b/tests/e2e/iothub_e2e/pytest.ini @@ -6,6 +6,7 @@ junit_family=xunit2 junit_log_passing_tests=True asyncio_mode=auto asyncio_legacy_mode=true +asyncio_default_fixture_loop_scope = function addopts= --testdox --force-testdox diff --git a/tests/e2e/provisioning_e2e/pytest.ini b/tests/e2e/provisioning_e2e/pytest.ini index eff4df9b4..1555f776d 100644 --- a/tests/e2e/provisioning_e2e/pytest.ini +++ b/tests/e2e/provisioning_e2e/pytest.ini @@ -1,7 +1,9 @@ [pytest] timeout=30 +testdox_format=plaintext asyncio_mode=auto asyncio_legacy_mode=true +asyncio_default_fixture_loop_scope = function addopts= --testdox --force-testdox \ No newline at end of file From c70d29aece5bfade683ba23d0f4a79f3bdbad22c Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Tue, 20 Jan 2026 11:17:57 -0800 Subject: [PATCH 14/15] removed event loop fixture --- tests/e2e/iothub_e2e/aio/test_c2d.py | 3 ++- tests/e2e/iothub_e2e/aio/test_connect_disconnect.py | 5 +++-- tests/e2e/iothub_e2e/aio/test_sas_renewal.py | 5 ++--- tests/e2e/iothub_e2e/aio/test_twin.py | 5 ++--- tests/e2e/iothub_e2e/aio/test_twin_stress.py | 6 ++++-- tests/e2e/iothub_e2e/pytest.ini | 2 +- tests/e2e/provisioning_e2e/pytest.ini | 1 - 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/tests/e2e/iothub_e2e/aio/test_c2d.py b/tests/e2e/iothub_e2e/aio/test_c2d.py index 07905e135..9a38962c2 100644 --- a/tests/e2e/iothub_e2e/aio/test_c2d.py +++ b/tests/e2e/iothub_e2e/aio/test_c2d.py @@ -19,8 +19,9 @@ class TestReceiveC2d(object): @pytest.mark.it("Can receive C2D") @pytest.mark.quicktest_suite - async def test_receive_c2d(self, client, service_helper, event_loop, leak_tracker): + async def test_receive_c2d(self, client, service_helper, leak_tracker): leak_tracker.set_initial_object_list() + event_loop = asyncio.get_running_loop() message = json.dumps(get_random_dict()) diff --git a/tests/e2e/iothub_e2e/aio/test_connect_disconnect.py b/tests/e2e/iothub_e2e/aio/test_connect_disconnect.py index 01df331de..e99d317a9 100644 --- a/tests/e2e/iothub_e2e/aio/test_connect_disconnect.py +++ b/tests/e2e/iothub_e2e/aio/test_connect_disconnect.py @@ -42,7 +42,7 @@ async def test_connect_disconnect(self, brand_new_client, leak_tracker): # see "This assert fails because of initial and secondary disconnects" below @pytest.mark.skip(reason="two stage disconnect causes assertion in test code") async def test_connect_in_the_middle_of_disconnect( - self, brand_new_client, event_loop, service_helper, random_message, leak_tracker + self, brand_new_client, service_helper, random_message, leak_tracker ): """ Explanation: People will call `connect` inside `on_connection_state_change` handlers. @@ -52,6 +52,7 @@ async def test_connect_in_the_middle_of_disconnect( assert client leak_tracker.set_initial_object_list() + event_loop = asyncio.get_running_loop() reconnected_event = asyncio.Event() @@ -110,7 +111,6 @@ async def handle_on_connection_state_change(): async def test_disconnect_in_the_middle_of_connect( self, brand_new_client, - event_loop, service_helper, random_message, first_connect, @@ -126,6 +126,7 @@ async def test_disconnect_in_the_middle_of_connect( disconnect_on_next_connect_event = False leak_tracker.set_initial_object_list() + event_loop = asyncio.get_running_loop() disconnected_event = asyncio.Event() diff --git a/tests/e2e/iothub_e2e/aio/test_sas_renewal.py b/tests/e2e/iothub_e2e/aio/test_sas_renewal.py index faabe343a..47621fe3b 100644 --- a/tests/e2e/iothub_e2e/aio/test_sas_renewal.py +++ b/tests/e2e/iothub_e2e/aio/test_sas_renewal.py @@ -22,10 +22,9 @@ class TestSasRenewal(object): @pytest.mark.it("Renews and reconnects before expiry") @pytest.mark.parametrize(*parametrize.connection_retry_disabled_and_enabled) @pytest.mark.parametrize(*parametrize.auto_connect_disabled_and_enabled) - async def test_sas_renews( - self, client, event_loop, service_helper, random_message, leak_tracker - ): + async def test_sas_renews(self, client, service_helper, random_message, leak_tracker): leak_tracker.set_initial_object_list() + event_loop = asyncio.get_running_loop() connected_event = asyncio.Event() disconnected_event = asyncio.Event() diff --git a/tests/e2e/iothub_e2e/aio/test_twin.py b/tests/e2e/iothub_e2e/aio/test_twin.py index 628764263..a530246c8 100644 --- a/tests/e2e/iothub_e2e/aio/test_twin.py +++ b/tests/e2e/iothub_e2e/aio/test_twin.py @@ -190,10 +190,9 @@ async def test_updates_reported_if_reject_before_sending( class TestDesiredProperties(object): @pytest.mark.it("Receives a patch for a simple desired property") @pytest.mark.quicktest_suite - async def test_receives_simple_desired_patch( - self, client, event_loop, service_helper, leak_tracker - ): + async def test_receives_simple_desired_patch(self, client, service_helper, leak_tracker): leak_tracker.set_initial_object_list() + event_loop = asyncio.get_running_loop() received_patch = None received = asyncio.Event() diff --git a/tests/e2e/iothub_e2e/aio/test_twin_stress.py b/tests/e2e/iothub_e2e/aio/test_twin_stress.py index dec866aa7..dc57798ec 100644 --- a/tests/e2e/iothub_e2e/aio/test_twin_stress.py +++ b/tests/e2e/iothub_e2e/aio/test_twin_stress.py @@ -157,13 +157,14 @@ async def test_stress_parallel_reported_property_updates( ) @pytest.mark.it("Can receive continuous desired property updates that were sent one-at-a-time") async def test_stress_serial_desired_property_updates( - self, client, service_helper, toxic, iteration_count, event_loop, leak_tracker + self, client, service_helper, toxic, iteration_count, leak_tracker ): """ Update desired properties, one at a time, and verify that the desired property arrives at the client before the next update. """ leak_tracker.set_initial_object_list() + event_loop = asyncio.get_running_loop() patches = asyncio.Queue() @@ -200,13 +201,14 @@ async def handle_on_patch_received(patch): "Can receive continuous desired property updates that may have been sent in parallel" ) async def test_stress_parallel_desired_property_updates( - self, client, service_helper, toxic, iteration_count, batch_size, event_loop, leak_tracker + self, client, service_helper, toxic, iteration_count, batch_size, leak_tracker ): """ Update desired properties in batches. Each batch updates `batch_size` properties, with each property being updated in it's own `PATCH`. """ leak_tracker.set_initial_object_list() + event_loop = asyncio.get_running_loop() patches = asyncio.Queue() diff --git a/tests/e2e/iothub_e2e/pytest.ini b/tests/e2e/iothub_e2e/pytest.ini index 7c59e7388..820212900 100644 --- a/tests/e2e/iothub_e2e/pytest.ini +++ b/tests/e2e/iothub_e2e/pytest.ini @@ -6,7 +6,7 @@ junit_family=xunit2 junit_log_passing_tests=True asyncio_mode=auto asyncio_legacy_mode=true -asyncio_default_fixture_loop_scope = function +; asyncio_default_fixture_loop_scope = function addopts= --testdox --force-testdox diff --git a/tests/e2e/provisioning_e2e/pytest.ini b/tests/e2e/provisioning_e2e/pytest.ini index 1555f776d..758a72854 100644 --- a/tests/e2e/provisioning_e2e/pytest.ini +++ b/tests/e2e/provisioning_e2e/pytest.ini @@ -3,7 +3,6 @@ timeout=30 testdox_format=plaintext asyncio_mode=auto asyncio_legacy_mode=true -asyncio_default_fixture_loop_scope = function addopts= --testdox --force-testdox \ No newline at end of file From 9f25ad2516ac99bae514d044dadff42ccbfd3bba Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Tue, 20 Jan 2026 11:42:27 -0800 Subject: [PATCH 15/15] removed event loop support from service helper entirely --- dev_utils/dev_utils/service_helper.py | 12 +----------- tests/e2e/iothub_e2e/pytest.ini | 2 -- tests/e2e/provisioning_e2e/pytest.ini | 3 +-- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/dev_utils/dev_utils/service_helper.py b/dev_utils/dev_utils/service_helper.py index d9eb379a8..0e69ca4d2 100644 --- a/dev_utils/dev_utils/service_helper.py +++ b/dev_utils/dev_utils/service_helper.py @@ -12,25 +12,15 @@ def __init__( iothub_connection_string, eventhub_connection_string, eventhub_consumer_group, - event_loop=None, executor=None, ): - # Keep the passed-in loop as a fallback, but prefer the currently running loop when - # scheduling work so we never attach futures to a different loop than the caller. - self._event_loop_override = event_loop self._executor = executor or concurrent.futures.ThreadPoolExecutor() self._inner_object = ServiceHelperSync( iothub_connection_string, eventhub_connection_string, eventhub_consumer_group ) - def _get_event_loop(self): - try: - return asyncio.get_running_loop() - except RuntimeError: - return self._event_loop_override or asyncio.get_event_loop() - async def _run_in_executor(self, func, *args): - loop = self._get_event_loop() + loop = asyncio.get_running_loop() return await loop.run_in_executor(self._executor, func, *args) def set_identity(self, device_id, module_id): diff --git a/tests/e2e/iothub_e2e/pytest.ini b/tests/e2e/iothub_e2e/pytest.ini index 820212900..f38019b98 100644 --- a/tests/e2e/iothub_e2e/pytest.ini +++ b/tests/e2e/iothub_e2e/pytest.ini @@ -5,8 +5,6 @@ junit_logging=all junit_family=xunit2 junit_log_passing_tests=True asyncio_mode=auto -asyncio_legacy_mode=true -; asyncio_default_fixture_loop_scope = function addopts= --testdox --force-testdox diff --git a/tests/e2e/provisioning_e2e/pytest.ini b/tests/e2e/provisioning_e2e/pytest.ini index 758a72854..2243b227d 100644 --- a/tests/e2e/provisioning_e2e/pytest.ini +++ b/tests/e2e/provisioning_e2e/pytest.ini @@ -2,7 +2,6 @@ timeout=30 testdox_format=plaintext asyncio_mode=auto -asyncio_legacy_mode=true addopts= --testdox - --force-testdox \ No newline at end of file + --force-testdox