diff --git a/tests/__init__.py b/tests/__init__.py index eda947472..cb306a886 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -334,7 +334,11 @@ class PortalMock: """ def __init__( - self, dbus_test_case, portal_name: str, app_id: str = "org.example.App" + self, + dbus_test_case, + portal_name: str, + app_id: str = "org.example.App", + umockdev=None, ): self.dbus_test_case = dbus_test_case self.portal_name = portal_name @@ -344,6 +348,7 @@ def __init__( self.portal_interfaces: Dict[str, dbus.Interface] = {} self.app_id = app_id self.busses = {dbusmock.BusType.SYSTEM: {}, dbusmock.BusType.SESSION: {}} + self.umockdev = umockdev @property def interface_name(self) -> str: @@ -420,6 +425,22 @@ def mock_interface(self): ) return dbus.Interface(obj, dbusmock.MOCK_IFACE) + def _maybe_add_asan_preload(self, executable, env): + # ASAN really wants to be the first library to get loaded but we also + # LD_PRELOAD umockdev and LD_PRELOAD gets loaded before any "normally" + # linked libraries. This uses ldd to find the version of libasan.so that + # should be loaded and puts it in front of LD_PRELOAD. + # This way, LD_PRELOAD and ASAN can be used at the same time. + ldd = subprocess.check_output(["ldd", executable]).decode("utf-8") + libs = [line.split()[0] for line in ldd.splitlines()] + try: + libasan = next(filter(lambda lib: lib.startswith("libasan"), libs)) + except StopIteration: + return + + preload = env.get("LD_PRELOAD", "") + env["LD_PRELOAD"] = f"{libasan}:{preload}" + def start_xdp(self): """ Start the xdg-desktop-portal process @@ -437,6 +458,9 @@ def start_xdp(self): env["XDG_CURRENT_DESKTOP"] = "test" env["XDG_DESKTOP_PORTAL_TEST_APP_ID"] = self.app_id + if self.umockdev: + env["UMOCKDEV_DIR"] = self.umockdev.get_root_dir() + asan_suppression = ( Path(os.getenv("G_TEST_SRCDIR", "tests")) / "asan.suppression" ) @@ -471,6 +495,9 @@ def start_portal_frontend(self, env): f"{portal_frontend} does not exist, try running from meson build dir or setting G_TEST_BUILDDIR" ) + env = env.copy() + self._maybe_add_asan_preload(portal_frontend, env) + portal_frontend = subprocess.Popen([portal_frontend], env=env) for _ in range(50): @@ -510,6 +537,9 @@ def start_permission_store(self, env): f"{permission_store_path} does not exist, try running from meson build dir or setting G_TEST_BUILDDIR" ) + env = env.copy() + self._maybe_add_asan_preload(permission_store_path, env) + permission_store = subprocess.Popen([permission_store_path], env=env) for _ in range(50): diff --git a/tests/conftest.py b/tests/conftest.py index e6ec5fea5..76af3ce70 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,15 +7,25 @@ import pytest import dbusmock import os +import sys import tempfile from tests import PortalMock def pytest_configure(): + ensure_umockdev_loaded() create_test_dirs() +def ensure_umockdev_loaded(): + umockdev_preload = "libumockdev-preload.so" + preload = os.environ.get("LD_PRELOAD", "") + if umockdev_preload not in preload: + os.environ["LD_PRELOAD"] = f"{umockdev_preload}:{preload}" + os.execv(sys.executable, [sys.executable] + sys.argv) + + def create_test_dirs(): env_dirs = [ "HOME", @@ -123,14 +133,22 @@ def app_id(): return "org.example.App" +@pytest.fixture +def umockdev(): + """ + Default fixture providing a umockdev testbed + """ + return None + + @pytest.fixture def portal_mock( - dbus_test_case, portal_name, required_templates, template_params, app_id + dbus_test_case, portal_name, required_templates, template_params, app_id, umockdev ) -> PortalMock: """ Fixture yielding a PortalMock object with the impl started, if applicable. """ - pmock = PortalMock(dbus_test_case, portal_name, app_id) + pmock = PortalMock(dbus_test_case, portal_name, app_id, umockdev) for template, params in required_templates.items(): params = template_params.get(template, params)