Skip to content

Commit

Permalink
fix: pass doctests, s/doctest/doctests/, run them in gha, s/asyncpg/p…
Browse files Browse the repository at this point in the history
…sycopg/ in doctest, fix keycloak flakiness: wait for first user (#505)

Doctests are run as interpreted code from docstrings. In order to run
these tests, libraries need to be available for the example code, and
usage of async code either needs to be wrapped in an `asyncio` call or
avoided completely.

This PR fixes up all failing doctests and makes `make doctests` target
run successfully again.
Summary:
- Renames Make target `doctest` to `doctests` to follow naming
convention from `tests` target
- Adds `doctests` step to Github Action workflow runs
- Replaces `asyncpg` example from `index.rst` with `psycopg` to be able
to run as a doctest. Also added `psycopg` as dev dependency (`asyncpg`
was already missing from here)
- Fixes Keycloak doctest by providing expected output, also did the same
for regular test
- Also: Fixed `wait_for_container` method in `Keycloak` module to
actually wait for the first user to be created (in order to be able to
authenticate at all) before returning the started container, if the
command is `dev-start`. This is needed in order to prevent race
conditions in flaky tests and for the sample usage code.
  • Loading branch information
santi authored Mar 27, 2024
1 parent dd55082 commit 545240d
Show file tree
Hide file tree
Showing 11 changed files with 59 additions and 28 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci-community.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,5 @@ jobs:
run: poetry install -E ${{ matrix.module }}
- name: Run tests
run: make modules/${{ matrix.module }}/tests
- name: Run doctests
run: make modules/${{ matrix.module }}/doctests
2 changes: 2 additions & 0 deletions .github/workflows/ci-core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,5 @@ jobs:
run: poetry build && poetry run twine check dist/*.tar.gz
- name: Run tests
run: make core/tests
- name: Run doctests
run: make core/doctests
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ UPLOAD = $(addsuffix /upload,${PACKAGES})
# All */tests folders for each of the test suites.
TESTS = $(addsuffix /tests,$(filter-out meta,${PACKAGES}))
TESTS_DIND = $(addsuffix -dind,${TESTS})
DOCTESTS = $(addsuffix /doctest,$(filter-out meta,${PACKAGES}))
DOCTESTS = $(addsuffix /doctests,$(filter-out modules/README.md,${PACKAGES}))
# All linting targets.
LINT = $(addsuffix /lint,${PACKAGES})

Expand Down Expand Up @@ -56,10 +56,10 @@ ${TESTS_DIND} : %/tests-dind : image
docs :
poetry run sphinx-build -nW . docs/_build

doctest : ${DOCTESTS}
doctests : ${DOCTESTS}
poetry run sphinx-build -b doctest . docs/_build

${DOCTESTS} : %/doctest :
${DOCTESTS} : %/doctests :
poetry run sphinx-build -b doctest -c doctests $* docs/_build

# Remove any generated files.
Expand Down
2 changes: 1 addition & 1 deletion conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "meta/README.rst", ".venv"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".venv", ".git"]

# The name of the Pygments (syntax highlighting) style to use.
pygments_style = "sphinx"
Expand Down
24 changes: 12 additions & 12 deletions index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ testcontainers-python facilitates the use of Docker containers for functional an
modules/elasticsearch/README
modules/google/README
modules/influxdb/README
modules/k3s/README
modules/kafka/README
modules/keycloak/README
modules/localstack/README
Expand All @@ -36,7 +37,6 @@ testcontainers-python facilitates the use of Docker containers for functional an
modules/rabbitmq/README
modules/redis/README
modules/selenium/README
modules/k3s/README

Getting Started
---------------
Expand All @@ -46,32 +46,32 @@ Getting Started
>>> from testcontainers.postgres import PostgresContainer
>>> import sqlalchemy

>>> with PostgresContainer("postgres:latest") as postgres:
>>> with PostgresContainer("postgres:16") as postgres:
... psql_url = postgres.get_connection_url()
... engine = sqlalchemy.create_engine(psql_url)
... with engine.begin() as connection:
... result = connection.execute(sqlalchemy.text("select version()"))
... version, = result.fetchone()
... version, = connection.execute(sqlalchemy.text("SELECT version()")).fetchone()
>>> version
'PostgreSQL ...'
'PostgreSQL 16...'

The snippet above will spin up the current latest version of a postgres database in a container. The :code:`get_connection_url()` convenience method returns a :code:`sqlalchemy` compatible url (using the :code:`psycopg2` driver per default) to connect to the database and retrieve the database version.

.. doctest::

>>> import asyncpg
>>> from testcontainers.postgres import PostgresContainer
>>> import psycopg

