Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-python-dbusmock for 
openSUSE:Factory checked in at 2024-08-20 16:12:28
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-python-dbusmock (Old)
 and      /work/SRC/openSUSE:Factory/.python-python-dbusmock.new.2698 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-python-dbusmock"

Tue Aug 20 16:12:28 2024 rev:12 rq:1194576 version:0.32.1

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-python-dbusmock/python-python-dbusmock.changes
    2024-03-18 16:44:32.170908315 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-python-dbusmock.new.2698/python-python-dbusmock.changes
  2024-08-20 16:12:31.083980002 +0200
@@ -1,0 +2,8 @@
+Sun Aug 18 17:06:33 UTC 2024 - Dirk Müller <dmuel...@suse.com>
+
+- update to 0.32.1:
+  * ModemManager: Add initial mock
+  * bluez5: Add advertising API
+  * Fix loading of libglib on macOS
+
+-------------------------------------------------------------------

Old:
----
  python-dbusmock-0.31.1.tar.gz

New:
----
  python-dbusmock-0.32.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-python-dbusmock.spec ++++++
--- /var/tmp/diff_new_pack.OkB9uR/_old  2024-08-20 16:12:31.692005257 +0200
+++ /var/tmp/diff_new_pack.OkB9uR/_new  2024-08-20 16:12:31.692005257 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           python-python-dbusmock
-Version:        0.31.1
+Version:        0.32.1
 Release:        0
 Summary:        Python library for creating mock D-Bus objects
 License:        LGPL-3.0-or-later

++++++ python-dbusmock-0.31.1.tar.gz -> python-dbusmock-0.32.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-dbusmock-0.31.1/.github/workflows/release.yml 
new/python-dbusmock-0.32.1/.github/workflows/release.yml
--- old/python-dbusmock-0.31.1/.github/workflows/release.yml    2024-02-23 
14:06:17.000000000 +0100
+++ new/python-dbusmock-0.32.1/.github/workflows/release.yml    2024-07-14 
09:17:09.000000000 +0200
@@ -45,7 +45,7 @@
           rm -rf tmp
 
       - name: Create GitHub release
-        uses: 
cockpit-project/action-release@88d994da62d1451c7073e26748c18413fcdf46e9
+        uses: 
cockpit-project/action-release@7d2e2657382e8d34f88a24b5987f2b81ea165785
         with:
           filename: "dist/python-dbusmock-${{ github.ref_name }}.tar.gz"
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-dbusmock-0.31.1/.github/workflows/tests.yml 
new/python-dbusmock-0.32.1/.github/workflows/tests.yml
--- old/python-dbusmock-0.31.1/.github/workflows/tests.yml      2024-02-23 
14:06:17.000000000 +0100
+++ new/python-dbusmock-0.32.1/.github/workflows/tests.yml      2024-07-14 
09:17:09.000000000 +0200
@@ -17,8 +17,8 @@
           - docker.io/ubuntu:latest
           - registry.fedoraproject.org/fedora:latest
           - registry.fedoraproject.org/fedora:rawhide
-          - quay.io/centos/centos:stream8
           - quay.io/centos/centos:stream9
+          - quay.io/centos/centos:stream10-development
 
     timeout-minutes: 30
     steps:
@@ -28,6 +28,14 @@
           # need this to also fetch tags
           fetch-depth: 0
 
+      - name: Workaround for https://github.com/actions/checkout/pull/697
+        run: |
+          set -ex
+          TAG=$(git describe --tags)
+          if echo "$TAG" | grep -q '^[0-9.]\+$'; then
+            git fetch --force origin $TAG:refs/tags/$TAG
+          fi
+
       - name: Run unit tests
         run: |
           dpkg -s podman docker || true
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-dbusmock-0.31.1/PKG-INFO 
new/python-dbusmock-0.32.1/PKG-INFO
--- old/python-dbusmock-0.31.1/PKG-INFO 2024-02-23 14:06:27.321855500 +0100
+++ new/python-dbusmock-0.32.1/PKG-INFO 2024-07-14 09:17:21.458633000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: python-dbusmock
-Version: 0.31.1
+Version: 0.32.1
 Summary: Mock D-Bus objects
 Home-page: https://github.com/martinpitt/python-dbusmock
 Author: Martin Pitt
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-dbusmock-0.31.1/dbusmock/__main__.py 
new/python-dbusmock-0.32.1/dbusmock/__main__.py
--- old/python-dbusmock-0.31.1/dbusmock/__main__.py     2024-02-23 
14:06:17.000000000 +0100
+++ new/python-dbusmock-0.32.1/dbusmock/__main__.py     2024-07-14 
09:17:09.000000000 +0200
@@ -16,6 +16,7 @@
 import argparse
 import json
 import os
+import platform
 import subprocess
 import sys
 
@@ -51,7 +52,7 @@
         help="template to load (instead of specifying name, path, interface)",
     )
     parser.add_argument(
-        "name",  # fmt: off
+        "name",
         metavar="NAME",
         nargs="?",
         help='D-Bus name to claim (e. g. "com.example.MyService") (if not 
using -t)',
@@ -160,7 +161,10 @@
     if args.template:
         main_object.AddTemplate(args.template, parameters)
 
-    libglib = ctypes.cdll.LoadLibrary("libglib-2.0.so.0")
+    if platform.system() == "Darwin":
+        libglib = ctypes.cdll.LoadLibrary("libglib-2.0.dylib")
+    else:
+        libglib = ctypes.cdll.LoadLibrary("libglib-2.0.so.0")
 
     dbusmock.mockobject.objects[args.path] = main_object
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-dbusmock-0.31.1/dbusmock/_version.py 
new/python-dbusmock-0.32.1/dbusmock/_version.py
--- old/python-dbusmock-0.31.1/dbusmock/_version.py     2024-02-23 
14:06:27.000000000 +0100
+++ new/python-dbusmock-0.32.1/dbusmock/_version.py     2024-07-14 
09:17:21.000000000 +0200
@@ -1,2 +1,2 @@
 '''auto-generated version from setuptools_scm'''
-__version__ = "0.31.1"
+__version__ = "0.32.1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-dbusmock-0.31.1/dbusmock/templates/bluez5.py 
new/python-dbusmock-0.32.1/dbusmock/templates/bluez5.py
--- old/python-dbusmock-0.31.1/dbusmock/templates/bluez5.py     2024-02-23 
14:06:17.000000000 +0100
+++ new/python-dbusmock-0.32.1/dbusmock/templates/bluez5.py     2024-07-14 
09:17:09.000000000 +0200
@@ -38,9 +38,17 @@
 NETWORK_SERVER_IFACE = "org.bluez.Network1"
 DEVICE_IFACE = "org.bluez.Device1"
 
+LE_ADVERTISING_MANAGER_IFACE = "org.bluez.LEAdvertisingManager1"
+LE_ADVERTISEMENT_IFACE = "org.bluez.LEAdvertisement1"
+ADVERTISEMENT_MONITOR_MANAGER_IFACE = "org.bluez.AdvertisementMonitorManager1"
+ADVERTISEMENT_MONITOR_IFACE = "org.bluez.AdvertisementMonitor1"
+
 # The device class of some arbitrary Android phone.
 MOCK_PHONE_CLASS = 5898764
 
+# Maximum number of BLE advertisements supported per adapter.
+MAX_ADVERTISEMENT_INSTANCES = 5
+
 
 @dbus.service.method(AGENT_MANAGER_IFACE, in_signature="os", out_signature="")
 def RegisterAgent(manager, agent_path, capability):
@@ -48,9 +56,13 @@
 
     if agent_path in manager.agent_paths:
         raise dbus.exceptions.DBusException(
-            "Another agent is already registered " + manager.agent_path, 
name="org.bluez.Error.AlreadyExists"
+            "Another agent is already registered " + agent_path, 
name="org.bluez.Error.AlreadyExists"
         )
 
