diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/CMakeLists.txt | 61 | ||||
-rw-r--r-- | tests/Makefile.am | 64 | ||||
-rw-r--r-- | tests/device-provider-mock.c | 107 | ||||
-rw-r--r-- | tests/device-provider-mock.h | 79 | ||||
-rw-r--r-- | tests/glib-fixture.h | 141 | ||||
-rw-r--r-- | tests/indicator-power-service-cmdline-battery.cc | 127 | ||||
-rw-r--r-- | tests/manual | 87 | ||||
-rw-r--r-- | tests/test-device.cc | 243 | ||||
-rw-r--r-- | tests/test-notify.cc | 410 |
9 files changed, 1140 insertions, 179 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..a0d24af --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,61 @@ +# build libgtest +add_library (gtest STATIC + ${GTEST_SOURCE_DIR}/gtest-all.cc + ${GTEST_SOURCE_DIR}/gtest_main.cc) +set_target_properties (gtest PROPERTIES INCLUDE_DIRECTORIES ${INCLUDE_DIRECTORIES} ${GTEST_INCLUDE_DIR}) +set_target_properties (gtest PROPERTIES COMPILE_FLAGS ${COMPILE_FLAGS} -w) + +# dbustest +pkg_check_modules(DBUSTEST REQUIRED + dbustest-1>=14.04.0) +include_directories (SYSTEM ${DBUSTEST_INCLUDE_DIRS}) + +# add warnings +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${C_WARNING_ARGS}") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-weak-vtables -Wno-global-constructors") # Google Test + +# build the necessary schemas +set_directory_properties (PROPERTIES + ADDITIONAL_MAKE_CLEAN_FILES gschemas.compiled) +set_source_files_properties (gschemas.compiled GENERATED) + +# GSettings: +# compile the indicator-power schema into a gschemas.compiled file in this directory, +# and help the tests to find that file by setting -DSCHEMA_DIR +set (SCHEMA_DIR ${CMAKE_CURRENT_BINARY_DIR}) +add_definitions(-DSCHEMA_DIR="${SCHEMA_DIR}") +execute_process (COMMAND ${PKG_CONFIG_EXECUTABLE} gio-2.0 --variable glib_compile_schemas + OUTPUT_VARIABLE COMPILE_SCHEMA_EXECUTABLE + OUTPUT_STRIP_TRAILING_WHITESPACE) +add_custom_command (OUTPUT gschemas.compiled + DEPENDS ${CMAKE_BINARY_DIR}/data/com.canonical.indicator.power.gschema.xml + COMMAND cp -f ${CMAKE_BINARY_DIR}/data/*gschema.xml ${SCHEMA_DIR} + COMMAND ${COMPILE_SCHEMA_EXECUTABLE} ${SCHEMA_DIR}) + +# look for headers in our src dir, and also in the directories where we autogenerate files... +include_directories (${CMAKE_SOURCE_DIR}/src) +include_directories (${CMAKE_BINARY_DIR}/src) +include_directories (${CMAKE_CURRENT_BINARY_DIR}) + +### +### + +function(add_test_by_name name) + set (TEST_NAME ${name}) + add_executable (${TEST_NAME} ${TEST_NAME}.cc gschemas.compiled) + add_test (${TEST_NAME} ${TEST_NAME}) + add_dependencies (${TEST_NAME} libindicatorpowerservice) + target_link_libraries (${TEST_NAME} indicatorpowerservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) +endfunction() +add_test_by_name(test-notify) +add_test(NAME dear-reader-the-next-test-takes-80-seconds COMMAND true) +add_test_by_name(test-device) + +### +### + +set (APP_NAME indicator-power-service-cmdline-battery) +add_executable (${APP_NAME} ${APP_NAME}.cc device-provider-mock.c) +add_dependencies (${APP_NAME} libindicatorpowerservice) +target_link_libraries (${APP_NAME} indicatorpowerservice ${SERVICE_DEPS_LIBRARIES}) + diff --git a/tests/Makefile.am b/tests/Makefile.am deleted file mode 100644 index 47b3e84..0000000 --- a/tests/Makefile.am +++ /dev/null @@ -1,64 +0,0 @@ -TESTS = -CLEANFILES = -BUILT_SOURCES = -check_PROGRAMS = - -### -### tests: stock tests on user-visible strings -### - -include $(srcdir)/Makefile.am.strings - -### -### gtest library -### - -check_LIBRARIES = libgtest.a -nodist_libgtest_a_SOURCES = \ - $(GTEST_SOURCE)/src/gtest-all.cc \ - $(GTEST_SOURCE)/src/gtest_main.cc - -AM_CPPFLAGS = $(GTEST_CPPFLAGS) -I${top_srcdir}/src -Wall -Werror -std=c++11 -AM_CXXFLAGS = $(GTEST_CXXFLAGS) - -### -### tests: indicator-power-device -### - -TEST_LIBS = $(COVERAGE_LDFLAGS) libgtest.a -lpthread -TEST_CPPFLAGS = $(SERVICE_DEPS_CFLAGS) $(AM_CPPFLAGS) - -TESTS += test-device -check_PROGRAMS += test-device -test_device_SOURCES = test-device.cc -test_device_CPPFLAGS = $(TEST_CPPFLAGS) -Wall -Werror -Wextra -test_device_LDADD = \ - $(top_builddir)/src/libindicatorpower-service.a \ - $(SERVICE_DEPS_LIBS) \ - $(TEST_LIBS) - - -# (FIXME: incomplete) -# -### -### tests: indicator-power-service -### -# -# build a local copy of the GSettings schemas -#BUILT_SOURCES += gschemas.compiled -#CLEANFILES += gschemas.compiled -#gschemas.compiled: Makefile -# @glib-compile-schemas --targetdir=$(abs_builddir) $(top_builddir)/data -# -#TESTS += test-service -#check_PROGRAMS += test-service -#test_service_SOURCES = test-service.cc -#test_service_LDADD = $(TEST_LIBS) -#test_service_CPPFLAGS = $(TEST_CPPFLAGS) -DSCHEMA_DIR="\"$(top_builddir)/tests/\"" -# -#TESTS += test-dbus-listener -#check_PROGRAMS += test-dbus-listener -#test_dbus_listener_SOURCES = test-dbus-listener.cc -#test_dbus_listener_LDADD = $(TEST_LIBS) -#test_dbus_listener_CPPFLAGS = $(TEST_CPPFLAGS) -# diff --git a/tests/device-provider-mock.c b/tests/device-provider-mock.c new file mode 100644 index 0000000..afca178 --- /dev/null +++ b/tests/device-provider-mock.c @@ -0,0 +1,107 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include "device.h" +#include "device-provider.h" +#include "device-provider-mock.h" + +/*** +**** GObject boilerplate +***/ + +static void indicator_power_device_provider_interface_init ( + IndicatorPowerDeviceProviderInterface * iface); + +G_DEFINE_TYPE_WITH_CODE ( + IndicatorPowerDeviceProviderMock, + indicator_power_device_provider_mock, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (INDICATOR_TYPE_POWER_DEVICE_PROVIDER, + indicator_power_device_provider_interface_init)) + +/*** +**** IndicatorPowerDeviceProvider virtual functions +***/ + +static GList * +my_get_devices (IndicatorPowerDeviceProvider * provider) +{ + IndicatorPowerDeviceProviderMock * self = INDICATOR_POWER_DEVICE_PROVIDER_MOCK(provider); + + return g_list_copy_deep (self->devices, (GCopyFunc)g_object_ref, NULL); +} + +/*** +**** GObject virtual functions +***/ + +static void +my_dispose (GObject * o) +{ + IndicatorPowerDeviceProviderMock * self = INDICATOR_POWER_DEVICE_PROVIDER_MOCK(o); + + g_list_free_full (self->devices, g_object_unref); + + G_OBJECT_CLASS (indicator_power_device_provider_mock_parent_class)->dispose (o); +} + +/*** +**** Instantiation +***/ + +static void +indicator_power_device_provider_mock_class_init (IndicatorPowerDeviceProviderMockClass * klass) +{ + GObjectClass * object_class; + + object_class = G_OBJECT_CLASS (klass); + object_class->dispose = my_dispose; +} + +static void +indicator_power_device_provider_interface_init (IndicatorPowerDeviceProviderInterface * iface) +{ + iface->get_devices = my_get_devices; +} + +static void +indicator_power_device_provider_mock_init (IndicatorPowerDeviceProviderMock * self) +{ +} + +/*** +**** Public API +***/ + +IndicatorPowerDeviceProvider * +indicator_power_device_provider_mock_new (void) +{ + gpointer o = g_object_new (INDICATOR_TYPE_POWER_DEVICE_PROVIDER_MOCK, NULL); + + return INDICATOR_POWER_DEVICE_PROVIDER (o); +} + +void +indicator_power_device_provider_add_device (IndicatorPowerDeviceProviderMock * provider, + IndicatorPowerDevice * device) +{ + provider->devices = g_list_append (provider->devices, g_object_ref(device)); + + g_signal_connect_swapped (device, "notify", G_CALLBACK(indicator_power_device_provider_emit_devices_changed), provider); +} diff --git a/tests/device-provider-mock.h b/tests/device-provider-mock.h new file mode 100644 index 0000000..4d06924 --- /dev/null +++ b/tests/device-provider-mock.h @@ -0,0 +1,79 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#ifndef __INDICATOR_POWER_DEVICE_PROVIDER_MOCK__H__ +#define __INDICATOR_POWER_DEVICE_PROVIDER_MOCK__H__ + +#include <glib-object.h> /* parent class */ + +#include "device.h" +#include "device-provider.h" + +G_BEGIN_DECLS + +#define INDICATOR_TYPE_POWER_DEVICE_PROVIDER_MOCK \ + (indicator_power_device_provider_mock_get_type()) + +#define INDICATOR_POWER_DEVICE_PROVIDER_MOCK(o) \ + (G_TYPE_CHECK_INSTANCE_CAST ((o), \ + INDICATOR_TYPE_POWER_DEVICE_PROVIDER_MOCK, \ + IndicatorPowerDeviceProviderMock)) + +#define INDICATOR_POWER_DEVICE_PROVIDER_MOCK_GET_CLASS(o) \ + (G_TYPE_INSTANCE_GET_CLASS ((o), \ + INDICATOR_TYPE_POWER_DEVICE_PROVIDER_MOCK, \ + IndicatorPowerDeviceProviderMockClass)) + +#define INDICATOR_IS_POWER_DEVICE_PROVIDER_MOCK(o) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((o), \ + INDICATOR_TYPE_POWER_DEVICE_PROVIDER_MOCK)) + +typedef struct _IndicatorPowerDeviceProviderMock + IndicatorPowerDeviceProviderMock; +typedef struct _IndicatorPowerDeviceProviderMockPriv + IndicatorPowerDeviceProviderMockPriv; +typedef struct _IndicatorPowerDeviceProviderMockClass + IndicatorPowerDeviceProviderMockClass; + +/** + * An IndicatorPowerDeviceProvider which gets its devices from Mock. + */ +struct _IndicatorPowerDeviceProviderMock +{ + GObject parent_instance; + + /*< private >*/ + GList * devices; +}; + +struct _IndicatorPowerDeviceProviderMockClass +{ + GObjectClass parent_class; +}; + +GType indicator_power_device_provider_mock_get_type (void); + +IndicatorPowerDeviceProvider * indicator_power_device_provider_mock_new (void); + +void indicator_power_device_provider_add_device (IndicatorPowerDeviceProviderMock * provider, + IndicatorPowerDevice * device); + +G_END_DECLS + +#endif /* __INDICATOR_POWER_DEVICE_PROVIDER_MOCK__H__ */ diff --git a/tests/glib-fixture.h b/tests/glib-fixture.h new file mode 100644 index 0000000..ce834a0 --- /dev/null +++ b/tests/glib-fixture.h @@ -0,0 +1,141 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <map> + +#include <glib.h> +#include <glib/gstdio.h> +#include <gio/gio.h> + +#include <gtest/gtest.h> + +#include <locale.h> // setlocale() + +class GlibFixture : public ::testing::Test +{ + private: + + GLogFunc realLogHandler; + + std::map<GLogLevelFlags,size_t> expected_log; + std::map<GLogLevelFlags,std::vector<std::string>> log; + + void test_log_counts() + { + const GLogLevelFlags levels_to_test[] = { G_LOG_LEVEL_ERROR, + G_LOG_LEVEL_CRITICAL, + G_LOG_LEVEL_MESSAGE, + G_LOG_LEVEL_WARNING }; + + for(const auto& level : levels_to_test) + { + const auto& v = log[level]; + const auto n = v.size(); + + EXPECT_EQ(expected_log[level], n); + + if (expected_log[level] != n) + for (size_t i=0; i<n; ++i) + g_print("%d %s\n", (n+1), v[i].c_str()); + } + + expected_log.clear(); + log.clear(); + } + + static void default_log_handler(const gchar * log_domain, + GLogLevelFlags log_level, + const gchar * message, + gpointer self) + { + auto tmp = g_strdup_printf ("%s:%d \"%s\"", log_domain, int(log_level), message); + static_cast<GlibFixture*>(self)->log[log_level].push_back(tmp); + g_free(tmp); + } + + protected: + + void increment_expected_errors(GLogLevelFlags level, size_t n=1) + { + expected_log[level] += n; + } + + virtual void SetUp() + { + setlocale(LC_ALL, "C.UTF-8"); + + loop = g_main_loop_new(nullptr, false); + + g_log_set_default_handler(default_log_handler, this); + + g_unsetenv("DISPLAY"); + } + + virtual void TearDown() + { + test_log_counts(); + + g_log_set_default_handler(realLogHandler, this); + + g_clear_pointer(&loop, g_main_loop_unref); + } + + private: + + static gboolean + wait_for_signal__timeout(gpointer name) + { + g_error("%s: timed out waiting for signal '%s'", G_STRLOC, static_cast<char*>(name)); + return G_SOURCE_REMOVE; + } + + static gboolean + wait_msec__timeout(gpointer loop) + { + g_main_loop_quit(static_cast<GMainLoop*>(loop)); + return G_SOURCE_CONTINUE; + } + + protected: + + /* convenience func to loop while waiting for a GObject's signal */ + void wait_for_signal(gpointer o, const gchar * signal, const guint timeout_seconds=5) + { + // wait for the signal or for timeout, whichever comes first + const auto handler_id = g_signal_connect_swapped(o, signal, + G_CALLBACK(g_main_loop_quit), + loop); + const auto timeout_id = g_timeout_add_seconds(timeout_seconds, + wait_for_signal__timeout, + loop); + g_main_loop_run(loop); + g_source_remove(timeout_id); + g_signal_handler_disconnect(o, handler_id); + } + + /* convenience func to loop for N msec */ + void wait_msec(guint msec=50) + { + const auto id = g_timeout_add(msec, wait_msec__timeout, loop); + g_main_loop_run(loop); + g_source_remove(id); + } + + GMainLoop * loop; +}; diff --git a/tests/indicator-power-service-cmdline-battery.cc b/tests/indicator-power-service-cmdline-battery.cc new file mode 100644 index 0000000..50ed2bb --- /dev/null +++ b/tests/indicator-power-service-cmdline-battery.cc @@ -0,0 +1,127 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + +#include <cstdlib> + +#include <locale.h> // setlocale() +#include <libintl.h> // bindtextdomain() +#include <unistd.h> // STDIN_FILENO + +#include <gio/gio.h> + +#include "device-provider-mock.h" + +#include "service.h" + +/*** +**** +***/ + +static void +on_name_lost (gpointer instance G_GNUC_UNUSED, gpointer loop) +{ + g_message ("exiting: service couldn't acquire or lost ownership of busname"); + g_main_loop_quit (static_cast<GMainLoop*>(loop)); +} + +static IndicatorPowerDevice * battery = nullptr; + +static GMainLoop * loop = nullptr; + +static gboolean on_command_stream_available (GIOChannel *source, + GIOCondition /*condition*/, + gpointer /*user_data*/) +{ + gchar * str = nullptr; + GError * error = nullptr; + auto status = g_io_channel_read_line (source, &str, nullptr, nullptr, &error); + g_assert_no_error (error); + + if (status == G_IO_STATUS_NORMAL) + { + g_strstrip (str); + + if (!g_strcmp0 (str, "charging")) + { + g_object_set (battery, INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_CHARGING, nullptr); + } + else if (!g_strcmp0 (str, "discharging")) + { + g_object_set (battery, INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_DISCHARGING, nullptr); + } + else + { + g_object_set (battery, INDICATOR_POWER_DEVICE_PERCENTAGE, atof(str), nullptr); + } + } + else if (status == G_IO_STATUS_EOF) + { + g_main_loop_quit (loop); + } + + g_free (str); + return G_SOURCE_CONTINUE; +} + +/* this is basically indicator-power-service with a custom provider */ +int +main (int argc G_GNUC_UNUSED, char ** argv G_GNUC_UNUSED) +{ + g_print("This test app has the same code as indicator-power-service\n" + "except instead of listening to UPower, it has a fake battery\n" + "which you can edit with keyboard inputs. Supported commands:\n" + "1. A number in [0..100] to set battery level\n" + "2. 'charging'\n" + "3. 'discharging'\n" + "4. ctrl-c to exit\n"); + + IndicatorPowerDeviceProvider * device_provider; + IndicatorPowerService * service; + + g_assert(g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, true)); + g_assert(g_setenv("GSETTINGS_BACKEND", "memory", true)); + + /* boilerplate i18n */ + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR); + textdomain (GETTEXT_PACKAGE); + + /* read lines from the command line */ + auto channel = g_io_channel_unix_new (STDIN_FILENO); + auto watch_tag = g_io_add_watch (channel, G_IO_IN, on_command_stream_available, nullptr); + + /* run */ + battery = indicator_power_device_new ("/some/path", UP_DEVICE_KIND_BATTERY, 50.0, UP_DEVICE_STATE_DISCHARGING, 30*60); + device_provider = indicator_power_device_provider_mock_new (); + indicator_power_device_provider_add_device (INDICATOR_POWER_DEVICE_PROVIDER_MOCK(device_provider), battery); + service = indicator_power_service_new (device_provider); + loop = g_main_loop_new (NULL, FALSE); + g_signal_connect (service, INDICATOR_POWER_SERVICE_SIGNAL_NAME_LOST, + G_CALLBACK(on_name_lost), loop); + g_main_loop_run (loop); + + /* cleanup */ + g_main_loop_unref (loop); + g_source_remove (watch_tag); + g_io_channel_unref (channel); + g_clear_object (&service); + g_clear_object (&device_provider); + g_clear_object (&battery); + return 0; +} diff --git a/tests/manual b/tests/manual index d3a22e1..1ec1524 100644 --- a/tests/manual +++ b/tests/manual @@ -1,24 +1,89 @@ +Notes on Battery Testing + +When building from source, an executable 'indicator-power-service-cmdline-battery' will be built in the tests/ directory. This has the same code as indicator-power-service, except instead of listening to UPower it has a single fake battery that can be set from the command line to set its charge level and whether it's charging or discharging. + +You'll need to stop the current indicator-power-service before starting the test one. After that, you enter in a number, or 'charging', or 'discharging', to set the fake battery. ctrl-c exits. + +Example: + +$ stop indicator-power # stop the real indicator-power service +$ build/tests/indicator-power-service-cmdline-battery # start the test service +50 # sets the fake battery level to 50% +30 # sets the fake battery level to 30% +charging # sets the fake battery to charging +discharging # sets the fake battery to discharging +ctrl-c # exits the test service +$ start indicator-power # restart the real service + + + Test-case indicator-power/unity7-items-check <dl> - <dt>Log in to a Unity 7 user session</dt> - <dt>Go to the panel and click on the Power indicator</dt> - <dd>Ensure there are items in the menu</dd> + <dt>Log in to a Unity 7 user session</dt> + <dt>Go to the panel and click on the Power indicator</dt> + <dd>Ensure there are items in the menu</dd> </dl> Test-case indicator-power/unity7-greeter-items-check <dl> - <dt>Start a system and wait for the greeter or logout of the current user session</dt> - <dt>Go to the panel and click on the Power indicator</dt> - <dd>Ensure there are items in the menu</dd> + <dt>Start a system and wait for the greeter or logout of the current user session</dt> + <dt>Go to the panel and click on the Power indicator</dt> + <dd>Ensure there are items in the menu</dd> </dl> Test-case indicator-power/unity8-items-check <dl> - <dt>Login to a user session running Unity 8</dt> - <dt>Pull down the top panel until it sticks open</dt> - <dt>Navigate through the tabs until "Battery" is shown</dt> - <dd>Battery is at the top of the menu</dd> - <dd>The menu is populated with items</dd> + <dt>Login to a user session running Unity 8</dt> + <dt>Pull down the top panel until it sticks open</dt> + <dt>Navigate through the tabs until "Battery" is shown</dt> + <dd>Battery is at the top of the menu</dd> + <dd>The menu is populated with items</dd> </dl> +Test-case indicator-power/detect-charging-or-discharging +<dl> + <dt>Begin with a discharging device</dt> + <dd>The indicator's icon should denote a discharging battery; e.g. an icon without the '⚡' sign</dd> + <dt>Plug it in so that its battery is charging</dt> + <dd>The indicator's icon should change to show a charging battery</dd> + <dt>Unplug it again</dt> + <dd>The indicator's icon should revert back to the same one in step one</dd> +</dl> + +Test-case indicator-power/low-power-notifications +<dl> + <dt>Wait for the system's battery level to drop to 10% (or fake it, see 'Notes on Battery Testing' above)</dt> + <dd>A notification should appear</dd> + <dd>Its title should read "Battery Low"</dd> + <dd>Its text should read "10% charge remaining"</dd> + <dd>The icon should be a low power icon</dd> + <dd>It should have two actions, "Battery settings" and "OK". </dd> + <dt>Tap OK to dismiss the popup</dt> + <dt>Wait (or fake) the battery level to drop to 9%</dt> + <dd>No new notification should appear -- we're still at the "Low" level </dd> + <dt>Wait (or fake) the battery level to drop to 4%</dt> + <dd>A notification should appear</dd> + <dd>Its title should read "Battery Critical"</dd> + <dd>Its text should read "4% charge remaining"</dd> + <dd>The icon should be a critical power icon</dd> + <dd>It should have two actions, "Battery settings" and "OK". </dd> + <dt>Tap 'Battery Settings'</dt> + <dd>ubuntu-system-settings should be launched to the Battery page </dd> +</dl> + +Test-case indicator-power/device-brightness-slider +<dl> + <dt>On a device, pull down the power indicator's menu</dt> + <dd>The menu should include a brightness slider with icons</dd> + <dt>Slide the brightness slider back and forth</dt> + <dd>The screen should get brighter and darker in sync with the slider's position</dd> + <dt>Launch unity-system-settings' Brightness panel</dt> + <dt>Move both the indicator's and the settings panel's sliders</dt> + <dd>Both sliders' positions should stay in sync with each other</dd> + <dd>Both should have the same effect on the screen's brightness</dd> + <dt>Make a note of the current brightness level and slider position</dt> + <dt>Reboot the device</dt> + <dd>The screen brightness should be the same as it was before rebooting</dd> + <dd>The indicator's brightness slider should be in the same position as it was before rebooting</dd> +</dl> diff --git a/tests/test-device.cc b/tests/test-device.cc index 4d4a89f..fbeb1c0 100644 --- a/tests/test-device.cc +++ b/tests/test-device.cc @@ -19,6 +19,7 @@ with this program. If not, see <http://www.gnu.org/licenses/>. #include <gio/gio.h> #include <gtest/gtest.h> +#include <cmath> // ceil() #include "device.h" #include "service.h" @@ -171,11 +172,11 @@ TEST_F(DeviceTest, Properties) key = INDICATOR_POWER_DEVICE_PERCENTAGE; g_object_set (o, key, 50.0, NULL); g_object_get (o, key, &d, NULL); - ASSERT_EQ((int)d, 50); + ASSERT_EQ(int(d), 50); // TIME key = INDICATOR_POWER_DEVICE_TIME; - g_object_set (o, key, (guint64)30, NULL); + g_object_set (o, key, guint64(30), NULL); g_object_get (o, key, &u64, NULL); ASSERT_EQ(u64, 30); @@ -192,11 +193,11 @@ TEST_F(DeviceTest, New) 30); ASSERT_TRUE (device != NULL); ASSERT_TRUE (INDICATOR_IS_POWER_DEVICE(device)); - ASSERT_EQ (indicator_power_device_get_kind(device), UP_DEVICE_KIND_BATTERY); - ASSERT_EQ (indicator_power_device_get_state(device), UP_DEVICE_STATE_CHARGING); - ASSERT_STREQ (indicator_power_device_get_object_path(device), "/object/path"); - ASSERT_EQ ((int)indicator_power_device_get_percentage(device), 50); - ASSERT_EQ (indicator_power_device_get_time(device), 30); + ASSERT_EQ (UP_DEVICE_KIND_BATTERY, indicator_power_device_get_kind(device)); + ASSERT_EQ (UP_DEVICE_STATE_CHARGING, indicator_power_device_get_state(device)); + ASSERT_STREQ ("/object/path", indicator_power_device_get_object_path(device)); + ASSERT_EQ (50, int(indicator_power_device_get_percentage(device))); + ASSERT_EQ (30, indicator_power_device_get_time(device)); // cleanup g_object_unref (device); @@ -204,22 +205,22 @@ TEST_F(DeviceTest, New) TEST_F(DeviceTest, NewFromVariant) { - GVariant * variant = g_variant_new ("(susdut)", - "/object/path", - (guint32) UP_DEVICE_KIND_BATTERY, - "icon", - (gdouble) 50.0, - (guint32) UP_DEVICE_STATE_CHARGING, - (guint64) 30); + auto variant = g_variant_new("(susdut)", + "/object/path", + guint32(UP_DEVICE_KIND_BATTERY), + "icon", + 50.0, + guint32(UP_DEVICE_STATE_CHARGING), + guint64(30)); IndicatorPowerDevice * device = indicator_power_device_new_from_variant (variant); ASSERT_TRUE (variant != NULL); ASSERT_TRUE (device != NULL); ASSERT_TRUE (INDICATOR_IS_POWER_DEVICE(device)); - ASSERT_EQ (indicator_power_device_get_kind(device), UP_DEVICE_KIND_BATTERY); - ASSERT_EQ (indicator_power_device_get_state(device), UP_DEVICE_STATE_CHARGING); - ASSERT_STREQ (indicator_power_device_get_object_path(device), "/object/path"); - ASSERT_EQ ((int)indicator_power_device_get_percentage(device), 50); - ASSERT_EQ (indicator_power_device_get_time(device), 30); + ASSERT_EQ (UP_DEVICE_KIND_BATTERY, indicator_power_device_get_kind(device)); + ASSERT_EQ (UP_DEVICE_STATE_CHARGING, indicator_power_device_get_state(device)); + ASSERT_STREQ ("/object/path", indicator_power_device_get_object_path(device)); + ASSERT_EQ (50, int(indicator_power_device_get_percentage(device))); + ASSERT_EQ (30, indicator_power_device_get_time(device)); // cleanup g_object_unref (device); @@ -323,8 +324,9 @@ TEST_F(DeviceTest, IconNames) INDICATOR_POWER_DEVICE_PERCENTAGE, 95.0, NULL); - g_string_append_printf (expected, "%s-full-charging-symbolic;", kind_str); + g_string_append_printf (expected, "%s-100-charging;", kind_str); g_string_append_printf (expected, "gpm-%s-100-charging;", kind_str); + g_string_append_printf (expected, "%s-full-charging-symbolic;", kind_str); g_string_append_printf (expected, "%s-full-charging", kind_str); check_icon_names (device, expected->str); g_string_truncate (expected, 0); @@ -334,8 +336,9 @@ TEST_F(DeviceTest, IconNames) INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_CHARGING, INDICATOR_POWER_DEVICE_PERCENTAGE, 85.0, NULL); - g_string_append_printf (expected, "%s-full-charging-symbolic;", kind_str); + g_string_append_printf (expected, "%s-080-charging;", kind_str); g_string_append_printf (expected, "gpm-%s-080-charging;", kind_str); + g_string_append_printf (expected, "%s-full-charging-symbolic;", kind_str); g_string_append_printf (expected, "%s-full-charging", kind_str); check_icon_names (device, expected->str); g_string_truncate (expected, 0); @@ -345,8 +348,9 @@ TEST_F(DeviceTest, IconNames) INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_CHARGING, INDICATOR_POWER_DEVICE_PERCENTAGE, 50.0, NULL); - g_string_append_printf (expected, "%s-good-charging-symbolic;", kind_str); + g_string_append_printf (expected, "%s-060-charging;", kind_str); g_string_append_printf (expected, "gpm-%s-060-charging;", kind_str); + g_string_append_printf (expected, "%s-good-charging-symbolic;", kind_str); g_string_append_printf (expected, "%s-good-charging", kind_str); check_icon_names (device, expected->str); g_string_truncate (expected, 0); @@ -356,8 +360,9 @@ TEST_F(DeviceTest, IconNames) INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_CHARGING, INDICATOR_POWER_DEVICE_PERCENTAGE, 25.0, NULL); - g_string_append_printf (expected, "%s-low-charging-symbolic;", kind_str); + g_string_append_printf (expected, "%s-020-charging;", kind_str); g_string_append_printf (expected, "gpm-%s-020-charging;", kind_str); + g_string_append_printf (expected, "%s-low-charging-symbolic;", kind_str); g_string_append_printf (expected, "%s-low-charging", kind_str); check_icon_names (device, expected->str); g_string_truncate (expected, 0); @@ -367,8 +372,9 @@ TEST_F(DeviceTest, IconNames) INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_CHARGING, INDICATOR_POWER_DEVICE_PERCENTAGE, 5.0, NULL); - g_string_append_printf (expected, "%s-caution-charging-symbolic;", kind_str); + g_string_append_printf (expected, "%s-000-charging;", kind_str); g_string_append_printf (expected, "gpm-%s-000-charging;", kind_str); + g_string_append_printf (expected, "%s-caution-charging-symbolic;", kind_str); g_string_append_printf (expected, "%s-caution-charging", kind_str); check_icon_names (device, expected->str); g_string_truncate (expected, 0); @@ -401,7 +407,7 @@ TEST_F(DeviceTest, IconNames) g_object_set (o, INDICATOR_POWER_DEVICE_KIND, kind, INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_DISCHARGING, INDICATOR_POWER_DEVICE_PERCENTAGE, 50.0, - INDICATOR_POWER_DEVICE_TIME, (guint64)(60*60), + INDICATOR_POWER_DEVICE_TIME, guint64(60*60), NULL); g_string_append_printf (expected, "%s-060;", kind_str); g_string_append_printf (expected, "gpm-%s-060;", kind_str); @@ -414,12 +420,12 @@ TEST_F(DeviceTest, IconNames) g_object_set (o, INDICATOR_POWER_DEVICE_KIND, kind, INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_DISCHARGING, INDICATOR_POWER_DEVICE_PERCENTAGE, 25.0, - INDICATOR_POWER_DEVICE_TIME, (guint64)(60*60), + INDICATOR_POWER_DEVICE_TIME, guint64(60*60), NULL); - g_string_append_printf (expected, "%s-040;", kind_str); - g_string_append_printf (expected, "gpm-%s-040;", kind_str); - g_string_append_printf (expected, "%s-good-symbolic;", kind_str); - g_string_append_printf (expected, "%s-good", kind_str); + g_string_append_printf (expected, "%s-020;", kind_str); + g_string_append_printf (expected, "gpm-%s-020;", kind_str); + g_string_append_printf (expected, "%s-low-symbolic;", kind_str); + g_string_append_printf (expected, "%s-low", kind_str); check_icon_names (device, expected->str); g_string_truncate (expected, 0); @@ -427,7 +433,7 @@ TEST_F(DeviceTest, IconNames) g_object_set (o, INDICATOR_POWER_DEVICE_KIND, kind, INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_DISCHARGING, INDICATOR_POWER_DEVICE_PERCENTAGE, 25.0, - INDICATOR_POWER_DEVICE_TIME, (guint64)(60*15), + INDICATOR_POWER_DEVICE_TIME, guint64(60*15), NULL); g_string_append_printf (expected, "%s-020;", kind_str); g_string_append_printf (expected, "gpm-%s-020;", kind_str); @@ -440,12 +446,12 @@ TEST_F(DeviceTest, IconNames) g_object_set (o, INDICATOR_POWER_DEVICE_KIND, kind, INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_DISCHARGING, INDICATOR_POWER_DEVICE_PERCENTAGE, 5.0, - INDICATOR_POWER_DEVICE_TIME, (guint64)(60*60), + INDICATOR_POWER_DEVICE_TIME, guint64(60*60), NULL); - g_string_append_printf (expected, "%s-040;", kind_str); - g_string_append_printf (expected, "gpm-%s-040;", kind_str); - g_string_append_printf (expected, "%s-good-symbolic;", kind_str); - g_string_append_printf (expected, "%s-good", kind_str); + g_string_append_printf (expected, "%s-000;", kind_str); + g_string_append_printf (expected, "gpm-%s-000;", kind_str); + g_string_append_printf (expected, "%s-caution-symbolic;", kind_str); + g_string_append_printf (expected, "%s-caution", kind_str); check_icon_names (device, expected->str); g_string_truncate (expected, 0); @@ -453,7 +459,7 @@ TEST_F(DeviceTest, IconNames) g_object_set (o, INDICATOR_POWER_DEVICE_KIND, kind, INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_DISCHARGING, INDICATOR_POWER_DEVICE_PERCENTAGE, 5.0, - INDICATOR_POWER_DEVICE_TIME, (guint64)(60*15), + INDICATOR_POWER_DEVICE_TIME, guint64(60*15), NULL); g_string_append_printf (expected, "%s-000;", kind_str); g_string_append_printf (expected, "gpm-%s-000;", kind_str); @@ -489,12 +495,12 @@ TEST_F(DeviceTest, Labels) log_count_ipower_expected++; check_label (NULL, NULL); log_count_ipower_expected += 5; - check_header (NULL, NULL, NULL, NULL, NULL); + check_header (nullptr, nullptr, nullptr, nullptr, nullptr); // bad args: a GObject that isn't a device GObject * o = G_OBJECT(g_cancellable_new()); log_count_ipower_expected++; - check_label ((IndicatorPowerDevice*)o, NULL); + check_label (INDICATOR_POWER_DEVICE(o), nullptr); log_count_ipower_expected += 5; check_header (NULL, NULL, NULL, NULL, NULL); g_object_unref (o); @@ -668,77 +674,106 @@ TEST_F(DeviceTest, Inestimable___this_takes_80_seconds) g_free (real_lang); } - -/* The menu title should tell you at a glance what you need to know most: what - device will lose power soonest (and optionally when), or otherwise which - device will take longest to charge (and optionally how long it will take). */ +/* If a device has multiple batteries and uses only one of them at a time, + they should be presented as separate items inside the battery menu, + but everywhere else they should be aggregated (bug 880881). + Their percentages should be averaged. If any are discharging, + the aggregated time remaining should be the maximum of the times + for all those that are discharging, plus the sum of the times + for all those that are idle. Otherwise, the aggregated time remaining + should be the the maximum of the times for all those that are charging. */ TEST_F(DeviceTest, ChoosePrimary) { - GList * device_list; - IndicatorPowerDevice * a; - IndicatorPowerDevice * b; - - a = indicator_power_device_new ("/org/freedesktop/UPower/devices/mouse", - UP_DEVICE_KIND_MOUSE, - 0.0, - UP_DEVICE_STATE_DISCHARGING, - 0); - b = indicator_power_device_new ("/org/freedesktop/UPower/devices/battery", - UP_DEVICE_KIND_BATTERY, - 0.0, - UP_DEVICE_STATE_DISCHARGING, - 0); - - /* device states + time left to {discharge,charge} + % of charge left, - sorted in order of preference wrt the spec's criteria. - So tests[i] should be picked over any test with an index greater than i */ - struct { - int kind; - int state; + struct Description + { + const char * path; + UpDeviceKind kind; + UpDeviceState state; guint64 time; double percentage; - } tests[] = { - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 49, 50.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 50, 50.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 50, 100.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 51, 50.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 50, 50.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 49, 50.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 49, 100.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 48, 50.0 }, - { UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_FULLY_CHARGED, 0, 50.0 }, - { UP_DEVICE_KIND_KEYBOARD, UP_DEVICE_STATE_FULLY_CHARGED, 0, 50.0 }, - { UP_DEVICE_KIND_LINE_POWER, UP_DEVICE_STATE_UNKNOWN, 0, 0.0 } }; - device_list = NULL; - device_list = g_list_append (device_list, a); - device_list = g_list_append (device_list, b); + const Description descriptions[] = { + { "/some/path/d0", UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 10, 60.0 }, // 0 + { "/some/path/d1", UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 20, 80.0 }, // 1 + { "/some/path/d2", UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 30, 100.0 }, // 2 - for (int i=0, n=G_N_ELEMENTS(tests); i<n; i++) - { - for (int j=i+1; j<n; j++) - { - g_object_set (a, INDICATOR_POWER_DEVICE_KIND, tests[i].kind, - INDICATOR_POWER_DEVICE_STATE, tests[i].state, - INDICATOR_POWER_DEVICE_TIME, guint64(tests[i].time), - INDICATOR_POWER_DEVICE_PERCENTAGE, tests[i].percentage, - NULL); - g_object_set (b, INDICATOR_POWER_DEVICE_KIND, tests[j].kind, - INDICATOR_POWER_DEVICE_STATE, tests[j].state, - INDICATOR_POWER_DEVICE_TIME, guint64(tests[j].time), - INDICATOR_POWER_DEVICE_PERCENTAGE, tests[j].percentage, - NULL); - ASSERT_EQ (a, indicator_power_service_choose_primary_device(device_list)); - g_object_unref (G_OBJECT(a)); - - /* reverse the list to check that list order doesn't matter */ - device_list = g_list_reverse (device_list); - ASSERT_EQ (a, indicator_power_service_choose_primary_device(device_list)); - g_object_unref (G_OBJECT(a)); - } - } + { "/some/path/c0", UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 10, 60.0 }, // 3 + { "/some/path/c1", UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 20, 80.0 }, // 4 + { "/some/path/c2", UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 30, 100.0 }, // 5 - // cleanup - g_list_free_full (device_list, g_object_unref); + { "/some/path/f0", UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_FULLY_CHARGED, 0, 100.0 }, // 6 + { "/some/path/m0", UP_DEVICE_KIND_MOUSE, UP_DEVICE_STATE_DISCHARGING, 20, 80.0 }, // 7 + { "/some/path/m1", UP_DEVICE_KIND_MOUSE, UP_DEVICE_STATE_FULLY_CHARGED, 0, 100.0 }, // 8 + { "/some/path/pw", UP_DEVICE_KIND_LINE_POWER, UP_DEVICE_STATE_UNKNOWN, 0, 0.0 } // 9 + }; + + std::vector<IndicatorPowerDevice*> devices; + for(const auto& desc : descriptions) + devices.push_back(indicator_power_device_new(desc.path, desc.kind, desc.percentage, desc.state, time_t(desc.time))); + + const struct { + std::vector<unsigned int> device_indices; + Description expected; + } tests[] = { + + { { 0 }, descriptions[0] }, // 1 discharging + { { 0, 1 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 20, 70.0 } }, // 2 discharging + { { 1, 2 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 30, 90.0 } }, // 2 discharging + { { 0, 1, 2 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 30, 80.0 } }, // 3 discharging + + { { 3 }, descriptions[3] }, // 1 charging + { { 3, 4 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 20, 70.0 } }, // 2 charging + { { 4, 5 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 30, 90.0 } }, // 2 charging + { { 3, 4, 5 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 30, 80.0 } }, // 3 charging + + { { 6 }, descriptions[6] }, // 1 charged + { { 6, 0 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 10, 80.0 } }, // 1 charged, 1 discharging + { { 6, 3 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_CHARGING, 10, 80.0 } }, // 1 charged, 1 charging + { { 6, 0, 3 }, { nullptr, UP_DEVICE_KIND_BATTERY, UP_DEVICE_STATE_DISCHARGING, 10, 73.3 } }, // 1 charged, 1 charging, 1 discharging + + { { 0, 7 }, descriptions[0] }, // 1 discharging battery, 1 discharging mouse. pick the one with the least time left. + { { 2, 7 }, descriptions[7] }, // 1 discharging battery, 1 discharging mouse. pick the one with the least time left. + + { { 0, 8 }, descriptions[0] }, // 1 discharging battery, 1 fully-charged mouse. pick the one that's discharging. + { { 6, 7 }, descriptions[7] }, // 1 discharging mouse, 1 fully-charged battery. pick the one that's discharging. + + { { 0, 9 }, descriptions[0] }, // everything comes before power lines + { { 3, 9 }, descriptions[3] }, + { { 7, 9 }, descriptions[7] } + }; + + for(const auto& test : tests) + { + const auto& x = test.expected; + + GList * device_glist = nullptr; + for(const auto& i : test.device_indices) + device_glist = g_list_append(device_glist, devices[i]); + + auto primary = indicator_power_service_choose_primary_device(device_glist); + EXPECT_STREQ(x.path, indicator_power_device_get_object_path(primary)); + EXPECT_EQ(x.kind, indicator_power_device_get_kind(primary)); + EXPECT_EQ(x.state, indicator_power_device_get_state(primary)); + EXPECT_EQ(x.time, indicator_power_device_get_time(primary)); + EXPECT_EQ(int(ceil(x.percentage)), int(ceil(indicator_power_device_get_percentage(primary)))); + g_object_unref(primary); + + // reverse the list and repeat the test + // to confirm that list order doesn't matter + device_glist = g_list_reverse (device_glist); + primary = indicator_power_service_choose_primary_device (device_glist); + EXPECT_STREQ(x.path, indicator_power_device_get_object_path(primary)); + EXPECT_EQ(x.kind, indicator_power_device_get_kind(primary)); + EXPECT_EQ(x.state, indicator_power_device_get_state(primary)); + EXPECT_EQ(x.time, indicator_power_device_get_time(primary)); + EXPECT_EQ(int(ceil(x.percentage)), int(ceil(indicator_power_device_get_percentage(primary)))); + g_object_unref(primary); + + // cleanup + g_list_free(device_glist); + } + + for (auto& device : devices) + g_object_unref (device); } diff --git a/tests/test-notify.cc b/tests/test-notify.cc new file mode 100644 index 0000000..d056f3f --- /dev/null +++ b/tests/test-notify.cc @@ -0,0 +1,410 @@ +/* + * Copyright 2014 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, 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/>. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + */ + + +#include "glib-fixture.h" + +#include "dbus-shared.h" +#include "device.h" +#include "notifier.h" + +#include <gtest/gtest.h> + +#include <libdbustest/dbus-test.h> + +#include <libnotify/notify.h> + +#include <glib.h> +#include <gio/gio.h> + +/*** +**** +***/ + +class NotifyFixture: public GlibFixture +{ +private: + + typedef GlibFixture super; + + static constexpr char const * NOTIFY_BUSNAME {"org.freedesktop.Notifications"}; + static constexpr char const * NOTIFY_INTERFACE {"org.freedesktop.Notifications"}; + static constexpr char const * NOTIFY_PATH {"/org/freedesktop/Notifications"}; + +protected: + + DbusTestService * service = nullptr; + DbusTestDbusMock * mock = nullptr; + DbusTestDbusMockObject * obj = nullptr; + GDBusConnection * bus = nullptr; + + static constexpr int FIRST_NOTIFY_ID {1234}; + + static constexpr int NOTIFICATION_CLOSED_EXPIRED {1}; + static constexpr int NOTIFICATION_CLOSED_DISMISSED {2}; + static constexpr int NOTIFICATION_CLOSED_API {3}; + static constexpr int NOTIFICATION_CLOSED_UNDEFINED {4}; + + static constexpr char const * APP_NAME {"indicator-power-service"}; + + static constexpr char const * METHOD_CLOSE {"CloseNotification"}; + static constexpr char const * METHOD_NOTIFY {"Notify"}; + static constexpr char const * METHOD_GET_CAPS {"GetCapabilities"}; + static constexpr char const * METHOD_GET_INFO {"GetServerInformation"}; + static constexpr char const * SIGNAL_CLOSED {"NotificationClosed"}; + + static constexpr char const * HINT_TIMEOUT {"x-canonical-snap-decisions-timeout"}; + +protected: + + void SetUp() + { + super::SetUp(); + + // init DBusMock / dbus-test-runner + + service = dbus_test_service_new(nullptr); + + GError * error = nullptr; + mock = dbus_test_dbus_mock_new(NOTIFY_BUSNAME); + obj = dbus_test_dbus_mock_get_object(mock, + NOTIFY_PATH, + NOTIFY_INTERFACE, + &error); + g_assert_no_error (error); + + // METHOD_GET_INFO + dbus_test_dbus_mock_object_add_method(mock, obj, METHOD_GET_INFO, + nullptr, + G_VARIANT_TYPE("(ssss)"), + "ret = ('mock-notify', 'test vendor', '1.0', '1.1')", + &error); + g_assert_no_error (error); + + // METHOD_NOTIFY + auto str = g_strdup_printf("try:\n" + " self.NextNotifyId\n" + "except AttributeError:\n" + " self.NextNotifyId = %d\n" + "ret = self.NextNotifyId\n" + "self.NextNotifyId += 1\n", + FIRST_NOTIFY_ID); + dbus_test_dbus_mock_object_add_method(mock, obj, METHOD_NOTIFY, + G_VARIANT_TYPE("(susssasa{sv}i)"), + G_VARIANT_TYPE_UINT32, + str, + &error); + g_assert_no_error (error); + g_free (str); + + // METHOD_CLOSE + str = g_strdup_printf("self.EmitSignal('%s', '%s', 'uu', [ args[0], %d ])", + NOTIFY_INTERFACE, + SIGNAL_CLOSED, + NOTIFICATION_CLOSED_API); + dbus_test_dbus_mock_object_add_method(mock, obj, METHOD_CLOSE, + G_VARIANT_TYPE("(u)"), + nullptr, + str, + &error); + g_assert_no_error (error); + g_free (str); + + dbus_test_service_add_task(service, DBUS_TEST_TASK(mock)); + dbus_test_service_start_tasks(service); + + bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); + g_dbus_connection_set_exit_on_close(bus, FALSE); + g_object_add_weak_pointer(G_OBJECT(bus), reinterpret_cast<gpointer*>(&bus)); + + notify_init(APP_NAME); + } + + virtual void TearDown() + { + notify_uninit(); + + g_clear_object(&mock); + g_clear_object(&service); + g_object_unref(bus); + + // wait a little while for the scaffolding to shut down, + // but don't block on it forever... + unsigned int cleartry = 0; + while ((bus != nullptr) && (cleartry < 50)) + { + g_usleep(100000); + while (g_main_pending()) + g_main_iteration(true); + cleartry++; + } + + super::TearDown(); + } +}; + +/*** +**** +***/ + +// simple test to confirm the NotifyFixture plumbing all works +TEST_F(NotifyFixture, HelloWorld) +{ +} + +/*** +**** +***/ + + +namespace +{ + static constexpr double percent_critical {2.0}; + static constexpr double percent_very_low {5.0}; + static constexpr double percent_low {10.0}; + + void set_battery_percentage (IndicatorPowerDevice * battery, gdouble p) + { + g_object_set (battery, INDICATOR_POWER_DEVICE_PERCENTAGE, p, nullptr); + } +} + +TEST_F(NotifyFixture, PercentageToLevel) +{ + auto battery = indicator_power_device_new ("/object/path", + UP_DEVICE_KIND_BATTERY, + 50.0, + UP_DEVICE_STATE_DISCHARGING, + 30); + + // confirm that the power levels trigger at the right percentages + for (int i=100; i>=0; --i) + { + set_battery_percentage (battery, i); + const auto level = indicator_power_notifier_get_power_level(battery); + + if (i <= percent_critical) + EXPECT_STREQ (POWER_LEVEL_STR_CRITICAL, level); + else if (i <= percent_very_low) + EXPECT_STREQ (POWER_LEVEL_STR_VERY_LOW, level); + else if (i <= percent_low) + EXPECT_STREQ (POWER_LEVEL_STR_LOW, level); + else + EXPECT_STREQ (POWER_LEVEL_STR_OK, level); + } + + g_object_unref (battery); +} + +/*** +**** +***/ + +// scaffolding to monitor PropertyChanged signals +namespace +{ + enum + { + FIELD_POWER_LEVEL = (1<<0), + FIELD_IS_WARNING = (1<<1) + }; + + struct ChangedParams + { + std::string power_level = POWER_LEVEL_STR_OK; + bool is_warning = false; + uint32_t fields = 0; + }; + + void on_battery_property_changed (GDBusConnection *connection G_GNUC_UNUSED, + const gchar *sender_name G_GNUC_UNUSED, + const gchar *object_path G_GNUC_UNUSED, + const gchar *interface_name G_GNUC_UNUSED, + const gchar *signal_name G_GNUC_UNUSED, + GVariant *parameters, + gpointer gchanged_params) + { + g_return_if_fail (g_variant_n_children (parameters) == 3); + auto dict = g_variant_get_child_value (parameters, 1); + g_return_if_fail (g_variant_is_of_type (dict, G_VARIANT_TYPE_DICTIONARY)); + auto changed_params = static_cast<ChangedParams*>(gchanged_params); + + const char * power_level; + if (g_variant_lookup (dict, "PowerLevel", "&s", &power_level, nullptr)) + { + changed_params->power_level = power_level; + changed_params->fields |= FIELD_POWER_LEVEL; + } + + gboolean is_warning; + if (g_variant_lookup (dict, "IsWarning", "b", &is_warning, nullptr)) + { + changed_params->is_warning = is_warning; + changed_params->fields |= FIELD_IS_WARNING; + } + + g_variant_unref (dict); + } +} + +TEST_F(NotifyFixture, LevelsDuringBatteryDrain) +{ + auto battery = indicator_power_device_new ("/object/path", + UP_DEVICE_KIND_BATTERY, + 50.0, + UP_DEVICE_STATE_DISCHARGING, + 30); + + // set up a notifier and give it the battery so changing the battery's + // charge should show up on the bus. + auto notifier = indicator_power_notifier_new (); + indicator_power_notifier_set_battery (notifier, battery); + indicator_power_notifier_set_bus (notifier, bus); + wait_msec(); + + ChangedParams changed_params; + auto sub_tag = g_dbus_connection_signal_subscribe (bus, + nullptr, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + BUS_PATH"/Battery", + nullptr, + G_DBUS_SIGNAL_FLAGS_NONE, + on_battery_property_changed, + &changed_params, + nullptr); + + // confirm that draining the battery puts + // the power_level change through its paces + for (int i=100; i>=0; --i) + { + changed_params = ChangedParams(); + EXPECT_TRUE (changed_params.fields == 0); + + const auto old_level = indicator_power_notifier_get_power_level(battery); + set_battery_percentage (battery, i); + const auto new_level = indicator_power_notifier_get_power_level(battery); + wait_msec(); + + if (old_level == new_level) + { + EXPECT_EQ (0, (changed_params.fields & FIELD_POWER_LEVEL)); + } + else + { + EXPECT_EQ (FIELD_POWER_LEVEL, (changed_params.fields & FIELD_POWER_LEVEL)); + EXPECT_EQ (new_level, changed_params.power_level); + } + } + + // cleanup + g_dbus_connection_signal_unsubscribe (bus, sub_tag); + g_object_unref (notifier); + g_object_unref (battery); +} + +/*** +**** +***/ + +TEST_F(NotifyFixture, EventsThatChangeNotifications) +{ + // GetCapabilities returns an array containing 'actions', so that we'll + // get snap decisions and the 'IsWarning' property + GError * error = nullptr; + dbus_test_dbus_mock_object_add_method (mock, + obj, + METHOD_GET_CAPS, + nullptr, + G_VARIANT_TYPE_STRING_ARRAY, + "ret = ['actions', 'body']", + &error); + g_assert_no_error (error); + + auto battery = indicator_power_device_new ("/object/path", + UP_DEVICE_KIND_BATTERY, + percent_low + 1.0, + UP_DEVICE_STATE_DISCHARGING, + 30); + + // set up a notifier and give it the battery so changing the battery's + // charge should show up on the bus. + auto notifier = indicator_power_notifier_new (); + indicator_power_notifier_set_battery (notifier, battery); + indicator_power_notifier_set_bus (notifier, bus); + ChangedParams changed_params; + auto sub_tag = g_dbus_connection_signal_subscribe (bus, + nullptr, + "org.freedesktop.DBus.Properties", + "PropertiesChanged", + BUS_PATH"/Battery", + nullptr, + G_DBUS_SIGNAL_FLAGS_NONE, + on_battery_property_changed, + &changed_params, + nullptr); + + // test setup case + wait_msec(); + EXPECT_STREQ (POWER_LEVEL_STR_OK, changed_params.power_level.c_str()); + + // change the percent past the 'low' threshold and confirm that + // a) the power level changes + // b) we get a notification + changed_params = ChangedParams(); + set_battery_percentage (battery, percent_low); + wait_msec(); + EXPECT_EQ (FIELD_POWER_LEVEL|FIELD_IS_WARNING, changed_params.fields); + EXPECT_EQ (indicator_power_notifier_get_power_level(battery), changed_params.power_level); + EXPECT_TRUE (changed_params.is_warning); + + // now test that the warning changes if the level goes down even lower... + changed_params = ChangedParams(); + set_battery_percentage (battery, percent_very_low); + wait_msec(); + EXPECT_EQ (FIELD_POWER_LEVEL, changed_params.fields); + EXPECT_STREQ (POWER_LEVEL_STR_VERY_LOW, changed_params.power_level.c_str()); + + // ...and that the warning is taken down if the battery is plugged back in... + changed_params = ChangedParams(); + g_object_set (battery, INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_CHARGING, nullptr); + wait_msec(); + EXPECT_EQ (FIELD_IS_WARNING, changed_params.fields); + EXPECT_FALSE (changed_params.is_warning); + + // ...and that it comes back if we unplug again... + changed_params = ChangedParams(); + g_object_set (battery, INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_DISCHARGING, nullptr); + wait_msec(); + EXPECT_EQ (FIELD_IS_WARNING, changed_params.fields); + EXPECT_TRUE (changed_params.is_warning); + + // ...and that it's taken down if the power level is OK + changed_params = ChangedParams(); + set_battery_percentage (battery, percent_low+1); + wait_msec(); + EXPECT_EQ (FIELD_POWER_LEVEL|FIELD_IS_WARNING, changed_params.fields); + EXPECT_STREQ (POWER_LEVEL_STR_OK, changed_params.power_level.c_str()); + EXPECT_FALSE (changed_params.is_warning); + + // cleanup + g_dbus_connection_signal_unsubscribe (bus, sub_tag); + g_object_unref (notifier); + g_object_unref (battery); +} |