diff options
author | Charles Kerr <charles.kerr@canonical.com> | 2014-08-22 09:55:30 -0500 |
---|---|---|
committer | Charles Kerr <charles.kerr@canonical.com> | 2014-08-22 09:55:30 -0500 |
commit | 3d4c3f2d2f0ebf2b39738531bd8b708d53d2206f (patch) | |
tree | b43fcc6a9bbde59a321fc2dc43fd4b9fe716fb3b /tests | |
parent | a402b61b0acc81a808f9cb353e0df81bb9b14de0 (diff) | |
parent | c7e1740203b41bb9cb12da0ea97477363224e3ee (diff) | |
download | ayatana-indicator-power-3d4c3f2d2f0ebf2b39738531bd8b708d53d2206f.tar.gz ayatana-indicator-power-3d4c3f2d2f0ebf2b39738531bd8b708d53d2206f.tar.bz2 ayatana-indicator-power-3d4c3f2d2f0ebf2b39738531bd8b708d53d2206f.zip |
sync with trunk.
Diffstat (limited to 'tests')
-rw-r--r-- | tests/CMakeLists.txt | 17 | ||||
-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 | 124 | ||||
-rw-r--r-- | tests/manual | 17 | ||||
-rw-r--r-- | tests/test-notify.cc | 410 |
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); +} |