+    # Fallback to "KeyboardDisplay" as per BlueZ spec
+    if not capability:
+        capability = "KeyboardDisplay"
+
     if capability not in all_caps:
         raise dbus.exceptions.DBusException(
             "Unsupported capability " + capability, 
name="org.bluez.Error.InvalidArguments"
@@ -107,6 +119,11 @@
     bluez.capabilities = {}
     bluez.default_agent = None
 
+    # whether to expose the LEAdvertisingManager1 interface on adapters (BLE 
advertising)
+    bluez.enable_advertise_api = _parameters.get("enable_advertise_api", True)
+    # whether to expose the AdvertisementMonitorManager1 interface on adapters 
(Passive scanning)
+    bluez.enable_monitor_api = _parameters.get("enable_monitor_api", True)
+
 
 @dbus.service.method(ADAPTER_IFACE, in_signature="o", out_signature="")
 def RemoveDevice(adapter, path):
@@ -221,6 +238,7 @@
         "Class": dbus.UInt32(268, variant_level=1),  # Computer, Laptop
         "DiscoverableTimeout": dbus.UInt32(180, variant_level=1),
         "PairableTimeout": dbus.UInt32(0, variant_level=1),
+        "Roles": dbus.Array(["central", "peripheral"], variant_level=1),
     }
 
     self.AddObject(
@@ -253,6 +271,63 @@
         ],
     )
 
+    bluez = mockobject.objects["/org/bluez"]
+
+    # Advertising Manager
+    if bluez.enable_advertise_api:
+        # Example values below from an Intel AX200 adapter
+        advertising_manager_properties = {
+            "ActiveInstances": dbus.Byte(0, variant_level=1),
+            "SupportedInstances": dbus.Byte(MAX_ADVERTISEMENT_INSTANCES, 
variant_level=1),
+            "SupportedIncludes": dbus.Array(["tx-power", "appearance", 
"local-name", "rssi"], variant_level=1),
+            "SupportedSecondaryChannels": dbus.Array(["1M", "2M", "Coded"], 
variant_level=1),
+            "SupportedCapabilities": dbus.Dictionary(
+                {
+                    "MaxAdvLen": dbus.Byte(251),
+                    "MaxScnRspLen": dbus.Byte(251),
+                    "MinTxPower": dbus.Int16(-34),
+                    "MaxTxPower": dbus.Int16(7),
+                },
+                signature="sv",
+                variant_level=1,
+            ),
+            "SupportedFeatures": dbus.Array(
+                [
+                    "CanSetTxPower",
+                    "HardwareOffload",
+                ],
+                variant_level=1,
+            ),
+        }
+        adapter.AddProperties(LE_ADVERTISING_MANAGER_IFACE, 
advertising_manager_properties)
+        adapter.AddMethods(
+            LE_ADVERTISING_MANAGER_IFACE,
+            [
+                ("RegisterAdvertisement", "oa{sv}", "", RegisterAdvertisement),
+                ("UnregisterAdvertisement", "o", "", UnregisterAdvertisement),
+            ],
+        )
+
+        # Track advertisements per adapter
+        adapter.advertisements = []
+
+    # Advertisement Monitor Manager
+    if bluez.enable_monitor_api:
+        advertisement_monitor_manager_properties = {
+            "SupportedMonitorTypes": dbus.Array(["or_patterns"], 
variant_level=1),
+        }
+        adapter.AddProperties(ADVERTISEMENT_MONITOR_MANAGER_IFACE, 
advertisement_monitor_manager_properties)
+        adapter.AddMethods(
+            ADVERTISEMENT_MONITOR_MANAGER_IFACE,
+            [
+                ("RegisterMonitor", "o", "", RegisterMonitor),
+                ("UnregisterMonitor", "o", "", UnregisterMonitor),
+            ],
+        )
+
+        # Track advertisement monitors per adapter
+        adapter.monitors = []
+
     manager = mockobject.objects["/"]
     manager.EmitSignal(
         OBJECT_MANAGER_IFACE,
@@ -652,3 +727,137 @@
             [],
         ],
     )
