Skip to content

Commit 84dbef4

Browse files
committed
chore(test): port keycloak-version unit test to testcontainers-python
Replace the 350-line hand-rolled podman orchestration with a ~70-line testcontainers-python test that pulls each supported Keycloak major (18 through 26) as an upstream image, waits on a log marker, and runs the plugin from the host against the exposed port. Upsides compared to the old run file: - No more hardcoded EOL-date regexes. The old test baked `EOL 2022-07-27 -30d [WARNING]` style assertions into the regex, which broke every time wall-clock time moved the EOL-offset around. The new assertions check "version string is reported" and "state is one of OK/WARN/CRIT". - Declarative image list, single `IMAGES = [...]` block. Adding a new major means a single line. - Uses the `Listening on:` log marker as the readiness probe instead of `time.sleep(3) + time.sleep(30)`. - Runs the plugin from the host, so the upstream Keycloak image is enough - no need to maintain per-version Containerfiles that install Python into the service image. - 213s total for 9 versions end-to-end (was 20+ minutes when the old run actually completed - the py39 tox run timed out on the first container). The helper behind this is `lib.lftest.run_container()`, shipped in linuxfabrik-lib 2026041201. CONTRIBUTING's "Container-based tests" section is rewritten accordingly: reference example, guidance against state-shifting assertions, `DOCKER_HOST` / `TESTCONTAINERS_RYUK_DISABLED` setup for rootless podman.
1 parent 5eb08ea commit 84dbef4

File tree

2 files changed

+151
-333
lines changed

2 files changed

+151
-333
lines changed

CONTRIBUTING.md

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -854,12 +854,65 @@ sudo apt install python3.9-dev python3.10-dev python3.11-dev \
854854

855855
#### Container-based tests
856856

857-
If you want to implement unit tests based on containers, the following rules apply:
857+
For checks that must run against a real service (Keycloak, Redis, a database, a web API), use the `lib.lftest.run_container()` helper from the `linuxfabrik-lib` package. It wraps [testcontainers-python](https://testcontainers-python.readthedocs.io/) so that container lifecycle, port exposure, environment variables and log-based readiness waits are declarative rather than hand-rolled podman orchestration.
858858

859-
* Each container file does everything necessary to set up a running environment for the check plugin (e.g. install Python if you want to run the plugin inside the container).
860-
* The `./run` unit test simply calls podman and, for each containerfile found, builds the container, injects the libs and the check plugin, and runs the tests - but does not modify the container in any other way.
861-
* See the `keycloak-version` plugin for how to do this.
862-
* `tools/run-unit-tests` auto-detects container tests by looking for a `containerfiles/` subdirectory, so `--no-container` / `--only-container` filter correctly without any per-plugin annotation.
859+
Minimal example (see `check-plugins/keycloak-version/unit-test/run` for the full reference):
860+
861+
```python
862+
import subprocess
863+
import sys
864+
import unittest
865+
866+
sys.path.append('..')
867+
868+
import lib.lftest
869+
from lib.globals import STATE_OK, STATE_WARN, STATE_CRIT
870+
871+
IMAGES = [
872+
('quay.io/keycloak/keycloak:25.0.6', 'v25'),
873+
('quay.io/keycloak/keycloak:26.6', 'v26'),
874+
]
875+
876+
877+
class TestCheck(unittest.TestCase):
878+
def test(self):
879+
for image, version_tag in IMAGES:
880+
with self.subTest(image=image):
881+
with lib.lftest.run_container(
882+
image,
883+
env={
884+
'KEYCLOAK_ADMIN': 'admin',
885+
'KEYCLOAK_ADMIN_PASSWORD': 'admin',
886+
},
887+
ports=[8080],
888+
command='start-dev',
889+
wait_log='Listening on:',
890+
) as container:
891+
url = f'http://{container.get_container_host_ip()}:{container.get_exposed_port(8080)}'
892+
result = subprocess.run(
893+
['python3', '../keycloak-version',
894+
f'--url={url}', '--username=admin', '--password=admin',
895+
'--path=/nonexistent'],
896+
capture_output=True, text=True,
897+
)
898+
self.assertRegex(
899+
result.stdout + result.stderr,
900+
rf'Keycloak\s+{version_tag}',
901+
)
902+
self.assertIn(
903+
result.returncode, (STATE_OK, STATE_WARN, STATE_CRIT),
904+
)
905+
```
906+
907+
Rules and tips:
908+
909+
* **Pull upstream images whenever possible.** You do not need a custom `Containerfile` that injects Python into the service image, because the plugin runs from the host and connects to the container via the exposed port. That is the common case for API-driven checks.
910+
* **Wait on a log marker, not a sleep.** The `wait_log` argument takes a substring that the service writes to stdout/stderr when it is ready (e.g. `Listening on:` for Keycloak, `ready for connections.` for MariaDB). Use `wait_log_timeout` for services that take longer than 2 minutes to start.
911+
* **Do not hardcode state-shifting assertions.** If the plugin reports something that depends on today's date (EOL windows, "last seen N days ago", "expires in X days"), assert only that the plugin returned a valid state (any of `STATE_OK`, `STATE_WARN`, `STATE_CRIT`) and that the output contains the expected version / service identifier. Locking in a specific state will break the test every time the calendar moves past a boundary.
912+
* **Multi-version matrix** goes in an `IMAGES` list at the top of the test file, iterated via `self.subTest(image=...)`. Add a new major release at the bottom of the list when it becomes available upstream.
913+
* **Rootless podman**: testcontainers-python works, but the Ryuk cleanup container needs to be disabled. Set `TESTCONTAINERS_RYUK_DISABLED=true` and `DOCKER_HOST=unix:///run/user/$UID/podman/podman.sock` before running the tests. A lightweight wrapper can live in `tools/run-container-tests` to set these for you.
914+
* **Do not run container tests via `tox`.** They are integration tests and belong in `tools/run-container-tests`, not in the multi-Python matrix. `tools/run-unit-tests` detects them automatically by inspecting the `run` file for `podman` or `testcontainers` references.
915+
* **Keep hand-rolled podman orchestration out of new tests.** If you find a plugin that still builds containers via `subprocess.run(['podman', 'build', ...])`, migrate it to `lib.lftest.run_container()`; the old pattern is being retired.
863916

864917

865918
### sudoers File

0 commit comments

Comments
 (0)