chore(convert-to-jubilant): migrate integration tests from pytest-asyncio to jubilant (charmkeeper)#597
chore(convert-to-jubilant): migrate integration tests from pytest-asyncio to jubilant (charmkeeper)#597alithethird wants to merge 35 commits into
Conversation
- Remove pytest-operator, pytest-asyncio, juju==3.6.1.3 from all
pyproject.toml dependency groups (lint and integration)
- Add pytest-jubilant>=2,<3 to all integration and lint dependency groups
- Register --charm-file and no-op --keep-models in all tests/conftest.py files
- Replace custom juju fixtures with simple wait_timeout overrides in all
sub-charm integration conftest.py files (haproxy-operator,
haproxy-ddos-protection-configurator, haproxy-route-policy-operator,
haproxy-spoe-auth-operator)
- Fix root-level tests/integration/conftest.py lxd_juju fixture:
remove --model support, fix always-true error condition check,
change return to yield inside temp_model context manager
- Convert legacy integration tests (haproxy-operator/tests/integration/legacy/):
- conftest.py: replace all pytest_asyncio fixtures with sync pytest
fixtures; replace Application/Model/OpsTest types with strings and
jubilant.Juju; add sync helper functions (get_unit_ip_address,
get_unit_address, get_ingress_url_for_application, DNSResolverHTTPSAdapter)
- helper.py: remove async get_ingress_url_for_application, add sync version
using juju.cli(); remove juju/pytest-operator imports
- test_action.py: async -> sync, use juju.run()/juju.ssh()
- test_charm.py: async -> sync, use get_unit_address helper
- test_config.py: async -> sync, use juju.config()/juju.wait()/juju.ssh()
- test_cos.py: async -> sync, use juju.ssh() replacing ops_test.juju()
- test_ha.py: async -> sync, use juju.config()/juju.integrate()/juju.wait()
- test_haproxy_route.py: async -> sync, use juju.integrate()/juju.run()
- test_http_interface.py: async -> sync, use juju.run()/juju.config()
- test_ingress.py: async -> sync, use get_ingress_url_for_application()
- test_website.py: async -> sync, use juju.integrate()/juju.wait()
- Fix test_ingress_per_unit.py: remove spurious async def
- Fix haproxy-ddos-protection-configurator test_charm.py: remove
@pytest.mark.asyncio and async def; use 10*60 timeout notation
- Update copyright year to 2026 in heavily modified files
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR migrates the repository’s integration test suite from the pytest-asyncio / pytest-operator / python-libjuju stack to the synchronous jubilant + pytest-jubilant approach, updating fixtures, helpers, and dependencies accordingly.
Changes:
- Replaced async integration test patterns with synchronous Jubilant equivalents (
juju.wait,juju.integrate,juju.run,juju.ssh,juju.cli). - Updated integration fixtures (
conftest.py) across subprojects and added CLI options (--charm-file,--keep-models) for CI compatibility. - Updated dependencies in
pyproject.tomlfiles and regenerateduv.lock(removingpytest-asyncio/pytest-operator/jujuand addingpytest-jubilant>=2,<3).
Reviewed changes
Copilot reviewed 28 out of 29 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| uv.lock | Updates lockfile to reflect removal of legacy test stack deps and addition of pytest-jubilant. |
| pyproject.toml | Switches root dependency groups from pytest-operator/asyncio to pytest-jubilant. |
| tests/conftest.py | Adds CLI options used by integration workflows (--charm-file, --keep-models). |
| tests/integration/conftest.py | Fixes LXD temp model lifecycle (return → yield) and updates related fixture code. |
| haproxy-operator/pyproject.toml | Updates operator project test dependencies to pytest-jubilant. |
| haproxy-operator/tests/conftest.py | Adds CLI options for operator tests (CI compatibility). |
| haproxy-operator/tests/integration/conftest.py | Migrates integration fixtures to pytest-jubilant/Jubilant patterns (but introduces a recursive juju fixture override). |
| haproxy-operator/tests/integration/test_ingress_per_unit.py | Converts test from async def to sync Jubilant usage. |
| haproxy-operator/tests/integration/legacy/conftest.py | Converts legacy integration fixtures and helpers to Jubilant APIs. |
| haproxy-operator/tests/integration/legacy/helper.py | Converts helper utilities to Jubilant CLI usage for show-unit parsing. |
| haproxy-operator/tests/integration/legacy/test_action.py | Converts action/SSH logic to juju.run / juju.ssh. |
| haproxy-operator/tests/integration/legacy/test_charm.py | Converts HTTP smoke test to sync with Jubilant address helper. |
| haproxy-operator/tests/integration/legacy/test_config.py | Converts config validation and unit file inspection to juju.config / juju.ssh. |
| haproxy-operator/tests/integration/legacy/test_cos.py | Converts metrics collection to juju.ssh. |
| haproxy-operator/tests/integration/legacy/test_ha.py | Converts HA integration flow to Jubilant calls and sync waits. |
| haproxy-operator/tests/integration/legacy/test_haproxy_route.py | Converts route integration flow to juju.integrate / juju.run / juju.wait. |
| haproxy-operator/tests/integration/legacy/test_http_interface.py | Converts reverseproxy relation test to Jubilant operations and sync waits. |
| haproxy-operator/tests/integration/legacy/test_ingress.py | Converts ingress relation test to Jubilant helpers and sync waits. |
| haproxy-operator/tests/integration/legacy/test_website.py | Converts website relation test to Jubilant integration/wait patterns. |
| haproxy-route-policy-operator/pyproject.toml | Updates route-policy operator integration deps to pytest-jubilant. |
| haproxy-route-policy-operator/tests/conftest.py | Adds CLI options for route-policy operator tests. |
| haproxy-route-policy-operator/tests/integration/conftest.py | Migrates fixtures to pytest-jubilant/Jubilant patterns (but introduces a recursive juju fixture override). |
| haproxy-spoe-auth-operator/pyproject.toml | Updates spoe-auth operator integration deps to pytest-jubilant. |
| haproxy-spoe-auth-operator/tests/conftest.py | Adds CLI options for spoe-auth operator tests. |
| haproxy-spoe-auth-operator/tests/integration/conftest.py | Migrates fixtures to pytest-jubilant/Jubilant patterns (but introduces a recursive juju fixture override). |
| haproxy-ddos-protection-configurator/pyproject.toml | Updates ddos configurator integration deps to pytest-jubilant. |
| haproxy-ddos-protection-configurator/tests/conftest.py | Adds CLI options for ddos configurator tests. |
| haproxy-ddos-protection-configurator/tests/integration/conftest.py | Migrates fixtures to pytest-jubilant/Jubilant patterns (but introduces a recursive juju fixture override). |
| haproxy-ddos-protection-configurator/tests/integration/test_charm.py | Converts async test to sync wait with explicit timeout. |
- Fix pre-existing codespell typo: Alternatvies -> Alternatives - tests/integration/conftest.py: encode ca_cert str to bytes before writing to binary NamedTemporaryFile (mypy call-overload error) - tests/integration/conftest.py: add type: ignore for opcli and playwright imports (runtime-only, not available at lint time) - tests/integration/haproxy_route_requirer.py: add type: ignore for any-charm runtime imports (apt, any_charm_base, haproxy_route) - tests/integration/test_haproxy_route_policy.py: suppress union-attr on lxd_juju.model.split() with type: ignore comment Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Top-level import of subprocess triggers B404; subprocess.check_output with a fixed safe command triggers B603/B607. Suppress with nosec comments since the commands are hardcoded and trusted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
javierdelapuente
left a comment
There was a problem hiding this comment.
A few minor comments
| } | ||
|
|
||
| # juju.deploy(charm, application_name="haproxy", trust=True, constraints=constraints) | ||
| juju.deploy(charm, trust=True, app=app_name, constraints=constraints) |
There was a problem hiding this comment.
Good point, I think this is deploying into machine and trust should not be needed.
@Thanhphan1147 wdyt?
| @pytest_asyncio.fixture(scope="module", name="any_charm_src") | ||
| async def any_charm_src_fixture() -> dict[str, str]: | ||
| """any-charm configuration to test with haproxy.""" | ||
| def get_ingress_url_for_application(juju: jubilant.Juju, app_name: str) -> ParseResult: |
There was a problem hiding this comment.
This is used for an any-charm, why not getting it directly from the any-charm with rpc?
There is a juju 4 issue found by Fouad that will not allow this to work in Juju 4.
| @@ -1,22 +1,21 @@ | |||
| # Copyright 2025 Canonical Ltd. | |||
| # Copyright 2026 Canonical Ltd. | |||
There was a problem hiding this comment.
Should we move these files outside of the legacy directory? They are not legacy anymore..
There was a problem hiding this comment.
I would do this in another pr. There are some differences in the tests and I think it needs to be done carefully.
| } | ||
| ) | ||
| # Write large src-overwrite to a file to avoid ARG_MAX CLI limit | ||
| with tempfile.NamedTemporaryFile(dir=".") as tf: |
There was a problem hiding this comment.
This is the second time of this in the PR, maybe we can create helpers.
Ensure TLS configuration and certificate relations are established before tests run, wait for hooks to settle, and resolve formatting and type-check failures exposed by CI. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Require both HAProxy and the ingress requirer to remain active and idle before reading the ingress URL from relation data. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Summary
Converts all integration tests from the
pytest-asyncio/pytest-operator/python-libjujustack to the synchronousjubilant+pytest-jubilantstandard.Changes
Dependencies (all 5
pyproject.tomlfiles)pytest-asyncio,pytest-operator,juju==3.6.1.3pytest-jubilant>=2,<3uv.lockaccordinglyTest files converted (28 files total)
conftest.pyfiles: Updated fixtures to usejubilant.Juju, replaced@pytest_asyncio.fixturewith@pytest.fixture, registered--charm-fileand--keep-modelsCLI optionshaproxy-operator/tests/integration/legacy/: Full conversion fromasync deftodef,python-libjujuAPI calls replaced withjubilantequivalentsreturn→yieldinsidetemp_modelcontext manager to prevent model being destroyed before tests runif not "already exists":→if "already exists" not in str(err):inlxd_jujufixtureKey API mappings used
app.set_config()juju.config()model.wait_for_idle()juju.wait()unit.run_action()juju.run()ops_test.juju("ssh", ...)juju.ssh()model.add_relation()juju.integrate()ops_test.juju("show-unit")juju.cli()