|
22 | 22 | import subprocess
|
23 | 23 | import tempfile
|
24 | 24 | import time
|
| 25 | +from asyncio import run_coroutine_threadsafe |
25 | 26 | from typing import Generator
|
26 | 27 |
|
27 | 28 | import google
|
28 | 29 | import pytest_asyncio
|
29 | 30 | from google.auth import compute_engine
|
30 | 31 | from google.cloud import secretmanager, storage
|
31 | 32 |
|
| 33 | +from toolbox_core import ToolboxSyncClient |
| 34 | + |
32 | 35 |
|
33 | 36 | #### Define Utility Functions
|
34 | 37 | def get_env_var(key: str) -> str:
|
@@ -92,6 +95,38 @@ def get_auth_token(client_id: str) -> str:
|
92 | 95 |
|
93 | 96 |
|
94 | 97 | #### Define Fixtures
|
| 98 | +@pytest_asyncio.fixture(autouse=True) |
| 99 | +def patch_sync_client_for_deadlock(monkeypatch): |
| 100 | + """ |
| 101 | + Automatically replaces the blocking `ToolboxSyncClient.close()` method |
| 102 | + with a non-blocking version for the entire test run. |
| 103 | +
|
| 104 | + The original `ToolboxSyncClient.close()` is a blocking method because it |
| 105 | + calls `.result()`. In the pytest environment, this blocking call creates a |
| 106 | + deadlock during the test teardown phase when it conflicts with other fixtures |
| 107 | + (like `sync_client_environment` or `toolbox_server`) that are also managing |
| 108 | + background processes and threads. |
| 109 | +
|
| 110 | + By replacing `close` with this safe, non-blocking version, we prevent the |
| 111 | + deadlock and allow the test suite's fixtures to tear down cleanly. |
| 112 | + This change is only active during the test run. |
| 113 | + """ |
| 114 | + |
| 115 | + def non_blocking_close(self): |
| 116 | + """A replacement for close() that doesn't block.""" |
| 117 | + if hasattr(self.__class__, "_ToolboxSyncClient__loop") and hasattr( |
| 118 | + self, "_ToolboxSyncClient__async_client" |
| 119 | + ): |
| 120 | + loop = self.__class__._ToolboxSyncClient__loop |
| 121 | + async_client = self._ToolboxSyncClient__async_client |
| 122 | + |
| 123 | + if loop and loop.is_running(): |
| 124 | + coro = async_client.close() |
| 125 | + run_coroutine_threadsafe(coro, loop) |
| 126 | + |
| 127 | + monkeypatch.setattr(ToolboxSyncClient, "close", non_blocking_close) |
| 128 | + |
| 129 | + |
95 | 130 | @pytest_asyncio.fixture(scope="session")
|
96 | 131 | def project_id() -> str:
|
97 | 132 | return get_env_var("GOOGLE_CLOUD_PROJECT")
|
|
0 commit comments