Skip to content

Commit

Permalink
tests/py: Enable umockdev based tests
Browse files Browse the repository at this point in the history
We re-exec the test case in pytest_configure if the LD_PRELOAD doesn't
contain libumockdev-preload.so. Even if the system doesn't have it, it
will show up in the environment and we can continue.

It also sets the UMOCKDEV_DIR env for the portal frontend. In theory
dbusmock should already do this for us but it doesn't seem to be
working.

Finally, it makes sure that ASAN keeps working even when LD_PRELOAD is
being used which is always the case because we require umockdev.
  • Loading branch information
swick committed Oct 28, 2024
1 parent 54fbfd4 commit 41d06b8
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 3 deletions.
32 changes: 31 additions & 1 deletion tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand All @@ -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"
)
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down
22 changes: 20 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 41d06b8

Please sign in to comment.