Skip to content

Commit 545240d

Browse files
authoredMar 27, 2024··
fix: pass doctests, s/doctest/doctests/, run them in gha, s/asyncpg/psycopg/ 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.
1 parent dd55082 commit 545240d

File tree

11 files changed

+59
-28
lines changed

11 files changed

+59
-28
lines changed
 

‎.github/workflows/ci-community.yml

+2
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,5 @@ jobs:
5757
run: poetry install -E ${{ matrix.module }}
5858
- name: Run tests
5959
run: make modules/${{ matrix.module }}/tests
60+
- name: Run doctests
61+
run: make modules/${{ matrix.module }}/doctests

‎.github/workflows/ci-core.yml

+2
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,5 @@ jobs:
2828
run: poetry build && poetry run twine check dist/*.tar.gz
2929
- name: Run tests
3030
run: make core/tests
31+
- name: Run doctests
32+
run: make core/doctests

‎Makefile

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ UPLOAD = $(addsuffix /upload,${PACKAGES})
1111
# All */tests folders for each of the test suites.
1212
TESTS = $(addsuffix /tests,$(filter-out meta,${PACKAGES}))
1313
TESTS_DIND = $(addsuffix -dind,${TESTS})
14-
DOCTESTS = $(addsuffix /doctest,$(filter-out meta,${PACKAGES}))
14+
DOCTESTS = $(addsuffix /doctests,$(filter-out modules/README.md,${PACKAGES}))
1515
# All linting targets.
1616
LINT = $(addsuffix /lint,${PACKAGES})
1717

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

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

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

6565
# Remove any generated files.

‎conf.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
# List of patterns, relative to source directory, that match files and
7575
# directories to ignore when looking for source files.
7676
# This patterns also effect to html_static_path and html_extra_path
77-
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "meta/README.rst", ".venv"]
77+
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".venv", ".git"]
7878

7979
# The name of the Pygments (syntax highlighting) style to use.
8080
pygments_style = "sphinx"

‎index.rst

+12-12
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ testcontainers-python facilitates the use of Docker containers for functional an
2121
modules/elasticsearch/README
2222
modules/google/README
2323
modules/influxdb/README
24+
modules/k3s/README
2425
modules/kafka/README
2526
modules/keycloak/README
2627
modules/localstack/README
@@ -36,7 +37,6 @@ testcontainers-python facilitates the use of Docker containers for functional an
3637
modules/rabbitmq/README
3738
modules/redis/README
3839
modules/selenium/README
39-
modules/k3s/README
4040

4141
Getting Started
4242
---------------
@@ -46,32 +46,32 @@ Getting Started
4646
>>> from testcontainers.postgres import PostgresContainer
4747
>>> import sqlalchemy
4848

49-
>>> with PostgresContainer("postgres:latest") as postgres:
49+
>>> with PostgresContainer("postgres:16") as postgres:
5050
... psql_url = postgres.get_connection_url()
5151
... engine = sqlalchemy.create_engine(psql_url)
5252
... with engine.begin() as connection:
53-
... result = connection.execute(sqlalchemy.text("select version()"))
54-
... version, = result.fetchone()
53+
... version, = connection.execute(sqlalchemy.text("SELECT version()")).fetchone()
5554
>>> version
56-
'PostgreSQL ...'
55+
'PostgreSQL 16...'
5756

5857
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.
5958

6059
.. doctest::
6160

62-
>>> import asyncpg
6361
>>> from testcontainers.postgres import PostgresContainer
62+
>>> import psycopg
6463

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

7272
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).
7373

74-
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.
74+
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.
7575

7676

7777
Installation

‎modules/keycloak/testcontainers/keycloak/__init__.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717

1818
from keycloak import KeycloakAdmin
1919
from testcontainers.core.container import DockerContainer
20-
from testcontainers.core.waiting_utils import wait_container_is_ready
20+
from testcontainers.core.waiting_utils import wait_container_is_ready, wait_for_logs
21+
22+
_DEFAULT_DEV_COMMAND = "start-dev"
2123

2224

2325
class KeycloakContainer(DockerContainer):
@@ -30,8 +32,9 @@ class KeycloakContainer(DockerContainer):
3032
3133
>>> from testcontainers.keycloak import KeycloakContainer
3234
33-
>>> with KeycloakContainer() as kc:
34-
... keycloak = kc.get_client()
35+
>>> with KeycloakContainer(f"quay.io/keycloak/keycloak:24.0.1") as keycloak:
36+
... keycloak.get_client().users_count()
37+
1
3538
"""
3639

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

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

7176
def start(self) -> "KeycloakContainer":
7277
self._configure()

‎modules/keycloak/tests/test_keycloak.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
@pytest.mark.parametrize("image_version", ["24.0.1", "18.0"])
66
def test_docker_run_keycloak(image_version: str):
77
with KeycloakContainer(f"quay.io/keycloak/keycloak:{image_version}") as keycloak_admin:
8-
keycloak_admin.get_client().users_count()
8+
assert keycloak_admin.get_client().users_count() == 1

‎modules/postgres/testcontainers/postgres/__init__.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ class PostgresContainer(DbContainer):
3838
>>> from testcontainers.postgres import PostgresContainer
3939
>>> import sqlalchemy
4040
41-
>>> postgres_container = PostgresContainer("postgres:16")
42-
>>> with postgres_container as postgres:
41+
>>> with PostgresContainer("postgres:16") as postgres:
4342
... engine = sqlalchemy.create_engine(postgres.get_connection_url())
4443
... with engine.begin() as connection:
4544
... result = connection.execute(sqlalchemy.text("select version()"))

‎modules/postgres/tests/test_postgres.py

-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import sys
2-
31
import pytest
42

53
from testcontainers.postgres import PostgresContainer

‎poetry.lock

+25-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎pyproject.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ description = "Python library for throwaway instances of anything that can run i
55
authors = ["Sergey Pirogov <automationremarks@gmail.com>"]
66
maintainers = [
77
"Balint Bartha <totallyzen@users.noreply.github.com>",
8-
"David Ankin <daveankin@gmail.com>"
8+
"David Ankin <daveankin@gmail.com>",
9+
"Vemund Santi <vemund@santi.no>"
910
]
1011
readme = "README.md"
1112
keywords = ["testing", "logging", "docker", "test automation"]
@@ -120,6 +121,7 @@ anyio = "^4.3.0"
120121
psycopg2-binary = "*"
121122
pg8000 = "*"
122123
sqlalchemy = "*"
124+
psycopg = "*"
123125
kafka-python = "^2.0.2"
124126

125127
[[tool.poetry.source]]

0 commit comments

Comments
 (0)
Please sign in to comment.