+
+
+def RegisterAdvertisement(manager, adv_path, options):  # pylint: 
disable=unused-argument
+    if adv_path in manager.advertisements:
+        raise dbus.exceptions.DBusException("Already registered: " + adv_path, 
name="org.bluez.Error.AlreadyExists")
+
+    if len(manager.advertisements) >= MAX_ADVERTISEMENT_INSTANCES:
+        raise dbus.exceptions.DBusException(
+            f"Maximum number of advertisements reached: 
{MAX_ADVERTISEMENT_INSTANCES}",
+            name="org.bluez.Error.NotPermitted",
+        )
+
+    manager.advertisements.append(adv_path)
+
+    manager.UpdateProperties(
+        LE_ADVERTISING_MANAGER_IFACE,
+        {
+            "ActiveInstances": dbus.Byte(len(manager.advertisements)),
+            "SupportedInstances": dbus.Byte(MAX_ADVERTISEMENT_INSTANCES - 
len(manager.advertisements)),
+        },
+    )
+
+
+def UnregisterAdvertisement(manager, adv_path):
+    try:
+        manager.advertisements.remove(adv_path)
+    except ValueError:
+        raise dbus.exceptions.DBusException(
+            "Unknown advertisement: " + adv_path, 
name="org.bluez.Error.DoesNotExist"
+        ) from None
+
+    manager.UpdateProperties(
+        LE_ADVERTISING_MANAGER_IFACE,
+        {
+            "ActiveInstances": dbus.Byte(len(manager.advertisements)),
+            "SupportedInstances": dbus.Byte(MAX_ADVERTISEMENT_INSTANCES - 
len(manager.advertisements)),
+        },
+    )
+
+
+@dbus.service.method(BLUEZ_MOCK_IFACE, in_signature="s", out_signature="s")
+def AddAdvertisement(self, adv_name):
+    """Convenience method to add an Advertisement object
+
+    Creates a simple broadcast advertisement with some manufacturer data.
+
+    Returns the new object path.
+    """
+    path = "/org/dbusmock/bluez/advertisement/" + adv_name
+
+    adv_properties = {
+        "Type": dbus.String("broadcast", variant_level=1),
+        "ManufacturerData": dbus.Dictionary(
+            # 0xFFFF is the Bluetooth Company Identifier reserved for internal 
use and testing.
+            {dbus.UInt16(0xFFFF): dbus.Array([0x00, 0x01], variant_level=2)},
+            signature="qv",
+            variant_level=1,
+        ),
+        "Includes": dbus.Array(["local-name"], variant_level=1),
+    }
+
+    self.AddObject(
+        path,
+        LE_ADVERTISEMENT_IFACE,
+        adv_properties,
+        [
+            ("Release", "", "", ""),
+        ],
+    )
+
+    return path
+
+
+@dbus.service.method(BLUEZ_MOCK_IFACE, in_signature="s", out_signature="s")
+def AddMonitor(self, monitor_name):
+    """Convenience method to add an Advertisement Monitor
+
+    Returns the new object path.
+    """
+    path = "/org/dbusmock/bluez/monitor/" + monitor_name
+
+    monitor_properties = {
+        "Type": dbus.String("or_patterns", variant_level=1),
+        # Example pattern that could be used to scan for an advertisement 
created by AddAdvertisement()
+        "Patterns": dbus.Struct(
+            (
+                # Start position: 0
+                dbus.Byte(0),
+                # AD data type: Manufacturer data
+                dbus.Byte(0xFF),
+                # Vaue of the pattern: 0xFFFF (company identifier), followed 
by 0x01
+                dbus.Array(
+                    [
+                        dbus.UInt16(0xFFFF),
+                        dbus.Byte(0x01),
+                    ]
+                ),
+            ),
+            signature="yyay",
+            variant_level=2,
+        ),
+    }
+
+    self.AddObject(
+        path,
+        ADVERTISEMENT_MONITOR_IFACE,
+        monitor_properties,
+        [
+            ("Release", "", "", ""),
+            ("Activate", "", "", ""),
+            ("DeviceFound", "o", "", ""),
+            ("DeviceLost", "o", "", ""),
+        ],
+    )
+
+    return path
+
+
+def RegisterMonitor(manager, monitor_path):
+    if monitor_path in manager.monitors:
+        raise dbus.exceptions.DBusException(
+            "Already registered: " + monitor_path, 
name="org.bluez.Error.AlreadyExists"
+        )
+
+    manager.monitors.append(monitor_path)
+
+
+def UnregisterMonitor(manager, monitor_path):
+    try:
+        manager.monitors.remove(monitor_path)
+    except ValueError:
+        raise dbus.exceptions.DBusException(
+            "Unknown monitor: " + monitor_path, 
name="org.bluez.Error.DoesNotExist"
+        ) from None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-dbusmock-0.31.1/dbusmock/templates/gnome_screensaver.py 
new/python-dbusmock-0.32.1/dbusmock/templates/gnome_screensaver.py
--- old/python-dbusmock-0.31.1/dbusmock/templates/gnome_screensaver.py  
2024-02-23 14:06:17.000000000 +0100
+++ new/python-dbusmock-0.32.1/dbusmock/templates/gnome_screensaver.py  
2024-07-14 09:17:09.000000000 +0200
@@ -28,8 +28,12 @@
         [
             ("GetActive", "", "b", "ret = self.is_active"),
             ("GetActiveTime", "", "u", "ret = 1"),
-            # fmt: off
-            ("SetActive", "b", "", 'self.is_active = args[0]; 
self.EmitSignal("", "ActiveChanged", "b", [self.is_active])'),
+            (
+                "SetActive",
+                "b",
+                "",
+                'self.is_active = args[0]; self.EmitSignal("", 
"ActiveChanged", "b", [self.is_active])',
+            ),
             ("Lock", "", "", "time.sleep(1); self.SetActive(True)"),
             ("ShowMessage", "sss", "", ""),
             ("SimulateUserActivity", "", "", ""),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-dbusmock-0.31.1/dbusmock/templates/modemmanager.py 
new/python-dbusmock-0.32.1/dbusmock/templates/modemmanager.py
--- old/python-dbusmock-0.31.1/dbusmock/templates/modemmanager.py       
1970-01-01 01:00:00.000000000 +0100
+++ new/python-dbusmock-0.32.1/dbusmock/templates/modemmanager.py       
2024-07-14 09:17:09.000000000 +0200
@@ -0,0 +1,209 @@
+"""ModemManager mock template
+
+This creates the expected methods and properties of the main
+ModemManager object, but no devices. You can specify any property
+such as DaemonVersion in "parameters".
+"""
+
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 3 of the License, or (at your option) any
+# later version.  See http://www.gnu.org/copyleft/lgpl.html for the full text
+# of the license.
+
+__author__ = "Guido Günther"
+__copyright__ = "2024 The Phosh Developers"
+
+import dbus
+
+from dbusmock import MOCK_IFACE, OBJECT_MANAGER_IFACE, mockobject
+
+BUS_NAME = "org.freedesktop.ModemManager1"
+MAIN_OBJ = "/org/freedesktop/ModemManager1"
+MAIN_IFACE = "org.freedesktop.ModemManager1"
+SYSTEM_BUS = True
+IS_OBJECT_MANAGER = True
+MODEM_IFACE = "org.freedesktop.ModemManager1.Modem"
+MODEM_3GPP_IFACE = "org.freedesktop.ModemManager1.Modem.Modem3gpp"
+MODEM_VOICE_IFACE = "org.freedesktop.ModemManager1.Modem.Voice"
+SIM_IFACE = "org.freedesktop.ModemManager1.Sim"
+
+
+class MMModemMode:
+    """
+    See
+    
https://www.freedesktop.org/software/ModemManager/doc/latest/ModemManager/ModemManager-Flags-and-Enumerations.html#MMModemMode
+    """
+
+    MODE_NONE = 0
+    MODE_CS = 1 << 0
+    MODE_2G = 1 << 1
+    MODE_3G = 1 << 2
+    MODE_4G = 1 << 3
+    MODE_5G = 1 << 4
+
+
+class MMModemState:
+    """
+    See
+    
https://www.freedesktop.org/software/ModemManager/doc/latest/ModemManager/ModemManager-Flags-and-Enumerations.html#MMModemState
+    """
+
+    STATE_FAILED = -1
+    STATE_UNKNOWN = 0
+    STATE_INITIALIZING = 1
+    STATE_LOCKED = 2
+    STATE_DISABLED = 3
+    STATE_DISABLING = 4
+    STATE_ENABLING = 5
+    STATE_ENABLED = 6
+    STATE_SEARCHING = 7
+    STATE_REGISTERED = 8
+    STATE_DISCONNECTING = 9
+    STATE_CONNECTING = 10
+    STATE_CONNECTED = 11
+
+
+class MMModemPowerState:
+    """
+    See
+    
https://www.freedesktop.org/software/ModemManager/doc/latest/ModemManager/ModemManager-Flags-and-Enumerations.html#MMModemPowerState
+    """
+
+    POWER_STATE_UNKNOWN = 0
+    POWER_STATE_OFF = 1
+    POWER_STATE_LOW = 2
+    POWER_STATE_ON = 3
+
+
+class MMModemAccesssTechnology:
+    """
+    See
+    
https://www.freedesktop.org/software/ModemManager/doc/latest/ModemManager/ModemManager-Flags-and-Enumerations.html#MMModemAccessTechnology
+    """
+
+    ACCESS_TECHNOLOGY_UNKNOWN = 0
+    ACCESS_TECHNOLOGY_POTS = 1 << 0
+    ACCESS_TECHNOLOGY_GSM = 1 << 1
+    ACCESS_TECHNOLOGY_GSM_COMPACT = 1 << 2
+    ACCESS_TECHNOLOGY_GPRS = 1 << 3
+    ACCESS_TECHNOLOGY_EDGE = 1 << 4
+    ACCESS_TECHNOLOGY_UMTS = 1 << 5
+    ACCESS_TECHNOLOGY_HSDPA = 1 << 6
+    ACCESS_TECHNOLOGY_HSUPA = 1 << 7
+    ACCESS_TECHNOLOGY_HSPA = 1 << 8
+    ACCESS_TECHNOLOGY_HSPA_PLUS = 1 << 9
+    ACCESS_TECHNOLOGY_1XRTT = 1 << 10
+    ACCESS_TECHNOLOGY_EVDO0 = 1 << 11
+    ACCESS_TECHNOLOGY_EVDOA = 1 << 12
+    ACCESS_TECHNOLOGY_EVDOB = 1 << 13
+    ACCESS_TECHNOLOGY_LTE = 1 << 14
+    ACCESS_TECHNOLOGY_5GNR = 1 << 15
+    ACCESS_TECHNOLOGY_LTE_CAT_M = 1 << 16
+    ACCESS_TECHNOLOGY_LTE_NB_IOT = 1 << 17
+
+
+def load(mock, parameters):
+    methods = [
+        ("ScanDevices", "", "", ""),
+    ]
+
+    props = dbus.Dictionary(
+        {
+            "Version": parameters.get("DaemonVersion", "1.22"),
+        },
+        signature="sv",
+    )
+
+    mock.AddMethods(MAIN_IFACE, methods)
+    mock.AddProperties(MAIN_IFACE, props)
+
+
+@dbus.service.method(MOCK_IFACE, in_signature="", out_signature="ss")
+def AddSimpleModem(self):
+    """Convenience method to add a simple Modem object
+
+    Please note that this does not set any global properties.
+
+    Returns the new object path.
+    """
+    modem_path = "/org/freedesktop/ModemManager1/Modems/8"
+    sim_path = "/org/freedesktop/ModemManager1/SIM/2"
+    manager = mockobject.objects[MAIN_OBJ]
+
+    modem_props = {
+        "State": dbus.Int32(MMModemState.STATE_ENABLED),
+        "Model": dbus.String("E1750"),
+        "Revision": dbus.String("11.126.08.01.00"),
+        "AccessTechnologies": 
dbus.UInt32(MMModemAccesssTechnology.ACCESS_TECHNOLOGY_LTE),
+        "PowerState": dbus.UInt32(MMModemPowerState.POWER_STATE_ON),
+        "UnlockRequired": dbus.UInt32(0),
+        "UnlockRetries": dbus.Dictionary([], signature="uu"),
+        "CurrentModes": dbus.Struct(
+            (dbus.UInt32(MMModemMode.MODE_4G), 
dbus.UInt32(MMModemMode.MODE_4G)), signature="(uu)"
+        ),
+        "SignalQuality": dbus.Struct(
+            (dbus.UInt32(70), dbus.Boolean(True)),
+        ),
+        "Sim": dbus.ObjectPath(sim_path),
+        "SupportedModes": [
+            (dbus.UInt32(MMModemMode.MODE_4G), 
dbus.UInt32(MMModemMode.MODE_4G)),
+            (dbus.UInt32(MMModemMode.MODE_3G | MMModemMode.MODE_2G), 
dbus.UInt32(MMModemMode.MODE_3G)),
+        ],
+        "SupportedBands": [dbus.UInt32(0)],
+    }
+    self.AddObject(modem_path, MODEM_IFACE, modem_props, [])
+
+    modem_3gpp_props = {
+        "Imei": dbus.String("doesnotmatter", variant_level=1),
+        "OperatorName": dbus.String("TheOperator"),
+        "Pco": dbus.Array([], signature="(ubay)"),
+    }
+    modem = mockobject.objects[modem_path]
+    modem.AddProperties(MODEM_3GPP_IFACE, modem_3gpp_props)
+
+    modem_voice_props = {
+        "Calls": dbus.Array([], signature="o"),
+        "EmergencyOnly": False,
+    }
+
+    modem.call_waiting = False
+    modem_voice_methods = [
+        ("CallWaitingQuery", "", "b", "ret = self.call_waiting"),
+        ("CallWaitingSetup", "b", "", "self.call_waiting = args[0]"),
+    ]
+    modem = mockobject.objects[modem_path]
+    modem.AddProperties(MODEM_VOICE_IFACE, modem_voice_props)
+    modem.AddMethods(MODEM_VOICE_IFACE, modem_voice_methods)
+
+    manager.EmitSignal(
+        OBJECT_MANAGER_IFACE,
+        "InterfacesAdded",
+        "oa{sa{sv}}",
+        [
+            dbus.ObjectPath(modem_path),
+            {
+                MODEM_IFACE: modem_props,
+                MODEM_3GPP_IFACE: modem_3gpp_props,
+                MODEM_VOICE_IFACE: modem_voice_props,
+            },
+        ],
+    )
+
+    sim_props = {
+        "Active": dbus.Boolean(True),
+        "Imsi": dbus.String("doesnotmatter"),
+        "PreferredNetworks": dbus.Array([], signature="(su)"),
+    }
+    self.AddObject(sim_path, SIM_IFACE, sim_props, [])
+    manager.EmitSignal(
+        OBJECT_MANAGER_IFACE,
+        "InterfacesAdded",
+        "oa{sa{sv}}",
+        [
+            dbus.ObjectPath(sim_path),
+            {SIM_IFACE: sim_props},
+        ],
+    )
+
+    return (modem_path, sim_path)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-dbusmock-0.31.1/dbusmock/templates/networkmanager.py 
new/python-dbusmock-0.32.1/dbusmock/templates/networkmanager.py
--- old/python-dbusmock-0.31.1/dbusmock/templates/networkmanager.py     
2024-02-23 14:06:17.000000000 +0100
+++ new/python-dbusmock-0.32.1/dbusmock/templates/networkmanager.py     
2024-07-14 09:17:09.000000000 +0200
@@ -42,7 +42,7 @@
 
 
 # these really want to be dataclasses, but need to drop support for Python 3.6 
for that
-class NMState:  # pylint: disable=too-few-public-methods
+class NMState:
     """Global state
 
     As per 
https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NMState
@@ -58,7 +58,7 @@
     NM_STATE_CONNECTED_GLOBAL = 70
 
 
-class NMConnectivityState:  # pylint: disable=too-few-public-methods
+class NMConnectivityState:
     """Connectvity state
 
     As per 
https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NMConnectivityState
@@ -71,7 +71,7 @@
     NM_CONNECTIVITY_FULL = 4
 
 
-class NMActiveConnectionState:  # pylint: disable=too-few-public-methods
+class NMActiveConnectionState:
     """Active connection state
 
     As per 
https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NMActiveConnectionState
@@ -84,7 +84,7 @@
     NM_ACTIVE_CONNECTION_STATE_DEACTIVATED = 4
 
 
-class InfrastructureMode:  # pylint: disable=too-few-public-methods
+class InfrastructureMode:
     """Infrastructure mode
 
     As per 
https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NM80211Mode
@@ -103,7 +103,7 @@
     }
 
 
