#!/usr/bin/env python3 """ Modern D-Bus integration test for apt-ostreed daemon using dbus-next only. Tests all major methods, properties, and signals. """ import asyncio import sys from dbus_next.aio import MessageBus from dbus_next import BusType, Message SERVICE = 'org.debian.aptostree1' SYSROOT_PATH = '/org/debian/aptostree1/Sysroot' OS_PATH = '/org/debian/aptostree1/OS/default' SYSROOT_IFACE = 'org.debian.aptostree1.Sysroot' OS_IFACE = 'org.debian.aptostree1.OS' PROPS_IFACE = 'org.freedesktop.DBus.Properties' async def main(): bus = await MessageBus(bus_type=BusType.SYSTEM).connect() failures = 0 def ok(msg): print(f'\033[32m✔ {msg}\033[0m') def fail(msg): nonlocal failures print(f'\033[31m✘ {msg}\033[0m') failures += 1 # Test methods async def call_method(path, iface, member, body=None): try: m = Message(destination=SERVICE, path=path, interface=iface, member=member, body=body or []) reply = await bus.call(m) ok(f'{iface}.{member} succeeded') return reply except Exception as e: fail(f'{iface}.{member} failed: {e}') return None await call_method(SYSROOT_PATH, SYSROOT_IFACE, 'GetStatus') await call_method(SYSROOT_PATH, SYSROOT_IFACE, 'InstallPackages', [['curl'], False]) await call_method(SYSROOT_PATH, SYSROOT_IFACE, 'RemovePackages', [['curl'], False]) await call_method(SYSROOT_PATH, SYSROOT_IFACE, 'Deploy', ['test-deployment']) await call_method(SYSROOT_PATH, SYSROOT_IFACE, 'Upgrade') await call_method(SYSROOT_PATH, SYSROOT_IFACE, 'Rollback') # CreateComposeFSLayer, RegisterClient, UnregisterClient not in current interface await call_method(SYSROOT_PATH, SYSROOT_IFACE, 'Reload') await call_method(SYSROOT_PATH, SYSROOT_IFACE, 'ReloadConfig') await call_method(SYSROOT_PATH, SYSROOT_IFACE, 'GetOS') await call_method(OS_PATH, OS_IFACE, 'GetBootedDeployment') await call_method(OS_PATH, OS_IFACE, 'GetDefaultDeployment') await call_method(OS_PATH, OS_IFACE, 'ListDeployments') # Test properties async def get_prop(path, iface, prop): try: m = Message(destination=SERVICE, path=path, interface=PROPS_IFACE, member='Get', body=[iface, prop]) reply = await bus.call(m) ok(f'Property {iface}.{prop} accessible') except Exception as e: fail(f'Property {iface}.{prop} failed: {e}') # Test Sysroot properties (only those that exist in the interface) for prop in ['Booted', 'ActiveTransaction', 'ActiveTransactionPath', 'Deployments', 'AutomaticUpdatePolicy', 'Path']: await get_prop(SYSROOT_PATH, SYSROOT_IFACE, prop) # OS interface doesn't have properties in current implementation # Test GetAll try: m = Message(destination=SERVICE, path=SYSROOT_PATH, interface=PROPS_IFACE, member='GetAll', body=[SYSROOT_IFACE]) reply = await bus.call(m) ok('GetAll properties succeeded') except Exception as e: fail(f'GetAll properties failed: {e}') # Test signal subscription (TransactionProgress as example) signal_received = asyncio.Event() def on_signal(msg): if msg.message_type == 2 and msg.interface == SYSROOT_IFACE and msg.member == 'TransactionProgress': ok('TransactionProgress signal received') signal_received.set() bus.add_message_handler(on_signal) # Trigger a method that emits a signal await call_method(SYSROOT_PATH, SYSROOT_IFACE, 'Deploy', ['signal-test']) try: await asyncio.wait_for(signal_received.wait(), timeout=3) except asyncio.TimeoutError: fail('TransactionProgress signal not received') # Test error handling try: await call_method(SYSROOT_PATH, SYSROOT_IFACE, 'InvalidMethod') fail('InvalidMethod should have failed') except Exception as e: ok(f'InvalidMethod properly rejected: {e}') print(f'\nTotal failures: {failures}') sys.exit(1 if failures else 0) if __name__ == '__main__': asyncio.run(main())