Egor Rachkovskii 352ea91aa8
Add support for Python 3.13 and 3.14 in CI workflows and update README accordingly (#4)
Co-authored-by: Egor Rachkovskii <egorrachkovskii@status.im>
2026-05-08 21:22:39 +01:00

101 lines
5.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# logos-integration-test-framework
Pytest plugin + helpers for writing integration tests against a `logoscore` daemon. Built on top of [`logos-co/logos-logoscore-py`](https://github.com/logos-co/logos-logoscore-py).
## What's in the box
```
src/logos_integration_test_framework/
├── __init__.py # exports: subscribe, wait_for_event, Waiter, EventTimeout
├── waits.py # Queue-backed adapters over LogoscoreClient.on_event
└── fixtures.py # pytest fixtures: local_daemon, local_client, docker_daemon, docker_client
# (auto-loaded via pytest11 entry-point — no import needed)
```
That's the whole package. Client / transport / topology layers live upstream in `logoscore-py` — don't reimplement them.
## Install (consumer-side)
In your module's test repo (e.g. `logos-chat-module/tests/integration/`):
```toml
# pyproject.toml
[project.optional-dependencies]
test = [
"logos-integration-test-framework @ git+https://github.com/logos-messaging/logos-integration-test-framework.git@<commit-sha>",
"pytest>=8.0",
]
```
Pin a commit SHA, not a branch. Then `pip install -e '.[test]'` and the four daemon/client fixtures are immediately available in your tests — no `pytest_plugins` declaration needed.
## Writing a test (consumer)
Open the subscription **before** triggering the action — the upstream `logoscore watch` subprocess takes a moment to come live, and events fired before that window are lost. A short sleep or a known-pumped sentinel event is enough.
```python
import time
from logos_integration_test_framework import subscribe
def test_my_module(local_client): # local_client comes from the auto-loaded plugin
local_client.load_module("my_module")
with subscribe(local_client, "my_module", "DoneEvent") as w:
time.sleep(0.3) # let the watcher come live
request_id = local_client.call("my_module", "do_something", "arg")
event = w.next(
predicate=lambda e: e["data"][0] == request_id,
timeout=10.0,
)
assert event["event"] == "DoneEvent"
```
Multiple waits in one test — re-use the same subscription:
```python
def test_two_waits(local_client):
with subscribe(local_client, "my_module") as w:
time.sleep(0.3)
local_client.call("my_module", "fire", "first")
first = w.next(lambda e: e["data"][0] == 1, timeout=5.0)
local_client.call("my_module", "fire", "second")
second = w.next(lambda e: e["data"][0] == 2, timeout=5.0)
assert first != second
```
Predicate exceptions surface on the test thread (unlike exceptions raised inside an upstream `on_event` callback, which the watcher's pump catches and routes to `error_callback`).
The one-shot `wait_for_event(...)` is a convenience for cases where the trigger has *already* happened and the event is still in flight; for the more common subscribe-then-trigger pattern, use `subscribe(...)` directly.
## Fixtures
| Fixture | Scope | Provides | Skip condition |
|---|---|---|---|
| `local_daemon` | module | `logoscore.LogoscoreDaemon` (local subprocess) | `logoscore` not on `PATH` or `LOGOS_MODULES_DIR` unset / missing |
| `local_client` | function | `LogoscoreClient` from `local_daemon` | (inherits) |
| `docker_daemon` | module | `logoscore.LogoscoreDockerDaemon` (containerised) | `docker` CLI absent, image absent, `LOGOSCORE_IMAGE`/`LOGOS_MODULES_DIR` unset |
| `docker_client` | function | `LogoscoreClient` from `docker_daemon` | (inherits) |
Default scopes are `module` for daemons (function-scope is too slow given upstream's `startup_timeout=15.0`; session-scope hides cross-test leaks) and `function` for clients (`client.stop()` would otherwise poison every later test in the module). Override per-test with `pytest.fixture(scope=...)` if needed.
The `docker_*` fixtures need the `docker` CLI on `PATH` — upstream `LogoscoreDockerDaemon` shells out to it (it does not use docker-py). The fixture skips automatically if the binary is absent.
To opt out of the auto-loaded plugin in a particular run: `pytest -p no:logos_integration_test_framework`.
## Contributing to this repo
```bash
python -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]' # ← required: registers the pytest11 entry-point
pytest tests/unit -q # 13 tests: surface check + waits.py
```
`pip install -e '.[dev]'` is **required** before `pytest` — otherwise the entry-point isn't registered and the smoke test (`tests/test_smoke.py`) won't see the `local_*` fixtures. CI does this automatically.
Lint / type / unit runs are gated in `.github/workflows/unit.yml` (Python 3.11 3.14). Direct pushes to `master` are rejected by branch-protection rules; merge via PR with green CI.
## Related
- [`logos-co/logos-logoscore-py`](https://github.com/logos-co/logos-logoscore-py) — the Python wrapper this builds on (`LogoscoreDaemon` / `LogoscoreDockerDaemon` / `LogoscoreClient`).
- [`logos-co/logos-test-framework`](https://github.com/logos-co/logos-test-framework) — **different layer**: a C++ unit-test framework for module internals (gtest-style, link-time mock substitution). No overlap with this Python package.