/mandos/trunk

To get this branch, use:
bzr branch http://bzr.recompile.se/loggerhead/mandos/trunk

« back to all changes in this revision

Viewing changes to mandos-ctl

  • Committer: Teddy Hogeborn
  • Date: 2019-03-31 09:13:31 UTC
  • Revision ID: teddy@recompile.se-20190331091331-avb4odg8uz1yb5tm
mandos-ctl: Add support for D-Bus module "pydbus"

* mandos-ctl: Try to import pydbus and gi modules.
  (main): Use pydbus if available, fall back to dbus-python.
  (pydbus_adapter): New.
  (Test_pydbus_adapter_SystemBus): - '' -
  (Test_pydbus_adapter_CachingBus): - '' -

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/python3 -bbI
2
 
# -*- after-save-hook: (lambda () (let ((command (if (fboundp 'file-local-name) (file-local-name (buffer-file-name)) (or (file-remote-p (buffer-file-name) 'localname) (buffer-file-name))))) (if (= (progn (if (get-buffer "*Test*") (kill-buffer "*Test*")) (process-file-shell-command (format "%s --check" (shell-quote-argument command)) nil "*Test*")) 0) (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w))) (progn (with-current-buffer "*Test*" (compilation-mode)) (display-buffer "*Test*" '(display-buffer-in-side-window)))))); coding: utf-8 -*-
 
1
#!/usr/bin/python
 
2
# -*- mode: python; coding: utf-8; after-save-hook: (lambda () (let ((command (if (and (boundp 'tramp-file-name-structure) (string-match (car tramp-file-name-structure) (buffer-file-name))) (tramp-file-name-localname (tramp-dissect-file-name (buffer-file-name))) (buffer-file-name)))) (if (= (shell-command (format "%s --check" (shell-quote-argument command)) "*Test*") 0) (let ((w (get-buffer-window "*Test*"))) (if w (delete-window w)) (kill-buffer "*Test*")) (display-buffer "*Test*")))); -*-
3
3
#
4
4
# Mandos Monitor - Control and monitor the Mandos server
5
5
#
46
46
import tempfile
47
47
import contextlib
48
48
 
49
 
if sys.version_info.major == 2:
50
 
    __metaclass__ = type
51
 
    str = unicode
52
 
 
53
 
class gi:
54
 
    """Dummy gi module, for the tests"""
55
 
    class repository:
56
 
        class GLib:
57
 
            class Error(Exception):
58
 
                pass
59
 
dbussy = None
60
 
ravel = None
61
 
dbus_python = None
62
 
pydbus = None
63
 
 
64
49
try:
65
 
    import dbussy
66
 
    import ravel
 
50
    import pydbus
 
51
    import gi
 
52
    dbus_python = None
67
53
except ImportError:
68
 
    try:
69
 
        import pydbus
70
 
        import gi
71
 
    except ImportError:
72
 
        import dbus as dbus_python
73
 
 
 
54
    import dbus as dbus_python
 
55
    pydbus = None
 
56
    class gi(object):
 
57
        """Dummy gi module, for the tests"""
 
58
        class repository(object):
 
59
            class GLib(object):
 
60
                class Error(Exception):
 
61
                    pass
74
62
 
75
63
# Show warnings by default
76
64
if not sys.warnoptions:
84
72
logging.captureWarnings(True)   # Show warnings via the logging system
85
73
 
86
74
if sys.version_info.major == 2:
 
75
    str = unicode
87
76
    import StringIO
88
77
    io.StringIO = StringIO.StringIO
89
78
 
90
79
locale.setlocale(locale.LC_ALL, "")
91
80
 
92
 
version = "1.8.9"
 
81
version = "1.8.3"
93
82
 
94
83
 
95
84
def main():
104
93
    if options.debug:
105
94
        log.setLevel(logging.DEBUG)
106
95
 
107
 
    if dbussy is not None and ravel is not None:
108
 
        bus = dbussy_adapter.CachingBus(dbussy, ravel)
109
 
    elif pydbus is not None:
 
96
    if pydbus is not None:
110
97
        bus = pydbus_adapter.CachingBus(pydbus)
111
98
    else:
112
99
        bus = dbus_python_adapter.CachingBus(dbus_python)
