Skip to content

Commit

Permalink
Pavel's review - 9 - Update the doc
Browse files Browse the repository at this point in the history
  • Loading branch information
aplopez committed May 29, 2024
1 parent c6a7cdb commit ecafcff
Showing 1 changed file with 101 additions and 211 deletions.
312 changes: 101 additions & 211 deletions docs/guides/testing-dbus.rst
Original file line number Diff line number Diff line change
@@ -1,57 +1,19 @@
Testing D-Bus Services
======================
Class :class:`sssd_test_framework.utils.sbus.SBUSUtils` provides an API to
operate on D-Bus services. This class has two methods:

* :meth:`sssd_test_framework.utils.sbus.SBUSUtils.getInfoPipe()`
* :meth:`sssd_test_framework.utils.sbus.SBUSUtils.getDestination()`

It is important to notice that you don't need to instantiate an object of class
:class:`sssd_test_framework.utils.sbus.SBUSUtils` because the ``Client`` role already
provides one when you invoke the
:meth:`sssd_test_framework.roles.client.Client.sbus()` method.

:meth:`sssd_test_framework.utils.sbus.SBUSUtils.getInfoPipe()` returns a preinstantiated
object of class :class:`sssd_test_framework.utils.sbus.DBUSDestination`, which gives you
direct access to the `Infopipe` destination; while the method
:meth:`sssd_test_framework.utils.sbus.SBUSUtils.getDestination()` allows you to
create a new instance of :class:`sssd_test_framework.utils.sbus.DBUSDestination`
for which you can specify the actual `destination` (a server) and the ``bus`` to
use to contact that destination.

.. code-block:: python
:caption: Getting a destination
ifp_dest = client.sbus().getInfoPipe()
monitor_dest = client.sbus().getDestination(dest="sssd.monitor", bus=DBUSKnownBus.MONITOR)
The bus to provide to :meth:`sssd_test_framework.utils.sbus.SBUSUtils.getDestination()`
must be an object of class :class:`sssd_test_framework.utils.sbus.DBUSKnownBus` which
provides some predefined buses:

* :attr:`sssd_test_framework.utils.sbus.DBUSKnownBus.SYSTEM`
* :attr:`sssd_test_framework.utils.sbus.DBUSKnownBus.SESSION`
* :attr:`sssd_test_framework.utils.sbus.DBUSKnownBus.MONITOR`

.. note::
If no bus is provided, the default ``SYSTEM`` bus is used.

But it also provides the class method
:meth:`sssd_test_framework.utils.sbus.DBUSKnownBus.forDomain()` which generates
the bus object for the domain whose name was passed as parameter.

.. code-block:: python
:caption: Accessing the destination ``sssd.nss`` on domain ``MyDomain``
bus = DBUSKnownBus.forDomain("MyDomain")
dest = client.sbus().getDestination(dest="sssd.nss", bus=bus)
With the destination, it is possible to get the different objects it provides. Each object
has its own ``object path``. These objects are obtained by calling the destination's
:meth:`sssd_test_framework.utils.sbus.DBUSDestination.getObject()` method. The object path
must be passed as argument. A list containing all the available object paths can be retrieved
with the method :meth:`sssd_test_framework.utils.sbus.DBUSDestination.getObjectPaths()`.
Infopipe
--------
Access to the Infopipe services is achieved through the
:class:`sssd_test_framework.roles.client.Client` class. It provides the
:meth:`sssd_test_framework.roles.client.Client.infopipe()` method that
returns a pre-instantiated object of class :class:`sssd_test_framework.utils.sbus.DBUSDestination`, which gives you
direct access to the `Infopipe` destination.

With the destination, it is possible to get the different objects it provides.
Each object has its own ``object path``. These objects are obtained by calling
the destination's :meth:`sssd_test_framework.utils.sbus.DBUSDestination.getObject()`
method. The object path must be passed as argument. A list containing all the
available object paths can be retrieved with the method
:meth:`sssd_test_framework.utils.sbus.DBUSDestination.getObjectPaths()`.

