Skip to content

Commit

Permalink
Merge branch 'main' into changelog-changes
Browse files Browse the repository at this point in the history
  • Loading branch information
tonyandrewmeyer authored Mar 26, 2024
2 parents 413e16f + e2a7f7d commit e5d89c7
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 71 deletions.
3 changes: 2 additions & 1 deletion CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Features

* Add `Model.get_cloud_spec` which uses the `credential-get` hook tool to get details of the cloud where the model is deployed (#1152)
* Added `Model.get_cloud_spec` which uses the `credential-get` hook tool to get details of the cloud where the model is deployed (#1152)

## Fixes

Expand All @@ -19,6 +19,7 @@
## Documentation

* Use "integrate with" rather than "relate to" (#1145)
* Updated code examples in the docstring of `ops.testing` from unittest to pytest style (#1157)

# 2.11.0 - 29 Feb 2024

Expand Down
137 changes: 67 additions & 70 deletions ops/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,47 +176,49 @@ class Harness(Generic[CharmType]):
The model created is from the viewpoint of the charm that is being tested.
Below is an example test using :meth:`begin_with_initial_hooks` that ensures
the charm responds correctly to config changes::
Always call ``harness.cleanup()`` after creating a :class:`Harness`::
class TestCharm(unittest.TestCase):
def test_foo(self):
harness = Harness(MyCharm)
self.addCleanup(harness.cleanup) # always clean up after ourselves
@pytest.fixture()
def harness():
harness = Harness(MyCharm)
yield harness
harness.cleanup()
# Instantiate the charm and trigger events that Juju would on startup
harness.begin_with_initial_hooks()
Below is an example test using :meth:`begin_with_initial_hooks` that ensures
the charm responds correctly to config changes (the parameter ``harness`` in the
test function is a pytest fixture that does setup/teardown, see :class:`Harness`)::
# Update charm config and trigger config-changed
harness.update_config({'log_level': 'warn'})
def test_foo(harness):
# Instantiate the charm and trigger events that Juju would on startup
harness.begin_with_initial_hooks()
# Check that charm properly handled config-changed, for example,
# the charm added the correct Pebble layer
plan = harness.get_container_pebble_plan('prometheus')
self.assertIn('--log.level=warn', plan.services['prometheus'].command)
# Update charm config and trigger config-changed
harness.update_config({'log_level': 'warn'})
# Check that charm properly handled config-changed, for example,
# the charm added the correct Pebble layer
plan = harness.get_container_pebble_plan('prometheus')
assert '--log.level=warn' in plan.services['prometheus'].command
To set up the model without triggering events (or calling charm code), perform the
harness actions before calling :meth:`begin`. Below is an example that adds a
relation before calling ``begin``, and then updates config to trigger the
``config-changed`` event in the charm::
class TestCharm(unittest.TestCase):
def test_bar(self):
harness = Harness(MyCharm)
self.addCleanup(harness.cleanup) # always clean up after ourselves
``config-changed`` event in the charm (the parameter ``harness`` in the test function
is a pytest fixture that does setup/teardown, see :class:`Harness`)::
# Set up model before "begin" (no events triggered)
harness.set_leader(True)
harness.add_relation('db', 'postgresql', unit_data={'key': 'val'})
def test_bar(harness):
# Set up model before "begin" (no events triggered)
harness.set_leader(True)
harness.add_relation('db', 'postgresql', unit_data={'key': 'val'})
# Now instantiate the charm to start triggering events as the model changes
harness.begin()
harness.update_config({'some': 'config'})
# Now instantiate the charm to start triggering events as the model changes
harness.begin()
harness.update_config({'some': 'config'})
# Check that charm has properly handled config-changed, for example,
# has written the app's config file
root = harness.get_filesystem_root('container')
assert (root / 'etc' / 'app.conf').exists()
# Check that charm has properly handled config-changed, for example,
# has written the app's config file
root = harness.get_filesystem_root('container')
assert (root / 'etc' / 'app.conf').exists()
Args:
charm_cls: The Charm class to test.
Expand Down Expand Up @@ -962,8 +964,8 @@ def add_relation_unit(self, relation_id: int, remote_unit_name: str) -> None:
Example::
rel_id = harness.add_relation('db', 'postgresql')
harness.add_relation_unit(rel_id, 'postgresql/0')
rel_id = harness.add_relation('db', 'postgresql')
harness.add_relation_unit(rel_id, 'postgresql/0')
Args:
relation_id: The integer relation identifier (as returned by :meth:`add_relation`).
Expand Down Expand Up @@ -1004,10 +1006,10 @@ def remove_relation_unit(self, relation_id: int, remote_unit_name: str) -> None:
Example::
rel_id = harness.add_relation('db', 'postgresql')
harness.add_relation_unit(rel_id, 'postgresql/0')
...
harness.remove_relation_unit(rel_id, 'postgresql/0')
rel_id = harness.add_relation('db', 'postgresql')
harness.add_relation_unit(rel_id, 'postgresql/0')
...
harness.remove_relation_unit(rel_id, 'postgresql/0')
This will trigger a `relation_departed` event. This would
normally be followed by a `relation_changed` event triggered
Expand Down Expand Up @@ -1698,7 +1700,8 @@ def get_filesystem_root(self, container: Union[str, Container]) -> pathlib.Path:
ownership. To circumvent this limitation, the testing harness maps all user and group
options related to file operations to match the current user and group.
Example usage::
Example usage (the parameter ``harness`` in the test function is a pytest fixture
that does setup/teardown, see :class:`Harness`)::
# charm.py
class ExampleCharm(ops.CharmBase):
Expand All @@ -1711,15 +1714,12 @@ def _on_pebble_ready(self, event: ops.PebbleReadyEvent):
self.hostname = event.workload.pull("/etc/hostname").read()
# test_charm.py
class TestCharm(unittest.TestCase):
def test_hostname(self):
harness = Harness(ExampleCharm)
self.addCleanup(harness.cleanup)
root = harness.get_filesystem_root("mycontainer")
(root / "etc").mkdir()
(root / "etc" / "hostname").write_text("hostname.example.com")
harness.begin_with_initial_hooks()
assert harness.charm.hostname == "hostname.example.com"
def test_hostname(harness):
root = harness.get_filesystem_root("mycontainer")
(root / "etc").mkdir()
(root / "etc" / "hostname").write_text("hostname.example.com")
harness.begin_with_initial_hooks()
assert harness.charm.hostname == "hostname.example.com"
Args:
container: The name of the container or the container instance.
Expand Down Expand Up @@ -1914,8 +1914,10 @@ def set_cloud_spec(self, spec: 'model.CloudSpec'):
Call this method before the charm calls :meth:`ops.Model.get_cloud_spec`.
Example usage::
Example usage (the parameter ``harness`` in the test function is
a pytest fixture that does setup/teardown, see :class:`Harness`)::
# charm.py
class MyVMCharm(ops.CharmBase):
def __init__(self, framework: ops.Framework):
super().__init__(framework)
Expand All @@ -1924,30 +1926,25 @@ def __init__(self, framework: ops.Framework):
def _on_start(self, event: ops.StartEvent):
self.cloud_spec = self.model.get_cloud_spec()
class TestCharm(unittest.TestCase):
def setUp(self):
self.harness = ops.testing.Harness(MyVMCharm)
self.addCleanup(self.harness.cleanup)
def test_start(self):
cloud_spec_dict = {
'name': 'localhost',
'type': 'lxd',
'endpoint': 'https://127.0.0.1:8443',
'credential': {
'authtype': 'certificate',
'attrs': {
'client-cert': 'foo',
'client-key': 'bar',
'server-cert': 'baz'
},
# test_charm.py
def test_start(harness):
cloud_spec = ops.model.CloudSpec.from_dict({
'name': 'localhost',
'type': 'lxd',
'endpoint': 'https://127.0.0.1:8443',
'credential': {
'auth-type': 'certificate',
'attrs': {
'client-cert': 'foo',
'client-key': 'bar',
'server-cert': 'baz'
},
}
self.harness.set_cloud_spec(ops.CloudSpec.from_dict(cloud_spec_dict))
self.harness.begin()
self.harness.charm.on.start.emit()
expected = ops.CloudSpec.from_dict(cloud_spec_dict)
self.assertEqual(harness.charm.cloud_spec, expected)
},
})
harness.set_cloud_spec(cloud_spec)
harness.begin()
harness.charm.on.start.emit()
assert harness.charm.cloud_spec == cloud_spec
"""
self._backend._cloud_spec = spec
Expand Down

0 comments on commit e5d89c7

Please sign in to comment.