aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/CMakeLists.txt17
-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-notify.cc410
7 files changed, 894 insertions, 1 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index c5ad09d..a0d24af 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -5,6 +5,11 @@ add_library (gtest STATIC
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
@@ -40,7 +45,17 @@ function(add_test_by_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 ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS})
+ 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/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-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);
+}