From 5cdee8f859da4804b235e09ac5b14db5d7c1a8b0 Mon Sep 17 00:00:00 2001 From: Chris Caron Date: Tue, 29 Jul 2025 22:25:22 -0400 Subject: [PATCH] dbus/glib testing bulletproofing --- tests/test_plugin_dbus.py | 67 +++++++++++++++++++-------------------- tests/test_plugin_glib.py | 34 ++++++++++---------- 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/tests/test_plugin_dbus.py b/tests/test_plugin_dbus.py index 23756cef..ee4ba342 100644 --- a/tests/test_plugin_dbus.py +++ b/tests/test_plugin_dbus.py @@ -43,48 +43,47 @@ logging.disable(logging.CRITICAL) @pytest.fixture -def enabled_dbus_environment(mocker): - """Fully mocked D-Bus and GI environment with plugin reloaded and - enabled.""" +def enabled_dbus_environment(monkeypatch): + """ + Fully mocked DBus and GI environment that works in local and CI environments. + """ - # Step 1: Mock DBus Interface - interface_mock = mocker.patch("dbus.Interface", spec=True, Notify=Mock()) - mocker.patch( - "dbus.SessionBus", - spec=True, - **{"get_object.return_value": interface_mock}, - ) + # --- Handle dbus (real or fake) --- + try: + import dbus + except ImportError: + dbus = types.ModuleType("dbus") + dbus.DBusException = type("DBusException", (Exception,), {}) + dbus.Interface = Mock() + dbus.SessionBus = Mock() - # Step 2: Inject valid dbus.mainloop.glib and .qt into sys.modules - fake_loop = Mock(name="FakeMainLoop") - sys.modules["dbus.mainloop.glib"] = types.SimpleNamespace( - DBusGMainLoop=lambda: fake_loop - ) - sys.modules["dbus.mainloop.qt"] = types.SimpleNamespace( - DBusQtMainLoop=lambda set_as_default=False: fake_loop - ) + sys.modules["dbus"] = dbus - # Step 3: Mock GI and GdkPixbuf (also covers image handling) + # Inject mainloop support if not already present + if "dbus.mainloop.glib" not in sys.modules: + glib_loop = types.ModuleType("dbus.mainloop.glib") + glib_loop.DBusGMainLoop = lambda: Mock(name="FakeLoop") + sys.modules["dbus.mainloop.glib"] = glib_loop + + if "dbus.mainloop" not in sys.modules: + sys.modules["dbus.mainloop"] = types.ModuleType("dbus.mainloop") + + # Patch specific attributes always, even if real module is present + monkeypatch.setattr("dbus.Interface", Mock()) + monkeypatch.setattr("dbus.SessionBus", Mock()) + monkeypatch.setattr("dbus.DBusException", type("DBusException", (Exception,), {})) + + # --- Mock GI / GdkPixbuf --- gi = types.ModuleType("gi") - gi.repository = types.ModuleType("gi.repository") - - mock_pixbuf = Mock() - mock_image = Mock() - mock_pixbuf.new_from_file.return_value = mock_image - mock_image.get_width.return_value = 100 - mock_image.get_height.return_value = 100 - mock_image.get_rowstride.return_value = 1 - mock_image.get_has_alpha.return_value = 0 - mock_image.get_bits_per_sample.return_value = 8 - mock_image.get_n_channels.return_value = 1 - mock_image.get_pixels.return_value = b"" - - gi.repository.GdkPixbuf = types.SimpleNamespace(Pixbuf=mock_pixbuf) gi.require_version = Mock() + gi.repository = types.SimpleNamespace( + GdkPixbuf=types.SimpleNamespace(Pixbuf=Mock()) + ) + sys.modules["gi"] = gi sys.modules["gi.repository"] = gi.repository - # Step 4: Reload plugin after all mocks are in place + # --- Reload plugin with controlled env --- reload_plugin("dbus") diff --git a/tests/test_plugin_glib.py b/tests/test_plugin_glib.py index 02f37fbf..d86fd855 100644 --- a/tests/test_plugin_glib.py +++ b/tests/test_plugin_glib.py @@ -41,19 +41,20 @@ logging.disable(logging.CRITICAL) @pytest.fixture -def enabled_glib_environment(mocker): - """Mocks a working GI/GLib/Gio environment""" - # Patch GLib.Error and Gio +def enabled_glib_environment(monkeypatch): + """ + Fully mocked GI/GLib/Gio/GdkPixbuf environment for local and CI. + """ + # Step 1: Fake gi and repository gi = types.ModuleType("gi") - gi.repository = types.SimpleNamespace() + gi.require_version = Mock() + + fake_variant = Mock(name="Variant") + fake_error = type("GLibError", (Exception,), {}) + fake_pixbuf = Mock() + fake_image = Mock() - fake_variant = mocker.MagicMock(name="Variant") - fake_error_type = type("GLibError", (Exception,), {}) - fake_pixbuf = mocker.MagicMock(name="Pixbuf") - fake_image = mocker.MagicMock(name="PixbufImage") fake_pixbuf.new_from_file.return_value = fake_image - - # Provide fake return values fake_image.get_width.return_value = 100 fake_image.get_height.return_value = 100 fake_image.get_rowstride.return_value = 1 @@ -62,16 +63,17 @@ def enabled_glib_environment(mocker): fake_image.get_n_channels.return_value = 1 fake_image.get_pixels.return_value = b"" - gi.require_version = Mock() - gi.repository.GLib = types.SimpleNamespace( - Error=fake_error_type, Variant=fake_variant) - gi.repository.Gio = mocker.MagicMock(name="Gio") - gi.repository.GdkPixbuf = types.SimpleNamespace( - Pixbuf=fake_pixbuf) + gi.repository = types.SimpleNamespace( + Gio=Mock(), + GLib=types.SimpleNamespace(Variant=fake_variant, Error=fake_error), + GdkPixbuf=types.SimpleNamespace(Pixbuf=fake_pixbuf), + ) + # Step 2: Inject into sys.modules sys.modules["gi"] = gi sys.modules["gi.repository"] = gi.repository + # Step 3: Reload plugin with all mocks in place reload_plugin("glib")