263
250
def rfc3339_duration_to_delta(duration):
264
251
    """Parse an RFC 3339 "duration" and return a datetime.timedelta
265
252
 
266
 
    >>> rfc3339_duration_to_delta("P7D") == datetime.timedelta(7)
267
 
    True
268
 
    >>> rfc3339_duration_to_delta("PT60S") == datetime.timedelta(0, 60)
269
 
    True
270
 
    >>> rfc3339_duration_to_delta("PT60M") == datetime.timedelta(hours=1)
271
 
    True
272
 
    >>> # 60 months
273
 
    >>> rfc3339_duration_to_delta("P60M") == datetime.timedelta(1680)
274
 
    True
275
 
    >>> rfc3339_duration_to_delta("PT24H") == datetime.timedelta(1)
276
 
    True
277
 
    >>> rfc3339_duration_to_delta("P1W") == datetime.timedelta(7)
278
 
    True
279
 
    >>> rfc3339_duration_to_delta("PT5M30S") == datetime.timedelta(0, 330)
280
 
    True
281
 
    >>> rfc3339_duration_to_delta("P1DT3M20S") == datetime.timedelta(1, 200)
282
 
    True
 
253
    >>> rfc3339_duration_to_delta("P7D")
 
254
    datetime.timedelta(7)
 
255
    >>> rfc3339_duration_to_delta("PT60S")
 
256
    datetime.timedelta(0, 60)
 
257
    >>> rfc3339_duration_to_delta("PT60M")
 
258
    datetime.timedelta(0, 3600)
 
259
    >>> rfc3339_duration_to_delta("P60M")
 
260
    datetime.timedelta(1680)
 
261
    >>> rfc3339_duration_to_delta("PT24H")
 
262
    datetime.timedelta(1)
 
263
    >>> rfc3339_duration_to_delta("P1W")
 
264
    datetime.timedelta(7)
 
265
    >>> rfc3339_duration_to_delta("PT5M30S")
 
266
    datetime.timedelta(0, 330)
 
267
    >>> rfc3339_duration_to_delta("P1DT3M20S")
 
268
    datetime.timedelta(1, 200)
283
269
    >>> # Can not be empty:
284
270
    >>> rfc3339_duration_to_delta("")
285
271
    Traceback (most recent call last):
395
381
    """Parse an interval string as documented by Mandos before 1.6.1,
396
382
    and return a datetime.timedelta
397
383
 
398
 
    >>> parse_pre_1_6_1_interval('7d') == datetime.timedelta(days=7)
399
 
    True
400
 
    >>> parse_pre_1_6_1_interval('60s') == datetime.timedelta(0, 60)
401
 
    True
402
 
    >>> parse_pre_1_6_1_interval('60m') == datetime.timedelta(hours=1)
403
 
    True
404
 
    >>> parse_pre_1_6_1_interval('24h') == datetime.timedelta(days=1)
405
 
    True
406
 
    >>> parse_pre_1_6_1_interval('1w') == datetime.timedelta(days=7)
407
 
    True
408
 
    >>> parse_pre_1_6_1_interval('5m 30s') == datetime.timedelta(0, 330)
409
 
    True
410
 
    >>> parse_pre_1_6_1_interval('') == datetime.timedelta(0)
411
 
    True
 
384
    >>> parse_pre_1_6_1_interval('7d')
 
385
    datetime.timedelta(7)
 
386
    >>> parse_pre_1_6_1_interval('60s')
 
387
    datetime.timedelta(0, 60)
 
388
    >>> parse_pre_1_6_1_interval('60m')
 
389
    datetime.timedelta(0, 3600)
 
390
    >>> parse_pre_1_6_1_interval('24h')
 
391
    datetime.timedelta(1)
 
392
    >>> parse_pre_1_6_1_interval('1w')
 
393
    datetime.timedelta(7)
 
394
    >>> parse_pre_1_6_1_interval('5m 30s')
 
395
    datetime.timedelta(0, 330)
 
396
    >>> parse_pre_1_6_1_interval('')
 
397
    datetime.timedelta(0)
412
398
    >>> # Ignore unknown characters, allow any order and repetitions
413
 
    >>> parse_pre_1_6_1_interval('2dxy7zz11y3m5m') == datetime.timedelta(2, 480, 18000)
414
 
    True
 
399
    >>> parse_pre_1_6_1_interval('2dxy7zz11y3m5m')
 
400
    datetime.timedelta(2, 480, 18000)
415
401
 
