ci: use charmcraft test for example charm#2440
ci: use charmcraft test for example charm#2440tonyandrewmeyer wants to merge 17 commits intocanonical:mainfrom
Conversation
I wonder if Concierge should learn how to handle this? Why does Concierge normally work but isn't via spread?
…rking on ubuntu-latest.
…nd networking on ubuntu-latest." This reverts commit c7892ee.
james-garner-canonical
left a comment
There was a problem hiding this comment.
I'm a bit wary about adding this to the tutorial charms without updating the tutorial as a whole to explain how/if you should use it. I wonder if it would be better to just do it for the httpbin charm currently. I guess the downside is that we'd need a separate example charm integration test workflow in that case -- but sounds like that's what you're doing separately.
|
@dwilding @james-garner-canonical I've shrunk this down as discussed. |
dwilding
left a comment
There was a problem hiding this comment.
I think this scope is much better.
I haven't finished reviewing, but I need to pause for today.
| (write-integration-tests-for-a-charm-split-across-modules)= | ||
| ### Split tests across modules | ||
|
|
||
| As your suite grows, split your integration tests across several `test_*.py` modules, grouped by feature. Tests within a module share a single `juju` fixture (and therefore a single Juju model), so keep tests that need to build on each other together. Across modules, each gets a fresh model, which makes modules independent of each other. |
There was a problem hiding this comment.
| As your suite grows, split your integration tests across several `test_*.py` modules, grouped by feature. Tests within a module share a single `juju` fixture (and therefore a single Juju model), so keep tests that need to build on each other together. Across modules, each gets a fresh model, which makes modules independent of each other. | |
| As your suite grows, split your integration tests across several `test_*.py` modules, grouped by feature. Tests within a module share a single `juju` fixture (and therefore a single Juju model), so keep tests that need to build on each other together. Each module gets a fresh model, which makes modules independent of each other. |
| A common split: | ||
|
|
||
| - `test_charm.py` — smoke tests: pack, deploy, and check the charm reaches active status. | ||
| - `test_<feature>.py` — one module per feature area (for example, `test_backups.py`, `test_tls.py`, `test_upgrade.py`, `test_scaling.py`). |
There was a problem hiding this comment.
I think the meaning is clear enough from the examples.
| - `test_<feature>.py` — one module per feature area (for example, `test_backups.py`, `test_tls.py`, `test_upgrade.py`, `test_scaling.py`). | |
| - `test_<feature>.py` — for example, `test_backups.py`, `test_tls.py`, `test_upgrade.py`, `test_scaling.py`. |
| - `test_charm.py` — smoke tests: pack, deploy, and check the charm reaches active status. | ||
| - `test_<feature>.py` — one module per feature area (for example, `test_backups.py`, `test_tls.py`, `test_upgrade.py`, `test_scaling.py`). | ||
|
|
||
| The main reason to split is **parallel CI execution**: each module can run as a separate job, so total wall-clock time is governed by the slowest module rather than the sum of all modules. `tox -e integration` still runs every module sequentially on a single machine; it's the CI matrix (see {ref}`set-up-ci-integration`) that turns module boundaries into parallel jobs. Adding a new `test_*.py` file then automatically adds a new CI job — no workflow changes needed. |
There was a problem hiding this comment.
I found this a bit confusing. The first example in our CI doc doesn't have a matrix, so new jobs wouldn't automatically be created for each test_*.py file, right?
My interpretation is more like this: Use separate modules so that you have the option of using a matrix in CI. If you're going to do that, we recommend using the charmcraft test approach described in the CI doc.
|
|
||
| The main reason to split is **parallel CI execution**: each module can run as a separate job, so total wall-clock time is governed by the slowest module rather than the sum of all modules. `tox -e integration` still runs every module sequentially on a single machine; it's the CI matrix (see {ref}`set-up-ci-integration`) that turns module boundaries into parallel jobs. Adding a new `test_*.py` file then automatically adds a new CI job — no workflow changes needed. | ||
|
|
||
| For real-world examples of module-per-feature splits, see: |
There was a problem hiding this comment.
I think this would be easier to read.
| For real-world examples of module-per-feature splits, see: | |
| For real-world examples of split tests, see: |
| - [postgresql-k8s-operator](https://github.com/canonical/postgresql-k8s-operator/tree/main/tests/integration) — the Kubernetes equivalent. | ||
| - [opensearch-operator](https://github.com/canonical/opensearch-operator/tree/main/tests/integration) — larger suite with per-cloud backup modules. | ||
|
|
||
| For a minimal scaffold that you can lift into your own charm, see the [httpbin-demo](https://github.com/canonical/operator/tree/main/examples/httpbin-demo) example charm in the Ops repository. |
There was a problem hiding this comment.
Personal preference perhaps 🙂
| For a minimal scaffold that you can lift into your own charm, see the [httpbin-demo](https://github.com/canonical/operator/tree/main/examples/httpbin-demo) example charm in the Ops repository. | |
| For a minimal scaffold that you can use in your own charm, see the [httpbin-demo](https://github.com/canonical/operator/tree/main/examples/httpbin-demo) example charm in the Ops repository. |
| (set-up-ci-charmcraft-test)= | ||
| ## Run integration tests in parallel with `charmcraft test` | ||
|
|
||
| If you initialised your charm with `charmcraft init --profile test-machine` or `--profile test-kubernetes` (both currently experimental), your charm includes a `spread.yaml` and one `spread/integration/<module>/task.yaml` per test module. You can use `charmcraft test` in CI to run each module as its own matrix job, so total wall-clock time is bounded by the slowest module rather than the sum of all modules. Adding a new `test_*.py` module — along with its `task.yaml` — automatically adds a new CI job. |
There was a problem hiding this comment.
This is quite dense. How about expanding it (visually) and with a bit more context on spread:
| If you initialised your charm with `charmcraft init --profile test-machine` or `--profile test-kubernetes` (both currently experimental), your charm includes a `spread.yaml` and one `spread/integration/<module>/task.yaml` per test module. You can use `charmcraft test` in CI to run each module as its own matrix job, so total wall-clock time is bounded by the slowest module rather than the sum of all modules. Adding a new `test_*.py` module — along with its `task.yaml` — automatically adds a new CI job. | |
| If you initialised your charm with `charmcraft init --profile test-machine` or `--profile test-kubernetes` (both currently experimental), your charm includes extra testing machinery: | |
| - A [spread](https://github.com/canonical/spread) configuration file called `spread.yaml`. | |
| - One file `spread/integration/<module>/task.yaml` per test module. | |
| You can use `charmcraft test` in CI to run each module as its own matrix job, so total wall-clock time is bounded by the slowest module rather than the sum of all modules. Adding a new `test_*.py` module — along with its `task.yaml` — automatically adds a new CI job. |
There was a problem hiding this comment.
I'd also consider moving the sentence about automatically adding CI jobs. I'll make a separate suggestion about that.
| run: charmcraft test "craft:ubuntu-24.04:spread/integration/${{ matrix.task }}" | ||
| ``` | ||
|
|
||
| For a complete workflow that discovers modules dynamically (no hard-coded matrix), see the Ops repository's [example-charm-charmcraft-test.yaml](https://github.com/canonical/operator/blob/main/.github/workflows/example-charm-charmcraft-test.yaml). For the matching charm-side files, see the [httpbin-demo](https://github.com/canonical/operator/tree/main/examples/httpbin-demo) example charm. |
There was a problem hiding this comment.
Since the example in the doc doesn't show how to discover modules, I'd move the earlier sentence here. Something like this:
| For a complete workflow that discovers modules dynamically (no hard-coded matrix), see the Ops repository's [example-charm-charmcraft-test.yaml](https://github.com/canonical/operator/blob/main/.github/workflows/example-charm-charmcraft-test.yaml). For the matching charm-side files, see the [httpbin-demo](https://github.com/canonical/operator/tree/main/examples/httpbin-demo) example charm. | |
| It's also possible to discover modules dynamically (no hard-coded matrix), so that when you add a `test_*.py` module and corresponding `task.yaml` file, you automatically get a new CI job. | |
| For an example, see the Ops repository's [example-charm-charmcraft-test.yaml workflow](https://github.com/canonical/operator/blob/main/.github/workflows/example-charm-charmcraft-test.yaml). This workflow runs integration tests for the [httpbin-demo](https://github.com/canonical/operator/tree/main/examples/httpbin-demo) example charm. |
This PR updates the one example charm (httpbin-demo) to use
charmcraft test(which is spread, underneath).charmcraft init --profile test-kubernetes --force. There are changes coming to Charmcraft in regards to "craft test" in 26.10, so for now it seems that using the existing profile setup is the best move. This is what creates thespread.yaml,spread/.extension(unchanged), and a default task (that I've replaced).spread/deploytospread/integration.concierge prepare, installedastral-uvandtox, extended theexcludeto include more Python artefacts, and increased thekill-timeoutto 90s. This is probably the most debatable point -- perhaps it would be better to remain closer to whatcharmcraftprovides, but I think we should stick with our recommended tooling, particularly Concierge.task.yamlfor each Python integration test module (one, for this example charm, but set up in a way that supports more). These are pretty simple, taking the packed charm path from Charmcraft and runningtox -e integrationwith the specific module.task.yamlis added, it will automatically be added and run concurrently via the native GitHub functionality.tox -e integrationdoes not change in this PR. It's available for use, and still needs a local Juju controller set up, and something to do the packing.I've added small notes to the docs as well. I don't think we want to have too much more than this at the moment, since the profiles and
charmcraft testare still marked as experimental (even though they are somehow also considered to be used by everyone), and because we know that there are naming (and maybe other) changes coming in 26.10. I'd like input on whether having this extra setup in the tutorial charms particularly is too inconsistent with what you'd get strictly following the tutorial.Preview for the CI how-to, preview for the integration test how-to, preview for the machine tutorial (the tip box), preview for the K8s tutorial start (again the tip box).
I've left the integration tests unchanged, but I wonder if the final K8s tutorial one should be changed to use a separate module for the COS test, to demonstrate that aspect working?
Example CI run in my fork.