From 59e78a182ceaeb35eb6201e2ae768a3a9115c8ce Mon Sep 17 00:00:00 2001 From: "Jason C. Nucciarone" Date: Thu, 18 Jul 2024 13:44:55 -0400 Subject: [PATCH] tests: update tests to not use watch.yaml - Reverted SystemdNotices in test charm back to the original constructor API. Signed-off-by: Jason C. Nucciarone --- .../notices-charm/src/charm.py | 3 +- tests/unit/test_juju_systemd_notices.py | 62 +++++++++---------- 2 files changed, 29 insertions(+), 36 deletions(-) diff --git a/tests/integration/juju_systemd_notices/notices-charm/src/charm.py b/tests/integration/juju_systemd_notices/notices-charm/src/charm.py index 26c15ae6..56426d22 100755 --- a/tests/integration/juju_systemd_notices/notices-charm/src/charm.py +++ b/tests/integration/juju_systemd_notices/notices-charm/src/charm.py @@ -12,7 +12,6 @@ import charms.operator_libs_linux.v1.systemd as systemd import daemon from charms.operator_libs_linux.v0.juju_systemd_notices import ( - Service, ServiceStartedEvent, ServiceStoppedEvent, SystemdNotices, @@ -30,7 +29,7 @@ class NoticesCharm(CharmBase): def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) - self._systemd_notices = SystemdNotices(self, Service("test")) + self._systemd_notices = SystemdNotices(self, ["test"]) event_handler_bindings = { self.on.install: self._on_install, self.on.start: self._on_start, diff --git a/tests/unit/test_juju_systemd_notices.py b/tests/unit/test_juju_systemd_notices.py index 4ce2dfb1..074d9074 100644 --- a/tests/unit/test_juju_systemd_notices.py +++ b/tests/unit/test_juju_systemd_notices.py @@ -7,7 +7,6 @@ import argparse import subprocess import unittest -from pathlib import Path from unittest.mock import AsyncMock, patch from charms.operator_libs_linux.v0.juju_systemd_notices import ( @@ -47,7 +46,7 @@ def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.notices = SystemdNotices( - self, Service("foobar"), Service("snap.test.service", alias="service") + self, ["foobar", Service("snap.test.service", alias="service")] ) event_handler_bindings = { self.on.install: self._on_install, @@ -96,45 +95,33 @@ def test_generate_hooks(self, mock_exists, _) -> None: self.harness.charm.notices._generate_hooks() @patch("pathlib.Path.exists") - def test_generate_config(self, mock_exists) -> None: + def test_generate_service(self, mock_exists) -> None: """Test that watch configuration file is generated correctly.""" with Patcher() as patcher: - # Set charm_dir to "/" because mocked charm_dir default - # `no-disk-path` does not exist within the fake filesystem. - # Tried creating `no-disk-path` in the fake filesystem, but - # the mocked objects could not find it. - self.harness.charm.framework.charm_dir = Path("/") - patcher.fs.create_file("watch.yaml") + name = self.harness.charm.unit.name.replace("/", "-") + patcher.fs.create_file(f"/etc/systemd/system/juju-{name}-systemd-notices.service") # Scenario 1 - Generate success but no pre-existing watch configuration. mock_exists.return_value = False - self.harness.charm.notices._generate_config() + self.harness.charm.notices._generate_service() # Scenario 2 - Generate success but pre-existing watch configuration. mock_exists.return_value = True - self.harness.charm.notices._generate_config() + self.harness.charm.notices._generate_service() - @patch("pathlib.Path.write_text") - @patch("pathlib.Path.symlink_to") @patch("subprocess.check_output") - @patch("pathlib.Path.exists") - def test_start(self, mock_exists, mock_subp, *_) -> None: + def test_start(self, mock_subp) -> None: """Test that start method correctly starts notices daemon.""" - # Scenario 1 - Subscribe success but no pre-existing service file. - mock_exists.return_value = False + # Scenario 1 - systemctl successfully starts notices daemon. self.harness.charm.notices._start() - # Scenario 2 - Subscribe success and pre-existing service file. - mock_exists.return_value = True - self.harness.charm.notices._start() - - # Scenario 3 - Subscribe success but systemctl fails to start notices daemon. + # Scenario 2 - systemctl fails to start notices daemon. mock_subp.side_effect = subprocess.CalledProcessError(1, "systemctl start foobar") with self.assertRaises(subprocess.CalledProcessError): self.harness.charm.notices._start() @patch("charms.operator_libs_linux.v0.juju_systemd_notices.SystemdNotices._generate_hooks") - @patch("charms.operator_libs_linux.v0.juju_systemd_notices.SystemdNotices._generate_config") + @patch("charms.operator_libs_linux.v0.juju_systemd_notices.SystemdNotices._generate_service") @patch("charms.operator_libs_linux.v0.juju_systemd_notices.SystemdNotices._start") def test_subscribe(self, *_) -> None: """Test `subscribe` method.""" @@ -221,7 +208,7 @@ async def test_systemd_unit_changed(self, mock_message, mock_variant, *_) -> Non self.assertTrue(_systemd_unit_changed(mock_message)) @patch( - "charms.operator_libs_linux.v0.juju_systemd_notices._read_config", + "charms.operator_libs_linux.v0.juju_systemd_notices._observed_services", return_value={"foobar": "foobar"}, ) @patch("charms.operator_libs_linux.v0.juju_systemd_notices._juju_unit", "foobar/0") @@ -259,35 +246,42 @@ async def test_get_service_state(self) -> None: state = await _get_service_state(mock_sysbus, "foobar") self.assertEqual(state, "unknown") + @patch( + "charms.operator_libs_linux.v0.juju_systemd_notices._observed_services", + {"foobar": "foobar", "snap.test.service": "service"}, + ) @patch("charms.operator_libs_linux.v0.juju_systemd_notices._get_service_state") - @patch("charms.operator_libs_linux.v0.juju_systemd_notices._read_config") - async def test_async_load_services(self, mock_config, mock_state) -> None: - mock_config.return_value = {} + async def test_async_load_services(self, mock_state) -> None: + mock_state.return_value = "active" await _async_load_services() - mock_config.return_value = {"foobar": "foobar"} - mock_state.return_value = "active" + # Somehow calling `_async_load_services()` fully + # covers the for-loop without any additional changes to + # the `_observed_services` global ¯\_(ツ)_/¯ + mock_state.return_value = "stopped" await _async_load_services() @patch("pathlib.Path.exists", return_value=False) @patch("asyncio.Event.wait") async def test_juju_systemd_notices_daemon(self, *_) -> None: # Desired outcome is that _juju_systemd_notices does not fail to start. - with Patcher() as patcher: - patcher.fs.create_file("watch.yaml", contents=mock_watch_config) - await _juju_systemd_notices_daemon() + await _juju_systemd_notices_daemon() @patch("charms.operator_libs_linux.v0.juju_systemd_notices._juju_systemd_notices_daemon") @patch("argparse.ArgumentParser.parse_args") def test_main(self, mocked_args, *_) -> None: # Scenario 1 - Desired outcome (juju-systemd-notices daemon starts successfully) # and debug is set to True. - mocked_args.return_value = argparse.Namespace(debug=True, unit="foobar/0") + mocked_args.return_value = argparse.Namespace( + debug=True, unit="foobar/0", services=["foobar=foobar", "snap.test.service=service"] + ) _main() # Scenario 2 - Desired outcome (juju-systemd-notices daemon starts successfully) # and debug is set to False - mocked_args.return_value = argparse.Namespace(debug=False, unit="foobar/0") + mocked_args.return_value = argparse.Namespace( + debug=False, unit="foobar/0", services=["foobar=foobar", "snap.test.service=service"] + ) _main() # Scenario 3 - Debug flag is passed to script but no unit name.