.. note::
Passing an ``object path`` that doesn't exist will not generate an error nor
Expand All @@ -63,97 +25,49 @@ provided by that destination by simply treating them as attributes of the
parent object. **Methods** and **properties** are accessed in the same way.

.. note::
Accessing an attribute that can't be mapped to a subobject, attribute or method,
will raise an :class:`AttributeError` exception.

Accessing an attribute that can't be mapped to a subobject, attribute or
method, will raise an :class:`AttributeError` exception.

Examples
--------
In the following examples, there is an LDAP domain called `test`.

.. code-block:: python
:caption: Infopipe Test Example
:caption: Infopipe Example
@pytest.mark.topology(KnownTopologyGroup.AnyProvider)
@pytest.mark.topology(KnownTopology.LDAP)
def test_example__infopipe(client: Client, provider: GenericProvider):
provider.user("user-1").add(uid=10001)
provider.group("group-1").add(gid=20001)
client.sssd.start()
with client.sbus().getInfoPipe() as ifp:
res = ifp.getObjectPaths()
assert isinstance(res, list)
assert "/" in res
assert "/org/freedesktop/sssd/infopipe" in res
assert "/org/freedesktop/sssd/infopipe/Components" in res
assert "/org/freedesktop/sssd/infopipe/Users" in res
assert "/org/freedesktop/sssd/infopipe/Groups" in res
assert "/org/freedesktop/sssd/infopipe/Domains" in res
assert "/org/freedesktop/sssd/infopipe/Domains/test" in res
assert len(res) == 11 # Some entries are duplicated for some obscure reason
with ifp.getObject("/org/freedesktop/sssd/infopipe") as ifpObj:
res = ifpObj.FindMonitor()
assert "/org/freedesktop/sssd/infopipe/Components/monitor" == res
res = ifpObj.Ping("Ping")
assert "PONG" == res
with ifpObj.Users as users:
res = users.ListByName('user-1', 0)
assert isinstance(res, list)
assert "/org/freedesktop/sssd/infopipe/Users/test/10001" in res
assert len(res) == 1
with ifp.getObject("/org/freedesktop/sssd/infopipe/Groups") as groups:
res = groups.ListByName('group-1', 0)
assert isinstance(res, list)
assert "/org/freedesktop/sssd/infopipe/Groups/test/20001" in res
assert len(res) == 1
with ifpObj.Domains.test as test:
res = test.IsOnline()
assert isinstance(res, bool)
assert res
res = test.GetAll("org.freedesktop.sssd.infopipe.Domains")
assert isinstance(res, dict)
assert res["name"] == "test"
assert res["provider"] == "ldap"
assert res["login_format"] == r"^((?P<name>.+)@(?P<domain>[^@]+)|(?P<name>[^@]+))$"
assert isinstance(res["primary_servers"], list)
assert len(res["primary_servers"]) == 1
assert res["primary_servers"][0] == "ldap://master.ldap.test"
res = test.enumerable
assert isinstance(res, bool)
assert not res
try:
res = ifpObj.badChild
assert False
except AttributeError:
pass
try:
res = ifpObj.badMethod("XXXX")
assert False
except AttributeError:
pass
with client.infopipe() as ifp:
users = ifp.getObject("/org/freedesktop/sssd/infopipe/Users")
result = users.ListByName("user-1", 0)
assert "/org/freedesktop/sssd/infopipe/Users/test/10001" in result
assert len(result) == 1
Internals
---------
Although :class:`sssd_test_framework.roles.client.Client` provides the
:meth:`sssd_test_framework.roles.client.Client.infopipe()` method, the framework
allows you to access any D-Bus destination. To achieve that you need to
instantiate the :class:`sssd_test_framework.utils.sbus.DBUSDestination` class,
providing the MultihostHost where the D-Bus services are running, the destination
(the application) to contact and the bus to connect to.

The bus can be the bus' path as a string, or one of the predefined buses:

