diff options
-rw-r--r-- | tests/glib-fixture.h | 138 | ||||
-rw-r--r-- | tests/test-notify.cc | 192 |
2 files changed, 316 insertions, 14 deletions
diff --git a/tests/glib-fixture.h b/tests/glib-fixture.h new file mode 100644 index 0000000..46b9640 --- /dev/null +++ b/tests/glib-fixture.h @@ -0,0 +1,138 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * Authors: + * Charles Kerr <charles.kerr@canonical.com> + * + * 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/>. + */ + +#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; + + protected: + + std::map<GLogLevelFlags,int> logCounts; + + void testLogCount(GLogLevelFlags log_level, int /*expected*/) + { +#if 0 + EXPECT_EQ(expected, logCounts[log_level]); +#endif + + logCounts.erase(log_level); + } + + private: + + static void default_log_handler(const gchar * log_domain, + GLogLevelFlags log_level, + const gchar * message, + gpointer self) + { + g_print("%s - %d - %s\n", log_domain, (int)log_level, message); + static_cast<GlibFixture*>(self)->logCounts[log_level]++; + } + + protected: + + 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); + + // only use local, temporary settings + g_assert(g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, true)); + g_assert(g_setenv("GSETTINGS_BACKEND", "memory", true)); + g_debug("SCHEMA_DIR is %s", SCHEMA_DIR); + + g_unsetenv("DISPLAY"); + + } + + virtual void TearDown() + { +#if 0 + // confirm there aren't any unexpected log messages + EXPECT_EQ(0, logCounts[G_LOG_LEVEL_ERROR]); + EXPECT_EQ(0, logCounts[G_LOG_LEVEL_CRITICAL]); + EXPECT_EQ(0, logCounts[G_LOG_LEVEL_WARNING]); + EXPECT_EQ(0, logCounts[G_LOG_LEVEL_MESSAGE]); + EXPECT_EQ(0, logCounts[G_LOG_LEVEL_INFO]); +#endif + + // revert to glib's log handler + //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, unsigned int timeout_seconds=5u) + { + // 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(unsigned int msec=50u) + { + 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/test-notify.cc b/tests/test-notify.cc index 0b75177..6ac3d82 100644 --- a/tests/test-notify.cc +++ b/tests/test-notify.cc @@ -17,30 +17,119 @@ * Charles Kerr <charles.kerr@canonical.com> */ + +#include "glib-fixture.h" + #include "device.h" -#include "service.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 NotifyTest : public ::testing::Test +/*** +**** +***/ + +class NotifyFixture: public GlibFixture { - private: +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"}; + + DbusTestService * service = nullptr; + DbusTestDbusMock * mock = nullptr; + DbusTestDbusMockObject * obj = nullptr; + GDBusConnection * bus = nullptr; + +protected: + + static constexpr int 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_NOTIFY {"Notify"}; + static constexpr char const * METHOD_GET_CAPS {"GetCapabilities"}; + static constexpr char const * METHOD_GET_INFO {"GetServerInformation"}; + + 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(NULL); + + GError * error = NULL; + 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); + + dbus_test_dbus_mock_object_add_method(mock, obj, METHOD_GET_INFO, + NULL, + G_VARIANT_TYPE("(ssss)"), + "ret = ('mock-notify', 'test vendor', '1.0', '1.1')", // python + &error); + g_assert_no_error (error); + + auto python_str = g_strdup_printf ("ret = %d", NOTIFY_ID); + dbus_test_dbus_mock_object_add_method(mock, obj, METHOD_NOTIFY, + G_VARIANT_TYPE("(susssasa{sv}i)"), + G_VARIANT_TYPE_UINT32, + python_str, + &error); + g_free (python_str); + g_assert_no_error (error); + + 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, NULL, NULL); + g_dbus_connection_set_exit_on_close(bus, FALSE); + g_object_add_weak_pointer(G_OBJECT(bus), (gpointer *)&bus); - typedef ::testing::Test super; + notify_init(APP_NAME); + } - protected: + virtual void TearDown() + { + notify_uninit(); - virtual void SetUp() - { - super::SetUp(); - } + g_clear_object(&mock); + g_clear_object(&service); + g_object_unref(bus); - virtual void TearDown() - { - super::TearDown(); - } + // wait a little while for the scaffolding to shut down, + // but don't block on it forever... + unsigned int cleartry = 0; + while ((bus != NULL) && (cleartry < 50)) + { + g_usleep(100000); + while (g_main_pending()) + g_main_iteration(true); + cleartry++; + } + + super::TearDown(); + } }; /*** @@ -53,7 +142,82 @@ class NotifyTest : public ::testing::Test // popup should appear exactly twice: once at 10%, once at 5% -TEST_F(NotifyTest, HelloWorld) +TEST_F(NotifyFixture, HelloWorld) { } +#if 0 +using namespace unity::indicator::datetime; + +/*** +**** +***/ + +using namespace unity::indicator::datetime; + +class SnapFixture: public GlibFixture +{ + +/*** +**** +***/ + +namespace +{ + gboolean quit_idle (gpointer gloop) + { + g_main_loop_quit(static_cast<GMainLoop*>(gloop)); + return G_SOURCE_REMOVE; + }; +} + +TEST_F(SnapFixture, InteractiveDuration) +{ + static constexpr int duration_minutes = 120; + auto settings = std::make_shared<Settings>(); + settings->alarm_duration.set(duration_minutes); + auto timezones = std::make_shared<Timezones>(); + auto clock = std::make_shared<LiveClock>(timezones); + Snap snap (clock, settings); + + // GetCapabilities returns an array containing 'actions', + // so our snap decision will be interactive. + // For this test, it means we should get a timeout Notify Hint + // that matches duration_minutes + 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); + + // call the Snap Decision + auto func = [this](const Appointment&){g_idle_add(quit_idle, loop);}; + snap(appt, func, func); + + // confirm that Notify got called once + guint len = 0; + auto calls = dbus_test_dbus_mock_object_get_method_calls (mock, obj, METHOD_NOTIFY, &len, &error); + g_assert_no_error(error); + ASSERT_EQ(1, len); + + // confirm that the app_name passed to Notify was APP_NAME + const auto& params = calls[0].params; + ASSERT_EQ(8, g_variant_n_children(params)); + const char * str = nullptr; + g_variant_get_child (params, 0, "&s", &str); + ASSERT_STREQ(APP_NAME, str); + + // confirm that the icon passed to Notify was "alarm-clock" + g_variant_get_child (params, 2, "&s", &str); + ASSERT_STREQ("alarm-clock", str); + + // confirm that the hints passed to Notify included a timeout matching duration_minutes + int32_t i32; + bool b; + auto hints = g_variant_get_child_value (params, 6); + b = g_variant_lookup (hints, HINT_TIMEOUT, "i", &i32); + EXPECT_TRUE(b); + const auto duration = std::chrono::minutes(duration_minutes); + EXPECT_EQ(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count(), i32); + g_variant_unref(hints); +} +#endif + |