[mutter] tests: Add MetaOrientationManager tests via SensorsProxy mock



commit bf54a16f9229fa92e2abbdfe6bb1f7616c551925
Author: Marco Trevisan (TreviƱo) <mail 3v1n0 net>
Date:   Mon May 4 19:22:35 2020 +0200

    tests: Add MetaOrientationManager tests via SensorsProxy mock
    
    Create a test system bus and use it to run all the tests, add a mock
    SensorsProxy (via dbusmock template) server that implements the
    net.hadess.SensorProxy interface.
    
    To make testing easier, the service is created on request of a proxy for
    it, whose lifetime controls the mock service lifetime as well.
    This is done using a further mock service that is used to manage the
    others, using python-dbusmock to simplify the handling.
    
    Add basic tests for the orientation manager.
    
    As per the usage dbusmock, we're now launching all the tests under such
    wrapper, so that local dbus environment won't ever considered, and
    there's no risk that it may affect the tests results both locally and in
    CI.
    
    Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1233>

 .gitlab-ci.yml                                     |  12 +-
 meson.build                                        |  11 +
 src/backends/meta-orientation-manager.h            |   6 +
 src/tests/dbusmock-templates/iio-sensors-proxy.py  | 204 +++++++++++++++++
 src/tests/dbusmock-templates/meta-mocks-manager.py |  83 +++++++
 src/tests/meson.build                              |  14 ++
 src/tests/meta-dbus-runner.py                      | 125 ++++++++++
 src/tests/meta-monitor-manager-test.h              |   2 +
 src/tests/meta-sensors-proxy-mock.c                | 255 +++++++++++++++++++++
 src/tests/meta-sensors-proxy-mock.h                |  41 ++++
 src/tests/mutter-all.test.in                       |   2 +-
 src/tests/orientation-manager-unit-tests.c         | 131 +++++++++++
 src/tests/orientation-manager-unit-tests.h         |  26 +++
 src/tests/unit-tests.c                             |   3 +
 14 files changed, 912 insertions(+), 3 deletions(-)
---
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8513aae6b5..bf1cb57c42 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -16,8 +16,16 @@ variables:
 .mutter.fedora:34@common:
   variables:
     FDO_DISTRIBUTION_VERSION: 34
-    BASE_TAG: '2021-08-25.0'
-    FDO_DISTRIBUTION_PACKAGES: 'gdm gnome-shell xorg-x11-server-Xvfb sassc gcovr clang uncrustify'
+    BASE_TAG: '2021-09-04.0'
+    FDO_DISTRIBUTION_PACKAGES:
+      clang
+      gcovr
+      gdm
+      gnome-shell
+      python3-dbusmock
+      sassc
+      uncrustify
+      xorg-x11-server-Xvfb
 
     FDO_DISTRIBUTION_EXEC: |
       dnf install -y 'dnf-command(builddep)' &&
diff --git a/meson.build b/meson.build
index 6211181bee..d0b960b2f9 100644
--- a/meson.build
+++ b/meson.build
@@ -295,11 +295,22 @@ if have_tests
   have_clutter_tests = get_option('clutter_tests')
   have_installed_tests = get_option('installed_tests')
 
