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).
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:
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_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`.
`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.
- [`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.