>>> with PostgresContainer("postgres:16", driver=None) as postgres:
... psql_url = container.get_connection_url()
... with asyncpg.create_pool(dsn=psql_url,server_settings={"jit": "off"}) as pool:
... conn = await pool.acquire()
... ret = await conn.fetchval("SELECT 1")
... assert ret == 1
... psql_url = postgres.get_connection_url()
... with psycopg.connect(psql_url) as connection:
... with connection.cursor() as cursor:
... version, = cursor.execute("SELECT version()").fetchone()
>>> version
'PostgreSQL 16...'

This snippet does the same, however using a specific version and the driver is set to None, to influence the :code:`get_connection_url()` convenience method to not include a driver in the URL (e.g. for compatibility with :code:`psycopg` v3).

Note, that the :code:`sqlalchemy` and :code:`psycopg2` packages are no longer a dependency of :code:`testcontainers[postgres]` and not needed to launch the Postgres container. Your project therefore needs to declare a dependency on the used driver and db access methods you use in your code.
Note, that the :code:`sqlalchemy` and :code:`psycopg` packages are no longer a dependency of :code:`testcontainers[postgres]` and not needed to launch the Postgres container. Your project therefore needs to declare a dependency on the used driver and db access methods you use in your code.


Installation
Expand Down
13 changes: 9 additions & 4 deletions modules/keycloak/testcontainers/keycloak/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@

from keycloak import KeycloakAdmin
from testcontainers.core.container import DockerContainer
from testcontainers.core.waiting_utils import wait_container_is_ready
from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs

_DEFAULT_DEV_COMMAND = "start-dev"


class KeycloakContainer(DockerContainer):
Expand All @@ -30,8 +32,9 @@ class KeycloakContainer(DockerContainer):
>>> from testcontainers.keycloak import KeycloakContainer
>>> with KeycloakContainer() as kc:
... keycloak = kc.get_client()
>>> with KeycloakContainer(f"quay.io/keycloak/keycloak:24.0.1") as keycloak:
... keycloak.get_client().users_count()
1
"""

def __init__(
Expand All @@ -55,7 +58,7 @@ def _configure(self) -> None:
self.with_env("KC_HEALTH_ENABLED", "true")
# Starting Keycloak in development mode
# see: https://www.keycloak.org/server/configuration#_starting_keycloak_in_development_mode
self.with_command("start-dev")
self.with_command(_DEFAULT_DEV_COMMAND)

def get_url(self) -> str:
host = self.get_container_host_ip()
Expand All @@ -67,6 +70,8 @@ def _readiness_probe(self) -> None:
# Keycloak provides an REST API endpoints for health checks: https://www.keycloak.org/server/health
response = requests.get(f"{self.get_url()}/health/ready", timeout=1)
response.raise_for_status()
if self._command == _DEFAULT_DEV_COMMAND:
wait_for_logs(self, "Added user .* to realm .*")

def start(self) -> "KeycloakContainer":
self._configure()
Expand Down
2 changes: 1 addition & 1 deletion modules/keycloak/tests/test_keycloak.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
@pytest.mark.parametrize("image_version", ["24.0.1", "18.0"])
def test_docker_run_keycloak(image_version: str):
with KeycloakContainer(f"quay.io/keycloak/keycloak:{image_version}") as keycloak_admin:
keycloak_admin.get_client().users_count()
assert keycloak_admin.get_client().users_count() == 1
3 changes: 1 addition & 2 deletions modules/postgres/testcontainers/postgres/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ class PostgresContainer(DbContainer):
>>> from testcontainers.postgres import PostgresContainer
>>> import sqlalchemy
>>> postgres_container = PostgresContainer("postgres:16")
>>> with postgres_container as postgres:
>>> with PostgresContainer("postgres:16") as postgres:
... engine = sqlalchemy.create_engine(postgres.get_connection_url())
... with engine.begin() as connection:
... result = connection.execute(sqlalchemy.text("select version()"))
Expand Down
2 changes: 0 additions & 2 deletions modules/postgres/tests/test_postgres.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import sys

import pytest

from testcontainers.postgres import PostgresContainer
Expand Down
27 changes: 25 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ description = "Python library for throwaway instances of anything that can run i
authors = ["Sergey Pirogov <[email protected]>"]
maintainers = [
"Balint Bartha <[email protected]>",
"David Ankin <[email protected]>"
"David Ankin <[email protected]>",
"Vemund Santi <[email protected]>"
]
readme = "README.md"
keywords = ["testing", "logging", "docker", "test automation"]
Expand Down Expand Up @@ -120,6 +121,7 @@ anyio = "^4.3.0"
psycopg2-binary = "*"
pg8000 = "*"
sqlalchemy = "*"
psycopg = "*"
kafka-python = "^2.0.2"

[[tool.poetry.source]]
Expand Down

0 comments on commit 545240d

Please sign in to comment.