on xenial help sign in
Made improvements for laggy indicator backends (lp#1390136). Fixes: #1390136

Approved by: Michał Sawicz

commit977a1a62b33f8523a7f40b879d407f707c850f28 authorNick Dedekind <nick.dedekind@canonical.com> dateThu 9 Apr 2015, 13:59 (12 years ago) parent7cb775dd2e4ef9
29 files changed+834−85
CMakeLists.txt+2 data/CMakeLists.txt+2 data/indicators-client.desktop.in+9 data/unity-mock-indicator-service.conf+7 debian/control+2−2 debian/indicators-client.install+1 debian/unity8-autopilot.install+3−1 include/paths.h.in+1−6 plugins/Utils/CMakeLists.txt+1 plugins/Utils/constants.cpp+27 plugins/Utils/constants.h+43 plugins/Utils/plugin.cpp+9 qml/Panel/Indicators/MenuItemFactory.qml+48−52 qml/Panel/Indicators/client/IndicatorsList.qml+2−2 run.sh+1−1 src/Panel/Indicators/client/CMakeLists.txt−2 tests/autopilot/unity8/fixture_setup.py+46−1 tests/autopilot/unity8/indicators/__init__.py+89−10 tests/autopilot/unity8/indicators/tests/__init__.py+7 tests/autopilot/unity8/indicators/tests/test_action_latency.py+232 tests/autopilot/unity8/indicators/tests/test_display_indicator.py+3−1 tests/autopilot/unity8/indicators/tests/test_indicators.py+2−2 tests/mocks/CMakeLists.txt+1 tests/mocks/data/unity/indicators/com.canonical.indicator.mock+10 tests/mocks/indicator-service/CMakeLists.txt+20 tests/mocks/indicator-service/mock-indicator-service.c+261 tests/plugins/Unity/Indicators/indicatorsmanagertest.cpp−2 tests/plugins/Unity/Indicators/indicatorsmodeltest.cpp−2 tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+5−1
CMakeLists.txt+2
@@ -105,6 +105,8 @@ declare_autopilot_test(shell unity8.shell ${CMAKE_SOURCE_DIR}/tests/autopilot/)
105105set(SHELL_APP unity8)
106106set(DASH_APP unity8-dash)
107107set(SCOPE_TOOL unity-scope-tool)
108+set(INDICATORS_CLIENT_APP indicators-client)
109+set(MOCK_INDICATOR_SERVICE_APP unity-mock-indicator-service)
108110
109111include_directories(
110112 ${CMAKE_SOURCE_DIR}/include
data/CMakeLists.txt+2
@@ -1,11 +1,13 @@
11# generate desktop files
22configure_file(${SHELL_APP}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${SHELL_APP}.desktop @ONLY)
33configure_file(${DASH_APP}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${DASH_APP}.desktop @ONLY)
4+configure_file(${INDICATORS_CLIENT_APP}.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/${INDICATORS_CLIENT_APP}.desktop @ONLY)
45
56# install desktop files
67install(FILES
78 ${CMAKE_CURRENT_BINARY_DIR}/${SHELL_APP}.desktop
89 ${CMAKE_CURRENT_BINARY_DIR}/${DASH_APP}.desktop
10+ ${CMAKE_CURRENT_BINARY_DIR}/${INDICATORS_CLIENT_APP}.desktop
911 DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
1012 )
1113
data/indicators-client.desktop.innew file+9
@@ -0,0 +1,9 @@
1+[Desktop Entry]
2+Type=Application
3+Name=Indicators Client
4+Comment=Application for testing the Indicators system
5+Exec=@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_BINDIR@/indicators-client
6+Terminal=false
7+Icon=
8+NoDisplay=false
9+X-Ubuntu-Touch=true
data/unity-mock-indicator-service.confnew file+7
@@ -0,0 +1,7 @@
1+description "Unity Shell v8 Mock Indicator"
2+author "Nick Dedekind <nick.dedekind@canonical.com>"
3+
4+respawn
5+respawn limit 2 10
6+
7+exec ${BINARY:-unity-mock-indicator-service} $ARGS
debian/control+2−2
@@ -47,7 +47,7 @@ Build-Depends: cmake,
4747 qtdeclarative5-dev-tools,
4848 qtdeclarative5-private-dev (>= 5.2.1),
4949 qtdeclarative5-qtmultimedia-plugin,
50− qtdeclarative5-ubuntu-settings-components (>= 0.5),
50+ qtdeclarative5-ubuntu-settings-components (>= 0.6),
5151 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.1.1239) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.1.1239),
5252 qtdeclarative5-ubuntu-web-plugin,
5353 ttf-ubuntu-font-family,
@@ -119,7 +119,7 @@ Description: Unity 8 shell
119119Package: unity8-common
120120Architecture: all
121121Depends: qml-module-qtquick-layouts,
122− qtdeclarative5-ubuntu-settings-components (>= 0.4),
122+ qtdeclarative5-ubuntu-settings-components (>= 0.6),
123123 qtdeclarative5-ubuntu-thumbnailer0.1 | ubuntu-thumbnailer-impl,
124124 qtdeclarative5-ubuntu-ui-toolkit-plugin (>= 1.1.1239) | qtdeclarative5-ubuntu-ui-toolkit-plugin-gles (>= 1.1.1239),
125125 qtdeclarative5-unity-notifications-plugin (>= 0.1.2) | unity-notifications-impl,
debian/indicators-client.install+1
@@ -1 +1,2 @@
1+usr/share/applications/indicators-client.desktop
12/usr/bin/indicators-client
debian/unity8-autopilot.install+3−1
@@ -1,2 +1,4 @@
1+data/unity-mock-indicator-service.conf usr/share/upstart/sessions/
2+usr/bin/unity-mock-indicator-service
13usr/lib/python*/*/unity*
2−usr/share/unity8/unlock-device
4+usr/share/unity8/unlock-device
include/paths.h.in+1−6
@@ -92,12 +92,7 @@ inline QString mockPluginsDir() {
9292
9393inline QStringList shellDataDirs() {
9494 QStringList dirs = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
95− if (!isRunningInstalled()) {
96− if (getenv("UNITY_TEST_ENV")==nullptr) {
97− dirs.prepend("@CMAKE_BINARY_DIR@/share");
98− }
99− }
100− else {
95+ if (isRunningInstalled()) {
10196 // append so by default we use xdg files.
10297 dirs.append(qmlDirectory());
10398 }
plugins/Utils/CMakeLists.txt+1
@@ -8,6 +8,7 @@ include_directories(
88)
99
1010set(QMLPLUGIN_SRC
11+ constants.cpp
1112 inputwatcher.cpp
1213 qlimitproxymodelqml.cpp
1314 unitysortfilterproxymodelqml.cpp
plugins/Utils/constants.cppnew file+27
@@ -0,0 +1,27 @@
1+/*
2+ * Copyright 2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+*/
16+
17+#include "constants.h"
18+
19+Constants::Constants(QObject *parent)
20+ : QObject(parent)
21+{
22+ if (qgetenv("QT_LOAD_TESTABILITY").isEmpty()) {
23+ m_indicatorValueTimeout = 30000;
24+ } else {
25+ m_indicatorValueTimeout = 5000;
26+ }
27+}
plugins/Utils/constants.hnew file+43
@@ -0,0 +1,43 @@
1+/*
2+ * Copyright 2015 Canonical Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU Lesser General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU Lesser General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU Lesser General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+*/
16+
17+#ifndef CONSTANTS_H
18+#define CONSTANTS_H
19+
20+#include <QObject>
21+
22+/**
23+ * @brief The Constants class
24+ *
25+ * This singleton class exposes contants to Qml
26+ *
27+ */
28+
29+class Constants: public QObject
30+{
31+ Q_OBJECT
32+ Q_PROPERTY(int indicatorValueTimeout READ indicatorValueTimeout CONSTANT)
33+
34+public:
35+ Constants(QObject *parent = 0);
36+
37+ int indicatorValueTimeout() const { return m_indicatorValueTimeout; }
38+
39+private:
40+ int m_indicatorValueTimeout;
41+};
42+
43+#endif
plugins/Utils/plugin.cpp+9
@@ -35,6 +35,7 @@
3535#include "windowkeysfilter.h"
3636#include "easingcurve.h"
3737#include "windowstatestorage.h"
38+#include "constants.h"
3839
3940static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEngine)
4041{
@@ -43,6 +44,13 @@ static QObject *createWindowStateStorage(QQmlEngine *engine, QJSEngine *scriptEn
4344 return new WindowStateStorage();
4445}
4546
47+static QObject *createConstants(QQmlEngine *engine, QJSEngine *scriptEngine)
48+{
49+ Q_UNUSED(engine)
50+ Q_UNUSED(scriptEngine)
51+ return new Constants();
52+}
53+
4654void UtilsPlugin::registerTypes(const char *uri)
4755{
4856 Q_ASSERT(uri == QLatin1String("Utils"));
@@ -57,6 +65,7 @@ void UtilsPlugin::registerTypes(const char *uri)
5765 qmlRegisterType<RelativeTimeFormatter>(uri, 0, 1, "RelativeTimeFormatter");
5866 qmlRegisterSingletonType<WindowStateStorage>(uri, 0, 1, "WindowStateStorage", createWindowStateStorage);
5967 qmlRegisterType<InputWatcher>(uri, 0, 1, "InputWatcher");
68+ qmlRegisterSingletonType<Constants>(uri, 0, 1, "Constants", createConstants);
6069}
6170
6271void UtilsPlugin::initializeEngine(QQmlEngine *engine, const char *uri)
qml/Panel/Indicators/MenuItemFactory.qml+48−52
@@ -19,6 +19,7 @@
1919
2020import QtQuick 2.0
2121import Ubuntu.Settings.Menus 0.1 as Menus
22+import Ubuntu.Settings.Components 0.1
2223import QMenuModel 0.1
2324import Utils 0.1 as Utils
2425import Ubuntu.Components.ListItems 0.1 as ListItems
@@ -84,6 +85,7 @@ Item {
8485 id: sliderMenu;
8586
8687 Menus.SliderMenu {
88+ id: sliderItem
8789 objectName: "sliderMenu"
8890 property QtObject menuData: null
8991 property var menuModel: menuFactory.menuModel
@@ -113,15 +115,6 @@ Item {
113115 onMenuIndexChanged: {
114116 loadAttributes();
115117 }
116− onServerValueChanged: {
117− // value can be changed by slider, so a binding won't work.
118− if (serverValue !== undefined) {
119− value = serverValue;
120− }
121− }
122− onUpdated: {
123− menuModel.changeState(menuIndex, value);
124− }
125118
126119 function loadAttributes() {
127120 if (!menuModel || menuIndex == -1) return;
@@ -130,6 +123,20 @@ Item {
130123 'min-icon': 'icon',
131124 'max-icon': 'icon'});
132125 }
126+
127+ ServerPropertySynchroniser {
128+ objectName: "sync"
129+ syncTimeout: Utils.Constants.indicatorValueTimeout
130+ bufferedSyncTimeout: true
131+ maximumWaitBufferInterval: 16
132+
133+ serverTarget: sliderItem
134+ serverProperty: "serverValue"
135+ userTarget: sliderItem
136+ userProperty: "value"
137+
138+ onSyncTriggered: menuModel.changeState(menuIndex, value)
139+ }
133140 }
134141 }
135142
@@ -280,6 +287,7 @@ Item {
280287 id: checkableMenu;
281288
282289 Menus.CheckableMenu {
290+ id: checkItem
283291 objectName: "checkableMenu"
284292 property QtObject menuData: null
285293 property int menuIndex: -1
@@ -290,26 +298,16 @@ Item {
290298 checked: serverChecked
291299 highlightWhenPressed: false
292300
293− onServerCheckedChanged: updateFromServer()
294− onTriggered: {
295− menuModel.activate(menuIndex);
296− resyncTimer.restart();
297− }
301+ ServerPropertySynchroniser {
302+ objectName: "sync"
303+ syncTimeout: Utils.Constants.indicatorValueTimeout
298304
299− // value can be changed by menu, so a binding won't work.
300− function updateFromServer() {
301− resyncTimer.stop();
302− if (checked != serverChecked) {
303− checked = serverChecked;
304− }
305− }
305+ serverTarget: checkItem
306+ serverProperty: "serverChecked"
307+ userTarget: checkItem
308+ userProperty: "checked"
306309
307− // Server value is not guaranteed to change to what we expect from an activation.
308− // In this case, we need to re-assert that we are presenting the UI with the set backend value.
309− Timer {
310− id: resyncTimer
311− interval: 1500
312− onTriggered: updateFromServer()
310+ onSyncTriggered: menuModel.activate(checkItem.menuIndex)
313311 }
314312 }
315313 }
@@ -318,6 +316,7 @@ Item {
318316 id: switchMenu;
319317
320318 Menus.SwitchMenu {
319+ id: switchItem
321320 objectName: "switchMenu"
322321 property QtObject menuData: null
323322 property int menuIndex: -1
@@ -329,26 +328,16 @@ Item {
329328 checked: serverChecked
330329 highlightWhenPressed: false
331330
332− onServerCheckedChanged: updateFromServer()
333− onTriggered: {
334− menuModel.activate(menuIndex);
335− resyncTimer.restart();
336− }
331+ ServerPropertySynchroniser {
332+ objectName: "sync"
333+ syncTimeout: Utils.Constants.indicatorValueTimeout
337334
338− // value can be changed by menu, so a binding won't work.
339− function updateFromServer() {
340− resyncTimer.stop();
341− if (checked != serverChecked) {
342− checked = serverChecked;
343− }
344− }
335+ serverTarget: switchItem
336+ serverProperty: "serverChecked"
337+ userTarget: switchItem
338+ userProperty: "checked"
345339
346− // Server value is not guaranteed to change to what we expect from an activation.
347− // In this case, we need to re-assert that we are presenting the UI with the set backend value.
348− Timer {
349− id: resyncTimer
350− interval: 1500
351− onTriggered: updateFromServer()
340+ onSyncTriggered: menuModel.activate(switchItem.menuIndex);
352341 }
353342 }
354343 }
@@ -464,6 +453,7 @@ Item {
464453 id: accessPoint;
465454
466455 Menus.AccessPointMenu {
456+ id: apItem
467457 objectName: "accessPoint"
468458 property QtObject menuData: null
469459 property var menuModel: menuFactory.menuModel
@@ -485,19 +475,12 @@ Item {
485475 signalStrength: strengthAction.valid ? strengthAction.state : 0
486476 highlightWhenPressed: false
487477
488− onServerCheckedChanged: {
489− // value can be changed by menu, so a binding won't work.
490− active = serverChecked;
491− }
492478 onMenuModelChanged: {
493479 loadAttributes();
494480 }
495481 onMenuIndexChanged: {
496482 loadAttributes();
497483 }
498− onTriggered: {
499− menuModel.activate(menuIndex);
500− }
501484
502485 function loadAttributes() {
503486 if (!menuModel || menuIndex == -1) return;
@@ -505,6 +488,19 @@ Item {
505488 'x-canonical-wifi-ap-is-secure': 'bool',
506489 'x-canonical-wifi-ap-strength-action': 'string'});
507490 }
491+
492+ ServerPropertySynchroniser {
493+ objectName: "sync"
494+ syncTimeout: Utils.Constants.indicatorValueTimeout
495+
496+ serverTarget: apItem
497+ serverProperty: "serverChecked"
498+ userTarget: apItem
499+ userProperty: "active"
500+ userTrigger: "onTriggered"
501+
502+ onSyncTriggered: menuModel.activate(apItem.menuIndex)
503+ }
508504 }
509505 }
510506
qml/Panel/Indicators/client/IndicatorsList.qml+2−2
@@ -31,8 +31,8 @@ Page {
3131
3232 Indicators.IndicatorsModel {
3333 id: indicatorsModel
34−
35− Component.onCompleted: load(profile)
34+ profile: page.profile
35+ Component.onCompleted: load()
3636 }
3737
3838 ListView {
run.sh+1−1
@@ -65,6 +65,6 @@ else
6565 echo "Unity8 is already running, please stop it first"
6666 exit 1
6767 fi
68− /sbin/initctl start unity8 BINARY="`readlink -f $QML_PHONE_SHELL_PATH` $QML_PHONE_SHELL_ARGS $@" QML2_IMPORT_PATH=$QML2_IMPORT_PATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH
68+ /sbin/initctl start unity8 BINARY="`readlink -f $QML_PHONE_SHELL_PATH` $QML_PHONE_SHELL_ARGS $@" XDG_DATA_DIRS=$XDG_DATA_DIRS QML2_IMPORT_PATH=$QML2_IMPORT_PATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH
6969 tailf -n 0 ~/.cache/upstart/unity8.log
7070fi
src/Panel/Indicators/client/CMakeLists.txt−2
@@ -1,5 +1,4 @@
11project(indicators_client)
2−set(INDICATORS_CLIENT_APP indicators-client)
32
43set(INDICATORS_CLIENT_SRC
54 main.cpp
@@ -15,7 +14,6 @@ add_executable(${INDICATORS_CLIENT_APP}
1514)
1615
1716qt5_use_modules(${INDICATORS_CLIENT_APP} Core Widgets Quick)
18−set_target_properties(${INDICATORS_CLIENT_APP} PROPERTIES COMPILE_FLAGS -fPIC)
1917
2018install(TARGETS ${INDICATORS_CLIENT_APP}
2119 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
tests/autopilot/unity8/fixture_setup.py+46−1
@@ -19,12 +19,17 @@
1919
2020import fixtures
2121import subprocess
22+import logging
2223
2324from autopilot import introspection
2425
25−from unity8 import process_helpers
26+from unity8 import (
27+ get_binary_path,
28+ process_helpers
29+)
2630from unity8.shell import emulators
2731
32+logger = logging.getLogger(__name__)
2833
2934class LaunchDashApp(fixtures.Fixture):
3035
@@ -96,3 +101,43 @@ class DisplayRotationLock(fixtures.Fixture):
96101 'rotation-lock', value_string
97102 ]
98103 subprocess.check_output(command)
104+
105+class LaunchMockIndicatorService(fixtures.Fixture):
106+
107+ """Fixture to launch the indicator test service."""
108+
109+ def __init__(self, action_delay, ensure_not_running=True):
110+ """Initialize an instance.
111+
112+ :param action_delay: The delay to use when activating actions.
113+ Measured in milliseconds. Value of -1 will result in infinite delay.
114+ :type action_delay: An integer.
115+ :param boolean ensure_not_running: Make sure service is not running
116+
117+ """
118+ super(LaunchMockIndicatorService, self).__init__()
119+ self.action_delay = action_delay
120+ self.ensure_not_running = ensure_not_running
121+
122+ def setUp(self):
123+ super(LaunchMockIndicatorService, self).setUp()
124+ if self.ensure_not_running:
125+ self.ensure_service_not_running()
126+ self.addCleanup(self.stop_service)
127+ self.application_proxy = self.launch_service()
128+
129+ def launch_service(self):
130+ logger.info("Starting unity-mock-indicator-service")
131+ binary_path = get_binary_path('unity-mock-indicator-service')
132+ binary_arg = 'BINARY={}'.format(binary_path)
133+ env_args = 'ARGS=-t {}'.format(self.action_delay)
134+ all_args = [binary_arg, env_args]
135+ process_helpers.start_job('unity-mock-indicator-service', *all_args)
136+
137+ def stop_service(self):
138+ logger.info("Stopping unity-mock-indicator-service")
139+ process_helpers.stop_job('unity-mock-indicator-service')
140+
141+ def ensure_service_not_running(self):
142+ if process_helpers.is_job_running('unity-mock-indicator-service'):
143+ self.stop_service()
tests/autopilot/unity8/indicators/__init__.py+89−10
@@ -19,6 +19,7 @@ import ubuntuuitoolkit
1919from autopilot import introspection
2020
2121from unity8.shell import emulators
22+from unity8 import fixture_setup
2223
2324
2425class IndicatorPage(emulators.UnityEmulatorBase):
@@ -33,26 +34,25 @@ class IndicatorPage(emulators.UnityEmulatorBase):
3334 def validate_dbus_object(cls, path, state):
3435 return False
3536
37+class Indicator():
3638
37−class DisplayIndicator():
38−
39− def __init__(self, main_window):
39+ def __init__(self, main_window, name):
4040 self._main_window = main_window
41+ self._name = name
4142
4243 def is_indicator_icon_visible(self):
4344 panel_item = self._main_window.wait_select_single(
44− objectName='indicator-rotation-lock-panelItem')
45+ objectName=self._name+'-panelItem')
4546 return panel_item.indicatorVisible
4647
4748 def open(self):
48− """Open the display indicator page.
49+ """Open the indicator page.
4950
50− :return: The custom proxy object for the display indicator page.
51+ :return: The custom proxy object for the indicator page.
5152
5253 """
5354 if self.is_indicator_icon_visible():
54− return self._main_window.open_indicator_page(
55− 'indicator-rotation-lock')
55+ return self._main_window.open_indicator_page(self._name)
5656 else:
5757 return self._open_indicator_with_icon_not_visible()
5858
@@ -61,10 +61,10 @@ class DisplayIndicator():
6161 self._main_window.open_indicator_page('indicator-datetime')
6262 self._make_indicator_icon_visible()
6363 indicator_rotation_icon = self._main_window.select_single(
64− objectName='indicator-rotation-lock-panelItem')
64+ objectName=self._name+'-panelItem')
6565 self._main_window.pointing_device.click_object(indicator_rotation_icon)
6666 return self._main_window.wait_select_single(
67− objectName='indicator-rotation-lock-page')
67+ objectName=self._name+'-page')
6868
6969 def _make_indicator_icon_visible(self):
7070 indicators_bar_flickable = self._main_window.select_single(
@@ -95,6 +95,13 @@ class DisplayIndicator():
9595 self._main_window.close_indicator_page()
9696
9797
98+class DisplayIndicator(Indicator):
99+
100+ def __init__(self, main_window):
101+ super(DisplayIndicator, self).__init__(main_window, 'indicator-rotation-lock')
102+ self._main_window = main_window
103+
104+
98105class DisplayIndicatorPage(IndicatorPage):
99106
100107 """Autopilot helper for the display indicator page."""
@@ -122,3 +129,75 @@ class DisplayIndicatorPage(IndicatorPage):
122129 switcher = self._get_switcher()
123130 switcher.uncheck()
124131 switcher.checked.wait_for(False)
132+
133+
134+class TestIndicator(Indicator):
135+
136+ def __init__(self, main_window):
137+ super(TestIndicator, self).__init__(main_window, 'indicator-mock')
138+ self._main_window = main_window
139+
140+
141+class TestIndicatorPage(IndicatorPage):
142+
143+ """Autopilot helper for the mock indicator page."""
144+
145+ @classmethod
146+ def validate_dbus_object(cls, path, state):
147+ name = introspection.get_classname_from_path(path)
148+ if name == b'IndicatorPage':
149+ if state['objectName'][1] == 'indicator-mock-page':
150+ return True
151+ return False
152+
153+ def get_switcher(self):
154+ return self.select_single(
155+ ubuntuuitoolkit.CheckBox, objectName='switcher')
156+
157+ def get_switch_menu(self):
158+ return self.select_single(
159+ 'SwitchMenu', objectName='indicator.action.switch')
160+
161+ def get_slider(self):
162+ return self.select_single(objectName='slider')
163+
164+ def get_slider_menu(self):
165+ return self.select_single(objectName='indicator.action.slider')
166+
167+
168+class Slider(emulators.UnityEmulatorBase):
169+
170+ """Autopilot helper for the Slider component."""
171+
172+ # XXX Because of https://bugs.launchpad.net/autopilot-qt/+bug/1341671
173+ # we need to make sure it does not match in any selection.
174+ # --elopio - 2015-01-20
175+
176+ @classmethod
177+ def validate_dbus_object(cls, path, state):
178+ name = introspection.get_classname_from_path(path)
179+ if name == b'Slider':
180+ return True
181+ return False
182+
183+ def slide_left(self, timeout=10):
184+ x, y, width, height = self.globalRect
185+
186+ rate = 5
187+ start_x = x + width/2
188+ start_y = stop_y = y + height/2
189+ stop_x = x
190+
191+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y, rate)
192+ self.value.wait_for(self.minimumValue, timeout);
193+
194+ def slide_right(self, timeout=10):
195+ x, y, width, height = self.globalRect
196+
197+ rate = 5
198+ start_x = x + width/2
199+ start_y = stop_y = y + height/2
200+ stop_x = x + width
201+
202+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y, rate)
203+ self.value.wait_for(self.maximumValue, timeout);
tests/autopilot/unity8/indicators/tests/__init__.py+7
@@ -26,6 +26,13 @@ class IndicatorTestCase(tests.UnityTestCase):
2626
2727 device_emulation_scenarios = tests._get_device_emulation_scenarios()
2828
29+ def setUp(self):
30+ super().setUp()
31+ self.unity_proxy = self.launch_unity()
32+ process_helpers.unlock_unity(self.unity_proxy)
33+
34+class DeviceIndicatorTestCase(IndicatorTestCase):
35+
2936 def setUp(self):
3037 if platform.model() == 'Desktop':
3138 self.skipTest('Test cannot be run on the desktop.')
tests/autopilot/unity8/indicators/tests/test_action_latency.pynew file+232
@@ -0,0 +1,232 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Indicators Autopilot Test Suite
4+# Copyright (C) 2015 Canonical
5+#
6+# This program is free software: you can redistribute it and/or modify
7+# it under the terms of the GNU General Public License as published by
8+# the Free Software Foundation, either version 3 of the License, or
9+# (at your option) any later version.
10+#
11+# This program is distributed in the hope that it will be useful,
12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+# GNU General Public License for more details.
15+#
16+# You should have received a copy of the GNU General Public License
17+# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
19+from testscenarios import multiply_scenarios
20+
21+from unity8 import (
22+ fixture_setup,
23+ indicators,
24+ process_helpers
25+)
26+from unity8.shell import tests
27+from unity8.indicators import tests
28+
29+from autopilot.matchers import Eventually
30+from testtools.matchers import Equals, NotEquals
31+import time
32+
33+
34+class TestIndicatorBaseTestCase(tests.IndicatorTestCase):
35+
36+ scenarios = [ tests.IndicatorTestCase.device_emulation_scenarios[0] ]
37+
38+ def setUp(self):
39+ super(TestIndicatorBaseTestCase, self).setUp()
40+
41+ self.launch_indicator_service()
42+
43+ # wait for the indicator to appear in unity
44+ self.indicator = indicators.TestIndicator(self.main_window)
45+ self.assertThat(
46+ self.indicator.is_indicator_icon_visible(),
47+ Eventually(Equals(True), timeout=20)
48+ )
49+ self.indicator_page = self.indicator.open()
50+
51+ def launch_indicator_service(self):
52+ launch_service_fixture = fixture_setup.LaunchMockIndicatorService(self.action_delay)
53+ self.useFixture(launch_service_fixture)
54+
55+
56+class TestServerValueUpdate(TestIndicatorBaseTestCase):
57+
58+ """Test that an action causes the server to update"""
59+
60+ time_scenarios = [
61+ ('Low', {'action_delay': 0}),
62+ ('Medium', {'action_delay': 2500}),
63+ ('High', {'action_delay': 8000}),
64+ ]
65+ scenarios = multiply_scenarios(
66+ time_scenarios,
67+ TestIndicatorBaseTestCase.scenarios
68+ )
69+
70+ def test_switch_reaches_server_value(self):
71+ switch = self.indicator_page.get_switcher()
72+ switch_menu = self.indicator_page.get_switch_menu()
73+
74+ switch.change_state()
75+
76+ self.assertThat(
77+ switch_menu.serverChecked,
78+ Eventually(Equals(switch.checked), timeout=20)
79+ )
80+
81+ def test_slider_reaches_server_value(self):
82+ slider = self.indicator_page.get_slider()
83+ slider_menu = self.indicator_page.get_slider_menu()
84+
85+ old_value = slider.value
86+ slider.slide_left()
87+
88+ self.assertThat(
89+ slider_menu.serverValue,
90+ Eventually(NotEquals(old_value), timeout=20)
91+ )
92+
93+ self.assertThat(
94+ slider_menu.serverValue,
95+ Eventually(Equals(slider.value), timeout=20)
96+ )
97+
98+class TestBuffering(TestIndicatorBaseTestCase):
99+
100+ """Test that switching multiple times will buffer activations
101+
102+ See https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1390136 .
103+ """
104+ action_delay = 2500
105+
106+ def test_switch_buffers_actvations(self):
107+
108+ switch = self.indicator_page.get_switcher()
109+ switch.change_state()
110+ intermediate_value = switch.checked;
111+
112+ # will buffer change until it receives the change from server
113+ switch.change_state()
114+ final_value = switch.checked;
115+
116+ # backend will respond to first switch.
117+ switch_menu = self.indicator_page.get_switch_menu()
118+ self.assertThat(
119+ switch_menu.serverChecked,
120+ Eventually(Equals(intermediate_value), timeout=20)
121+ )
122+ # The buffered activation should have gone to server now.
123+
124+ # front-end should not change as a result of server update while it is buffered
125+ self.assertThat(
126+ switch.checked,
127+ Equals(final_value)
128+ )
129+
130+ # server will respond to the second activate
131+ self.assertThat(
132+ switch_menu.serverChecked,
133+ Eventually(Equals(final_value), timeout=20)
134+ )
135+
136+ # make sure we've got the server value set.
137+ self.assertThat(
138+ switch.checked,
139+ Equals(switch_menu.serverChecked)
140+ )
141+
142+ def test_slider_buffers_activations(self):
143+
144+ slider = self.indicator_page.get_slider()
145+ original_value = slider.value;
146+ slider.slide_left()
147+
148+ # will buffer change until it receives the change from server
149+ slider.slide_right()
150+ final_value = slider.value;
151+
152+ # backend will respond to first slider. Since it's a live slider it'll probably
153+ # be a random value along the slide.
154+ slider_menu = self.indicator_page.get_slider_menu()
155+ self.assertThat(
156+ slider_menu.serverValue,
157+ Eventually(NotEquals(original_value), timeout=20)
158+ )
159+ # It wont yet have reached the final value due to the buffering
160+ # Second activate should have gone out by now
161+ self.assertThat(
162+ slider_menu.serverValue,
163+ NotEquals(final_value)
164+ )
165+
166+ # front-end should not change as a result of server update while it is buffered
167+ self.assertThat(
168+ slider.value,
169+ Equals(final_value)
170+ )
171+
172+ # server will respond to the second activate
173+ self.assertThat(
174+ slider_menu.serverValue,
175+ Eventually(Equals(final_value), timeout=20)
176+ )
177+
178+ # make sure we've got the server value set.
179+ self.assertThat(
180+ slider.value,
181+ Equals(slider_menu.serverValue)
182+ )
183+
184+
185+class TestClientRevertsToServerValue(TestIndicatorBaseTestCase):
186+
187+ """Test that an action which does not respond in time will revert
188+ to original value if not actioned in time.
189+
190+ See https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1390136 .
191+ """
192+ action_delay = -1 # never action.
193+
194+ def test_switch_reverts_on_late_response(self):
195+
196+ switch = self.indicator_page.get_switcher()
197+ switch_menu = self.indicator_page.get_switch_menu()
198+
199+ original_value = switch.checked;
200+ switch.change_state()
201+
202+ # switch should revert to original value after 5 seconds (30 seconds in real usage)
203+ self.assertThat(
204+ switch.checked,
205+ Eventually(Equals(original_value), timeout=20)
206+ )
207+
208+ # make sure we've got the server value set.
209+ self.assertThat(
210+ switch.checked,
211+ Equals(switch_menu.serverChecked)
212+ )
213+
214+ def test_slider_reverts_on_late_response(self):
215+
216+ slider = self.indicator_page.get_slider()
217+ slider_menu = self.indicator_page.get_slider_menu()
218+
219+ original_value = slider.value;
220+ slider.slide_left()
221+
222+ # slider should revert to original value after 5 seconds (30 seconds in real usage)
223+ self.assertThat(
224+ slider.value,
225+ Eventually(Equals(original_value), timeout=20)
226+ )
227+
228+ # make sure we've got the server value set.
229+ self.assertThat(
230+ slider.value,
231+ Equals(slider_menu.serverValue)
232+ )
tests/autopilot/unity8/indicators/tests/test_display_indicator.py+3−1
@@ -23,7 +23,9 @@ from unity8 import (
2323from unity8.indicators import tests
2424
2525
26−class DisplayIndicatorTestCase(tests.IndicatorTestCase):
26+class DisplayIndicatorTestCase(tests.DeviceIndicatorTestCase):
27+
28+ scenarios = tests.IndicatorTestCase.device_emulation_scenarios
2729
2830 def test_indicator_icon_must_be_visible_after_rotation_locked(self):
2931 rotation_unlocked = fixture_setup.DisplayRotationLock(False)
tests/autopilot/unity8/indicators/tests/test_indicators.py+2−2
@@ -23,7 +23,7 @@ from autopilot import platform
2323from unity8.indicators import tests
2424
2525
26−class IndicatorExistsTestCase(tests.IndicatorTestCase):
26+class IndicatorExistsTestCase(tests.DeviceIndicatorTestCase):
2727
2828 indicator_scenarios = [
2929 ('Bluetooth', dict(indicator_name='indicator-bluetooth')),
@@ -51,7 +51,7 @@ class IndicatorExistsTestCase(tests.IndicatorTestCase):
5151 )
5252
5353
54−class IndicatorPageTitleMatchesWidgetTestCase(tests.IndicatorTestCase):
54+class IndicatorPageTitleMatchesWidgetTestCase(tests.DeviceIndicatorTestCase):
5555
5656 indicator_scenarios = [
5757 ('Bluetooth', dict(indicator_name='indicator-bluetooth',
tests/mocks/CMakeLists.txt+1
@@ -30,6 +30,7 @@ endmacro()
3030
3131add_subdirectory(AccountsService)
3232add_subdirectory(GSettings.1.0)
33+add_subdirectory(indicator-service)
3334add_subdirectory(libusermetrics)
3435add_subdirectory(LightDM)
3536add_subdirectory(Lights)
tests/mocks/data/unity/indicators/com.canonical.indicator.mocknew file+10
@@ -0,0 +1,10 @@
1+[Indicator Service]
2+Name=indicator-mock
3+ObjectPath=/com/canonical/indicator/mock
4+Position=0
5+
6+[phone]
7+ObjectPath=/com/canonical/indicator/mock/desktop
8+
9+[desktop]
10+ObjectPath=/com/canonical/indicator/mock/desktop
tests/mocks/indicator-service/CMakeLists.txtnew file+20
@@ -0,0 +1,20 @@
1+project(mock-indicator-service)
2+pkg_search_module(GIO REQUIRED gio-2.0)
3+
4+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99")
5+
6+include_directories(
7+ ${CMAKE_CURRENT_SOURCE_DIR}
8+ ${GIO_INCLUDE_DIRS}
9+)
10+
11+add_executable(${MOCK_INDICATOR_SERVICE_APP}
12+ mock-indicator-service.c
13+)
14+
15+target_link_libraries(${MOCK_INDICATOR_SERVICE_APP} ${GIO_LDFLAGS})
16+
17+set_target_properties(${MOCK_INDICATOR_SERVICE_APP} PROPERTIES COMPILE_FLAGS -fPIC)
18+
19+install(TARGETS ${MOCK_INDICATOR_SERVICE_APP}
20+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
tests/mocks/indicator-service/mock-indicator-service.cnew file+261
@@ -0,0 +1,261 @@
1+/*
2+ * Copyright (C) 2015 Canonical Ltd.
3+ *
4+ * This program is free software: you can redistribute it and/or modify it
5+ * under the terms of the GNU General Public License version 3, as published
6+ * by the Free Software Foundation.
7+ *
8+ * This program is distributed in the hope that it will be useful, but
9+ * WITHOUT ANY WARRANTY; without even the implied warranties of
10+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11+ * PURPOSE. See the GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License along
14+ * with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+#include <gio/gio.h>
17+#include <stdio.h>
18+#include <stdlib.h>
19+
20+G_DEFINE_QUARK (SLIDER_VALUE, slider_value)
21+
22+typedef struct
23+{
24+ GSimpleActionGroup *actions;
25+ GMenu *menu;
26+
27+ guint actions_export_id;
28+ guint menu_export_id;
29+ int action_delay;
30+} IndicatorTestService;
31+
32+static void
33+bus_acquired (GDBusConnection *connection,
34+ const gchar *name,
35+ gpointer user_data)
36+{
37+ (void) name;
38+ IndicatorTestService *indicator = user_data;
39+ GError *error = NULL;
40+
41+ indicator->actions_export_id = g_dbus_connection_export_action_group (connection,
42+ "/com/canonical/indicator/mock",
43+ G_ACTION_GROUP (indicator->actions),
44+ &error);
45+ if (indicator->actions_export_id == 0)
46+ {
47+ g_warning ("cannot export action group: %s", error->message);
48+ g_error_free (error);
49+ return;
50+ }
51+
52+ indicator->menu_export_id = g_dbus_connection_export_menu_model (connection,
53+ "/com/canonical/indicator/mock/desktop",
54+ G_MENU_MODEL (indicator->menu),
55+ &error);
56+ if (indicator->menu_export_id == 0)
57+ {
58+ g_warning ("cannot export menu: %s", error->message);
59+ g_error_free (error);
60+ return;
61+ }
62+}
63+
64+static void
65+name_lost (GDBusConnection *connection,
66+ const gchar *name,
67+ gpointer user_data)
68+{
69+ (void) name;
70+ IndicatorTestService *indicator = user_data;
71+
72+ if (indicator->actions_export_id)
73+ g_dbus_connection_unexport_action_group (connection, indicator->actions_export_id);
74+
75+ if (indicator->menu_export_id)
76+ g_dbus_connection_unexport_menu_model (connection, indicator->menu_export_id);
77+}
78+
79+static void
80+activate_show (GSimpleAction *action,
81+ GVariant *parameter,
82+ gpointer user_data)
83+{
84+ (void) action;
85+ (void) parameter;
86+ (void) user_data;
87+ g_message ("showing");
88+}
89+
90+static gboolean
91+actual_switch (gpointer user_data)
92+{
93+ GSimpleAction *action = user_data;
94+
95+ GVariant* v = g_action_get_state(G_ACTION(action));
96+ gboolean state = g_variant_get_boolean (v);
97+ GVariant *new_state = g_variant_new_boolean (state == TRUE ? FALSE : TRUE);
98+
99+ g_simple_action_set_state(G_SIMPLE_ACTION(action), new_state);
100+
101+ g_variant_unref (v);
102+ g_message ("switching");
103+ return FALSE;
104+}
105+
106+static void
107+activate_switch (GSimpleAction *action,
108+ GVariant *parameter,
109+ gpointer user_data)
110+{
111+ (void) action;
112+ (void) parameter;
113+
114+ IndicatorTestService *indicator = user_data;
115+
116+ g_timeout_add(indicator->action_delay, actual_switch, action);
117+ g_message ("switch delay");
118+}
119+
120+static gboolean
121+actual_slide (gpointer user_data)
122+{
123+ GSimpleAction *action = user_data;
124+
125+ GVariant* new_state = g_object_get_qdata (G_OBJECT (action), slider_value_quark ());
126+
127+ g_simple_action_set_state(G_SIMPLE_ACTION(action), new_state);
128+
129+ g_message ("switching");
130+ return FALSE;
131+}
132+
133+void change_slider (GSimpleAction *action,
134+ GVariant *value,
135+ gpointer user_data)
136+{
137+ IndicatorTestService *indicator = user_data;
138+
139+ g_object_set_qdata (G_OBJECT (action), slider_value_quark (), g_variant_ref(value));
140+
141+ g_timeout_add(indicator->action_delay, actual_slide, action);
142+ g_message ("slide delay %.03f", g_variant_get_double(value));
143+}
144+
145+int
146+main (int argc, char **argv)
147+{
148+ IndicatorTestService indicator = { 0 };
149+ indicator.action_delay = -1;
150+ GMenuItem *item;
151+ GMenu *submenu;
152+ GActionEntry entries[] = {
153+ { "_header", NULL, NULL, "{'title': <'Test'>,"
154+ " 'label': <'Test'>,"
155+ " 'visible': <true>,"
156+ " 'accessible-desc': <'Test indicator'> }", NULL },
157+ { "action.show", activate_show, NULL, NULL, NULL },
158+ { "action.switch", activate_switch, NULL, "true", NULL },
159+ { "action.checkbox", activate_switch, NULL, "true", NULL },
160+ { "action.accessPoint", activate_switch, NULL, "false", NULL },
161+ { "action.slider", NULL, NULL, "0.5", change_slider }
162+ };
163+ GMainLoop *loop;
164+
165+ int help = 0;
166+ if (argc > 1)
167+ {
168+ int i;
169+ for (i = 1; i < argc; i++) {
170+ const char *arg = argv[i];
171+
172+ if (arg[0] == '-') {
173+ switch (arg[1])
174+ {
175+ case 't':
176+ {
177+ arg += 2;
178+ if (!arg[0] && i < argc-1) {
179+ i++;
180+ int delay = -1;
181+
182+ if (sscanf(argv[i], "%d", &delay) == 1) {
183+ indicator.action_delay = delay;
184+ } else {
185+ printf("Invalid action delay value: %s\n", argv[i]);
186+ help = 1;
187+ }
188+ } else {
189+ printf("Invalid action delay value: %s\n", argv[i]);
190+ help = 1;
191+ }
192+ break;
193+ }
194+ case 'h':
195+ help = 1;
196+ break;
197+ }
198+ }
199+ }
200+ }
201+
202+ if (help) {
203+ printf("Usage: %s [<options>]\n"
204+ " -t DELAY Action activation delay\n"
205+ " -h Show this help text\n"
206+ , argv[0]);
207+ return 0;
208+ }
209+
210+ indicator.actions = g_simple_action_group_new ();
211+ g_action_map_add_action_entries (G_ACTION_MAP (indicator.actions), entries, G_N_ELEMENTS (entries), &indicator);
212+
213+ submenu = g_menu_new ();
214+ g_menu_append (submenu, "Show", "indicator.action.show");
215+
216+ // Switch
217+ item = g_menu_item_new("Switch", "indicator.action.switch");
218+ g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.switch");
219+ g_menu_append_item(submenu, item);
220+
221+ // Checkbox
222+ item = g_menu_item_new("Checkbox", "indicator.action.checkbox");
223+ g_menu_append_item(submenu, item);
224+
225+ // Slider
226+ item = g_menu_item_new("Slider", "indicator.action.slider");
227+ g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.slider");
228+ g_menu_append_item(submenu, item);
229+
230+ // Access Point
231+ item = g_menu_item_new("Access Point", "indicator.action.accessPoint");
232+ g_menu_item_set_attribute (item, "x-canonical-type", "s", "unity.widgets.systemsettings.tablet.accesspoint");
233+ g_menu_append_item(submenu, item);
234+
235+
236+ item = g_menu_item_new (NULL, "indicator._header");
237+ g_menu_item_set_attribute (item, "x-canonical-type", "s", "com.canonical.indicator.root");
238+ g_menu_item_set_submenu (item, G_MENU_MODEL (submenu));
239+ indicator.menu = g_menu_new ();
240+ g_menu_append_item (indicator.menu, item);
241+
242+ g_bus_own_name (G_BUS_TYPE_SESSION,
243+ "com.canonical.indicator.mock",
244+ G_BUS_NAME_OWNER_FLAGS_NONE,
245+ bus_acquired,
246+ NULL,
247+ name_lost,
248+ &indicator,
249+ NULL);
250+
251+ loop = g_main_loop_new (NULL, FALSE);
252+ g_main_loop_run (loop);
253+
254+ g_object_unref (submenu);
255+ g_object_unref (item);
256+ g_object_unref (indicator.actions);
257+ g_object_unref (indicator.menu);
258+ g_object_unref (loop);
259+
260+ return 1;
261+}
tests/plugins/Unity/Indicators/indicatorsmanagertest.cpp−2
@@ -31,13 +31,11 @@ private Q_SLOTS:
3131
3232 void initTestCase()
3333 {
34− setenv("UNITY_TEST_ENV", "1", 1);
3534 setenv("XDG_DATA_DIRS", (sourceDirectory() + "/tests/data").toLatin1().data(), 1);
3635 }
3736
3837 void cleanupTestCase()
3938 {
40− unsetenv("UNITY_TEST_ENV");
4139 }
4240
4341 /*
tests/plugins/Unity/Indicators/indicatorsmodeltest.cpp−2
@@ -32,13 +32,11 @@ private Q_SLOTS:
3232
3333 void initTestCase()
3434 {
35− setenv("UNITY_TEST_ENV", "1", 1);
3635 setenv("XDG_DATA_DIRS", (sourceDirectory() + "/tests/data").toLatin1().data(), 1);
3736 }
3837
3938 void cleanupTestCase()
4039 {
41− unsetenv("UNITY_TEST_ENV");
4240 }
4341
4442 /*
tests/qmltests/Panel/Indicators/tst_MenuItemFactory.qml+5−1
@@ -230,7 +230,7 @@ Item {
230230 compare(loader.item.value, data.manualValue, "Slider value does not match manual set value");
231231
232232 menuData.actionState = data.value2;
233− compare(loader.item.value, data.value2, "Value does not match new data");
233+ tryCompare(loader.item, "value", data.value2, 10000, "Value does not match new data");
234234
235235 menuData.actionState = undefined;
236236 compare(loader.item.value, data.value2, "Undefined state should not update slider value");
@@ -819,6 +819,10 @@ Item {
819819 loader.data = menuData;
820820 loader.sourceComponent = factory.load(menuData);
821821
822+ var sync = findInvisibleChild(loader.item, "sync");
823+ verify(sync);
824+ sync.syncTimeout = 500;
825+
822826 compare(loader.item.checked, false, "Loader did not load check state");
823827 mouseClick(loader.item,
824828 loader.item.width / 2, loader.item.height / 2);