Skip to content

Commit baa62a8

Browse files
committed
Add support for dbus-next
Signed-off-by: Mario Limonciello <superm1@kernel.org>
1 parent 007d017 commit baa62a8

File tree

4 files changed

+117
-61
lines changed

4 files changed

+117
-61
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ classifiers = [
2929
"Operating System :: POSIX :: Linux",
3030
]
3131
dependencies = [
32+
"dbus-fast",
3233
"pyudev",
3334
"packaging",
3435
"pandas",

src/amd_debug/common.py

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
This module contains common utility functions and classes for various amd-debug-tools.
66
"""
77

8+
import asyncio
89
import importlib.metadata
910
import logging
1011
import os
@@ -238,18 +239,47 @@ def gb_to_pages(gb_value):
238239

239240
def reboot():
240241
"""Reboot the system"""
241-
try:
242-
import dbus # pylint: disable=import-outside-toplevel
243242

244-
bus = dbus.SystemBus()
245-
obj = bus.get_object("org.freedesktop.login1", "/org/freedesktop/login1")
246-
intf = dbus.Interface(obj, "org.freedesktop.login1.Manager")
247-
intf.Reboot(True)
243+
async def reboot_dbus_fast():
244+
"""Reboot using dbus-fast"""
245+
try:
246+
from dbus_fast.aio import ( # pylint: disable=import-outside-toplevel
247+
MessageBus,
248+
)
249+
from dbus_fast import BusType # pylint: disable=import-outside-toplevel
250+
251+
bus = await MessageBus(bus_type=BusType.SYSTEM).connect()
252+
introspection = await bus.introspect(
253+
"org.freedesktop.login1", "/org/freedesktop/login1"
254+
)
255+
proxy_obj = bus.get_proxy_object(
256+
"org.freedesktop.login1", "/org/freedesktop/login1", introspection
257+
)
258+
interface = proxy_obj.get_interface("org.freedesktop.login1.Manager")
259+
await interface.call_reboot(True)
260+
261+
except ImportError:
262+
return False
263+
return True
264+
265+
def reboot_dbus():
266+
"""Reboot using python-dbus"""
267+
try:
268+
import dbus # pylint: disable=import-outside-toplevel
269+
270+
bus = dbus.SystemBus()
271+
obj = bus.get_object("org.freedesktop.login1", "/org/freedesktop/login1")
272+
intf = dbus.Interface(obj, "org.freedesktop.login1.Manager")
273+
intf.Reboot(True)
274+
except ImportError:
275+
return False
248276
return True
249-
except ImportError:
250-
fatal_error("Missing dbus")
251-
except dbus.exceptions.DBusException as e:
252-
fatal_error({e})
277+
278+
loop = asyncio.get_event_loop()
279+
result = loop.run_until_complete(reboot_dbus_fast())
280+
if not result:
281+
return reboot_dbus()
282+
253283
return True
254284

255285

src/amd_debug/ttm.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# SPDX-License-Identifier: MIT
33
"""TTM configuration tool"""
44

5+
import asyncio
56
import os
67
import argparse
78
from amd_debug.common import (

src/test_common.py

Lines changed: 75 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,16 @@
44
"""
55
This module contains unit tests for the common functions in the amd-debug-tools package.
66
"""
7-
from unittest.mock import patch, mock_open, call
7+
from unittest.mock import patch, mock_open, call, Mock
88

9+
import asyncio
10+
import builtins
911
import logging
1012
import tempfile
1113
import unittest
1214
import os
1315
from platform import uname_result
16+
import sys
1417

1518

1619
from amd_debug.common import (
@@ -474,63 +477,84 @@ def test_get_system_mem_valid(self, _mock_join, mock_file):
474477
self.assertAlmostEqual(get_system_mem(), expected_gb)
475478
mock_file.assert_called_once_with("/proc/meminfo", "r", encoding="utf-8")
476479

477-
@patch("builtins.open", new_callable=mock_open, read_data="NoMemHere: 1234\n")
478-
@patch("os.path.join", return_value="/proc/meminfo")
479-
def test_get_system_mem_missing(self, _mock_join, _mock_file):
480-
"""Test get_system_mem raises ValueError if MemTotal is missing"""
481-
with self.assertRaises(ValueError):
482-
get_system_mem()
483-
484-
@patch("amd_debug.common.fatal_error")
485-
def test_reboot_importerror(self, mock_fatal_error):
486-
"""Test reboot handles ImportError"""
487-
with patch.dict("sys.modules", {"dbus": None}):
488-
reboot()
489-
mock_fatal_error.assert_called_once_with("Missing dbus")
490-
491-
@patch("amd_debug.common.fatal_error")
492-
def test_reboot_dbus_exception(self, mock_fatal_error):
493-
"""Test reboot handles dbus.exceptions.DBusException"""
494-
495-
class DummyDBusException(Exception):
496-
"""Dummy exception"""
497-
498-
class DummyIntf:
499-
"""Dummy interface"""
480+
def test_reboot_dbus_fast_success(self):
481+
"""Test reboot returns True when reboot_dbus_fast succeeds"""
500482

501-
def Reboot(self, _arg): # pylint: disable=invalid-name
502-
"""Dummy Reboot method"""
503-
raise DummyDBusException("fail")
504-
505-
class DummyObj: # pylint: disable=too-few-public-methods
506-
"""Dummy object"""
507-
508-
def __init__(self):
483+
# Create a mock loop that properly handles coroutines
484+
def mock_run_until_complete(coro):
485+
# Consume the coroutine to prevent the warning
486+
try:
487+
# Close the coroutine to prevent the warning
488+
coro.close()
489+
except (AttributeError, RuntimeError):
509490
pass
491+
return True
510492

511-
class DummyBus: # pylint: disable=too-few-public-methods
512-
"""Dummy bus"""
493+
mock_loop = Mock()
494+
mock_loop.run_until_complete.side_effect = mock_run_until_complete
513495

514-
def get_object(self, *args, **kwargs):
515-
"""Dummy get_object method"""
516-
return DummyObj()
496+
with patch("amd_debug.common.asyncio.get_event_loop", return_value=mock_loop):
497+
result = reboot()
498+
self.assertTrue(result)
499+
mock_loop.run_until_complete.assert_called_once()
517500

518-
class DummyDBus:
519-
"""Dummy dbus"""
501+
@patch("asyncio.get_event_loop")
502+
def test_reboot_dbus_fast_failure_and_dbus_success(self, mock_get_event_loop):
503+
"""Test reboot falls back to reboot_dbus when reboot_dbus_fast fails"""
520504

521-
class exceptions: # pylint: disable=invalid-name
522-
"""Dummy exceptions"""
505+
# Create a mock loop that properly handles coroutines
506+
def mock_run_until_complete(coro):
507+
# Consume the coroutine to prevent the warning
508+
try:
509+
coro.close()
510+
except (AttributeError, RuntimeError):
511+
pass
512+
return False
513+
514+
mock_loop = Mock()
515+
mock_loop.run_until_complete.side_effect = mock_run_until_complete
516+
mock_get_event_loop.return_value = mock_loop
517+
518+
# Mock the dbus module to avoid ImportError in CI
519+
mock_dbus = Mock()
520+
mock_bus = Mock()
521+
mock_obj = Mock()
522+
mock_intf = Mock()
523+
524+
mock_dbus.SystemBus.return_value = mock_bus
525+
mock_bus.get_object.return_value = mock_obj
526+
mock_obj.get_interface.return_value = mock_intf
527+
mock_dbus.Interface = Mock(return_value=mock_intf)
528+
529+
with patch.dict("sys.modules", {"dbus": mock_dbus}):
530+
result = reboot()
531+
self.assertTrue(result)
532+
533+
@patch("asyncio.get_event_loop")
534+
def test_reboot_dbus_fast_failure_and_dbus_failure(self, mock_get_event_loop):
535+
"""Test reboot returns False when both reboot_dbus_fast and reboot_dbus fail"""
536+
537+
# Create a mock loop that properly handles coroutines
538+
def mock_run_until_complete(coro):
539+
# Consume the coroutine to prevent the warning
540+
try:
541+
coro.close()
542+
except (AttributeError, RuntimeError):
543+
pass
544+
return False
523545

524-
DBusException = DummyDBusException
546+
mock_loop = Mock()
547+
mock_loop.run_until_complete.side_effect = mock_run_until_complete
548+
mock_get_event_loop.return_value = mock_loop
525549

526-
def SystemBus(self): # pylint: disable=invalid-name
527-
"""Dummy SystemBus method"""
528-
return DummyBus()
550+
# Mock the import to raise ImportError when dbus is imported
551+
original_import = builtins.__import__
529552

530-
def Interface(self, _obj, _name): # pylint: disable=invalid-name
531-
"""Dummy Interface method"""
532-
return DummyIntf()
553+
def mock_import(name, *args, **kwargs):
554+
if name == "dbus":
555+
raise ImportError("No module named 'dbus'")
556+
return original_import(name, *args, **kwargs)
533557

534-
with patch.dict("sys.modules", {"dbus": DummyDBus()}):
535-
reboot()
536-
self.assertTrue(mock_fatal_error.called)
558+
with patch("builtins.__import__", side_effect=mock_import):
559+
result = reboot()
560+
self.assertFalse(result)

0 commit comments

Comments
 (0)