416
402
    """
417
403
 
480
466
        parser.error("--remove can only be combined with --deny")
481
467
 
482
468
 
483
 
class dbus:
 
469
class dbus(object):
484
470
 
485
 
    class SystemBus:
 
471
    class SystemBus(object):
486
472
 
487
473
        object_manager_iface = "org.freedesktop.DBus.ObjectManager"
488
474
        def get_managed_objects(self, busname, objectpath):
497
483
                             self.properties_iface, interface, key,
498
484
                             value)
499
485
 
500
 
        def call_method(self, methodname, busname, objectpath,
501
 
                        interface, *args):
502
 
            raise NotImplementedError()
503
 
 
504
486
 
505
487
    class MandosBus(SystemBus):
506
488
        busname_domain = "se.recompile"
538
520
        pass
539
521
 
540
522
 
541
 
class dbus_python_adapter:
 
523
class dbus_python_adapter(object):
542
524
 
543
525
    class SystemBus(dbus.MandosBus):
544
526
        """Use dbus-python"""
589
571
                        for key, subval in value.items()}
590
572
            return value
591
573
 
592
 
        def set_client_property(self, objectpath, key, value):
593
 
            if key == "Secret":
594
 
                if not isinstance(value, bytes):
595
 
                    value = value.encode("utf-8")
596
 
                value = self.dbus_python.ByteArray(value)
597
 
            return self.set_property(self.busname, objectpath,
598
 
                                     self.client_interface, key,
599
 
                                     value)
600
574
 
601
 
    class SilenceLogger:
 
575
    class SilenceLogger(object):
602
576
        "Simple context manager to silence a particular logger"
603
577
        def __init__(self, loggername):
604
578
            self.logger = logging.getLogger(loggername)
633
607
                return new_object
634
608
 
635
609
 
636
 
class pydbus_adapter:
 
610
class pydbus_adapter(object):
637
611
    class SystemBus(dbus.MandosBus):
638
612
        def __init__(self, module=pydbus):
639
613
            self.pydbus = module
696
670
                return new_object
697
671
 
698
672
 
699
 
class dbussy_adapter:
700
 
    class SystemBus(dbus.SystemBus):
701
 
        """Use DBussy"""
702
 
 
703
 
        def __init__(self, dbussy, ravel):
704
 
            self.dbussy = dbussy
705
 
            self.ravel = ravel
706
 
            self.bus = ravel.system_bus()
707
 
 
708
 
        @contextlib.contextmanager
709
 
        def convert_exception(self, exception_class=dbus.Error):
710
 
            try:
711
 
                yield
712
 
            except self.dbussy.DBusError as e:
713
 
                # This does what "raise from" would do
714
 
                exc = exception_class(*e.args)
715
 
                exc.__cause__ = e
716
 
                raise exc
717
 
 
718
 
        def call_method(self, methodname, busname, objectpath,
719
 
                        interface, *args):
720
 
            proxy_object = self.get_object(busname, objectpath)
721
 
            log.debug("D-Bus: %s:%s:%s.%s(%s)", busname, objectpath,
722
 
                      interface, methodname,
723
 
                      ", ".join(repr(a) for a in args))
724
 
            iface = proxy_object.get_interface(interface)
725
 
            method = getattr(iface, methodname)
726
 
            with self.convert_exception(dbus.Error):
727
 
                value =  method(*args)
728
 
            # DBussy returns values either as an empty list or as a
729
 
            # tuple: (signature, value)
730
 
            if value:
731
 
                return self.type_filter(value[0])
732
 
 
733
 
        def get_object(self, busname, objectpath):
734
 
            log.debug("D-Bus: Connect to: (busname=%r, path=%r)",
735
 
                      busname, objectpath)
736
 
            with self.convert_exception(dbus.ConnectFailed):
737
 
                return self.bus[busname][objectpath]
738
 
 
739
 
        def type_filter(self, value):
740
 
            """Convert the most bothersome types to Python types"""
741
 
            if isinstance(value, tuple):
742
 
                if (len(value) == 2
743
 
                    and isinstance(value[0],
744
 
                                   self.dbussy.DBUS.Signature)):
745
 
                    return self.type_filter(value[1])
746
 
            elif isinstance(value, self.dbussy.DBUS.ObjectPath):
747
 
                return str(value)
748
 
            # Also recurse into dictionaries
749
 
            elif isinstance(value, dict):
750
 
                return {self.type_filter(key):
751
 
                        self.type_filter(subval)
752
 
                        for key, subval in value.items()}
753
 
            return value
754
 
 
755
 
        def set_property(self, busname, objectpath, interface, key,
756
 
                         value):
757
 
            proxy_object = self.get_object(busname, objectpath)
758
 
            log.debug("D-Bus: %s:%s:%s.Set(%r, %r, %r)", busname,
759
 
                      objectpath, self.properties_iface, interface,
760
 
                      key, value)
761
 
            if key == "Secret":
762
 
                # DBussy wants a Byte Array to be a sequence of
763
 
                # values, not a byte string
764
 
                value = tuple(value)
765
 
            setattr(proxy_object.get_interface(interface), key, value)
766
 
 
767
 
    class MandosBus(SystemBus, dbus.MandosBus):
768
 
        pass
769
 
 
770
 
    class CachingBus(MandosBus):
771
 
        """A caching layer for dbussy_adapter.MandosBus"""
772
 
        def __init__(self, *args, **kwargs):
773
 
            self.object_cache = {}
774
 
            super(dbussy_adapter.CachingBus, self).__init__(*args,
775
 
                                                            **kwargs)
776
 
        def get_object(self, busname, objectpath):
777
 
            try:
778
 
                return self.object_cache[(busname, objectpath)]
779
 
            except KeyError:
780
 
                new_object = super(
781
 
                    dbussy_adapter.CachingBus,
782
 
                    self).get_object(busname, objectpath)
783
 
                self.object_cache[(busname, objectpath)]  = new_object
784
 
                return new_object
785
 
 
786
 
 
787
673
def commands_from_options(options):
788
674
 
789
675
    commands = list(options.commands)
817
703
    return commands
818
704
 
819
705
 
820
 
class command:
 
706
class command(object):
821
707
    """A namespace for command classes"""
822
708
 
823
 
    class Base:
 
709
    class Base(object):
824
710
        """Abstract base class for commands"""
825
711
        def run(self, clients, bus=None):
826
712
            """Normal commands should implement run_on_one_client(),
