-
-
Notifications
You must be signed in to change notification settings - Fork 14
Moved tests to using testcontainers #232
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
1629117
f90cbf1
467b614
078fc07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,24 +1,79 @@ | ||||||
| import contextlib | ||||||
| import json | ||||||
| import os | ||||||
| import re | ||||||
| from collections.abc import Iterator | ||||||
| from pathlib import Path | ||||||
| from typing import Any, NamedTuple | ||||||
|
|
||||||
| import _pytest.mark | ||||||
| import httpx | ||||||
| import pytest | ||||||
| import sqlalchemy | ||||||
| from _pytest.config import Config | ||||||
| from _pytest.nodes import Item | ||||||
| from dotenv import load_dotenv | ||||||
| from fastapi.testclient import TestClient | ||||||
| from sqlalchemy import Connection, Engine, text | ||||||
| from testcontainers.mysql import LogMessageWaitStrategy, MySqlContainer | ||||||
|
|
||||||
| from database.setup import expdb_database, user_database | ||||||
| from database.setup import user_database | ||||||
| from main import create_api | ||||||
| from routers.dependencies import expdb_connection, userdb_connection | ||||||
|
|
||||||
| load_dotenv() | ||||||
|
|
||||||
| PHP_API_URL = "http://openml-php-rest-api:80/api/v1/json" | ||||||
|
|
||||||
|
|
||||||
| @pytest.fixture(scope="session", autouse=True) | ||||||
| def override_testcontainers_connect() -> None: | ||||||
| """ | ||||||
| Override MySqlContainer._connect once per test session. | ||||||
| Applied automatically everywhere. | ||||||
| """ | ||||||
|
|
||||||
| def _connect(self: MySqlContainer) -> None: | ||||||
| wait_strategy = LogMessageWaitStrategy( | ||||||
| re.compile( | ||||||
| r".*: ready for connections", | ||||||
| flags=re.DOTALL | re.MULTILINE, | ||||||
| ) | ||||||
| ) | ||||||
| wait_strategy.wait_until_ready(self) | ||||||
|
|
||||||
| MySqlContainer._connect = _connect # noqa: SLF001 | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove unused Ruff reports this directive as unused at Line 45; drop it to keep lint clean. 🧹 Proposed cleanup- MySqlContainer._connect = _connect # noqa: SLF001
+ MySqlContainer._connect = _connect📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.14.13)45-45: Unused Remove unused (RUF100) 🤖 Prompt for AI Agents |
||||||
|
|
||||||
|
|
||||||
| @pytest.fixture(scope="session") | ||||||
| def mysql_container() -> MySqlContainer: | ||||||
| container = MySqlContainer( | ||||||
| os.environ.get( | ||||||
| "OPENML_DATABASES_OPENML_URL", | ||||||
| "openml/test-database:20240105", | ||||||
| ), | ||||||
| username=os.environ.get("OPENML_DATABASES_OPENML_USERNAME", ""), | ||||||
| password=os.environ.get("OPENML_DATABASES_OPENML_PASSWORD", ""), | ||||||
| dbname="openml_expdb", | ||||||
| ) | ||||||
|
|
||||||
| container.start() | ||||||
| try: | ||||||
| yield container | ||||||
| finally: | ||||||
| container.stop() | ||||||
|
Comment on lines
+48
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: In testcontainers-python,
So, the exception type to catch for “Docker unavailable” is generally 🏁 Script executed: # Check if there are existing patterns for Docker error handling in the codebase
rg -i "docker|container" tests/conftest.py -A 3 -B 3Repository: openml/server-api Length of output: 1397 🏁 Script executed: # Search for any existing pytest.skip usage patterns related to Docker or external services
rg "pytest.skip" --type py -B 2 -A 2Repository: openml/server-api Length of output: 2038 🏁 Script executed: # Check imports in conftest.py to understand what's currently imported
head -30 tests/conftest.pyRepository: openml/server-api Length of output: 840 🏁 Script executed: # Check if there are any other exception handling patterns for testcontainers in the codebase
rg "testcontainers|MySqlContainer" --type pyRepository: openml/server-api Length of output: 622 🏁 Script executed: # Verify the fixture is used as a dependency in tests to understand impact scope
rg "mysql_container" tests/ --type pyRepository: openml/server-api Length of output: 265 Skip tests gracefully when Docker isn't available (CI currently fails). The pipeline reports a Docker connection error; 🛠️ Proposed fix+from docker.errors import DockerException
+
`@pytest.fixture`(scope="session")
def mysql_container() -> MySqlContainer:
container = MySqlContainer(
os.environ.get(
"OPENML_DATABASES_OPENML_URL",
"openml/test-database:20240105",
),
username=os.environ.get("OPENML_DATABASES_OPENML_USERNAME", ""),
password=os.environ.get("OPENML_DATABASES_OPENML_PASSWORD", ""),
dbname="openml_expdb",
)
- container.start()
+ started = False
+ try:
+ container.start()
+ started = True
+ except DockerException as exc:
+ pytest.skip(f"Docker unavailable: {exc}")
try:
yield container
finally:
- container.stop()
+ if started:
+ container.stop()🧰 Tools🪛 GitHub Actions: Tests[error] 50-50: Failed to initialize MySqlContainer due to docker connection error 🤖 Prompt for AI Agents |
||||||
|
|
||||||
|
|
||||||
| @pytest.fixture | ||||||
| def expdb_test(mysql_container: MySqlContainer) -> Connection: | ||||||
| url = mysql_container.get_connection_url() | ||||||
| url = url.replace("mysql://", "mysql+pymysql://") | ||||||
|
|
||||||
| engine = sqlalchemy.create_engine(url) | ||||||
| with engine.begin() as connection: | ||||||
| yield connection | ||||||
|
Comment on lines
+67
to
+74
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (testing): The new With |
||||||
|
|
||||||
|
|
||||||
| @contextlib.contextmanager | ||||||
| def automatic_rollback(engine: Engine) -> Iterator[Connection]: | ||||||
| with engine.connect() as connection: | ||||||
|
|
@@ -28,12 +83,6 @@ def automatic_rollback(engine: Engine) -> Iterator[Connection]: | |||||
| transaction.rollback() | ||||||
|
|
||||||
|
|
||||||
| @pytest.fixture | ||||||
| def expdb_test() -> Connection: | ||||||
| with automatic_rollback(expdb_database()) as connection: | ||||||
| yield connection | ||||||
|
|
||||||
|
|
||||||
| @pytest.fixture | ||||||
| def user_test() -> Connection: | ||||||
| with automatic_rollback(user_database()) as connection: | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
🧩 Analysis chain
🌐 Web query:
testcontainers-python MySqlContainer wait strategy customize public API💡 Result:
In testcontainers-python,
MySqlContainerdoesn’t expose a Java-style “wait strategy” hook on the class itself; the public API for customizing readiness is via the waiting utilities (log-based waiting) and/or by subclassing and overriding the connection check.1) Log-based waiting (public API)
Use
testcontainers.core.waiting_utils.wait_for_logs(container, predicate, timeout=None, interval=1)after the container is started (or inside a custom container’s startup flow). The predicate can be a regex string or a callable. The function supportstimeoutandinterval. [1]MySqlContaineritself is the standard MySQL wrapper you’re already using. [2]2) Connection-based waiting via
@wait_container_is_ready(public API)If you want “wait until I can connect” semantics (common for DBs), subclass
MySqlContainerand decorate your_connect()withwait_container_is_ready(...)so failures are retried until success/timeout. [3]3) Tuning retry/timeout behavior
Retry limits/sleep/timeout for readiness waiting are controlled via
testcontainers_config(global configuration). [3]Sources: [1] [2] [3]
Use
wait_for_logs()or subclass with@wait_container_is_ready()instead of monkey-patching_connect.Line 36 overrides a private method, which breaks on testcontainers-python upgrades if internals change. testcontainers-python provides public APIs:
wait_for_logs(): Call after container startup with a regex predicate for log-based readiness@wait_container_is_ready()decorator: Override_connectin a subclass with the decorator to handle retry logic safelyExample using
wait_for_logs:🤖 Prompt for AI Agents