-class DeviceState:  # pylint: disable=too-few-public-methods
+class DeviceState:
     """Device states
 
     As per 
https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NMDeviceState
@@ -124,7 +124,7 @@
     FAILED = 120
 
 
-class NM80211ApSecurityFlags:  # pylint: disable=too-few-public-methods
+class NM80211ApSecurityFlags:
     """Security flags
 
     As per 
https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NM80211ApSecurityFlags
@@ -147,7 +147,7 @@
     }
 
 
-class NM80211ApFlags:  # pylint: disable=too-few-public-methods
+class NM80211ApFlags:
     """Device flags
 
     As per 
https://developer.gnome.org/NetworkManager/unstable/nm-dbus-types.html#NM80211ApFlags
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-dbusmock-0.31.1/packaging/python-dbusmock.spec 
new/python-dbusmock-0.32.1/packaging/python-dbusmock.spec
--- old/python-dbusmock-0.31.1/packaging/python-dbusmock.spec   2024-02-23 
14:06:17.000000000 +0100
+++ new/python-dbusmock-0.32.1/packaging/python-dbusmock.spec   2024-07-14 
09:17:09.000000000 +0200
@@ -34,7 +34,7 @@
 %description -n python3-dbusmock %_description
 
 %prep
-%autosetup -n %{name}-%{version} -S git
+%autosetup -n %{name}-%{version}
 rm -rf python-%{modname}.egg-info
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-dbusmock-0.31.1/packit.yaml 
new/python-dbusmock-0.32.1/packit.yaml
--- old/python-dbusmock-0.31.1/packit.yaml      2024-02-23 14:06:17.000000000 
+0100
+++ new/python-dbusmock-0.32.1/packit.yaml      2024-07-14 09:17:09.000000000 
+0200
@@ -23,15 +23,17 @@
     trigger: pull_request
     targets:
     - fedora-all
-    - centos-stream-8-x86_64
     - centos-stream-9-x86_64
+    # FIXME: broken
+    # - centos-stream-10-x86_64
 
   - job: tests
     trigger: pull_request
     targets:
       - fedora-all
-      - centos-stream-8-x86_64
       - centos-stream-9-x86_64
+      # FIXME: broken rpm build above
+      # - centos-stream-10-x86_64
 
   - job: propose_downstream
     trigger: release
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-dbusmock-0.31.1/python_dbusmock.egg-info/PKG-INFO 
new/python-dbusmock-0.32.1/python_dbusmock.egg-info/PKG-INFO
--- old/python-dbusmock-0.31.1/python_dbusmock.egg-info/PKG-INFO        
2024-02-23 14:06:27.000000000 +0100
+++ new/python-dbusmock-0.32.1/python_dbusmock.egg-info/PKG-INFO        
2024-07-14 09:17:21.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: python-dbusmock
-Version: 0.31.1
+Version: 0.32.1
 Summary: Mock D-Bus objects
 Home-page: https://github.com/martinpitt/python-dbusmock
 Author: Martin Pitt
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-dbusmock-0.31.1/python_dbusmock.egg-info/SOURCES.txt 
new/python-dbusmock-0.32.1/python_dbusmock.egg-info/SOURCES.txt
--- old/python-dbusmock-0.31.1/python_dbusmock.egg-info/SOURCES.txt     
2024-02-23 14:06:27.000000000 +0100
+++ new/python-dbusmock-0.32.1/python_dbusmock.egg-info/SOURCES.txt     
2024-07-14 09:17:21.000000000 +0200
@@ -26,6 +26,7 @@
 dbusmock/templates/iio-sensors-proxy.py
 dbusmock/templates/logind.py
 dbusmock/templates/low_memory_monitor.py
+dbusmock/templates/modemmanager.py
 dbusmock/templates/networkmanager.py
 dbusmock/templates/notification_daemon.py
 dbusmock/templates/ofono.py
@@ -62,6 +63,7 @@
 tests/test_iio_sensors_proxy.py
 tests/test_logind.py
 tests/test_low_memory_monitor.py
+tests/test_modemmanager.py
 tests/test_networkmanager.py
 tests/test_notification_daemon.py
 tests/test_ofono.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-dbusmock-0.31.1/tests/run-centos 
new/python-dbusmock-0.32.1/tests/run-centos
--- old/python-dbusmock-0.31.1/tests/run-centos 2024-02-23 14:06:17.000000000 
+0100
+++ new/python-dbusmock-0.32.1/tests/run-centos 2024-07-14 09:17:09.000000000 
+0200
@@ -2,11 +2,14 @@
 set -eux
 # install build dependencies
 dnf -y install python3-setuptools python3 python3-gobject-base \
-    python3-dbus python3-pytest dbus-x11 util-linux \
+    python3-dbus dbus-x11 util-linux \
     upower NetworkManager bluez libnotify polkit
 
 if ! grep -q :el /etc/os-release; then
-    dnf -y install power-profiles-daemon iio-sensor-proxy
+    dnf -y install power-profiles-daemon iio-sensor-proxy python3-pytest
+else
+    dnf -y install python3-pip
+    pip install pytest
 fi
 
 if [ "$SKIP_STATIC_CHECKS" != "1" ]; then
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-dbusmock-0.31.1/tests/run-debian 
new/python-dbusmock-0.32.1/tests/run-debian
--- old/python-dbusmock-0.31.1/tests/run-debian 2024-02-23 14:06:17.000000000 
+0100
+++ new/python-dbusmock-0.32.1/tests/run-debian 2024-07-14 09:17:09.000000000 
+0200
@@ -14,7 +14,8 @@
 eatmydata apt-get install --no-install-recommends -y git \
     python3-all python3-setuptools python3-setuptools-scm python3-build 
python3-venv \
     python3-dbus python3-pytest python3-gi gir1.2-glib-2.0 \
-    dbus libnotify-bin upower network-manager bluez ofono ofono-scripts 
power-profiles-daemon
+    dbus libnotify-bin upower network-manager bluez ofono ofono-scripts 
power-profiles-daemon \
+    modemmanager
 
 # systemd's tools otherwise fail on "not been booted with systemd"
 mkdir -p /run/systemd/system
@@ -39,16 +40,14 @@
 
 # test sdist with direct setuptools
 ./setup.py sdist
-tar --wildcards --strip-components=1 -xvf 
dist/python-dbusmock-\${my_version}*.tar.gz '*/PKG-INFO'
+tar --wildcards --strip-components=1 -xvf 
dist/python[_-]dbusmock-\${my_version}*.tar.gz '*/PKG-INFO'
 grep "^Version: \${my_version}" PKG-INFO
 grep "^## Purpose" PKG-INFO
 git clean -ffdx
 
-# test sdist with PyPA build; note https://bugs.debian.org/1009916
-if dpkg --compare-versions \$(dpkg-query -f '\${Version}' --show 
python3-build) '>=' 0.7.0-3; then
-    python3 -m build --sdist
-    tar --wildcards --strip-components=1 -xvf 
dist/python-dbusmock-\${my_version}*.tar.gz '*/PKG-INFO'
-    grep "^Version: \${my_version}" PKG-INFO
-    grep "^## Purpose" PKG-INFO
-fi
+# test sdist with PyPA build
+python3 -m build --sdist
+tar --wildcards --strip-components=1 -xvf 
dist/python_dbusmock-\${my_version}*.tar.gz '*/PKG-INFO'
+grep "^Version: \${my_version}" PKG-INFO
+grep "^## Purpose" PKG-INFO
 EOF
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-dbusmock-0.31.1/tests/run-fedora 
new/python-dbusmock-0.32.1/tests/run-fedora
--- old/python-dbusmock-0.31.1/tests/run-fedora 2024-02-23 14:06:17.000000000 
+0100
+++ new/python-dbusmock-0.32.1/tests/run-fedora 2024-07-14 09:17:09.000000000 
+0200
@@ -2,11 +2,14 @@
 set -eux
 # install build dependencies
 dnf -y install python3-setuptools python3 python3-gobject-base \
-    python3-dbus python3-pytest dbus-x11 util-linux \
+    python3-dbus dbus-x11 util-linux \
     upower NetworkManager bluez libnotify polkit
 
 if ! grep -q :el /etc/os-release; then
-    dnf -y install power-profiles-daemon iio-sensor-proxy
+    dnf -y install power-profiles-daemon iio-sensor-proxy python3-pytest
+else
+    dnf -y install python3-pip
+    pip install pytest
 fi
 
 if [ "$SKIP_STATIC_CHECKS" != "1" ]; then
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-dbusmock-0.31.1/tests/run-ubuntu 
new/python-dbusmock-0.32.1/tests/run-ubuntu
--- old/python-dbusmock-0.31.1/tests/run-ubuntu 2024-02-23 14:06:17.000000000 
+0100
+++ new/python-dbusmock-0.32.1/tests/run-ubuntu 2024-07-14 09:17:09.000000000 
+0200
@@ -14,7 +14,8 @@
 eatmydata apt-get install --no-install-recommends -y git \
     python3-all python3-setuptools python3-setuptools-scm python3-build 
python3-venv \
     python3-dbus python3-pytest python3-gi gir1.2-glib-2.0 \
-    dbus libnotify-bin upower network-manager bluez ofono ofono-scripts 
power-profiles-daemon
+    dbus libnotify-bin upower network-manager bluez ofono ofono-scripts 
power-profiles-daemon \
+    modemmanager
 
 # systemd's tools otherwise fail on "not been booted with systemd"
 mkdir -p /run/systemd/system
@@ -39,16 +40,14 @@
 
 # test sdist with direct setuptools
 ./setup.py sdist
-tar --wildcards --strip-components=1 -xvf 
dist/python-dbusmock-\${my_version}*.tar.gz '*/PKG-INFO'
+tar --wildcards --strip-components=1 -xvf 
dist/python[_-]dbusmock-\${my_version}*.tar.gz '*/PKG-INFO'
 grep "^Version: \${my_version}" PKG-INFO
 grep "^## Purpose" PKG-INFO
 git clean -ffdx
 
-# test sdist with PyPA build; note https://bugs.debian.org/1009916
-if dpkg --compare-versions \$(dpkg-query -f '\${Version}' --show 
python3-build) '>=' 0.7.0-3; then
-    python3 -m build --sdist
-    tar --wildcards --strip-components=1 -xvf 
dist/python-dbusmock-\${my_version}*.tar.gz '*/PKG-INFO'
-    grep "^Version: \${my_version}" PKG-INFO
-    grep "^## Purpose" PKG-INFO
-fi
+# test sdist with PyPA build
+python3 -m build --sdist
+tar --wildcards --strip-components=1 -xvf 
dist/python_dbusmock-\${my_version}*.tar.gz '*/PKG-INFO'
+grep "^Version: \${my_version}" PKG-INFO
+grep "^## Purpose" PKG-INFO
 EOF
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-dbusmock-0.31.1/tests/test_bluez5.py 
new/python-dbusmock-0.32.1/tests/test_bluez5.py
--- old/python-dbusmock-0.31.1/tests/test_bluez5.py     2024-02-23 
14:06:17.000000000 +0100
+++ new/python-dbusmock-0.32.1/tests/test_bluez5.py     2024-07-14 
09:17:09.000000000 +0200
@@ -25,6 +25,7 @@
 from gi.repository import GLib
 
 import dbusmock
+from packaging.version import Version
 
 tracemalloc.start(25)
 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
@@ -89,6 +90,10 @@
         cls.dbus_con = cls.get_dbus(True)
         (cls.p_mock, cls.obj_bluez) = cls.spawn_server_template("bluez5", {}, 
stdout=subprocess.PIPE)
 
+        out = _run_bluetoothctl("version")
+        version = next(line.split(" ")[-1] for line in out if 
line.startswith("Version"))
+        cls.bluez5_version = Version(version)
+
     def setUp(self):
         self.obj_bluez.Reset()
         self.dbusmock = dbus.Interface(self.obj_bluez, dbusmock.MOCK_IFACE)
@@ -128,6 +133,43 @@
         self.assertIn("Discoverable: no", out)
         self.assertIn("Pairable: yes", out)
         self.assertIn("Discovering: no", out)
+        self.assertIn("Roles: central", out)
+        self.assertIn("Roles: peripheral", out)
+
+        # Advertising Manager
+        self.assertIn("Advertising Features:", out)
+        self.assertIn("ActiveInstances: 0x00 (0)", out)
+        self.assertIn("SupportedInstances: 0x05 (5)", out)
+        self.assertIn("SupportedIncludes: tx-power", out)
+        self.assertIn("SupportedIncludes: appearance", out)
+        self.assertIn("SupportedIncludes: local-name", out)
+        self.assertIn("SupportedSecondaryChannels: 1M", out)
+        self.assertIn("SupportedSecondaryChannels: 2M", out)
+
+        # SupportedFeatures was added to the API with BlueZ 5.57
+        if self.bluez5_version >= Version("5.57"):
+            self.assertIn("SupportedFeatures: CanSetTxPower", out)
+            self.assertIn("SupportedFeatures: HardwareOffload", out)
+
+        # Capabilities key-value format was changed in BlueZ 5.70
+        if self.bluez5_version <= Version("5.70"):
+            capabilities = [
+                ["SupportedCapabilities Key: MinTxPower", 
"SupportedCapabilities Value: -34"],
+                ["SupportedCapabilities Key: MaxTxPower", 
"SupportedCapabilities Value: 7"],
+                ["SupportedCapabilities Key: MaxAdvLen", 
"SupportedCapabilities Value: 0xfb (251)"],
+                ["SupportedCapabilities Key: MaxScnRspLen", 
"SupportedCapabilities Value: 0xfb (251)"],
+            ]
+            for capability in capabilities:
+                self.assertTrue(all(cap in out for cap in capability), 
f"Expected ${capability} in: ${out}")
+        else:
+            self.assertIn("SupportedCapabilities.MinTxPower: 0xffffffde 
(-34)", out)
+            self.assertIn("SupportedCapabilities.MaxTxPower: 0x0007 (7)", out)
+            self.assertIn("SupportedCapabilities.MaxAdvLen: 0xfb (251)", out)
+            self.assertIn("SupportedCapabilities.MaxScnRspLen: 0xfb (251)", 
out)
+
+        # Advertisement Monitor
+        self.assertIn("Advertisement Monitor Features:", out)
+        self.assertIn("SupportedMonitorTypes: or_patterns", out)
 
     def test_no_devices(self):
         # Add an adapter.
@@ -187,6 +229,283 @@
         self.assertIn("Device " + address, out)
         self.assertIn("Paired: yes", out)
 
+    def test_add_advertisement(self):
+        # When an advertisement is added
+        adv_path = self.dbusmock_bluez.AddAdvertisement("bc001")
+        # Then the path is returned
+        self.assertEqual(adv_path, "/org/dbusmock/bluez/advertisement/bc001")
+        # And the object is exported on the bus
+        adv = self.dbus_con.get_object("org.bluez", adv_path)
+        adv_type = adv.Get("org.bluez.LEAdvertisement1", "Type", 
dbus_interface="org.freedesktop.DBus.Properties")
+        # And has the correct properties
+        self.assertEqual(adv_type, "broadcast")
+
+    def test_register_advertisement(self):
+        # Given an adapter with the LEAdvertisingManager1 interface
+        path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
+        adapter = self.dbus_con.get_object("org.bluez", path)
+        adv_manager = dbus.Interface(adapter, 
"org.bluez.LEAdvertisingManager1")
+        props = dbus.Interface(adapter, "org.freedesktop.DBus.Properties")
+        active_instances = props.Get(adv_manager.dbus_interface, 
"ActiveInstances")
+        supported_instances = props.Get(adv_manager.dbus_interface, 
"SupportedInstances")
+
+        # And no active instances
+        self.assertEqual(active_instances, 0)
+        self.assertEqual(supported_instances, 5)
+
+        # When an advertisement is registered
+        # Then no error is raised
+        adv_manager.RegisterAdvertisement("/adv0", {})
+
+        # And active instances is incremented
+        active_instances = props.Get(adv_manager.dbus_interface, 
"ActiveInstances")
+        self.assertEqual(active_instances, 1)
+        # And supported instances is decremented
+        supported_instances = props.Get(adv_manager.dbus_interface, 
"SupportedInstances")
+        self.assertEqual(supported_instances, 4)
+
+    def test_register_advertisement_duplicate(self):
+        # Given an adapter with the LEAdvertisingManager1 interface
+        path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
+        adapter = self.dbus_con.get_object("org.bluez", path)
+        adv_manager = dbus.Interface(adapter, 
"org.bluez.LEAdvertisingManager1")
+        props = dbus.Interface(adapter, "org.freedesktop.DBus.Properties")
+
+        # When an advertisement is registered twice
+        adv_manager.RegisterAdvertisement("/adv0", {})
+
+        # Then an error is raised
+        with self.assertRaisesRegex(dbus.exceptions.DBusException, "Already 
registered") as ctx:
+            adv_manager.RegisterAdvertisement("/adv0", {})
+        self.assertEqual(ctx.exception.get_dbus_name(), 
"org.bluez.Error.AlreadyExists")
+
+        # And active instances is not incremented
+        active_instances = props.Get(adv_manager.dbus_interface, 
"ActiveInstances")
+        self.assertEqual(active_instances, 1)
+        # And supported instances is not decremented
+        supported_instances = props.Get(adv_manager.dbus_interface, 
"SupportedInstances")
+        self.assertEqual(supported_instances, 4)
+
+    def test_register_advertisement_max_instances(self):
+        # Given an adapter with the LEAdvertisingManager1 interface
+        path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
+        adapter = self.dbus_con.get_object("org.bluez", path)
+        adv_manager = dbus.Interface(adapter, 
"org.bluez.LEAdvertisingManager1")
+        props = dbus.Interface(adapter, "org.freedesktop.DBus.Properties")
+        max_instances = props.Get(adv_manager.dbus_interface, 
"SupportedInstances")
+
+        # When more advertisements are registered than supported
+        for i in range(max_instances):
+            adv_manager.RegisterAdvertisement(f"/adv{i}", {})
+
+        # Then an error is raised
+        with self.assertRaisesRegex(dbus.exceptions.DBusException, "Maximum 
number of advertisements reached") as ctx:
+            adv_manager.RegisterAdvertisement(f"/adv{int(max_instances)}", {})
+        self.assertEqual(ctx.exception.get_dbus_name(), 
"org.bluez.Error.NotPermitted")
+
+        # And active instances is equal to the number of supported instances
+        active_instances = props.Get(adv_manager.dbus_interface, 
"ActiveInstances")
+        self.assertEqual(active_instances, max_instances)
+        # And supported instances is now zero
+        supported_instances = props.Get(adv_manager.dbus_interface, 
"SupportedInstances")
+        self.assertEqual(supported_instances, 0)
+
+    def test_unregister_advertisement(self):
+        # Given an adapter with the LEAdvertisingManager1 interface
+        path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
+        adapter = self.dbus_con.get_object("org.bluez", path)
+        adv_manager = dbus.Interface(adapter, 
"org.bluez.LEAdvertisingManager1")
+        props = dbus.Interface(adapter, "org.freedesktop.DBus.Properties")
+
+        # And a registered advertisement
+        adv_manager.RegisterAdvertisement("/adv0", {})
+        active_instances = props.Get(adv_manager.dbus_interface, 
"ActiveInstances")
+        supported_instances = props.Get(adv_manager.dbus_interface, 
"SupportedInstances")
+        self.assertEqual(active_instances, 1)
+        self.assertEqual(supported_instances, 4)
+
+        # When the advertisement is unregistered
+        # Then no error is raised
+        adv_manager.UnregisterAdvertisement("/adv0")
+        # And active instances is decremented
+        active_instances = props.Get(adv_manager.dbus_interface, 
"ActiveInstances")
+        self.assertEqual(active_instances, 0)
+        # And supported instances is incremented
+        supported_instances = props.Get(adv_manager.dbus_interface, 
"SupportedInstances")
+        self.assertEqual(supported_instances, 5)
+
+    def test_unregister_advertisement_unknown(self):
+        # Given an adapter with the LEAdvertisingManager1 interface
+        path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
+        adapter = self.dbus_con.get_object("org.bluez", path)
+        adv_manager = dbus.Interface(adapter, 
"org.bluez.LEAdvertisingManager1")
+
+        # When an advertisement is unregistered without registering it first
+        # Then an error is raised
+        with self.assertRaisesRegex(dbus.exceptions.DBusException, "Unknown 
advertisement") as ctx:
+            adv_manager.UnregisterAdvertisement("/adv0")
+        self.assertEqual(ctx.exception.get_dbus_name(), 
"org.bluez.Error.DoesNotExist")
+
+    def test_add_monitor(self):
+        # When an advertisement monitor is added
+        adv_path = self.dbusmock_bluez.AddMonitor("mon001")
+        # Then the path is returned
+        self.assertEqual(adv_path, "/org/dbusmock/bluez/monitor/mon001")
+        # And the object is exported on the bus
+        adv = self.dbus_con.get_object("org.bluez", adv_path)
+        adv_type = adv.Get(
+            "org.bluez.AdvertisementMonitor1", "Type", 
dbus_interface="org.freedesktop.DBus.Properties"
+        )
+        # And has the correct properties
+        self.assertEqual(adv_type, "or_patterns")
+
+    def test_register_monitor(self):
+        # Given an adapter with the AdvertisementMonitorManager1 interface
+        path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
+        adapter = self.dbus_con.get_object("org.bluez", path)
+        adv_monitor_manager = dbus.Interface(adapter, 
"org.bluez.AdvertisementMonitorManager1")
+
+        # When an advertisement monitor is registered
+        # Then no error is raised
+        adv_monitor_manager.RegisterMonitor("/monitor0")
+
+    def test_register_monitor_duplicate(self):
+        # Given an adapter with the AdvertisementMonitorManager1 interface
+        path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
+        adapter = self.dbus_con.get_object("org.bluez", path)
+        adv_monitor_manager = dbus.Interface(adapter, 
"org.bluez.AdvertisementMonitorManager1")
+
+        # When an advertisement monitor is registered twice
+        adv_monitor_manager.RegisterMonitor("/monitor0")
+
+        # Then an error is raised
+        with self.assertRaisesRegex(dbus.exceptions.DBusException, "Already 
registered") as ctx:
+            adv_monitor_manager.RegisterMonitor("/monitor0")
+        self.assertEqual(ctx.exception.get_dbus_name(), 
"org.bluez.Error.AlreadyExists")
+
+    def test_unregister_monitor(self):
+        # Given an adapter with the AdvertisementMonitorManager1 interface
+        path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
+        adapter = self.dbus_con.get_object("org.bluez", path)
+        adv_monitor_manager = dbus.Interface(adapter, 
"org.bluez.AdvertisementMonitorManager1")
+
+        # And a registered advertisement monitor
+        adv_monitor_manager.RegisterMonitor("/monitor0")
+        # When the advertisement monitor is unregistered
+        # Then no error is raised
+        adv_monitor_manager.UnregisterMonitor("/monitor0")
+
+    def test_unregister_monitor_unknown(self):
+        # Given an adapter with the AdvertisementMonitorManager1 interface
+        path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
+        adapter = self.dbus_con.get_object("org.bluez", path)
+        adv_monitor_manager = dbus.Interface(adapter, 
"org.bluez.AdvertisementMonitorManager1")
+
+        # When an advertisement monitor is unregistered without registering it 
first
+        # Then an error is raised
+        with self.assertRaisesRegex(dbus.exceptions.DBusException, "Unknown 
monitor") as ctx:
+            adv_monitor_manager.UnregisterMonitor("/monitor0")
+        self.assertEqual(ctx.exception.get_dbus_name(), 
"org.bluez.Error.DoesNotExist")
+
+    def test_advertise(self):
+        # Given an adapter with the LEAdvertisingManager1 interface
+        path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
+        adapter = self.dbus_con.get_object("org.bluez", path)
+
+        # When an advertisement is started via bluetoothctl
+        _run_bluetoothctl("advertise broadcast")
+
+        # Then the RegisterAdvertisement method was called
+        mock_calls = adapter.GetMethodCalls("RegisterAdvertisement", 
dbus_interface="org.freedesktop.DBus.Mock")
+        self.assertEqual(len(mock_calls), 1)
+        path, *_ = mock_calls[0][1]
+        self.assertEqual(path, "/org/bluez/advertising")
+
+    def test_monitor(self):
+        # Given an adapter with the AdvertisementMonitorManager1 interface
+        path = self.dbusmock_bluez.AddAdapter("hci0", "my-computer")
+        adapter = self.dbus_con.get_object("org.bluez", path)
+
+        # When an advertisement monitor is configured via bluetoothctl
+        out = _run_bluetoothctl("monitor.add-or-pattern 0 255 0x01")
+
+        # Then bluetoothctl reports success
+        self.assertIn("Advertisement Monitor 0 added", out)
+
+        # And the RegisterMonitor method was called
+        mock_calls = adapter.GetMethodCalls("RegisterMonitor", 
dbus_interface="org.freedesktop.DBus.Mock")
+        self.assertEqual(len(mock_calls), 1)
+        path, *_ = mock_calls[0][1]
+        self.assertEqual(path, "/")
+
+    def test_register_agent(self):
+        # Given BlueZ with the AgentManager1 interface
+        bluez = self.dbus_con.get_object("org.bluez", "/org/bluez")
+        agent_manager = dbus.Interface(bluez, "org.bluez.AgentManager1")
+        agent_path = "/org/dbusmock/bluezagent"
+
+        # When an agent with the default capabiities is registered
+        # Then no error is raised
+        agent_manager.RegisterAgent(agent_path, "")
+
+    def test_register_agent_duplicate(self):
+        # Given BlueZ with the AgentManager1 interface
+        bluez = self.dbus_con.get_object("org.bluez", "/org/bluez")
+        agent_manager = dbus.Interface(bluez, "org.bluez.AgentManager1")
+        agent_path = "/org/dbusmock/bluezagent"
+
+        # When an agent is registered twice
+        agent_manager.RegisterAgent(agent_path, "")
+
+        # Then an error is raised
+        with self.assertRaisesRegex(
+            dbus.exceptions.DBusException, f"Another agent is already 
registered {agent_path}"
+        ) as ctx:
+            agent_manager.RegisterAgent(agent_path, "")
+        self.assertEqual(ctx.exception.get_dbus_name(), 
"org.bluez.Error.AlreadyExists")
+
+    def test_unregister_agent(self):
+        # Given BlueZ with the AgentManager1 interface
+        bluez = self.dbus_con.get_object("org.bluez", "/org/bluez")
+        agent_manager = dbus.Interface(bluez, "org.bluez.AgentManager1")
+        agent_path = "/org/dbusmock/bluezagent"
+
+        # And a registered agent
+        agent_manager.RegisterAgent(agent_path, "")
+        # When the agent is unregistered
+        # Then no error is raised
+        agent_manager.UnregisterAgent(agent_path)
+
+    def test_unregister_agent_unknown(self):
+        # Given BlueZ with the AgentManager1 interface
+        bluez = self.dbus_con.get_object("org.bluez", "/org/bluez")
+        agent_manager = dbus.Interface(bluez, "org.bluez.AgentManager1")
+        agent_path = "/org/dbusmock/bluezagent"
+
+        # When an agent is unregistered without registering it first
+        # Then an error is raised
+        with self.assertRaisesRegex(dbus.exceptions.DBusException, f"Agent not 
registered {agent_path}") as ctx:
+            agent_manager.UnregisterAgent(agent_path)
+        self.assertEqual(ctx.exception.get_dbus_name(), 
"org.bluez.Error.DoesNotExist")
+
+    def test_agent(self):
+        # Given BlueZ with the AgentManager1 interface
+        bluez = self.dbus_con.get_object("org.bluez", "/org/bluez")
+
+        # When bluetoothctl is started
+        out = _run_bluetoothctl("list")
+
+        # Then it reports that the agent was registered
+        if self.bluez5_version >= Version("5.57"):
+            self.assertIn("Agent registered", out)
+
+        # And the RegisterAgent method was called
+        mock_calls = bluez.GetMethodCalls("RegisterAgent", 
dbus_interface="org.freedesktop.DBus.Mock")
+        self.assertEqual(len(mock_calls), 1)
+        path, capabilities = mock_calls[0][1]
+        self.assertEqual(path, "/org/bluez/agent")
+        self.assertEqual(capabilities, "")
+
 
 @unittest.skipUnless(have_pbap_client, "pbap-client not installed (copy it 
from bluez/test)")
 class TestBlueZObex(dbusmock.DBusTestCase):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-dbusmock-0.31.1/tests/test_code.py 
new/python-dbusmock-0.32.1/tests/test_code.py
--- old/python-dbusmock-0.31.1/tests/test_code.py       2024-02-23 
14:06:17.000000000 +0100
+++ new/python-dbusmock-0.32.1/tests/test_code.py       2024-07-14 
09:17:09.000000000 +0200
@@ -35,6 +35,7 @@
                 "--score=n",
                 "--disable=missing-function-docstring,R0801",
                 "--disable=too-many-arguments,too-many-instance-attributes",
+                "--disable=too-few-public-methods",
                 "dbusmock/templates/",
             ]
         )
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-dbusmock-0.31.1/tests/test_modemmanager.py 
new/python-dbusmock-0.32.1/tests/test_modemmanager.py
--- old/python-dbusmock-0.31.1/tests/test_modemmanager.py       1970-01-01 
01:00:00.000000000 +0100
+++ new/python-dbusmock-0.32.1/tests/test_modemmanager.py       2024-07-14 
09:17:09.000000000 +0200
@@ -0,0 +1,144 @@
+""" Tests for accounts service """
+
+# This program is free software; you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 3 of the License, or (at your option) any
+# later version.  See http://www.gnu.org/copyleft/lgpl.html for the full text
+# of the license.
+
+__author__ = "Guido Günther"
+__copyright__ = """
+(c) 2024 The Phosh Developers
+"""
+
+import shutil
+import subprocess
+import sys
+import unittest
+
+import dbus
+import dbus.mainloop.glib
+
+import dbusmock
+
+dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
+have_mmcli = shutil.which("mmcli")
+
+
+class TestModemManagerBase(dbusmock.DBusTestCase):
+    """Test mocking ModemManager"""
+
+    dbus_interface = ""
+
+    @classmethod
+    def setUpClass(cls):
+        super().setUpClass()
+        cls.start_system_bus()
+        cls.dbus_con = cls.get_dbus(True)
+
+    def setUp(self):
+        super().setUp()
+        (self.p_mock, self.p_obj) = self.spawn_server_template("modemmanager", 
{}, stdout=subprocess.PIPE)
+
+    def tearDown(self):
+        if self.p_mock:
+            self.p_mock.stdout.close()
+            self.p_mock.terminate()
+            self.p_mock.wait()
+
+        super().tearDown()
+
+    def get_property(self, name):
+        return self.p_obj.Get(self.dbus_interface, name, 
dbus_interface=dbus.PROPERTIES_IFACE)
+
+
+@unittest.skipUnless(have_mmcli, "mmcli utility not available")
+class TestModemManagerMmcliBase(TestModemManagerBase):
+    """Base ModemManager interface tests using mmcli"""
+
+    ret = None
+
+    def run_mmcli(self, args):
+        self.assertIsNone(self.ret)
+        self.ret = subprocess.run(  # pylint: disable=subprocess-run-check
+            ["mmcli", *args], capture_output=True, text=True
+        )
+
+    def assertOutputEquals(self, expected_lines):
+        self.assertIsNotNone(self.ret)
+        lines = self.ret.stdout.split("\n")
+        self.assertEqual(len(lines), len(expected_lines))
+        for expected, line in zip(expected_lines, lines):
+            self.assertEqual(expected, line)
+
+    def assertOutputContainsLine(self, expected_line, ret=0):
+        self.assertEqual(self.ret.returncode, ret)
+        self.assertIn(expected_line, self.ret.stdout)
+
+
+class TestModemManagerModemMmcli(TestModemManagerMmcliBase):
+    """main ModemManager interface tests using mmcli"""
+
+    def test_no_modems(self):
+        self.run_mmcli(["-m", "any"])
+        self.assertEqual(self.ret.returncode, 1)
+        self.assertIn("error: couldn't find modem", self.ret.stderr)
+
+    def test_modem(self):
+        self.p_obj.AddSimpleModem()
+        self.run_mmcli(["-m", "any"])
+        self.assertOutputEquals(
+            [
+                "  -----------------------------",
+                "  General  |              path: 
/org/freedesktop/ModemManager1/Modems/8",
+                "  -----------------------------",
+                "  Hardware |             model: E1750",
+                "           | firmware revision: 11.126.08.01.00",
+                "  -----------------------------",
+                "  Status   |             state: enabled",
+                "           |       power state: on",
+                "           |       access tech: lte",
+                "           |    signal quality: 70% (recent)",
+                "  -----------------------------",
+                "  Modes    |         supported: allowed: 4g; preferred: 4g",
+                "           |                    allowed: 2g, 3g; preferred: 
3g",
+                "           |           current: allowed: 4g; preferred: 4g",
+                "  -----------------------------",
+                "  3GPP     |              imei: doesnotmatter",
+                "           |     operator name: TheOperator",
+                "           |      registration: idle",
+                "  -----------------------------",
+                "  SIM      |  primary sim path: 
/org/freedesktop/ModemManager1/SIM/2",
+                "",
+            ]
+        )
+
+    def test_sim(self):
+        self.p_obj.AddSimpleModem()
+        self.run_mmcli(["-i", "any"])
+        self.assertOutputEquals(
+            [
+                "  --------------------",
+                "  General    |   path: /org/freedesktop/ModemManager1/SIM/2",
+                "  --------------------",
+                "  Properties | active: yes",
+                "             |   imsi: doesnotmatter",
+                "",
+            ]
+        )
+
+    def test_voice_call_list(self):
+        self.p_obj.AddSimpleModem()
+        self.run_mmcli(["-m", "any", "--voice-list-calls"])
+        self.assertOutputContainsLine("No calls were found\n")
+
+    def test_voice_status(self):
+        self.p_obj.AddSimpleModem()
+        self.run_mmcli(["-m", "any", "--voice-status"])
+        self.assertOutputContainsLine("emergency only: no\n")
+
+
+if __name__ == "__main__":
+    # avoid writing to stderr
+    unittest.main(testRunner=unittest.TextTestRunner(stream=sys.stdout))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-dbusmock-0.31.1/tests/test_power_profiles_daemon.py 
new/python-dbusmock-0.32.1/tests/test_power_profiles_daemon.py
--- old/python-dbusmock-0.31.1/tests/test_power_profiles_daemon.py      
2024-02-23 14:06:17.000000000 +0100
+++ new/python-dbusmock-0.32.1/tests/test_power_profiles_daemon.py      
2024-07-14 09:17:09.000000000 +0200
@@ -12,6 +12,7 @@
 
 import fcntl
 import os
+import re
 import shutil
 import subprocess
 import sys
@@ -40,9 +41,8 @@
     def setUp(self):
         # depending on the installed client version, we need to pick the right 
template
         try:
-            version = subprocess.run(
-                ["powerprofilesctl", "version"], capture_output=True, 
text=True, check=True
-            ).stdout
+            out = subprocess.run(["powerprofilesctl", "version"], 
capture_output=True, text=True, check=True).stdout
+            version = re.search(r"[0-9.]+", out).group(0)
             version = ".".join(version.strip().split(".")[:2])
             template = "power_profiles_daemon" if float(version) < 0.2 else 
"upower_power_profiles_daemon"
         except subprocess.CalledProcessError as e:

Reply via email to