diff --git a/packages/toolbox-adk/integration.cloudbuild.yaml b/packages/toolbox-adk/integration.cloudbuild.yaml index 8d8b5678f..3bdf3fa4a 100644 --- a/packages/toolbox-adk/integration.cloudbuild.yaml +++ b/packages/toolbox-adk/integration.cloudbuild.yaml @@ -50,5 +50,5 @@ options: substitutions: _VERSION: '3.13' # Default values (can be overridden by triggers) - _TOOLBOX_VERSION: '1.5.0' + _TOOLBOX_VERSION: 'v1.6.0' _TOOLBOX_MANIFEST_VERSION: '34' diff --git a/packages/toolbox-adk/pyproject.toml b/packages/toolbox-adk/pyproject.toml index 3ad5c35e4..52072abf8 100644 --- a/packages/toolbox-adk/pyproject.toml +++ b/packages/toolbox-adk/pyproject.toml @@ -40,6 +40,7 @@ test = [ "pytest==9.0.3", "pytest-asyncio==1.4.0", "pytest-cov==7.1.0", + "numpy<2.2.0", "pytest-mock==3.15.1" ] diff --git a/packages/toolbox-adk/tests/integration/conftest.py b/packages/toolbox-adk/tests/integration/conftest.py index 7a0346c3a..20aea226d 100644 --- a/packages/toolbox-adk/tests/integration/conftest.py +++ b/packages/toolbox-adk/tests/integration/conftest.py @@ -25,7 +25,7 @@ from typing import Generator import google -import pytest_asyncio +import pytest from google.auth import compute_engine from google.cloud import secretmanager, storage @@ -75,7 +75,8 @@ def get_toolbox_binary_url(toolbox_version: str) -> str: arch = ( "arm64" if os_system == "darwin" and platform.machine() == "arm64" else "amd64" ) - return f"v{toolbox_version}/{os_system}/{arch}/toolbox" + ext = ".exe" if os_system == "windows" else "" + return f"{toolbox_version}/{os_system}/{arch}/toolbox{ext}" def get_auth_token(client_id: str) -> str: @@ -92,17 +93,17 @@ def get_auth_token(client_id: str) -> str: #### Define Fixtures -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def project_id() -> str: return get_env_var("GOOGLE_CLOUD_PROJECT") -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def toolbox_version() -> str: return get_env_var("TOOLBOX_VERSION") -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def tools_file_path(project_id: str) -> Generator[str]: """Provides a temporary file path containing the tools manifest.""" if os.environ.get("TEST_MOCK_GCP"): @@ -122,7 +123,7 @@ def tools_file_path(project_id: str) -> Generator[str]: os.remove(tools_file_path) -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def auth_token1(project_id: str) -> str: if os.environ.get("TEST_MOCK_GCP"): return "mock-token-1" @@ -132,7 +133,7 @@ def auth_token1(project_id: str) -> str: return get_auth_token(client_id) -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def auth_token2(project_id: str) -> str: if os.environ.get("TEST_MOCK_GCP"): return "mock-token-2" @@ -142,7 +143,7 @@ def auth_token2(project_id: str) -> str: return get_auth_token(client_id) -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def toolbox_server(toolbox_version: str, tools_file_path: str) -> Generator[None]: """Starts the toolbox server as a subprocess.""" if os.environ.get("TEST_MOCK_GCP"): @@ -151,7 +152,12 @@ def toolbox_server(toolbox_version: str, tools_file_path: str) -> Generator[None print("Downloading toolbox binary from gcs bucket...") source_blob_name = get_toolbox_binary_url(toolbox_version) - download_blob("mcp-toolbox-for-databases", source_blob_name, "toolbox") + bucket_name = ( + "mcp-toolbox-for-databases-dev" + if toolbox_version in ("main", "mcp-v202606") + else "mcp-toolbox-for-databases" + ) + download_blob(bucket_name, source_blob_name, "toolbox") print("Toolbox binary downloaded successfully.") try: @@ -159,20 +165,30 @@ def toolbox_server(toolbox_version: str, tools_file_path: str) -> Generator[None # Make toolbox executable os.chmod("toolbox", 0o700) # Run toolbox binary - toolbox_server = subprocess.Popen( - ["./toolbox", "--tools-file", tools_file_path] + toolbox_server_1 = subprocess.Popen( + ["./toolbox", "--port", "5000", "--tools-file", tools_file_path] + ) + toolbox_server_2 = subprocess.Popen( + [ + "./toolbox", + "--port", + "5001", + "--tools-file", + tools_file_path, + "--enable-draft-specs", + ] ) # Wait for server to start # Retry logic with a timeout for _ in range(5): # retries time.sleep(2) - print("Checking if toolbox is successfully started...") - if toolbox_server.poll() is None: - print("Toolbox server started successfully.") + print("Checking if both toolbox servers are successfully started...") + if toolbox_server_1.poll() is None and toolbox_server_2.poll() is None: + print("Toolbox servers started successfully.") break else: - raise RuntimeError("Toolbox server failed to start after 5 retries.") + raise RuntimeError("Toolbox servers failed to start after 5 retries.") except subprocess.CalledProcessError as e: print(e.stderr.decode("utf-8")) print(e.stdout.decode("utf-8")) @@ -180,5 +196,31 @@ def toolbox_server(toolbox_version: str, tools_file_path: str) -> Generator[None yield # Clean up toolbox server - toolbox_server.terminate() - toolbox_server.wait(timeout=5) + toolbox_server_1.terminate() + toolbox_server_2.terminate() + toolbox_server_1.wait(timeout=5) + toolbox_server_2.wait(timeout=5) + + +@pytest.fixture( + params=["http://localhost:5000", "http://localhost:5001"], scope="session" +) +def toolbox_server_url(request) -> str: + return request.param + + +@pytest.fixture(autouse=True) +def patch_toolbox_client_url(toolbox_server_url): + from toolbox_adk.toolset import ToolboxToolset + + original_init = ToolboxToolset.__init__ + + def new_init(self, server_url="http://localhost:5000", *args, **kwargs): + if server_url == "http://localhost:5000": + server_url = toolbox_server_url + original_init(self, server_url, *args, **kwargs) + + from unittest.mock import patch + + with patch.object(ToolboxToolset, "__init__", new_init, create=True): + yield diff --git a/packages/toolbox-core/integration.cloudbuild.yaml b/packages/toolbox-core/integration.cloudbuild.yaml index 17e388483..a384d4de7 100644 --- a/packages/toolbox-core/integration.cloudbuild.yaml +++ b/packages/toolbox-core/integration.cloudbuild.yaml @@ -45,5 +45,5 @@ options: logging: CLOUD_LOGGING_ONLY substitutions: _VERSION: '3.13' - _TOOLBOX_VERSION: '1.5.0' + _TOOLBOX_VERSION: 'v1.6.0' _TOOLBOX_MANIFEST_VERSION: '34' diff --git a/packages/toolbox-core/pyproject.toml b/packages/toolbox-core/pyproject.toml index 6c2bf1a6d..21237a970 100644 --- a/packages/toolbox-core/pyproject.toml +++ b/packages/toolbox-core/pyproject.toml @@ -56,6 +56,7 @@ test = [ "pytest-aioresponses==0.3.0", "pytest-asyncio==1.4.0", "pytest-cov==7.1.0", + "numpy<2.2.0", "pytest-mock==3.15.1", "google-cloud-secret-manager==2.28.0", "google-cloud-storage==3.10.1", diff --git a/packages/toolbox-core/tests/conftest.py b/packages/toolbox-core/tests/conftest.py index 338d031c0..2e500752e 100644 --- a/packages/toolbox-core/tests/conftest.py +++ b/packages/toolbox-core/tests/conftest.py @@ -25,7 +25,7 @@ from typing import Generator import google -import pytest_asyncio +import pytest from google.auth import compute_engine from google.cloud import secretmanager, storage @@ -75,7 +75,8 @@ def get_toolbox_binary_url(toolbox_version: str) -> str: arch = ( "arm64" if os_system == "darwin" and platform.machine() == "arm64" else "amd64" ) - return f"v{toolbox_version}/{os_system}/{arch}/toolbox" + ext = ".exe" if os_system == "windows" else "" + return f"{toolbox_version}/{os_system}/{arch}/toolbox{ext}" def get_auth_token(client_id: str) -> str: @@ -92,17 +93,17 @@ def get_auth_token(client_id: str) -> str: #### Define Fixtures -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def project_id() -> str: return get_env_var("GOOGLE_CLOUD_PROJECT") -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def toolbox_version() -> str: return get_env_var("TOOLBOX_VERSION") -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def tools_file_path(project_id: str) -> Generator[str]: """Provides a temporary file path containing the tools manifest.""" tools_manifest = access_secret_version( @@ -115,7 +116,7 @@ def tools_file_path(project_id: str) -> Generator[str]: os.remove(tools_file_path) -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def auth_token1(project_id: str) -> str: client_id = access_secret_version( project_id=project_id, secret_id="sdk_testing_client1" @@ -123,7 +124,7 @@ def auth_token1(project_id: str) -> str: return get_auth_token(client_id) -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def auth_token2(project_id: str) -> str: client_id = access_secret_version( project_id=project_id, secret_id="sdk_testing_client2" @@ -131,32 +132,47 @@ def auth_token2(project_id: str) -> str: return get_auth_token(client_id) -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def toolbox_server(toolbox_version: str, tools_file_path: str) -> Generator[None]: """Starts the toolbox server as a subprocess.""" print("Downloading toolbox binary from gcs bucket...") source_blob_name = get_toolbox_binary_url(toolbox_version) - download_blob("mcp-toolbox-for-databases", source_blob_name, "toolbox") + bucket_name = ( + "mcp-toolbox-for-databases-dev" + if toolbox_version in ("main", "mcp-v202606") + else "mcp-toolbox-for-databases" + ) + download_blob(bucket_name, source_blob_name, "toolbox") print("Toolbox binary downloaded successfully.") try: print("Opening toolbox server process...") # Make toolbox executable os.chmod("toolbox", 0o700) # Run toolbox binary - toolbox_server = subprocess.Popen( - ["./toolbox", "--tools-file", tools_file_path] + toolbox_server_1 = subprocess.Popen( + ["./toolbox", "--port", "5000", "--tools-file", tools_file_path] + ) + toolbox_server_2 = subprocess.Popen( + [ + "./toolbox", + "--port", + "5001", + "--tools-file", + tools_file_path, + "--enable-draft-specs", + ] ) # Wait for server to start # Retry logic with a timeout for _ in range(5): # retries time.sleep(2) - print("Checking if toolbox is successfully started...") - if toolbox_server.poll() is None: - print("Toolbox server started successfully.") + print("Checking if both toolbox servers are successfully started...") + if toolbox_server_1.poll() is None and toolbox_server_2.poll() is None: + print("Toolbox servers started successfully.") break else: - raise RuntimeError("Toolbox server failed to start after 5 retries.") + raise RuntimeError("Toolbox servers failed to start after 5 retries.") except subprocess.CalledProcessError as e: print(e.stderr.decode("utf-8")) print(e.stdout.decode("utf-8")) @@ -164,5 +180,39 @@ def toolbox_server(toolbox_version: str, tools_file_path: str) -> Generator[None yield # Clean up toolbox server - toolbox_server.terminate() - toolbox_server.wait(timeout=5) + toolbox_server_1.terminate() + toolbox_server_2.terminate() + toolbox_server_1.wait(timeout=5) + toolbox_server_2.wait(timeout=5) + + +@pytest.fixture( + params=["http://localhost:5000", "http://localhost:5001"], scope="session" +) +def toolbox_server_url(request) -> str: + return request.param + + +@pytest.fixture(autouse=True) +def patch_toolbox_client_url(toolbox_server_url): + from toolbox_core.client import ToolboxClient + from toolbox_core.sync_client import ToolboxSyncClient + + original_init = ToolboxClient.__init__ + original_sync_init = ToolboxSyncClient.__init__ + + def new_init(self, url="http://localhost:5000", *args, **kwargs): + if url == "http://localhost:5000": + url = toolbox_server_url + original_init(self, url, *args, **kwargs) + + def new_sync_init(self, url="http://localhost:5000", *args, **kwargs): + if url == "http://localhost:5000": + url = toolbox_server_url + original_sync_init(self, url, *args, **kwargs) + + from unittest.mock import patch + + with patch.object(ToolboxClient, "__init__", new_init, create=True): + with patch.object(ToolboxSyncClient, "__init__", new_sync_init, create=True): + yield diff --git a/packages/toolbox-langchain/integration.cloudbuild.yaml b/packages/toolbox-langchain/integration.cloudbuild.yaml index 39a34b5fe..ccadbb557 100644 --- a/packages/toolbox-langchain/integration.cloudbuild.yaml +++ b/packages/toolbox-langchain/integration.cloudbuild.yaml @@ -49,5 +49,5 @@ options: logging: CLOUD_LOGGING_ONLY substitutions: _VERSION: '3.13' - _TOOLBOX_VERSION: '1.5.0' + _TOOLBOX_VERSION: 'v1.6.0' _TOOLBOX_MANIFEST_VERSION: '34' diff --git a/packages/toolbox-langchain/pyproject.toml b/packages/toolbox-langchain/pyproject.toml index 247fdbfab..d8778d62d 100644 --- a/packages/toolbox-langchain/pyproject.toml +++ b/packages/toolbox-langchain/pyproject.toml @@ -50,6 +50,7 @@ test = [ "pytest-asyncio==1.4.0", "pytest==9.0.3", "pytest-cov==7.1.0", + "numpy<2.2.0", "Pillow==12.2.0; python_version >= '3.10'", "google-cloud-secret-manager==2.28.0", "google-cloud-storage==3.10.1", diff --git a/packages/toolbox-langchain/tests/conftest.py b/packages/toolbox-langchain/tests/conftest.py index a1c87a7d8..7fe322b6d 100644 --- a/packages/toolbox-langchain/tests/conftest.py +++ b/packages/toolbox-langchain/tests/conftest.py @@ -25,7 +25,7 @@ from typing import Generator import google -import pytest_asyncio +import pytest from google.auth import compute_engine from google.cloud import secretmanager, storage @@ -75,7 +75,8 @@ def get_toolbox_binary_url(toolbox_version: str) -> str: arch = ( "arm64" if os_system == "darwin" and platform.machine() == "arm64" else "amd64" ) - return f"v{toolbox_version}/{os_system}/{arch}/toolbox" + ext = ".exe" if os_system == "windows" else "" + return f"{toolbox_version}/{os_system}/{arch}/toolbox{ext}" def get_auth_token(client_id: str) -> str: @@ -92,17 +93,17 @@ def get_auth_token(client_id: str) -> str: #### Define Fixtures -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def project_id() -> str: return get_env_var("GOOGLE_CLOUD_PROJECT") -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def toolbox_version() -> str: return get_env_var("TOOLBOX_VERSION") -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def tools_file_path(project_id: str) -> Generator[str]: """Provides a temporary file path containing the tools manifest.""" tools_manifest = access_secret_version( @@ -115,7 +116,7 @@ def tools_file_path(project_id: str) -> Generator[str]: os.remove(tools_file_path) -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def auth_token1(project_id: str) -> str: client_id = access_secret_version( project_id=project_id, secret_id="sdk_testing_client1" @@ -123,7 +124,7 @@ def auth_token1(project_id: str) -> str: return get_auth_token(client_id) -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def auth_token2(project_id: str) -> str: client_id = access_secret_version( project_id=project_id, secret_id="sdk_testing_client2" @@ -131,32 +132,47 @@ def auth_token2(project_id: str) -> str: return get_auth_token(client_id) -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def toolbox_server(toolbox_version: str, tools_file_path: str) -> Generator[None]: """Starts the toolbox server as a subprocess.""" print("Downloading toolbox binary from gcs bucket...") source_blob_name = get_toolbox_binary_url(toolbox_version) - download_blob("mcp-toolbox-for-databases", source_blob_name, "toolbox") + bucket_name = ( + "mcp-toolbox-for-databases-dev" + if toolbox_version in ("main", "mcp-v202606") + else "mcp-toolbox-for-databases" + ) + download_blob(bucket_name, source_blob_name, "toolbox") print("Toolbox binary downloaded successfully.") try: print("Opening toolbox server process...") # Make toolbox executable os.chmod("toolbox", 0o700) # Run toolbox binary - toolbox_server = subprocess.Popen( - ["./toolbox", "--tools-file", tools_file_path] + toolbox_server_1 = subprocess.Popen( + ["./toolbox", "--port", "5000", "--tools-file", tools_file_path] + ) + toolbox_server_2 = subprocess.Popen( + [ + "./toolbox", + "--port", + "5001", + "--tools-file", + tools_file_path, + "--enable-draft-specs", + ] ) # Wait for server to start # Retry logic with a timeout for _ in range(5): # retries - time.sleep(2) - print("Checking if toolbox is successfully started...") - if toolbox_server.poll() is None: - print("Toolbox server started successfully.") + time.sleep(4) + print("Checking if both toolbox servers are successfully started...") + if toolbox_server_1.poll() is None and toolbox_server_2.poll() is None: + print("Toolbox servers started successfully.") break else: - raise RuntimeError("Toolbox server failed to start after 5 retries.") + raise RuntimeError("Toolbox servers failed to start after 5 retries.") except subprocess.CalledProcessError as e: print(e.stderr.decode("utf-8")) print(e.stdout.decode("utf-8")) @@ -164,5 +180,31 @@ def toolbox_server(toolbox_version: str, tools_file_path: str) -> Generator[None yield # Clean up toolbox server - toolbox_server.terminate() - toolbox_server.wait() + toolbox_server_1.terminate() + toolbox_server_2.terminate() + toolbox_server_1.wait() + toolbox_server_2.wait() + + +@pytest.fixture( + params=["http://localhost:5000", "http://localhost:5001"], scope="session" +) +def toolbox_server_url(request) -> str: + return request.param + + +@pytest.fixture(autouse=True) +def patch_toolbox_client_url(toolbox_server_url): + from toolbox_core.client import ToolboxClient + + original_init = ToolboxClient.__init__ + + def new_init(self, url="http://localhost:5000", *args, **kwargs): + if url == "http://localhost:5000": + url = toolbox_server_url + original_init(self, url, *args, **kwargs) + + from unittest.mock import patch + + with patch.object(ToolboxClient, "__init__", new_init, create=True): + yield diff --git a/packages/toolbox-llamaindex/integration.cloudbuild.yaml b/packages/toolbox-llamaindex/integration.cloudbuild.yaml index 0d31e0d69..2054900fa 100644 --- a/packages/toolbox-llamaindex/integration.cloudbuild.yaml +++ b/packages/toolbox-llamaindex/integration.cloudbuild.yaml @@ -49,5 +49,5 @@ options: logging: CLOUD_LOGGING_ONLY substitutions: _VERSION: '3.13' - _TOOLBOX_VERSION: '1.5.0' + _TOOLBOX_VERSION: 'v1.6.0' _TOOLBOX_MANIFEST_VERSION: '34' diff --git a/packages/toolbox-llamaindex/pyproject.toml b/packages/toolbox-llamaindex/pyproject.toml index ec8107ead..08b64b3d8 100644 --- a/packages/toolbox-llamaindex/pyproject.toml +++ b/packages/toolbox-llamaindex/pyproject.toml @@ -50,6 +50,7 @@ test = [ "pytest-asyncio==1.4.0", "pytest==9.0.3", "pytest-cov==7.1.0", + "numpy<2.2.0", "Pillow==12.2.0; python_version >= '3.10'", "google-cloud-secret-manager==2.28.0", "google-cloud-storage==3.10.1", diff --git a/packages/toolbox-llamaindex/tests/conftest.py b/packages/toolbox-llamaindex/tests/conftest.py index c8a8fa5f9..e5bd6f771 100644 --- a/packages/toolbox-llamaindex/tests/conftest.py +++ b/packages/toolbox-llamaindex/tests/conftest.py @@ -25,7 +25,7 @@ from typing import Generator import google -import pytest_asyncio +import pytest from google.auth import compute_engine from google.cloud import secretmanager, storage @@ -75,7 +75,8 @@ def get_toolbox_binary_url(toolbox_version: str) -> str: arch = ( "arm64" if os_system == "darwin" and platform.machine() == "arm64" else "amd64" ) - return f"v{toolbox_version}/{os_system}/{arch}/toolbox" + ext = ".exe" if os_system == "windows" else "" + return f"{toolbox_version}/{os_system}/{arch}/toolbox{ext}" def get_auth_token(client_id: str) -> str: @@ -92,17 +93,17 @@ def get_auth_token(client_id: str) -> str: #### Define Fixtures -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def project_id() -> str: return get_env_var("GOOGLE_CLOUD_PROJECT") -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def toolbox_version() -> str: return get_env_var("TOOLBOX_VERSION") -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def tools_file_path(project_id: str) -> Generator[str]: """Provides a temporary file path containing the tools manifest.""" tools_manifest = access_secret_version( @@ -115,7 +116,7 @@ def tools_file_path(project_id: str) -> Generator[str]: os.remove(tools_file_path) -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def auth_token1(project_id: str) -> str: client_id = access_secret_version( project_id=project_id, secret_id="sdk_testing_client1" @@ -123,7 +124,7 @@ def auth_token1(project_id: str) -> str: return get_auth_token(client_id) -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def auth_token2(project_id: str) -> str: client_id = access_secret_version( project_id=project_id, secret_id="sdk_testing_client2" @@ -131,32 +132,47 @@ def auth_token2(project_id: str) -> str: return get_auth_token(client_id) -@pytest_asyncio.fixture(scope="session") +@pytest.fixture(scope="session") def toolbox_server(toolbox_version: str, tools_file_path: str) -> Generator[None]: """Starts the toolbox server as a subprocess.""" print("Downloading toolbox binary from gcs bucket...") source_blob_name = get_toolbox_binary_url(toolbox_version) - download_blob("mcp-toolbox-for-databases", source_blob_name, "toolbox") + bucket_name = ( + "mcp-toolbox-for-databases-dev" + if toolbox_version in ("main", "mcp-v202606") + else "mcp-toolbox-for-databases" + ) + download_blob(bucket_name, source_blob_name, "toolbox") print("Toolbox binary downloaded successfully.") try: print("Opening toolbox server process...") # Make toolbox executable os.chmod("toolbox", 0o700) # Run toolbox binary - toolbox_server = subprocess.Popen( - ["./toolbox", "--tools-file", tools_file_path] + toolbox_server_1 = subprocess.Popen( + ["./toolbox", "--port", "5000", "--tools-file", tools_file_path] + ) + toolbox_server_2 = subprocess.Popen( + [ + "./toolbox", + "--port", + "5001", + "--tools-file", + tools_file_path, + "--enable-draft-specs", + ] ) # Wait for server to start # Retry logic with a timeout for _ in range(5): # retries time.sleep(4) - print("Checking if toolbox is successfully started...") - if toolbox_server.poll() is None: - print("Toolbox server started successfully.") + print("Checking if both toolbox servers are successfully started...") + if toolbox_server_1.poll() is None and toolbox_server_2.poll() is None: + print("Toolbox servers started successfully.") break else: - raise RuntimeError("Toolbox server failed to start after 5 retries.") + raise RuntimeError("Toolbox servers failed to start after 5 retries.") except subprocess.CalledProcessError as e: print(e.stderr.decode("utf-8")) print(e.stdout.decode("utf-8")) @@ -164,5 +180,31 @@ def toolbox_server(toolbox_version: str, tools_file_path: str) -> Generator[None yield # Clean up toolbox server - toolbox_server.terminate() - toolbox_server.wait() + toolbox_server_1.terminate() + toolbox_server_2.terminate() + toolbox_server_1.wait() + toolbox_server_2.wait() + + +@pytest.fixture( + params=["http://localhost:5000", "http://localhost:5001"], scope="session" +) +def toolbox_server_url(request) -> str: + return request.param + + +@pytest.fixture(autouse=True) +def patch_toolbox_client_url(toolbox_server_url): + from toolbox_core.client import ToolboxClient + + original_init = ToolboxClient.__init__ + + def new_init(self, url="http://localhost:5000", *args, **kwargs): + if url == "http://localhost:5000": + url = toolbox_server_url + original_init(self, url, *args, **kwargs) + + from unittest.mock import patch + + with patch.object(ToolboxClient, "__init__", new_init, create=True): + yield