Migrate integration tests from pytest-operator (async) to jubilant (sync)#750
Draft
Migrate integration tests from pytest-operator (async) to jubilant (sync)#750
Conversation
…r async to jubilant sync Co-authored-by: addyess <10090033+addyess@users.noreply.github.com>
Copilot
AI
changed the title
[WIP] Migrate pytest-operator async tests to jubilant
Migrate integration tests from pytest-operator (async) to jubilant (sync)
Nov 12, 2025
added 4 commits
November 13, 2025 14:11
…erences in conftest - Convert all async helper functions to sync (removed async/await) - Replace AsyncRetrying with Retrying, alru_cache with lru_cache - Update all OpsTest references to jubilant parameter - Fix jubilant.request references by adding request fixture parameter - Remove asyncio imports throughout - Add MIGRATION comments for clarity - All lint checks passing
- Removed pytest-operator (conflicts with pytest-jubilant) - Removed pytest-asyncio (no longer needed for sync tests) - Removed async-lru (replaced with functools.lru_cache) - Updated uv.lock to reflect dependency changes
- pytest-jubilant provides its own juju client wrapper - Removed juju and its transitive dependencies from integration group
4ee0c36 to
9944c3c
Compare
Contributor
Test results for commit 9944c3cTest coverage for 9944c3c Static code analysis report |
Contributor
Test results for commit 9944c3cTest coverage for 9944c3c Static code analysis report |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Applicable spec: N/A
Overview
Migrates
conftest.pyandtest_smoke.pyfrom pytest-operator's async pattern to jubilant's sync pattern. Updates juju-channel from3/stableto4/candidateper Juju 4 testing requirements.Rationale
pytest-jubilant provides synchronous equivalents for pytest-operator's async fixtures, simplifying test code and aligning with Juju 4 testing infrastructure.
Juju Events Changes
N/A
Module Changes
tests/integration/conftest.py
pytest_plugins = ["pytest_jubilant"]OpsTesttype withjubilantfixture parameterskip_by_cloud_type,deploy_model,kubernetes_cluster,api_client,metrics_agent,cos_model,cos_lite_installed,traefik_url,expected_dashboard_titles,related_grafana,grafana_password,related_prometheusAsyncGenerator[Model, None]→Generator[Model, None, None]pytest_asyncio,OpsTestimports, allasync def/awaitkeywordstests/integration/test_smoke.py
preserve_charm_configfixture and 2 test functions from async to syncasyncio.gather(*[...])with list comprehensionsasyncio,pytest_asyncioimports, allasync def/awaitkeywordspyproject.toml
pytest-jubilantto integration dependencies.github/workflows/integration_test.yaml
juju-channel: 3/stable→juju-channel: 4/candidateMigration pattern:
Library Changes
N/A
Checklist
src-docsurgent,trivial,complex)Test-only changes. Remaining integration test files (test_ceph.py, test_cos.py, test_dqlite.py, test_dualstack.py, test_etcd.py, test_ipv6_only.py, test_k8s.py, test_openstack.py, test_registry.py, test_upgrade.py) still use async patterns.
Original prompt
This section details on the original issue you should resolve
<issue_title>switching to jubilant for juju4 testing</issue_title>
<issue_description>### Enhancement Proposal
Task: Migrate pytest-operator async tests to jubilant (sync) and validate quickly with lint.
Context:
Repo: canonical/k8s-operator
Files to change (start here): conftest.py and test_smoke.py
We will NOT run integration tests in CI here. Instead use tox -e lint (mypy + lint) as a fast verification step.
Use the Jubilant migration guide as reference: https://documentation.ubuntu.com/jubilant/how-to/migrate-from-pytest-operator/
pytest-jubilant repo: https://github.com/canonical/pytest-jubilant
High-level goal:
Replace async pytest-operator usage (fixture ops_test, pytest_asyncio, async def, await, async with, asyncio.gather) with synchronous jubilant usage provided by pytest-jubilant.
Keep behavior identical where possible; make minimal changes. Add clear TODO comments where assumptions are made.
Run tox -e lint to verify mypy/lint issues, iterate until lint passes.
Detailed instructions for Copilot to perform changes
Top-level plugin enablement
In conftest.py add (near top, after imports) the line:
pytest_plugins = ["pytest_jubilant"]
Remove the import of OpsTest typing:
Remove: from pytest_operator.plugin import OpsTest
Replace pytest-async patterns with sync
Replace imports:
Remove import pytest_asyncio if only used for fixture decorators in this file.
Ensure import pytest remains.
Replace fixture decorators:
@pytest_asyncio.fixture -> @pytest.fixture
Keep scope="module" and params=... when present.
Convert async def fixture/test -> def and remove awaits inside their bodies.
Convert @contextlib.asynccontextmanager to @contextlib.contextmanager and async def -> def and change async with -> with.
Replace AsyncGenerator typing with normal Generator or remove typing if uncertain.
Replace ops_test usage with jubilant
Replace fixture parameter ops_test -> jubilant in fixtures/tests.
Remove await before jubilant.* calls. Example:
await ops_test.model.get_controller() -> jubilant.model.get_controller()
async with ops_test.fast_forward(ONE_MIN): -> with jubilant.fast_forward(ONE_MIN):
await ops_test.track_model(...) -> jubilant.track_model(...)
await ops_test.add_k8s(...) -> jubilant.add_k8s(...)
await ops_test.forget_model(...) -> jubilant.forget_model(...)
await ops_test.juju(...) -> jubilant.juju(...)
If code calls helper methods on ops_test that are async-only in pytest-operator and do not map directly to jubilant, add # TODO: verify jubilant API comment and keep the call but remove await (so mypy will flag it if needed).
Replace asyncio.gather / parallel awaits
Convert:
pre = await asyncio.gather(*[app.get_config() for app in apps])
-> pre = [app.get_config() for app in apps]
Convert any await asyncio.gather(...) into an appropriate synchronous equivalent (list comprehension or loop).
If any juju API remains asynchronous and no sync mapping exists, add a TODO comment and keep a placeholder that will be resolved later.
Tests conversion example (apply mechanically)
For test functions in test_smoke.py:
async def test_nodes_ready(kubernetes_cluster: juju.model.Model): -> def test_nodes_ready(kubernetes_cluster: juju.model.Model):
Remove await from calls inside the test.
If ready_nodes was async, replace with sync call or mark TODO.
Typing & mypy adjustments
Remove or change Async-specific typing:
Replace -> AsyncGenerator[Model, None] to -> Generator[Model, None, None] or drop the return type.
Where OpsTest type was used, remove the type annotation or replace with Any (import from typing if used).
Add in-file TODO comments for any unresolved types to make future iterations easier.
Add small in-code audit comments
Where you remove await from a call that previously returned an awaitable, add a short comment like:
MIGRATION: removed await per jubilant; verify this method is sync in jubilant
Where you convert async with to with, add:
MIGRATION: switched to non-async context manager (jubilant)
Minimal behavioral preservation
Preserve pytestmark = [...] lines and other test metadata unchanged.
Preserve DEFAULT_SNAP_INSTALLATION, TEST_DATA, and other constants.
Commit message and PR summary
Suggested commit message:
tests(integration): migrate conftest + test_smoke from pytest-operator async -> jubilant sync; verify with lint
Suggested PR description items to include:
What changed (files + short summary).
How it was validated (ran tox -e lint).
TODOs / follow-ups (list of other integration tests that still reference ops_test, any outstanding mypy errors left intentionally with comments).
Verification steps (fast checks only — do not run integration tests):
Collection check via lint target (this ensures imports and types are validated by mypy/flake):
Run locally:
tox -e lint
If tox -e lint is slow or not configured to run tests, run:
tox -e py39 -- -k test_smoke -q --collect-only
(only if you want pytest collection; still prefer tox...
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.