889
775
                keywords = self.all_keywords
890
776
            print(self.TableOfClients(clients.values(), keywords))
891
777
 
892
 
        class TableOfClients:
 
778
        class TableOfClients(object):
893
779
            tableheaders = {
894
780
                "Name": "Name",
895
781
                "Enabled": "Enabled",
1126
1012
                                                     "output"))
1127
1013
 
1128
1014
 
1129
 
class Unique:
 
1015
class Unique(object):
1130
1016
    """Class for objects which exist only to be unique objects, since
1131
1017
unittest.mock.sentinel only exists in Python 3.3"""
1132
1018
 
1416
1302
class Test_dbus_python_adapter_SystemBus(TestCaseWithAssertLogs):
1417
1303
 
1418
1304
    def MockDBusPython_func(self, func):
1419
 
        class mock_dbus_python:
 
1305
        class mock_dbus_python(object):
1420
1306
            """mock dbus-python module"""
1421
 
            class exceptions:
 
1307
            class exceptions(object):
1422
1308
                """Pseudo-namespace"""
1423
1309
                class DBusException(Exception):
1424
1310
                    pass
1425
 
            class SystemBus:
 
1311
            class SystemBus(object):
1426
1312
                @staticmethod
1427
1313
                def get_object(busname, objectpath):
1428
1314
                    DBusObject = collections.namedtuple(
1429
 
                        "DBusObject", ("methodname", "Set"))
 
1315
                        "DBusObject", ("methodname",))
1430
1316
                    def method(*args, **kwargs):
1431
1317
                        self.assertEqual({"dbus_interface":
1432
1318
                                          "interface"},
1433
1319
                                         kwargs)
1434
1320
                        return func(*args)
1435
 
                    def set_property(interface, key, value,
1436
 
                                     dbus_interface=None):
1437
 
                        self.assertEqual(
1438
 
                            "org.freedesktop.DBus.Properties",
1439
 
                            dbus_interface)
1440
 
                        self.assertEqual("Secret", key)
1441
 
                        return func(interface, key, value,
1442
 
                                    dbus_interface=dbus_interface)
1443
 
                    return DBusObject(methodname=method,
1444
 
                                      Set=set_property)
1445
 
            class Boolean:
 
1321
                    return DBusObject(methodname=method)
 
1322
            class Boolean(object):
1446
1323
                def __init__(self, value):
1447
1324
                    self.value = bool(value)
1448
1325
                def __bool__(self):
1453
1330
                pass
1454
1331
            class Dictionary(dict):
1455
1332
                pass
1456
 
            class ByteArray(bytes):
1457
 
                pass
1458
1333
        return mock_dbus_python
1459
1334
 