+  meta_dbus_runner = find_program('src/tests/meta-dbus-runner.py')
+  default_test_wrappers = [
+    meta_dbus_runner,
+  ]
+
+  add_test_setup('default',
+    is_default: true,
+    exe_wrapper: default_test_wrappers,
+  )
+
   add_test_setup('CI',
     env: [
       'MUTTER_DEBUG_DUMMY_MODE_SPECS=800x600@10.0',
     ],
     exe_wrapper: [
+      default_test_wrappers,
       find_program('catchsegv'),
       find_program('xvfb-run'), '-a', '-s', '+iglx -noreset',
     ],
diff --git a/src/backends/meta-orientation-manager.h b/src/backends/meta-orientation-manager.h
index 9ceefb81ea..f41c84331b 100644
--- a/src/backends/meta-orientation-manager.h
+++ b/src/backends/meta-orientation-manager.h
@@ -24,6 +24,8 @@
 
 #include <glib-object.h>
 
+#include "core/util-private.h"
+
 typedef enum
 {
   META_ORIENTATION_UNDEFINED,
@@ -35,11 +37,15 @@ typedef enum
 #define META_N_ORIENTATIONS (META_ORIENTATION_RIGHT_UP + 1)
 
 #define META_TYPE_ORIENTATION_MANAGER (meta_orientation_manager_get_type ())
+
+META_EXPORT_TEST
 G_DECLARE_FINAL_TYPE (MetaOrientationManager, meta_orientation_manager,
                       META, ORIENTATION_MANAGER, GObject)
 
+META_EXPORT_TEST
 MetaOrientation meta_orientation_manager_get_orientation (MetaOrientationManager *self);
 
+META_EXPORT_TEST
 gboolean meta_orientation_manager_has_accelerometer (MetaOrientationManager *self);
 
 #endif  /* META_ORIENTATION_MANAGER_H */
diff --git a/src/tests/dbusmock-templates/iio-sensors-proxy.py 
b/src/tests/dbusmock-templates/iio-sensors-proxy.py
new file mode 100644
index 0000000000..83ab989bdb
--- /dev/null
+++ b/src/tests/dbusmock-templates/iio-sensors-proxy.py
@@ -0,0 +1,204 @@
+'''sensors proxy mock template
+'''
+
+# 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__ = 'Marco Trevisan'
+__copyright__ = '(c) 2021 Canonical Ltd.'
+
+import re
+
+import dbus
+from dbusmock import MOCK_IFACE
+
+BUS_NAME = 'net.hadess.SensorProxy'
+MAIN_OBJ = '/net/hadess/SensorProxy'
+MAIN_IFACE = 'net.hadess.SensorProxy'
+COMPASS_IFACE = 'net.hadess.SensorProxy.Compass'
+SYSTEM_BUS = True
+
+CAMEL_TO_SNAKE_CASE_RE = re.compile(r'(?<!^)(?=[A-Z])')
+
+
+def load(mock, parameters=None):
+    mock.has_accelerometer = False
+    mock.accelerometer_owners = dict()
+    mock.accelerometer_orientation = 'undefined'
+    mock.has_ambient_light = False
+    mock.ambient_light_owners = dict()
+    mock.light_level_unit = 'lux'
+    mock.light_level = 0.0
+    mock.has_proximity = False
+    mock.proximity_near = False
+    mock.proximity_owners = dict()
+    mock.has_compass = False
+    mock.compass_owners = dict()
+    mock.compass_heading = -1.0
+
+    if parameters:
+        for p, v in parameters.items():
+            setattr(mock, p, v)
+
+    for iface in [MAIN_IFACE, COMPASS_IFACE]:
+        mock.AddProperties(iface, mock.GetAll(iface))
+
+
+def emit_signal_to_destination(mock, interface, name, signature, destination, *args):
+    # We need to do this manually, could be made easier via
+    # https://gitlab.freedesktop.org/dbus/dbus-python/-/merge_requests/13
+    message = dbus.lowlevel.SignalMessage(mock.path, interface, name)
+    if destination:
+        message.set_destination(destination)
+    message.append(signature=signature, *args)
+    for location in mock.locations:
+        location[0].send_message(message)
+
+
+def emit_properties_changed(mock, interface=MAIN_IFACE, properties=None,
+                            destination=None):
+    if properties is None:
+        properties = mock.GetAll(interface)
+    elif isinstance(properties, str):
+        properties = [properties]
+
+    if isinstance(properties, (list, set)):
+        properties = {p: mock.Get(interface, p) for p in properties}
+    elif not isinstance(properties, dict):
+        raise TypeError('Unsupported properties type')
+
+    emit_signal_to_destination(mock, dbus.PROPERTIES_IFACE, 'PropertiesChanged',
+                               'sa{sv}as', destination, interface, properties, [])
+
+
+@dbus.service.method(dbus.PROPERTIES_IFACE, in_signature='s',
+                     out_signature='a{sv}')
+def GetAll(self, interface):
+    if interface == MAIN_IFACE:
+        return {
+            'HasAccelerometer': dbus.Boolean(self.has_accelerometer),
+            'AccelerometerOrientation': dbus.String(self.accelerometer_orientation),
+            'HasAmbientLight': dbus.Boolean(self.has_ambient_light),
+            'LightLevelUnit': dbus.String(self.light_level_unit),
+            'LightLevel': dbus.Double(self.light_level),
+            'HasProximity': dbus.Boolean(self.has_proximity),
+            'ProximityNear': dbus.Boolean(self.proximity_near),
+        }
+    if interface == COMPASS_IFACE:
+        return {
+            'HasCompass': dbus.Boolean(self.has_compass),
+            'CompassHeading': dbus.Double(self.compass_heading),
+        }
+    return dbus.Dictionary({}, signature='sv')
+
+
+def register_owner(self, owners_dict, name):
+    if name in owners_dict:
+        return
+
+    def name_cb(unique_name):
+        if unique_name:
+            return
+        owners_dict.pop(name).cancel()
+
+    owners_dict[name] = self.connection.watch_name_owner(name, name_cb)
+
+
+def unregister_owner(owners_dict, name):
+    watcher = owners_dict.pop(name, None)
+    if watcher:
+        watcher.cancel()
+
+
+@dbus.service.method(MAIN_IFACE, sender_keyword='sender')
+def ClaimAccelerometer(self, sender):
+    register_owner(self, self.accelerometer_owners, sender)
+
+
+@dbus.service.method(MAIN_IFACE, sender_keyword='sender')
+def ReleaseAccelerometer(self, sender):
+    unregister_owner(self.accelerometer_owners, sender)
+
+
+@dbus.service.method(MAIN_IFACE, sender_keyword='sender')
+def ClaimLight(self, sender):
+    register_owner(self, self.ambient_light_owners, sender)
+
+
+@dbus.service.method(MAIN_IFACE, sender_keyword='sender')
+def ReleaseLight(self, sender):
+    unregister_owner(self.ambient_light_owners, sender)
+
+
+@dbus.service.method(MAIN_IFACE, sender_keyword='sender')
+def ClaimProximity(self, sender):
+    register_owner(self, self.proximity_owners, sender)
+
+
+@dbus.service.method(MAIN_IFACE, sender_keyword='sender')
+def ReleaseProximity(self, sender):
+    unregister_owner(self.proximity_owners, sender)
+
+
+@dbus.service.method(MAIN_IFACE, sender_keyword='sender')
+def ClaimCompass(self, sender):
+    register_owner(self, self.compass_owners, sender)
+
+
+@dbus.service.method(MAIN_IFACE, sender_keyword='sender')
+def ReleaseCompass(self, sender):
+    unregister_owner(self.compass_owners, sender)
+
+
+def sensor_to_attribute(sensor):
+    if sensor == 'light':
+        return 'ambient_light'
+    return sensor
+
+
+def is_valid_sensor_for_interface(sensor, interface):
+    if interface == 'net.hadess.SensorProxy':
+        return sensor in ['accelerometer', 'ambient_light', 'proximity']
+
+    if interface == 'net.hadess.SensorProxy.Compass':
+        return sensor == 'compass'
+
+    return False
+
+
+@dbus.service.method(MOCK_IFACE, in_signature='ssv')
+def SetInternalProperty(self, interface, property_name, value):
+    property_attribute = CAMEL_TO_SNAKE_CASE_RE.sub('_', property_name).lower()
+    sensor = sensor_to_attribute(property_attribute.split('_')[0])
+
+    owners = None
+    if is_valid_sensor_for_interface(sensor, interface):
+
+        if not getattr(self, 'has_{}'.format(sensor)):
+            raise Exception('No {} sensor available'.format(sensor))
+
+        owners = getattr(self, '{}_owners'.format(sensor))
+        # We allow setting a property from any client here, even if not claiming
+        # but only owners, if any, will be notified about sensors changes
+
+    pre_value = getattr(self, property_attribute)
+    if pre_value != value:
+        setattr(self, property_attribute, value)
+        if owners:
+            for owner in owners.keys():
+                emit_properties_changed(self, interface, property_name, owner)
+        elif owners is None:
+            emit_properties_changed(self, interface, property_name, None)
+
+
+@dbus.service.method(MOCK_IFACE, in_signature='s')
+def GetInternalProperty(self, property_name):
+    property_attribute = CAMEL_TO_SNAKE_CASE_RE.sub('_', property_name).lower()
+    value = getattr(self, property_attribute)
+
+    if property_name.endswith('Owners'):
+        return dbus.Array(value.keys(), signature='s')
+    return value
diff --git a/src/tests/dbusmock-templates/meta-mocks-manager.py 
b/src/tests/dbusmock-templates/meta-mocks-manager.py
new file mode 100644
index 0000000000..9a632a7138
--- /dev/null
+++ b/src/tests/dbusmock-templates/meta-mocks-manager.py
@@ -0,0 +1,83 @@
+# 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__ = 'Marco Trevisan'
+__copyright__ = '(c) 2021 Canonical Ltd.'
+
+import dbus
+import fcntl
+import os
+import subprocess
+
+from collections import OrderedDict
+from dbusmock import DBusTestCase
+
+BUS_NAME = 'org.gnome.Mutter.TestDBusMocksManager'
+MAIN_OBJ = '/org/gnome/Mutter/TestDBusMocksManager'
+MAIN_IFACE = 'org.gnome.Mutter.TestDBusMocksManager'
+SYSTEM_BUS = True
+
+
+def load(mock, parameters):
+    mock.mocks = OrderedDict()
+    DBusTestCase.setUpClass()
+    mock.dbus_mock = DBusTestCase()
+    mock.dbus_mock.setUp()
+    mock.templates_dir = parameters['templates-dir']
+
+
+def set_nonblock(fd):
+    '''Set a file object to non-blocking'''
+
+    flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+    fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+
+
+@dbus.service.method(MAIN_IFACE, in_signature='s')
+def StartFromTemplate(self, template):
+    if template in self.mocks.keys():
+        raise KeyError('Template {} already started'.format(template))
+
+    mock_server, mock_obj = self.dbus_mock.spawn_server_template(template, {},
+        stdout=subprocess.PIPE)
+    set_nonblock(mock_server.stdout)
+
+    self.mocks[template] = (mock_server, mock_obj)
+
+
+@dbus.service.method(MAIN_IFACE, in_signature='s')
+def StartFromLocalTemplate(self, template):
+    path = os.path.join(self.templates_dir, template + '.py')
+    return self.StartFromTemplate(path)
+
+
+@dbus.service.method(MAIN_IFACE, in_signature='s')
+def StopTemplate(self, template):
+    (mock_server, mock_obj) = self.mocks.pop(template)
+    mock_server.terminate()
+    mock_server.wait()
+
+
+@dbus.service.method(MAIN_IFACE, in_signature='s')
+def StopLocalTemplate(self, template):
+    path = os.path.join(self.templates_dir, template + '.py')
+    return self.StopTemplate(path)
+
+
+@dbus.service.method(MAIN_IFACE)
+def Cleanup(self):
+    for (mock_server, mock_obj) in reversed(self.mocks.values()):
+        mock_server.terminate()
+        mock_server.wait()
+
+    self.dbus_mock.tearDown()
+    DBusTestCase.tearDownClass()
+
+
+@dbus.service.method(MAIN_IFACE, out_signature='as')
+def ListRunningTemplates(self):
+    return list(self.mocks.keys())
+
diff --git a/src/tests/meson.build b/src/tests/meson.build
index 9106a9a70d..3d0aec394c 100644
--- a/src/tests/meson.build
+++ b/src/tests/meson.build
@@ -6,6 +6,8 @@ mutter_test_sources = [
   'meta-gpu-test.h',
   'meta-monitor-manager-test.c',
   'meta-monitor-manager-test.h',
+  'meta-sensors-proxy-mock.c',
+  'meta-sensors-proxy-mock.h',
   'monitor-test-utils.c',
   'monitor-test-utils.h',
   'meta-test-utils.c',
@@ -115,6 +117,15 @@ test_runner = executable('mutter-test-runner',
   install_dir: mutter_installed_tests_libexecdir,
 )
 
+if have_installed_tests
+  install_data('meta-dbus-runner.py',
+    install_dir: mutter_installed_tests_libexecdir,
+  )
+  install_subdir('dbusmock-templates',
+    install_dir: mutter_installed_tests_libexecdir,
+  )
+endif
+
 unit_tests = executable('mutter-test-unit-tests',
   sources: [
     'unit-tests.c',
@@ -122,6 +133,8 @@ unit_tests = executable('mutter-test-unit-tests',
     'boxes-tests.h',
     'meta-wayland-test-driver.c',
     'meta-wayland-test-driver.h',
+    'meta-gpu-test.c',
+    'meta-gpu-test.h',
     'monitor-config-migration-unit-tests.c',
     'monitor-config-migration-unit-tests.h',
     'monitor-store-unit-tests.c',
@@ -131,6 +144,7 @@ unit_tests = executable('mutter-test-unit-tests',
     'monitor-transform-tests.c',
     'monitor-transform-tests.h',
     'monitor-unit-tests.c',
+    'orientation-manager-unit-tests.c',
     'monitor-unit-tests.h',
     'wayland-unit-tests.c',
     'wayland-unit-tests.h',
diff --git a/src/tests/meta-dbus-runner.py b/src/tests/meta-dbus-runner.py
new file mode 100755
index 0000000000..3b2e57e04c
--- /dev/null
+++ b/src/tests/meta-dbus-runner.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python3
+
+import dbus
+import sys
+import os
+import fcntl
+import subprocess
+from collections import OrderedDict
+from dbusmock import DBusTestCase
+from dbus.mainloop.glib import DBusGMainLoop
+
+DBusGMainLoop(set_as_default=True)
+
+
+def set_nonblock(fd):
+    '''Set a file object to non-blocking'''
+
+    flags = fcntl.fcntl(fd, fcntl.F_GETFL)
+    fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)
+
+
+def get_templates_dir():
+    return os.path.join(os.path.dirname(__file__), 'dbusmock-templates')
+
+def get_template_path(template_name):
+    return os.path.join(get_templates_dir(), template_name + '.py')
+
+
+class MutterDBusTestCase(DBusTestCase):
+    @classmethod
+    def setUpClass(klass):
+        klass.mocks = OrderedDict()
+
+        DBusTestCase.setUpClass()
+        klass.start_session_bus()
+        klass.start_system_bus()
+
+        (klass.mocks_manager, klass.mock_obj) = klass.start_from_local_template(
+            'meta-mocks-manager', {'templates-dir': get_templates_dir()})
+
+        klass.start_from_template('logind')
+
+        klass.system_bus_con = klass.get_dbus(system_bus=True)
+        klass.session_bus_con = klass.get_dbus(system_bus=False)
+
+        if klass.session_bus_con.name_has_owner('org.gnome.Mutter.DisplayConfig'):
+            raise Exception(
+                'org.gnome.Mutter.DisplayConfig already has owner on the session bus, bailing')
+
+    @classmethod
+    def tearDownClass(klass):
+        klass.mock_obj.Cleanup()
+
+        for (mock_server, mock_obj) in reversed(klass.mocks.values()):
+            mock_server.terminate()
+            mock_server.wait()
+
+        DBusTestCase.tearDownClass()
+
+    @classmethod
+    def start_from_template(klass, template, params={}):
+        mock_server, mock_obj = \
+            klass.spawn_server_template(template,
+                                        params,
+                                        stdout=subprocess.PIPE)
+        set_nonblock(mock_server.stdout)
+
+        mocks = (mock_server, mock_obj)
+        assert klass.mocks.setdefault(template, mocks) == mocks
+        return mocks
+
+    @classmethod
+    def start_from_local_template(klass, template_file_name, params={}):
+        template = get_template_path(template_file_name)
+        return klass.start_from_template(template, params)
+
+    @classmethod
+    def start_from_template_managed(klass, template):
+        klass.mock_obj.StartFromTemplate(template)
+
+    @classmethod
+    def start_from_local_template_managed(klass, template_file_name):
+        template = get_template_path(template_file_name)
+        klass.mock_obj.StartFromLocalTemplate(template)
+
+    @classmethod
+    def start_from_class(klass, mock_class, params={}):
+        mock_server = \
+            klass.spawn_server(mock_class.BUS_NAME,
+                               mock_class.MAIN_OBJ,
+                               mock_class.MAIN_IFACE,
+                               mock_class.SYSTEM_BUS,
+                               stdout=subprocess.PIPE)
+        set_nonblock(mock_server.stdout)
+
+        bus = klass.get_dbus(system_bus=mock_class.SYSTEM_BUS)
+        mock_obj = bus.get_object(mock_class.BUS_NAME, mock_class.MAIN_OBJ)
+        mock_class.load(mock_obj, params)
+
+        mocks = (mock_server, mock_obj)
+        assert klass.mocks.setdefault(mock_class, mocks) == mocks
+        return mocks
+
+    def wrap_call(self, args):
+        env = {}
+        env.update(os.environ)
+        env['NO_AT_BRIDGE'] = '1'
+        env['GSETTINGS_BACKEND'] = 'memory'
+
+        wrapper = env.get('META_DBUS_RUNNER_WRAPPER')
+        if wrapper == 'gdb':
+            args = ['gdb', '-ex', 'r', '-ex', 'bt full', '--args'] + args
+        elif wrapper:
+            args = wrapper.split(' ') + args
+
+        p = subprocess.Popen(args, env=env)
+        self.assertEqual(p.wait(), 0)
+
+
+if __name__ == '__main__':
+    MutterDBusTestCase.setUpClass()
+    test_case = MutterDBusTestCase()
+    test_case.assertGreater(len(sys.argv), 1)
+    test_case.wrap_call(sys.argv[1:])
+    MutterDBusTestCase.tearDownClass()
diff --git a/src/tests/meta-monitor-manager-test.h b/src/tests/meta-monitor-manager-test.h
index 5b0b6d8e77..d95ee2978c 100644
--- a/src/tests/meta-monitor-manager-test.h
+++ b/src/tests/meta-monitor-manager-test.h
@@ -23,6 +23,7 @@
 #include "backends/meta-crtc.h"
 #include "backends/meta-monitor-manager-private.h"
 #include "backends/meta-output.h"
+#include "core/util-private.h"
 
 typedef struct _MetaMonitorTestSetup
 {
@@ -65,6 +66,7 @@ G_DECLARE_FINAL_TYPE (MetaMonitorManagerTest, meta_monitor_manager_test,
 META_EXPORT
 void meta_monitor_manager_test_init_test_setup (CreateTestSetupFunc func);
 
+META_EXPORT
 void meta_monitor_manager_test_read_current (MetaMonitorManager *manager);
 
 META_EXPORT
diff --git a/src/tests/meta-sensors-proxy-mock.c b/src/tests/meta-sensors-proxy-mock.c
new file mode 100644
index 0000000000..c72999a5cf
--- /dev/null
+++ b/src/tests/meta-sensors-proxy-mock.c
@@ -0,0 +1,255 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2020 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marco Trevisan <marco trevisan canonical com>
+ */
+
+#include "config.h"
+
+#include "meta-sensors-proxy-mock.h"
+
+#define SENSORS_MOCK_TEMPLATE "iio-sensors-proxy"
+
+static MetaSensorsProxyMock *sensors_proxy_mock = NULL;
+
+static const char *
+orientation_to_string (MetaOrientation orientation)
+{
+  const char *orientation_str = "undefined";
+
+  switch (orientation)
+    {
+    case META_ORIENTATION_UNDEFINED:
+      orientation_str = "undefined";
+      break;
+    case META_ORIENTATION_NORMAL:
+      orientation_str = "normal";
+      break;
+    case META_ORIENTATION_BOTTOM_UP:
+      orientation_str = "bottom-up";
+      break;
+    case META_ORIENTATION_LEFT_UP:
+      orientation_str = "left-up";
+      break;
+    case META_ORIENTATION_RIGHT_UP:
+      orientation_str = "right-up";
+      break;
+    }
+
+  return orientation_str;
+}
+
+static void
+on_proxy_call_cb (GObject      *object,
+                  GAsyncResult *res,
+                  gpointer      user_data)
+{
+  g_autoptr(GError) error = NULL;
+  GVariant **ret = user_data;
+
+  *ret = g_dbus_proxy_call_finish (G_DBUS_PROXY (object), res, &error);
+  g_assert_no_error (error);
+  g_assert_nonnull (ret);
+}
+
+static GVariant *
+get_internal_property_value (MetaSensorsProxyMock *proxy,
+                             const char           *property_name)
+{
+  g_autoptr (GVariant) ret = NULL;
+
+  g_dbus_proxy_call (proxy, "GetInternalProperty",
+                     g_variant_new ("(s)", property_name),
+                     G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL,
+                     on_proxy_call_cb, &ret);
+
+  while (!ret)
+    g_main_context_iteration (NULL, TRUE);
+
+  return g_variant_get_child_value (ret, 0);
+}
+
+static void
+ensure_property (MetaSensorsProxyMock *proxy,
+                 const char           *property_name,
+                 GVariant             *expected_value)
+{
+  g_autoptr (GVariant) value = NULL;
+  g_autoptr (GVariant) expected = NULL;
+  gboolean equal_properties;
+
+  value = get_internal_property_value (proxy, property_name);
+
+  if (!g_variant_is_of_type (value, G_VARIANT_TYPE_VARIANT))
+    {
+      g_autoptr (GVariant) tmp = g_variant_ref (value);
+      value = g_variant_new ("v", tmp);
+    }
+
+  if (g_variant_is_of_type (expected_value, G_VARIANT_TYPE_VARIANT))
+    expected = g_variant_ref (expected_value);
+  else
+    expected = g_variant_new ("v", expected_value);
+
+  equal_properties = g_variant_equal (expected, value);
+
+  if (!equal_properties)
+    {
+      g_autofree char *actual_str = g_variant_print (value, TRUE);
+      g_autofree char *expected_str = g_variant_print (expected, TRUE);
+
+      g_debug ("Property: %s", property_name);
+      g_debug ("Expected: %s", expected_str);
+      g_debug ("Actual: %s", actual_str);
+    }
+
+  g_assert_true (equal_properties);
+}
+
+static void
+stop_sensors_mock (GDBusConnection *connection)
+{
+  g_autoptr (GVariant) ret = NULL;
+  g_autoptr (GError) error = NULL;
+
+  ret = g_dbus_connection_call_sync (connection,
+                                     "org.gnome.Mutter.TestDBusMocksManager",
+                                     "/org/gnome/Mutter/TestDBusMocksManager",
+                                     "org.gnome.Mutter.TestDBusMocksManager",
+                                     "StopLocalTemplate",
+                                     g_variant_new ("(s)", SENSORS_MOCK_TEMPLATE),
+                                     NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1,
+                                     NULL, &error);
+
+  g_assert_no_error (error);
+  g_assert_nonnull (ret);
+}
+
+static void
+start_sensors_mock (void)
+{
+  g_autoptr (GDBusConnection) connection = NULL;
+  g_autoptr (GError) error = NULL;
+  g_autoptr (GVariant) ret = NULL;
+
+  connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+  g_assert_no_error (error);
+
+  ret = g_dbus_connection_call_sync (connection,
+                                     "org.gnome.Mutter.TestDBusMocksManager",
+                                     "/org/gnome/Mutter/TestDBusMocksManager",
+                                     "org.gnome.Mutter.TestDBusMocksManager",
+                                     "StartFromLocalTemplate",
+                                     g_variant_new ("(s)", SENSORS_MOCK_TEMPLATE),
+                                     NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1,
+                                     NULL, &error);
+
+  g_assert_no_error (error);
+  g_assert_nonnull (ret);
+}
+
+static void
+on_proxy_removed (gpointer data)
+{
+  g_autoptr (GDBusConnection) connection = data;
+
+  stop_sensors_mock (connection);
+}
+
+MetaSensorsProxyMock *
+meta_sensors_proxy_mock_get (void)
+{
+  GDBusProxy *proxy = NULL;
+  g_autoptr (GError) error = NULL;
+
+  if (sensors_proxy_mock)
+    return g_object_ref (sensors_proxy_mock);
+
+  start_sensors_mock ();
+
+  proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+                                         G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START |
+                                         G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS,
+                                         NULL,
+                                         "net.hadess.SensorProxy",
+                                         "/net/hadess/SensorProxy",
+                                         "org.freedesktop.DBus.Mock",
+                                         NULL, &error);
+  g_assert_true (G_IS_DBUS_PROXY (proxy));
+  g_assert_no_error (error);
+
+  while (TRUE)
+    {
+      g_autoptr (GVariant) ret = NULL;
+      size_t n_owners = 0;
+
+      ret = get_internal_property_value (proxy, "AccelerometerOwners");
+      if (g_variant_get_strv (ret, &n_owners) && n_owners)
+        {
+          g_assert_cmpuint (n_owners, ==, 1);
+          break;
+        }
+    }
+
+  sensors_proxy_mock = proxy;
+  g_object_add_weak_pointer (G_OBJECT (sensors_proxy_mock),
+                             (gpointer *) &sensors_proxy_mock);
+
+  g_object_set_data_full (G_OBJECT (proxy), "proxy-data",
+                          g_object_ref (g_dbus_proxy_get_connection (proxy)),
+                          on_proxy_removed);
+
+  return proxy;
+}
+
+void
+meta_sensors_proxy_mock_set_property (MetaSensorsProxyMock *proxy,
+                                      const gchar          *property_name,
+                                      GVariant             *value)
+{
+  g_autoptr (GVariant) ret = NULL;
+  g_autoptr (GVariant) reffed_value = g_variant_ref (value);
+
+  g_dbus_proxy_call (proxy, "SetInternalProperty",
+                     g_variant_new ("(ssv)",
+                                    "net.hadess.SensorProxy",
+                                    property_name,
+                                    reffed_value),
+                     G_DBUS_CALL_FLAGS_NONE,
+                     -1, NULL, on_proxy_call_cb, &ret);
+
+  while (!ret)
+    g_main_context_iteration (NULL, TRUE);
+
+  g_assert_nonnull (ret);
+
+  ensure_property (proxy, property_name, value);
+}
+
+void
+meta_sensors_proxy_mock_set_orientation (MetaSensorsProxyMock *proxy,
+                                         MetaOrientation       orientation)
+{
+  const char *orientation_str;
+
+  meta_sensors_proxy_mock_set_property (proxy, "HasAccelerometer",
+                                        g_variant_new_boolean (TRUE));
+
+  orientation_str = orientation_to_string (orientation);
+  meta_sensors_proxy_mock_set_property (proxy, "AccelerometerOrientation",
+                                        g_variant_new_string (orientation_str));
+}
diff --git a/src/tests/meta-sensors-proxy-mock.h b/src/tests/meta-sensors-proxy-mock.h
new file mode 100644
index 0000000000..b32c5868d6
--- /dev/null
+++ b/src/tests/meta-sensors-proxy-mock.h
@@ -0,0 +1,41 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2020 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marco Trevisan <marco trevisan canonical com>
+ */
+
+#ifndef META_SENSORS_PROXY_MOCK_H
+#define META_SENSORS_PROXY_MOCK_H
+
+#include "backends/meta-orientation-manager.h"
+
+typedef GDBusProxy MetaSensorsProxyMock;
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (MetaSensorsProxyMock, g_object_unref)
+
+META_EXPORT
+MetaSensorsProxyMock * meta_sensors_proxy_mock_get (void);
+
+META_EXPORT
+void meta_sensors_proxy_mock_set_property (MetaSensorsProxyMock *proxy,
+                                           const gchar          *property_name,
+                                           GVariant             *value);
+
+META_EXPORT
+void meta_sensors_proxy_mock_set_orientation (MetaSensorsProxyMock *proxy,
+                                              MetaOrientation       orientation);
+
+#endif /* META_SENSORS_PROXY_MOCK_H */
diff --git a/src/tests/mutter-all.test.in b/src/tests/mutter-all.test.in
index 6e103c51d0..413d2acd5f 100644
--- a/src/tests/mutter-all.test.in
+++ b/src/tests/mutter-all.test.in
@@ -1,6 +1,6 @@
 [Test]
 Description=All Mutter tests
 TestEnvironment=GSETTINGS_BACKEND=memory;
-Exec=sh -c 'env XDG_RUNTIME_DIR="$(mktemp -d -t mutter-@apiversion@-all-tests-XXXXXX)" dbus-run-session -- 
xvfb-run -a -s "+iglx -noreset" -- @libexecdir@/installed-tests/mutter-@apiversion@/mutter-test-runner --all'
+Exec=sh -c 'env XDG_RUNTIME_DIR="$(mktemp -d -t mutter-@apiversion@-all-tests-XXXXXX)" 
@libexecdir@/installed-tests/mutter-@apiversion@/meta-dbus-runner.py xvfb-run -a -s "+iglx -noreset" -- 
@libexecdir@/installed-tests/mutter-@apiversion@/mutter-test-runner --all'
 Type=session
 Output=TAP
diff --git a/src/tests/orientation-manager-unit-tests.c b/src/tests/orientation-manager-unit-tests.c
new file mode 100644
index 0000000000..74a861ab00
--- /dev/null
+++ b/src/tests/orientation-manager-unit-tests.c
@@ -0,0 +1,131 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2020 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marco Trevisan <marco trevisan canonical com>
+ */
+
+#include "config.h"
+
+#include "orientation-manager-unit-tests.h"
+
+#include "tests/meta-sensors-proxy-mock.h"
+
+static void
+meta_test_orientation_manager_no_daemon (void)
+{
+  g_autoptr (MetaOrientationManager) manager = NULL;
+
+  manager = g_object_new (META_TYPE_ORIENTATION_MANAGER, NULL);
+  g_assert_false (meta_orientation_manager_has_accelerometer (manager));
+  g_assert_cmpuint (meta_orientation_manager_get_orientation (manager),
+                    ==,
+                    META_ORIENTATION_UNDEFINED);
+}
+
+static void
+meta_test_orientation_manager_no_device (void)
+{
+  g_autoptr (MetaOrientationManager) manager = NULL;
+  MetaSensorsProxyMock* orientation_mock = NULL;
+
+  orientation_mock = meta_sensors_proxy_mock_get ();
+  manager = g_object_new (META_TYPE_ORIENTATION_MANAGER, NULL);
+  g_assert_false (meta_orientation_manager_has_accelerometer (manager));
+  g_assert_cmpuint (meta_orientation_manager_get_orientation (manager),
+                    ==,
+                    META_ORIENTATION_UNDEFINED);
+
+  g_object_unref (orientation_mock);
+}
+
+static void
+meta_test_orientation_manager_has_accelerometer (void)
+{
+  g_autoptr (MetaOrientationManager) manager = NULL;
+  g_autoptr (MetaSensorsProxyMock) orientation_mock = NULL;
+
+  manager = g_object_new (META_TYPE_ORIENTATION_MANAGER, NULL);
+  orientation_mock = meta_sensors_proxy_mock_get ();
+
+  meta_sensors_proxy_mock_set_property (orientation_mock,
+                                        "HasAccelerometer",
+                                        g_variant_new_boolean (TRUE));
+
+  g_debug ("Checking whether accelerometer is present");
+  g_assert_true (meta_orientation_manager_has_accelerometer (manager));
+  g_assert_cmpuint (meta_orientation_manager_get_orientation (manager),
+                    ==,
+                    META_ORIENTATION_UNDEFINED);
+}
+
+static void
+orientation_changed_cb (MetaOrientationManager *manager,
+                        gpointer                user_data)
+{
+  gboolean *changed_called = user_data;
+
+  *changed_called = TRUE;
+}
+
+static void
+meta_test_orientation_manager_accelerometer_orientations (void)
+{
+  g_autoptr (MetaOrientationManager) manager = NULL;
+  g_autoptr (MetaSensorsProxyMock) orientation_mock = NULL;
+
+  manager = g_object_new (META_TYPE_ORIENTATION_MANAGER, NULL);
+  orientation_mock = meta_sensors_proxy_mock_get ();
+
+  MetaOrientation initial;
+  gboolean changed_called;
+  unsigned i;
+
+  g_signal_connect (manager, "orientation-changed",
+                    G_CALLBACK (orientation_changed_cb),
+                    &changed_called);
+
+  initial = meta_orientation_manager_get_orientation (manager);
+
+  for (i = initial + 1; i != initial; i = (i + 1) % META_N_ORIENTATIONS)
+    {
+      changed_called = FALSE;
+      meta_sensors_proxy_mock_set_orientation (orientation_mock, i);
+
+      g_debug ("Checking orientation %d", i);
+      g_assert_cmpuint (meta_orientation_manager_get_orientation (manager),
+                        ==,
+                        i);
+
+      if (i != META_ORIENTATION_UNDEFINED)
+        g_assert_true (changed_called);
+      else
+        g_assert_false (changed_called);
+    }
+}
+
+void
+init_orientation_manager_tests (void)
+{
+  g_test_add_func ("/backends/orientation-manager/no-daemon",
+                   meta_test_orientation_manager_no_daemon);
+  g_test_add_func ("/backends/orientation-manager/no-device",
+                   meta_test_orientation_manager_no_device);
+  g_test_add_func ("/backends/orientation-manager/has-accelerometer",
+                   meta_test_orientation_manager_has_accelerometer);
+  g_test_add_func ("/backends/orientation-manager/accelerometer-orientations",
+                   meta_test_orientation_manager_accelerometer_orientations);
+}
diff --git a/src/tests/orientation-manager-unit-tests.h b/src/tests/orientation-manager-unit-tests.h
new file mode 100644
index 0000000000..34b175f0ce
--- /dev/null
+++ b/src/tests/orientation-manager-unit-tests.h
@@ -0,0 +1,26 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (C) 2020 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Marco Trevisan <marco trevisan canonical com>
+ */
+
+#ifndef ORIENTATION_MANAGER_UNIT_TESTS_H
+#define ORIENTATION_MANAGER_UNIT_TESTS_H
+
+void init_orientation_manager_tests (void);
+
+#endif /* ORIENTATION_MANAGER_UNIT_TESTS_H */
diff --git a/src/tests/unit-tests.c b/src/tests/unit-tests.c
index a31f505367..20f965ce4d 100644
--- a/src/tests/unit-tests.c
+++ b/src/tests/unit-tests.c
@@ -35,6 +35,8 @@
 #include "tests/monitor-unit-tests.h"
 #include "tests/monitor-store-unit-tests.h"
 #include "tests/monitor-transform-tests.h"
+#include "tests/meta-test-utils.h"
+#include "tests/orientation-manager-unit-tests.h"
 #include "tests/wayland-unit-tests.h"
 
 MetaContext *test_context;
@@ -232,6 +234,7 @@ init_tests (void)
   init_boxes_tests ();
   init_wayland_tests ();
   init_monitor_transform_tests ();
+  init_orientation_manager_tests ();
 }
 
 int


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]