aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/CMakeLists.txt61
-rw-r--r--tests/Makefile.am64
-rw-r--r--tests/device-provider-mock.c107
-rw-r--r--tests/device-provider-mock.h79
-rw-r--r--tests/glib-fixture.h141
-rw-r--r--tests/indicator-power-service-cmdline-battery.cc124
-rw-r--r--tests/manual17
-rw-r--r--tests/test-device.cc350
-rw-r--r--tests/test-notify.cc410
9 files changed, 1175 insertions, 178 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 5c7d802..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
-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..d333ab2
--- /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, (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..a7a86a1
--- /dev/null
+++ b/tests/indicator-power-service-cmdline-battery.cc
@@ -0,0 +1,124 @@
+/*
+ * 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 ((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_message ("This app is basically the same as indicator-power-service but,\n"
+ "instead of the system's real devices, sees a single fake battery\n"
+ "which can be manipulated by typing commands:\n"
+ "'charging', 'discharging', a charge percentage, or ctrl-c.");
+
+ 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..a542cac 100644
--- a/tests/manual
+++ b/tests/manual
@@ -22,3 +22,20 @@ Test-case indicator-power/unity8-items-check
<dd>The menu is populated with items</dd>
</dl>
+Test-case indicator-power/low-battery-popups
+<dl>
+ <dt>Open a terminal</dt>
+ <dt>Stop the currently-running power indicator: "stop indicator-power"</dt>
+ <dt>Start the fake battery harness in the tests/build/ directory: "indicator-power-service-cmdline-battery"</dt>
+ <dd>Battery indicator should update, showing a discharging battery with a 50% charge</dd>
+ <dt>Type: "10" (no quotes) and press Enter</dt>
+ <dd>A popup should appear saying 'Battery low - 10% charge remaining'</dd>
+ <dd>Battery indicator's icon should show a low charge</dd>
+ <dd>Battery indicator's "Charge level" menuitem should show a 10% charge</dd>
+ <dt>Type: "9" (no quotes) and press Enter</dt>
+ <dd>The 'Battery low' popup should NOT appear, since we've already been notified</dd>
+ <dd>Battery indicator's "Charge level" menuitem should show a 9% charge</dd>
+ <dt>Type: "5" (no quotes) and press Enter</dt>
+ <dd>No 'Battery low' popup SHOULD appear, since 5% is the next warning threshold</dd>
+ <dd>Battery indicator's "Charge level" menuitem should show a 5% charge</dd>
+</dl>
diff --git a/tests/test-device.cc b/tests/test-device.cc
index 2762d4a..85ba237 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"
@@ -72,11 +73,8 @@ class DeviceTest : public ::testing::Test
void check_label (const IndicatorPowerDevice * device,
const char * expected_label)
{
- char * label;
-
- label = indicator_power_device_get_label (device);
+ char * label = indicator_power_device_get_readable_text (device);
EXPECT_STREQ (expected_label, label);
-
g_free (label);
}
@@ -86,31 +84,39 @@ class DeviceTest : public ::testing::Test
const char * expected_percent,
const char * expected_a11y)
{
- char * label;
- char * a11y;
-
- indicator_power_device_get_header (device, true, true, &label, &a11y);
- EXPECT_STREQ (expected_time_and_percent, label);
- EXPECT_STREQ (expected_a11y, a11y);
- g_free (label);
- g_free (a11y);
-
- indicator_power_device_get_header (device, true, false, &label, &a11y);
- EXPECT_STREQ (expected_time, label);
- EXPECT_STREQ (expected_a11y, a11y);
- g_free (label);
- g_free (a11y);
-
- indicator_power_device_get_header (device, false, true, &label, &a11y);
- EXPECT_STREQ (expected_percent, label);
- EXPECT_STREQ (expected_a11y, a11y);
- g_free (label);
- g_free (a11y);
-
- indicator_power_device_get_header (device, false, false, &label, &a11y);
- ASSERT_TRUE (!label || !*label);
- EXPECT_STREQ (expected_a11y, a11y);
- g_free (label);
+ char * a11y = NULL;
+ char * title = NULL;
+
+ title = indicator_power_device_get_readable_title (device, true, true);
+ if (expected_time_and_percent)
+ EXPECT_STREQ (expected_time_and_percent, title);
+ else
+ EXPECT_EQ(NULL, title);
+ g_free (title);
+
+ title = indicator_power_device_get_readable_title (device, true, false);
+ if (expected_time)
+ EXPECT_STREQ (expected_time, title);
+ else
+ EXPECT_EQ(NULL, title);
+ g_free (title);
+
+ title = indicator_power_device_get_readable_title (device, false, true);
+ if (expected_percent)
+ EXPECT_STREQ (expected_percent, title);
+ else
+ EXPECT_EQ(NULL, title);
+ g_free (title);
+
+ title = indicator_power_device_get_readable_title (device, false, false);
+ EXPECT_EQ(NULL, title);
+ g_free (title);
+
+ a11y = indicator_power_device_get_accessible_title (device, false, false);
+ if (expected_a11y)
+ EXPECT_STREQ (expected_a11y, a11y);
+ else
+ EXPECT_EQ(NULL, a11y);
g_free (a11y);
}
};
@@ -318,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);
@@ -329,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);
@@ -340,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);
@@ -351,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);
@@ -362,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);
@@ -411,10 +422,10 @@ TEST_F(DeviceTest, IconNames)
INDICATOR_POWER_DEVICE_PERCENTAGE, 25.0,
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);
@@ -437,10 +448,10 @@ TEST_F(DeviceTest, IconNames)
INDICATOR_POWER_DEVICE_PERCENTAGE, 5.0,
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);
@@ -481,14 +492,16 @@ TEST_F(DeviceTest, Labels)
g_setenv ("LANG", "en_US.UTF-8", TRUE);
// bad args: NULL device
- log_count_ipower_expected += 5;
+ log_count_ipower_expected++;
check_label (NULL, NULL);
+ log_count_ipower_expected += 5;
check_header (NULL, NULL, NULL, NULL, NULL);
// bad args: a GObject that isn't a device
- log_count_ipower_expected += 5;
GObject * o = G_OBJECT(g_cancellable_new());
+ log_count_ipower_expected++;
check_label ((IndicatorPowerDevice*)o, NULL);
+ log_count_ipower_expected += 5;
check_header (NULL, NULL, NULL, NULL, NULL);
g_object_unref (o);
@@ -509,7 +522,7 @@ TEST_F(DeviceTest, Labels)
check_header (device, "(1:01, 50%)",
"(1:01)",
"(50%)",
- "Battery (1 hour 1 minute to charge, 50%)");
+ "Battery (1 hour 1 minute to charge)");
// discharging, < 12 hours left
g_object_set (o, INDICATOR_POWER_DEVICE_KIND, UP_DEVICE_KIND_BATTERY,
@@ -521,7 +534,7 @@ TEST_F(DeviceTest, Labels)
check_header (device, "(1:01, 50%)",
"(1:01)",
"(50%)",
- "Battery (1 hour 1 minute left, 50%)");
+ "Battery (1 hour 1 minute left)");
// discharging, > 24 hours left
// we don't show the clock time when > 24 hours discharging
@@ -532,9 +545,9 @@ TEST_F(DeviceTest, Labels)
NULL);
check_label (device, "Battery");
check_header (device, "(50%)",
- "",
+ NULL,
"(50%)",
- "Battery (50%)");
+ "Battery");
// fully charged
g_object_set (o, INDICATOR_POWER_DEVICE_KIND, UP_DEVICE_KIND_BATTERY,
@@ -544,9 +557,9 @@ TEST_F(DeviceTest, Labels)
NULL);
check_label (device, "Battery (charged)");
check_header (device, "(100%)",
- "",
+ NULL,
"(100%)",
- "Battery (charged, 100%)");
+ "Battery (charged)");
// percentage but no time estimate
g_object_set (o, INDICATOR_POWER_DEVICE_KIND, UP_DEVICE_KIND_BATTERY,
@@ -558,7 +571,7 @@ TEST_F(DeviceTest, Labels)
check_header (device, "(estimating…, 50%)",
"(estimating…)",
"(50%)",
- "Battery (estimating…, 50%)");
+ "Battery (estimating…)");
// no percentage, no time estimate
g_object_set (o, INDICATOR_POWER_DEVICE_KIND, UP_DEVICE_KIND_BATTERY,
@@ -566,8 +579,8 @@ TEST_F(DeviceTest, Labels)
INDICATOR_POWER_DEVICE_PERCENTAGE, 0.0,
INDICATOR_POWER_DEVICE_TIME, guint64(0),
NULL);
- check_label (device, "Battery (not present)");
- check_header (device, "", "", "", "Battery (not present)");
+ check_label (device, "Battery");
+ check_header (device, NULL, NULL, NULL, "Battery");
// power line
g_object_set (o, INDICATOR_POWER_DEVICE_KIND, UP_DEVICE_KIND_LINE_POWER,
@@ -576,7 +589,7 @@ TEST_F(DeviceTest, Labels)
INDICATOR_POWER_DEVICE_TIME, guint64(0),
NULL);
check_label (device, "AC Adapter");
- check_header (device, "", "", "", "AC Adapter");
+ check_header (device, NULL, NULL, NULL, "AC Adapter");
// cleanup
g_object_unref(o);
@@ -584,74 +597,183 @@ TEST_F(DeviceTest, Labels)
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). */
-TEST_F(DeviceTest, ChoosePrimary)
+
+TEST_F(DeviceTest, Inestimable___this_takes_80_seconds)
{
- 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;
- 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 }
+ // set our language so that i18n won't break these tests
+ auto real_lang = g_strdup(g_getenv ("LANG"));
+ g_setenv ("LANG", "en_US.UTF-8", true);
+
+ // set up the main loop
+ auto loop = g_main_loop_new (nullptr, false);
+ auto loop_quit_sourcefunc = [](gpointer l){
+ g_main_loop_quit(static_cast<GMainLoop*>(l));
+ return G_SOURCE_REMOVE;
};
- device_list = NULL;
- device_list = g_list_append (device_list, a);
- device_list = g_list_append (device_list, b);
+ auto device = INDICATOR_POWER_DEVICE (g_object_new (INDICATOR_POWER_DEVICE_TYPE, nullptr));
+ auto o = G_OBJECT(device);
- for (int i=0, n=G_N_ELEMENTS(tests); i<n; i++)
+ // percentage but no time estimate
+ auto timer = g_timer_new ();
+ g_object_set (o, INDICATOR_POWER_DEVICE_KIND, UP_DEVICE_KIND_BATTERY,
+ INDICATOR_POWER_DEVICE_STATE, UP_DEVICE_STATE_DISCHARGING,
+ INDICATOR_POWER_DEVICE_PERCENTAGE, 50.0,
+ INDICATOR_POWER_DEVICE_TIME, guint64(0),
+ nullptr);
+
+ /*
+ * “estimating…” if the time remaining has been inestimable for
+ * less than 30 seconds; otherwise “unknown” if the time remaining
+ * has been inestimable for between 30 seconds and one minute;
+ * otherwise the empty string.
+ */
+ for (;;)
{
- for (int j=i+1; j<n; j++)
+ g_timeout_add_seconds (1, loop_quit_sourcefunc, loop);
+ g_main_loop_run (loop);
+
+ const auto elapsed = g_timer_elapsed (timer, nullptr);
+
+ if (elapsed < 30)
{
- 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));
-
- /* 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));
+ check_label (device, "Battery (estimating…)");
+ check_header (device, "(estimating…, 50%)",
+ "(estimating…)",
+ "(50%)",
+ "Battery (estimating…)");
+ }
+ else if (elapsed < 60)
+ {
+ check_label (device, "Battery (unknown)");
+ check_header (device, "(unknown, 50%)",
+ "(unknown)",
+ "(50%)",
+ "Battery (unknown)");
+ }
+ else if (elapsed < 80)
+ {
+ check_label (device, "Battery");
+ check_header (device, "(50%)",
+ NULL,
+ "(50%)",
+ "Battery");
+ }
+ else
+ {
+ break;
}
}
+ g_main_loop_unref (loop);
+
// cleanup
- g_list_free_full (device_list, g_object_unref);
+ g_timer_destroy (timer);
+ g_object_unref (o);
+ g_setenv ("LANG", real_lang, TRUE);
+ g_free (real_lang);
+}
+
+/* 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)
+{
+ struct Description
+ {
+ const char * path;
+ UpDeviceKind kind;
+ UpDeviceState state;
+ guint64 time;
+ double percentage;
+ };
+
+ 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
+
+ { "/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
+
+ { "/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..b5166a0
--- /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), (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);
+}