* :attr:`sssd_test_framework.utils.sbus.DBUSKnownBus.SYSTEM`
* :attr:`sssd_test_framework.utils.sbus.DBUSKnownBus.SESSION`
* :attr:`sssd_test_framework.utils.sbus.DBUSKnownBus.MONITOR`

.. code-block:: python
:caption: Monitor Test Example
:caption: Accessing the monitor
@pytest.mark.topology(KnownTopologyGroup.AnyProvider)
def test_example__monitor(client: Client):
client.sssd.start()
monitor = client.sbus().getDestination(dest="sssd.monitor", bus=DBUSKnownBus.MONITOR)
paths = monitor.getObjectPaths()
assert len(paths) == 2
assert "/" in paths
assert "/sssd" in paths
monitor = DBUSDestination(client.host, dest="sssd.monitor", bus=DBUSKnownBus.MONITOR)
sssd = monitor.getObject(objpath="/sssd")
Expand All @@ -162,62 +76,38 @@ In the following examples, there is an LDAP domain called `test`.
sssd.debug_level = 0x0070
res = sssd.debug_level
assert res == 0x0070
.. code-block:: python
:caption: Accessing a User's Properties
@pytest.mark.topology(KnownTopology.LDAP)
def test_example__GetUserProperties(client: Client, provider: GenericProvider):
provider.user("user-1").add(uid=10001, gid=20001)
client.sssd.start()
ifp = client.sbus().getInfoPipe()
users = ifp.getObject("/org/freedesktop/sssd/infopipe/Users");
user_path = users.FindByName("user-1")
assert "/org/freedesktop/sssd/infopipe/Users/test/10001" == user_path
user = ifp.getObject(user_path)
uid = user.Get("org.freedesktop.sssd.infopipe.Users.User", "uidNumber")
assert uid == 10001
gid = user.gidNumber
assert gid == 20001
props = user.GetAll("org.freedesktop.sssd.infopipe.Users.User")
assert props["uidNumber"] == 10001
assert props["gidNumber"] == 20001
assert props["name"] == "user-1"
assert props["homeDirectory"] == "/home/user-1"
A Note On Type Conversion
-------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~
All methods and properties accept and return Python types. Internally they are
converted to some specific classes helping to treat them and map them to D-Bus types.
converted to some specific classes helping to treat them and map them to D-Bus
types.

Objects of these types represent values that are passed to/from the methods and properties.
Their type is given by the class. For instance, DBUSTypeString is a D-Bus string.
Objects of these types represent values that are passed to/from the methods and
properties. Their type is given by the class. For instance, DBUSTypeString is a
D-Bus string.

These classes are all subclasses of the abstract class
:class:`sssd_test_framework.utils.dbus_types.DBUSType` and they provide the
:class:`sssd_test_framework.utils.dbus.types.DBUSType` and they provide the
following methods:

* :attr:`sssd_test_framework.utils.dbus_types.DBUSType.value`: A property to read and
set the (Python) value.
* :meth:`sssd_test_framework.utils.dbus_types.DBUSType.clone()`: a method to clone
itself without copying the value.
* :meth:`sssd_test_framework.utils.dbus_types.DBUSType.param()`: The string representation
of the value in a format suitable to be used as a parameter for ``dbus-send``.
* :meth:`sssd_test_framework.utils.dbus_types.DBUSType.parse()`: Parses the string resulting
from an execution of `dbus-send` and set the value to the object.
* :attr:`sssd_test_framework.utils.dbus.types.DBUSType.value`: A property to read
and set the (Python) value.
* :meth:`sssd_test_framework.utils.dbus.types.DBUSType.mimic()`: a method to copy
itself without copying the value while maintaining the structure (subtypes).
* :meth:`sssd_test_framework.utils.dbus.types.DBUSType.param()`: The string
representation of the value in a format suitable to be used as a parameter for
``dbus-send``.
* :meth:`sssd_test_framework.utils.dbus.types.DBUSType.parse()`: Parses the
string resulting from an execution of `dbus-send` and set the value to the
object.

Basic types (integers, strings, booleans, etc.) are subclasses of the
:class:`sssd_test_framework.utils.dbus.types.DBUSTypeBasic` abstract class.