1460
1335
    def call_method(self, bus, methodname, busname, objectpath,
1632
1507
        finally:
1633
1508
            dbus_logger.removeFilter(counting_handler)
1634
1509
 
1635
 
        self.assertNotIsInstance(e.exception, dbus.ConnectFailed)
 
1510
        self.assertNotIsInstance(e, dbus.ConnectFailed)
1636
1511
 
1637
1512
        # Make sure the dbus logger was suppressed
1638
1513
        self.assertEqual(0, counting_handler.count)
1639
1514
 
1640
 
    def test_Set_Secret_sends_bytearray(self):
1641
 
        ret = [None]
1642
 
        def func(*args, **kwargs):
1643
 
            ret[0] = (args, kwargs)
1644
 
        mock_dbus_python = self.MockDBusPython_func(func)
1645
 
        bus = dbus_python_adapter.SystemBus(mock_dbus_python)
1646
 
        bus.set_client_property("objectpath", "Secret", "value")
1647
 
        expected_call = (("se.recompile.Mandos.Client", "Secret",
1648
 
                          mock_dbus_python.ByteArray(b"value")),
1649
 
                         {"dbus_interface":
1650
 
                          "org.freedesktop.DBus.Properties"})
1651
 
        self.assertEqual(expected_call, ret[0])
1652
 
        if sys.version_info.major == 2:
1653
 
            self.assertIsInstance(ret[0][0][-1],
1654
 
                                  mock_dbus_python.ByteArray)
1655
 
 
1656
1515
    def test_get_object_converts_to_correct_exception(self):
1657
1516
        bus = dbus_python_adapter.SystemBus(
1658
1517
            self.fake_dbus_python_raises_exception_on_connect)
1660
1519
            self.call_method(bus, "methodname", "busname",
1661
1520
                             "objectpath", "interface")
1662
1521
 
1663
 
    class fake_dbus_python_raises_exception_on_connect:
 
1522
    class fake_dbus_python_raises_exception_on_connect(object):
1664
1523
        """fake dbus-python module"""
1665
 
        class exceptions:
 
1524
        class exceptions(object):
1666
1525
            """Pseudo-namespace"""
1667
1526
            class DBusException(Exception):
1668
1527
                pass
1676
1535
 
1677
1536
 
1678
1537
class Test_dbus_python_adapter_CachingBus(unittest.TestCase):
1679
 
    class mock_dbus_python:
 
1538
    class mock_dbus_python(object):
1680
1539
        """mock dbus-python modules"""
1681
 
        class SystemBus:
 
1540
        class SystemBus(object):
1682
1541
            @staticmethod
1683
1542
            def get_object(busname, objectpath):
1684
1543
                return Unique()
1730
1589
class Test_pydbus_adapter_SystemBus(TestCaseWithAssertLogs):
1731
1590
 
1732
1591
    def Stub_pydbus_func(self, func):
1733
 
        class stub_pydbus:
 
1592
        class stub_pydbus(object):
1734
1593
            """stub pydbus module"""
1735
 
            class SystemBus:
 
1594
            class SystemBus(object):
1736
1595
                @staticmethod
1737
1596
                def get(busname, objectpath):
1738
1597
                    DBusObject = collections.namedtuple(
1775
1634
            self.call_method(bus, "methodname", "busname",
1776
1635
                             "objectpath", "interface")
1777
1636
 
1778
 
        self.assertNotIsInstance(e.exception, dbus.ConnectFailed)
 
1637
        self.assertNotIsInstance(e, dbus.ConnectFailed)
1779
1638
 
1780
1639
    def test_get_converts_to_correct_exception(self):
1781
1640
        bus = pydbus_adapter.SystemBus(
1784
1643
            self.call_method(bus, "methodname", "busname",
1785
1644
                             "objectpath", "interface")
1786
1645
 
1787
 
    class fake_pydbus_raises_exception_on_connect:
 
1646
    class fake_pydbus_raises_exception_on_connect(object):
1788
1647
        """fake dbus-python module"""
1789
1648
        @classmethod
1790
1649
        def SystemBus(cls):
1794
1653
            return Bus(get=get)
1795
1654
 
1796
1655
    def test_set_property_uses_setattr(self):
1797
 
        class Object:
 
1656
        class Object(object):
1798
1657
            pass
1799
1658
        obj = Object()
1800
 
        class pydbus_spy:
1801
 
            class SystemBus:
 
1659
        class pydbus_spy(object):
 
1660
            class SystemBus(object):
1802
1661
                @staticmethod
1803
1662
                def get(busname, objectpath):
1804
1663
                    return {"interface": obj}
1811
1670
    def test_get_suppresses_xml_deprecation_warning(self):
1812
1671
        if sys.version_info.major >= 3:
1813
1672
            return
1814
 
        class stub_pydbus_get:
1815
 
            class SystemBus:
 
1673
        class stub_pydbus_get(object):
 
1674
            class SystemBus(object):
1816
1675
                @staticmethod
1817
1676
                def get(busname, objectpath):
1818
1677
                    warnings.warn_explicit(
1826
1685
 
1827
1686
 
1828
1687
class Test_pydbus_adapter_CachingBus(unittest.TestCase):
1829
 
    class stub_pydbus:
 
1688
    class stub_pydbus(object):
1830
1689
        """stub pydbus module"""
1831
 
        class SystemBus:
 
1690
        class SystemBus(object):
1832
1691
            @staticmethod
1833
1692
            def get(busname, objectpath):
1834
1693
                return Unique()
1876
1735
        self.assertIs(obj1, obj1b)
1877
1736
 
1878
1737
 
1879
 
class Test_dbussy_adapter_SystemBus(TestCaseWithAssertLogs):
1880
 
 
1881
 
    class dummy_dbussy:
1882
 
        class DBUS:
1883
 
            class ObjectPath(str):
1884
 
                pass
1885
 
        class DBusError(Exception):
1886
 
            pass
1887
 
 
1888
 
    def fake_ravel_func(self, func):
1889
 
        class fake_ravel:
1890
 
            @staticmethod
1891
 
            def system_bus():
1892
 
                class DBusInterfaceProxy:
1893
 
                    @staticmethod
1894
 
                    def methodname(*args):
1895
 
                        return [func(*args)]
1896
 
                class DBusObject:
1897
 
                    @staticmethod
1898
 
                    def get_interface(interface):
1899
 
                        if interface == "interface":
1900
 
                            return DBusInterfaceProxy()
1901
 
                return {"busname": {"objectpath": DBusObject()}}
1902
 
        return fake_ravel
1903
 
 
1904
 
    def call_method(self, bus, methodname, busname, objectpath,
1905
 
                    interface, *args):
1906
 
        with self.assertLogs(log, logging.DEBUG):
1907
 
            return bus.call_method(methodname, busname, objectpath,
1908
 
                                   interface, *args)
1909
 
 
1910
 
    def test_call_method_returns(self):
1911
 
        expected_method_return = Unique()
1912
 
        method_args = (Unique(), Unique())
1913
 
        def func(*args):
1914
 
            self.assertEqual(len(method_args), len(args))
1915
 
            for marg, arg in zip(method_args, args):
1916
 
                self.assertIs(marg, arg)
1917
 
            return expected_method_return
1918
 
        fake_ravel = self.fake_ravel_func(func)
1919
 
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
1920
 
        ret = self.call_method(bus, "methodname", "busname",
1921
 
                               "objectpath", "interface",
1922
 
                               *method_args)
1923
 
        self.assertIs(ret, expected_method_return)
1924
 
 
1925
 
    def test_call_method_filters_objectpath(self):
1926
 
        def func():
1927
 
            return method_return
1928
 
        fake_ravel = self.fake_ravel_func(func)
1929
 
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
1930
 
        method_return = (self.dummy_dbussy.DBUS
1931
 
                         .ObjectPath("objectpath"))
1932
 
        ret = self.call_method(bus, "methodname", "busname",
1933
 
                               "objectpath", "interface")
1934
 
        self.assertEqual("objectpath", ret)
1935
 
        self.assertNotIsInstance(ret,
1936
 
                                 self.dummy_dbussy.DBUS.ObjectPath)
1937
 
 
1938
 
    def test_call_method_filters_objectpaths_in_dict(self):
1939
 
        ObjectPath = self.dummy_dbussy.DBUS.ObjectPath
1940
 
        def func():
1941
 
            return method_return
1942
 
        fake_ravel = self.fake_ravel_func(func)
1943
 
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
1944
 
        method_return = {
1945
 
            ObjectPath("objectpath_key_1"):
1946
 
            ObjectPath("objectpath_value_1"),
1947
 
            ObjectPath("objectpath_key_2"):
1948
 
            ObjectPath("objectpath_value_2"),
1949
 
        }
1950
 
        ret = self.call_method(bus, "methodname", "busname",
1951
 
                               "objectpath", "interface")
1952
 
        expected_method_return = {str(key): str(value)
1953
 
                                  for key, value in
1954
 
                                  method_return.items()}
1955
 
        for key, value in ret.items():
1956
 
            self.assertNotIsInstance(key, ObjectPath)
1957
 
            self.assertNotIsInstance(value, ObjectPath)
1958
 
        self.assertEqual(expected_method_return, ret)
1959
 
        self.assertIsInstance(ret, dict)
1960
 
 
1961
 
    def test_call_method_filters_objectpaths_in_dict_in_dict(self):
1962
 
        ObjectPath = self.dummy_dbussy.DBUS.ObjectPath
1963
 
        def func():
1964
 
            return method_return
1965
 
        fake_ravel = self.fake_ravel_func(func)
1966
 
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
1967
 
        method_return = {
1968
 
            ObjectPath("key1"): {
1969
 
                ObjectPath("key11"): ObjectPath("value11"),
1970
 
                ObjectPath("key12"): ObjectPath("value12"),
1971
 
            },
1972
 
            ObjectPath("key2"): {
1973
 
                ObjectPath("key21"): ObjectPath("value21"),
1974
 
                ObjectPath("key22"): ObjectPath("value22"),
1975
 
            },
1976
 
        }
1977
 
        ret = self.call_method(bus, "methodname", "busname",
1978
 
                               "objectpath", "interface")
1979
 
        expected_method_return = {
1980
 
            "key1": {"key11": "value11",
1981
 
                     "key12": "value12"},
1982
 
            "key2": {"key21": "value21",
1983
 
                     "key22": "value22"},
1984
 
        }
1985
 
        self.assertEqual(expected_method_return, ret)
1986
 
        for key, value in ret.items():
1987
 
            self.assertIsInstance(value, dict)
1988
 
            self.assertEqual(expected_method_return[key], value)
1989
 
            self.assertNotIsInstance(key, ObjectPath)
1990
 
            for inner_key, inner_value in value.items():
1991
 
                self.assertIsInstance(value, dict)
1992
 
                self.assertEqual(
1993
 
                    expected_method_return[key][inner_key],
1994
 
                    inner_value)
1995
 
                self.assertNotIsInstance(key, ObjectPath)
1996
 
 
1997
 
    def test_call_method_filters_objectpaths_in_dict_three_deep(self):
1998
 
        ObjectPath = self.dummy_dbussy.DBUS.ObjectPath
1999
 
        def func():
2000
 
            return method_return
2001
 
        fake_ravel = self.fake_ravel_func(func)
2002
 
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
2003
 
        method_return = {
2004
 
            ObjectPath("key1"): {
2005
 
                ObjectPath("key2"): {
2006
 
                    ObjectPath("key3"): ObjectPath("value"),
2007
 
                },
2008
 
            },
2009
 
        }
2010
 
        ret = self.call_method(bus, "methodname", "busname",
2011
 
                               "objectpath", "interface")
2012
 
        expected_method_return = {"key1": {"key2": {"key3": "value"}}}
2013
 
        self.assertEqual(expected_method_return, ret)
2014
 
        self.assertIsInstance(ret, dict)
2015
 
        self.assertNotIsInstance(next(iter(ret.keys())), ObjectPath)
2016
 
        self.assertIsInstance(ret["key1"], dict)
2017
 
        self.assertNotIsInstance(next(iter(ret["key1"].keys())),
2018
 
                                 ObjectPath)
2019
 
        self.assertIsInstance(ret["key1"]["key2"], dict)
2020
 
        self.assertNotIsInstance(
2021
 
            next(iter(ret["key1"]["key2"].keys())),
2022
 
            ObjectPath)
2023
 
        self.assertEqual("value", ret["key1"]["key2"]["key3"])
2024
 
        self.assertNotIsInstance(ret["key1"]["key2"]["key3"],
2025
 
                                 self.dummy_dbussy.DBUS.ObjectPath)
2026
 
 
2027
 
    def test_call_method_handles_exception(self):
2028
 
        def func():
2029
 
            raise self.dummy_dbussy.DBusError()
2030
 
 
2031
 
        fake_ravel = self.fake_ravel_func(func)
2032
 
        bus = dbussy_adapter.SystemBus(self.dummy_dbussy, fake_ravel)
2033
 
 
2034
 
        with self.assertRaises(dbus.Error) as e:
2035
 
            self.call_method(bus, "methodname", "busname",
2036
 
                             "objectpath", "interface")
2037
 
 
2038
 
        self.assertNotIsInstance(e.exception, dbus.ConnectFailed)
2039
 
 
2040
 
    def test_get_object_converts_to_correct_exception(self):
2041
 
        class fake_ravel_raises_exception_on_connect:
2042
 
            @staticmethod
2043
 
            def system_bus():
2044
 
                class Bus:
2045
 
                    @staticmethod
2046
 
                    def __getitem__(key):
2047
 
                        if key == "objectpath":
2048
 
                            raise self.dummy_dbussy.DBusError()
2049
 
                        raise Exception(key)
2050
 
                return {"busname": Bus()}
2051
 
        def func():
2052
 
            raise self.dummy_dbussy.DBusError()
2053
 
        bus = dbussy_adapter.SystemBus(
2054
 
            self.dummy_dbussy,
2055
 
            fake_ravel_raises_exception_on_connect)
2056
 
        with self.assertRaises(dbus.ConnectFailed):
2057
 
            self.call_method(bus, "methodname", "busname",
2058
 
                             "objectpath", "interface")
2059
 
 
2060
 
 
2061
1738
class Test_commands_from_options(unittest.TestCase):
2062
1739
 
2063
1740
    def setUp(self):
2068
1745
        self.assert_command_from_args(["--is-enabled", "client"],
2069
1746
                                      command.IsEnabled)
2070
1747
 
2071
 
    def assert_command_from_args(self, args, command_cls, length=1,
2072
 
                                 clients=None, **cmd_attrs):
 
1748
    def assert_command_from_args(self, args, command_cls,
 
1749
                                 **cmd_attrs):
2073
1750
        """Assert that parsing ARGS should result in an instance of
2074
1751
COMMAND_CLS with (optionally) all supplied attributes (CMD_ATTRS)."""
2075
1752
        options = self.parser.parse_args(args)
2076
1753
        check_option_syntax(self.parser, options)
2077
1754
        commands = commands_from_options(options)
2078
 
        self.assertEqual(length, len(commands))
2079
 
        for command in commands:
2080
 
            if isinstance(command, command_cls):
2081
 
                break
2082
 
        else:
2083
 
            self.assertIsInstance(command, command_cls)
2084
 
        if clients is not None:
2085
 
            self.assertEqual(clients, options.client)
 
1755
        self.assertEqual(1, len(commands))
 
1756
        command = commands[0]
 
1757
        self.assertIsInstance(command, command_cls)
2086
1758
        for key, value in cmd_attrs.items():
2087
1759
            self.assertEqual(value, getattr(command, key))
2088
1760
 
2089
 
    def assert_commands_from_args(self, args, commands, clients=None):
2090
 
        for cmd in commands:
2091
 
            self.assert_command_from_args(args, cmd,
2092
 
                                          length=len(commands),
2093
 
                                          clients=clients)
2094
 
 
2095
1761
    def test_is_enabled_short(self):
2096
1762
        self.assert_command_from_args(["-V", "client"],
2097
1763
                                      command.IsEnabled)
2288
1954
                                      verbose=True)
2289
1955
 
2290
1956
 
2291
 
    def test_manual_page_example_1(self):
2292
 
        self.assert_command_from_args("",
2293
 
                                      command.PrintTable,
2294
 
                                      clients=[],
2295
 
                                      verbose=False)
2296
 
 
2297
 
    def test_manual_page_example_2(self):
2298
 
        self.assert_command_from_args(
2299
 
            "--verbose foo1.example.org foo2.example.org".split(),
2300
 
            command.PrintTable, clients=["foo1.example.org",
2301
 
                                         "foo2.example.org"],
2302
 
            verbose=True)
2303
 
 
2304
 
    def test_manual_page_example_3(self):
2305
 
        self.assert_command_from_args("--enable --all".split(),
2306
 
                                      command.Enable,
2307
 
                                      clients=[])
2308
 
 
2309
 
    def test_manual_page_example_4(self):
2310
 
        self.assert_commands_from_args(
2311
 
            ("--timeout=PT5M --interval=PT1M foo1.example.org"
2312
 
             " foo2.example.org").split(),
2313
 
            [command.SetTimeout, command.SetInterval],
2314
 
            clients=["foo1.example.org", "foo2.example.org"])
2315
 
 
2316
 
    def test_manual_page_example_5(self):
2317
 
        self.assert_command_from_args("--approve --all".split(),
2318
 
                                      command.Approve,
2319
 
                                      clients=[])
2320
 
 
2321
 
 
2322
1957
class TestCommand(unittest.TestCase):
2323
1958
    """Abstract class for tests of command classes"""
2324
1959