Container types -- that is, the subclasses of the abstract class
:class:`sssd_test_framework.utils.dbus_types.DBUSTypeContainer` -- accept a parameter
for their constructors, another :class:`sssd_test_framework.utils.dbus_types.DBUSType`
object of the expected type.
:class:`sssd_test_framework.utils.dbus.types.DBUSTypeContainer` -- accept a
parameter for their constructors, another :class:`sssd_test_framework.utils.dbus.types.DBUSType` object of the expected type.

.. code-block:: python
:caption: Declaring an array of unit32
Expand All @@ -229,43 +119,42 @@ object of the expected type.
[1, 2, 3]
In some cases it may not be possible to know in advance the type of the elements
of a container type. In that case, no object is passed to the constructor. The type
will be guessed while parsing the result from ```dbus-send``.
of a container type. In that case, no object is passed to the constructor. The
type will be guessed while parsing the result from ```dbus-send``.

.. note::
``dbus-send`` doesnt explain very well how container types are combined as parameters,
and so far we didnt use them. So we might have to adapt the results of param() if they
are ever used.
``dbus-send`` doesn't explain very well how container types are combined as
parameters, and so far we didn't use them. So we might have to adapt the
results of param() if they are ever used.

.. note::
Using instrospection it is possible to get the methods and properties signatures.
Nevertheless, the signature for ``variant`` types does not include the type of the
contained type, as it does for the arrays and dictionaries. Because of this, it is
not possible to know in advance which type to expect and will have to be guessed
while parsing.


Implemeted Types
----------------
Using instrospection it is possible to get the methods and properties
signatures. Nevertheless, the signature for ``variant`` types does not include
the type of the contained type, as it does for the arrays and dictionaries.
Because of this, it is not possible to know in advance which type to expect
and will have to be guessed while parsing.

Implemented Types
~~~~~~~~~~~~~~~~~
The following classes are already implemented.

* :class:`sssd_test_framework.utils.dbus_types.DBUSType`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeBoolean`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeString`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeObjectPath`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeInteger`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeByte`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeInt16`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeInt32`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeInt64`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeUInt16`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeUInt32`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeUInt64`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeDouble`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeContainer`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeArray`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeDict`
* :class:`sssd_test_framework.utils.dbus_types.DBUSTypeVariant`
* :class:`sssd_test_framework.utils.dbus.types.DBUSType`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeBoolean`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeString`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeObjectPath`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeInteger`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeByte`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeInt16`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeInt32`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeInt64`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeUInt16`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeUInt32`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeUInt64`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeDouble`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeContainer`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeArray`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeDict`
* :class:`sssd_test_framework.utils.dbus.types.DBUSTypeVariant`

.. note::
Although the `D-Bus specification`_ considers ``dict entry`` a separate type,
Expand All @@ -275,17 +164,18 @@ The following classes are already implemented.
.. _D-Bus specification: https://dbus.freedesktop.org/doc/dbus-specification.html#type-system

Not Implemented Types
---------------------
~~~~~~~~~~~~~~~~~~~~~
Some other classes were not implemented because they are not accepted by `dbus-send`:

* signature
* UNIX FD
* struct

Helper Classes
--------------
Class :class:`sssd_test_framework.utils.dbus_types.DBUSSignatureReader` provides
a single class method :meth:`sssd_test_framework.utils.dbus_types.DBUSSignatureReader.read()`
used to read a method or property signature from a string and generate the corresponding
:class:`sssd_test_framework.utils.dbus_types.DBUSType` objects required for the method
or property.
~~~~~~~~~~~~~~
Class :class:`sssd_test_framework.utils.dbus.types.DBUSSignatureReader` provides
a single class method
:meth:`sssd_test_framework.utils.dbus.types.DBUSSignatureReader.read()`
used to read a method or property signature from a string and generate the
corresponding :class:`sssd_test_framework.utils.dbus.types.DBUSType` objects
required for the method or property.

0 comments on commit ecafcff

Please sign in to comment.