diff options
author | Charles Kerr <charles.kerr@canonical.com> | 2016-02-02 13:05:26 -0600 |
---|---|---|
committer | Robert Tari <robert@tari.in> | 2021-07-01 01:50:07 +0200 |
commit | 1e41f6a165a3cd636f98b513a044d85522108ae1 (patch) | |
tree | 478a833386eda1cdb4daf8df457f5a7457699b2a | |
parent | 2769c593b548d586b1285242a94bce3fa1936486 (diff) | |
download | ayatana-indicator-datetime-1e41f6a165a3cd636f98b513a044d85522108ae1.tar.gz ayatana-indicator-datetime-1e41f6a165a3cd636f98b513a044d85522108ae1.tar.bz2 ayatana-indicator-datetime-1e41f6a165a3cd636f98b513a044d85522108ae1.zip |
Move notifications tests into a new unit test. Extract notifications test fixture into a reusable header.
-rw-r--r-- | tests/CMakeLists.txt | 3 | ||||
-rw-r--r-- | tests/notification-fixture.h | 382 | ||||
-rw-r--r-- | tests/test-notification.cpp | 164 | ||||
-rw-r--r-- | tests/test-snap.cpp | 354 | ||||
-rw-r--r-- | tests/test-sound.cpp | 269 |
5 files changed, 823 insertions, 349 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5206259..bec0010 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -48,7 +48,8 @@ function(add_test_by_name name) endfunction() add_test_by_name(test-datetime) if(HAVE_UT_ACCTSERVICE_SYSTEMSOUND_SETTINGS) -add_test_by_name(test-snap) +add_test_by_name(test-sound) +add_test_by_name(test-notification) endif() add_test_by_name(test-actions) add_test_by_name(test-alarm-queue) diff --git a/tests/notification-fixture.h b/tests/notification-fixture.h new file mode 100644 index 0000000..b6b446b --- /dev/null +++ b/tests/notification-fixture.h @@ -0,0 +1,382 @@ +/* + * Copyright 2014-2016 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> + */ + +#pragma once + +#include "glib-fixture.h" + +#include <datetime/appointment.h> +#include <datetime/dbus-shared.h> +#include <datetime/settings.h> +#include <datetime/snap.h> + +#include <notifications/dbus-shared.h> +#include <notifications/notifications.h> + +#include <libdbustest/dbus-test.h> + +#include <unistd.h> // getuid() +#include <sys/types.h> // getuid() + +/*** +**** +***/ + +//using namespace ayatana::indicator::datetime; + +class NotificationFixture: 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"}; + + //namespace uin = ayatana::indicator::notifications; + + //using namespace ayatana::indicator::datetime; + +protected: + + static constexpr char const * HAPTIC_METHOD_VIBRATE_PATTERN {"VibratePattern"}; + + static constexpr int SCREEN_COOKIE {8675309}; + static constexpr char const * SCREEN_METHOD_KEEP_DISPLAY_ON {"keepDisplayOn"}; + static constexpr char const * SCREEN_METHOD_REMOVE_DISPLAY_ON_REQUEST {"removeDisplayOnRequest"}; + + static constexpr int POWERD_SYS_STATE_ACTIVE = 1; + static constexpr char const * POWERD_COOKIE {"567-48-8307"}; + static constexpr char const * POWERD_METHOD_REQUEST_SYS_STATE {"requestSysState"}; + static constexpr char const * POWERD_METHOD_CLEAR_SYS_STATE {"clearSysState"}; + + static constexpr int FIRST_NOTIFY_ID {1000}; + + 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 * METHOD_CLOSE {"CloseNotification"}; + static constexpr char const * METHOD_GET_CAPS {"GetCapabilities"}; + static constexpr char const * METHOD_GET_INFO {"GetServerInformation"}; + static constexpr char const * METHOD_NOTIFY {"Notify"}; + + static constexpr char const * SIGNAL_CLOSED {"NotificationClosed"}; + + static constexpr char const * HINT_TIMEOUT {"x-canonical-snap-decisions-timeout"}; + + static constexpr char const * AS_BUSNAME {"org.freedesktop.Accounts"}; + static constexpr char const * AS_INTERFACE {"com.lomiri.touch.AccountsService.Sound"}; + static constexpr char const * PROP_OTHER_VIBRATIONS {"OtherVibrate"}; + static constexpr char const * PROP_SILENT_MODE {"SilentMode"}; + + ayatana::indicator::datetime::Appointment appt; + ayatana::indicator::datetime::Appointment ualarm; + GDBusConnection * system_bus = nullptr; + GDBusConnection * session_bus = nullptr; + DbusTestService * service = nullptr; + DbusTestDbusMock * as_mock = nullptr; + DbusTestDbusMock * notify_mock = nullptr; + DbusTestDbusMock * powerd_mock = nullptr; + DbusTestDbusMock * screen_mock = nullptr; + DbusTestDbusMock * haptic_mock = nullptr; + DbusTestDbusMockObject * as_obj = nullptr; + DbusTestDbusMockObject * notify_obj = nullptr; + DbusTestDbusMockObject * powerd_obj = nullptr; + DbusTestDbusMockObject * screen_obj = nullptr; + DbusTestDbusMockObject * haptic_obj = nullptr; + + void SetUp() override + { + GError * error = nullptr; + char * str = nullptr; + + super::SetUp(); + + // init an Appointment + appt.color = "green"; + appt.summary = "Christmas"; + appt.uid = "D4B57D50247291478ED31DED17FF0A9838DED402"; + appt.type = ayatana::indicator::datetime::Appointment::EVENT; + const auto christmas = ayatana::indicator::datetime::DateTime::Local(2015,12,25,0,0,0); + appt.begin = christmas.start_of_day(); + appt.end = christmas.end_of_day(); + appt.alarms.push_back(ayatana::indicator::datetime::Alarm{"Ho Ho Ho!", "", appt.begin}); + + // init a Lomiri Alarm + ualarm.color = "red"; + ualarm.summary = "Wakeup"; + ualarm.uid = "E4B57D50247291478ED31DED17FF0A9838DED403"; + ualarm.type = ayatana::indicator::datetime::Appointment::UBUNTU_ALARM; + const auto tomorrow = ayatana::indicator::datetime::DateTime::NowLocal().add_days(1); + ualarm.begin = tomorrow; + ualarm.end = tomorrow; + ualarm.alarms.push_back(ayatana::indicator::datetime::Alarm{"It's Tomorrow!", "", appt.begin}); + + service = dbus_test_service_new(nullptr); + + /// + /// Add the AccountsService mock + /// + + as_mock = dbus_test_dbus_mock_new(AS_BUSNAME); + auto as_path = g_strdup_printf("/org/freedesktop/Accounts/User%lu", (gulong)getuid()); + as_obj = dbus_test_dbus_mock_get_object(as_mock, + as_path, + AS_INTERFACE, + &error); + g_free(as_path); + g_assert_no_error(error); + + // PROP_SILENT_MODE + dbus_test_dbus_mock_object_add_property(as_mock, + as_obj, + PROP_SILENT_MODE, + G_VARIANT_TYPE_BOOLEAN, + g_variant_new_boolean(false), + &error); + g_assert_no_error(error); + + // PROP_OTHER_VIBRATIONS + dbus_test_dbus_mock_object_add_property(as_mock, + as_obj, + PROP_OTHER_VIBRATIONS, + G_VARIANT_TYPE_BOOLEAN, + g_variant_new_boolean(true), + &error); + g_assert_no_error(error); + dbus_test_service_add_task(service, DBUS_TEST_TASK(as_mock)); + + /// + /// Add the Notifications mock + /// + + notify_mock = dbus_test_dbus_mock_new(NOTIFY_BUSNAME); + notify_obj = dbus_test_dbus_mock_get_object(notify_mock, + NOTIFY_PATH, + NOTIFY_INTERFACE, + &error); + g_assert_no_error(error); + + // METHOD_GET_INFO + str = g_strdup("ret = ('mock-notify', 'test vendor', '1.0', '1.1')"); + dbus_test_dbus_mock_object_add_method(notify_mock, + notify_obj, + METHOD_GET_INFO, + nullptr, + G_VARIANT_TYPE("(ssss)"), + str, + &error); + g_assert_no_error (error); + g_free (str); + + // METHOD_NOTIFY + 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(notify_mock, + notify_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(notify_mock, + notify_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(notify_mock)); + + /// + /// Add the powerd mock + /// + + powerd_mock = dbus_test_dbus_mock_new(BUS_POWERD_NAME); + powerd_obj = dbus_test_dbus_mock_get_object(powerd_mock, + BUS_POWERD_PATH, + BUS_POWERD_INTERFACE, + &error); + g_assert_no_error(error); + + str = g_strdup_printf ("ret = '%s'", POWERD_COOKIE); + dbus_test_dbus_mock_object_add_method(powerd_mock, + powerd_obj, + POWERD_METHOD_REQUEST_SYS_STATE, + G_VARIANT_TYPE("(si)"), + G_VARIANT_TYPE("(s)"), + str, + &error); + g_assert_no_error (error); + g_free (str); + + dbus_test_dbus_mock_object_add_method(powerd_mock, + powerd_obj, + POWERD_METHOD_CLEAR_SYS_STATE, + G_VARIANT_TYPE("(s)"), + nullptr, + "", + &error); + g_assert_no_error (error); + + dbus_test_service_add_task(service, DBUS_TEST_TASK(powerd_mock)); + + /// + /// Add the Screen mock + /// + + screen_mock = dbus_test_dbus_mock_new(BUS_SCREEN_NAME); + screen_obj = dbus_test_dbus_mock_get_object(screen_mock, + BUS_SCREEN_PATH, + BUS_SCREEN_INTERFACE, + &error); + g_assert_no_error(error); + + str = g_strdup_printf ("ret = %d", SCREEN_COOKIE); + dbus_test_dbus_mock_object_add_method(screen_mock, + screen_obj, + SCREEN_METHOD_KEEP_DISPLAY_ON, + nullptr, + G_VARIANT_TYPE("(i)"), + str, + &error); + g_assert_no_error (error); + g_free (str); + + dbus_test_dbus_mock_object_add_method(screen_mock, + screen_obj, + SCREEN_METHOD_REMOVE_DISPLAY_ON_REQUEST, + G_VARIANT_TYPE("(i)"), + nullptr, + "", + &error); + g_assert_no_error (error); + dbus_test_service_add_task(service, DBUS_TEST_TASK(screen_mock)); + + /// + /// Add the haptic mock + /// + + haptic_mock = dbus_test_dbus_mock_new(BUS_HAPTIC_NAME); + haptic_obj = dbus_test_dbus_mock_get_object(haptic_mock, + BUS_HAPTIC_PATH, + BUS_HAPTIC_INTERFACE, + &error); + + dbus_test_dbus_mock_object_add_method(haptic_mock, + haptic_obj, + HAPTIC_METHOD_VIBRATE_PATTERN, + G_VARIANT_TYPE("(auu)"), + nullptr, + "", + &error); + g_assert_no_error (error); + dbus_test_service_add_task(service, DBUS_TEST_TASK(haptic_mock)); + + + // start 'em up. + // make the system bus work off the mock bus too, since that's + // where the upower and screen are on the system bus... + + dbus_test_service_start_tasks(service); + g_setenv("DBUS_SYSTEM_BUS_ADDRESS", g_getenv("DBUS_SESSION_BUS_ADDRESS"), TRUE); + + session_bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); + ASSERT_NE(nullptr, session_bus); + g_dbus_connection_set_exit_on_close(session_bus, false); + g_object_add_weak_pointer(G_OBJECT(session_bus), (gpointer *)&session_bus); + + system_bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr); + ASSERT_NE(nullptr, system_bus); + g_dbus_connection_set_exit_on_close(system_bus, FALSE); + g_object_add_weak_pointer(G_OBJECT(system_bus), (gpointer *)&system_bus); + } + + void TearDown() override + { + g_clear_object(&haptic_mock); + g_clear_object(&screen_mock); + g_clear_object(&powerd_mock); + g_clear_object(¬ify_mock); + g_clear_object(&as_mock); + g_clear_object(&service); + g_object_unref(session_bus); + g_object_unref(system_bus); + + // wait a little while for the scaffolding to shut down, + // but don't block on it forever... + unsigned int cleartry = 0; + while (((system_bus != nullptr) || (session_bus != nullptr)) && (cleartry < 50)) + { + g_usleep(100000); + while (g_main_pending()) + g_main_iteration(true); + cleartry++; + } + + super::TearDown(); + } + + void make_interactive() + { + // 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(notify_mock, + notify_obj, + METHOD_GET_CAPS, + nullptr, + G_VARIANT_TYPE_STRING_ARRAY, + "ret = ['actions', 'body']", + &error); + g_assert_no_error (error); + } + + std::shared_ptr<ayatana::indicator::datetime::Snap> + create_snap(const std::shared_ptr<ayatana::indicator::notifications::Engine>& ne, + const std::shared_ptr<ayatana::indicator::notifications::SoundBuilder>& sb, + const std::shared_ptr<ayatana::indicator::datetime::Settings>& settings) + { + auto snap = std::make_shared<ayatana::indicator::datetime::Snap>(ne, sb, settings); + wait_msec(100); // wait a moment for the Snap to finish its async dbus bootstrapping + return snap; + } +}; + diff --git a/tests/test-notification.cpp b/tests/test-notification.cpp new file mode 100644 index 0000000..c1773e4 --- /dev/null +++ b/tests/test-notification.cpp @@ -0,0 +1,164 @@ +/* + * Copyright 2014-2016 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 <datetime/appointment.h> +#include <datetime/settings.h> +#include <datetime/snap.h> + +#include "notification-fixture.h" + +/*** +**** +***/ + +using namespace ayatana::indicator::datetime; + +namespace +{ + static constexpr char const * APP_NAME {"indicator-datetime-service"}; + + gboolean quit_idle (gpointer gloop) + { + g_main_loop_quit(static_cast<GMainLoop*>(gloop)); + return G_SOURCE_REMOVE; + }; +} + +/*** +**** +***/ + +TEST_F(NotificationFixture,Notification) +{ + // Feed different combinations of system settings, + // indicator-datetime settings, and event types, + // then see if notifications and haptic feedback behave as expected. + + auto settings = std::make_shared<Settings>(); + auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); + auto sb = std::make_shared<ayatana::indicator::notifications::DefaultSoundBuilder>(); + auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; + + // combinatorial factor #1: event type + struct { + Appointment appt; + bool expected_notify_called; + bool expected_vibrate_called; + } test_appts[] = { + { appt, true, true }, + { ualarm, true, true } + }; + + // combinatorial factor #2: indicator-datetime's haptic mode + struct { + const char* haptic_mode; + bool expected_notify_called; + bool expected_vibrate_called; + } test_haptics[] = { + { "none", true, false }, + { "pulse", true, true } + }; + + // combinatorial factor #3: system settings' "other vibrations" enabled + struct { + bool other_vibrations; + bool expected_notify_called; + bool expected_vibrate_called; + } test_other_vibrations[] = { + { true, true, true }, + { false, true, false } + }; + + // combinatorial factor #4: system settings' notifications app blacklist + const std::set<std::pair<std::string,std::string>> blacklist_calendar { std::make_pair(std::string{"com.lomiri.calendar"}, std::string{"calendar-app"}) }; + const std::set<std::pair<std::string,std::string>> blacklist_empty; + struct { + std::set<std::pair<std::string,std::string>> muted_apps; // apps that should not trigger notifications + bool expected_notify_called; // do we expect the notification tho show? + bool expected_vibrate_called; // do we expect the phone to vibrate? + } test_muted_apps[] = { + { blacklist_calendar, false, false }, + { blacklist_empty, true, true } + }; + + for (const auto& test_appt : test_appts) + { + for (const auto& test_haptic : test_haptics) + { + for (const auto& test_vibes : test_other_vibrations) + { + for (const auto& test_muted : test_muted_apps) + { + auto snap = create_snap(ne, sb, settings); + + const bool expected_notify_called = test_appt.expected_notify_called + && test_vibes.expected_notify_called + && test_muted.expected_notify_called + && test_haptic.expected_notify_called; + + const bool expected_vibrate_called = test_appt.expected_vibrate_called + && test_vibes.expected_vibrate_called + && test_muted.expected_vibrate_called + && test_haptic.expected_vibrate_called; + + // clear out any previous iterations' noise + GError * error = nullptr; + dbus_test_dbus_mock_object_clear_method_calls(haptic_mock, haptic_obj, &error); + g_assert_no_error(error); + dbus_test_dbus_mock_object_clear_method_calls(notify_mock, notify_obj, &error); + g_assert_no_error(error); + + // set the properties to match the test case + settings->muted_apps.set(test_muted.muted_apps); + settings->alarm_haptic.set(test_haptic.haptic_mode); + dbus_test_dbus_mock_object_update_property(as_mock, + as_obj, + PROP_OTHER_VIBRATIONS, + g_variant_new_boolean(test_vibes.other_vibrations), + &error); + g_assert_no_error(error); + wait_msec(100); + + // run the test + (*snap)(appt, appt.alarms.front(), func, func); + wait_msec(100); + + // test that the notification was as expected + const bool notify_called = dbus_test_dbus_mock_object_check_method_call(notify_mock, + notify_obj, + METHOD_NOTIFY, + nullptr, + &error); + g_assert_no_error(error); + EXPECT_EQ(expected_notify_called, notify_called); + + // test that the vibration was as expected + const bool vibrate_called = dbus_test_dbus_mock_object_check_method_call(haptic_mock, + haptic_obj, + HAPTIC_METHOD_VIBRATE_PATTERN, + nullptr, + &error); + g_assert_no_error(error); + EXPECT_EQ(expected_vibrate_called, vibrate_called); + } + } + } + } +} + diff --git a/tests/test-snap.cpp b/tests/test-snap.cpp index f663381..2c53900 100644 --- a/tests/test-snap.cpp +++ b/tests/test-snap.cpp @@ -25,19 +25,12 @@ #include <notifications/dbus-shared.h> #include <notifications/notifications.h> -#include <libdbustest/dbus-test.h> - -#include <glib.h> - -#include <unistd.h> // getuid() -#include <sys/types.h> // getuid() +#include "notification-fixture.h" using namespace ayatana::indicator::datetime; namespace uin = ayatana::indicator::notifications; -#include "glib-fixture.h" - /*** **** ***/ @@ -49,341 +42,6 @@ namespace using namespace ayatana::indicator::datetime; -class SnapFixture: 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: - - static constexpr char const * HAPTIC_METHOD_VIBRATE_PATTERN {"VibratePattern"}; - - static constexpr int SCREEN_COOKIE {8675309}; - static constexpr char const * SCREEN_METHOD_KEEP_DISPLAY_ON {"keepDisplayOn"}; - static constexpr char const * SCREEN_METHOD_REMOVE_DISPLAY_ON_REQUEST {"removeDisplayOnRequest"}; - - static constexpr int POWERD_SYS_STATE_ACTIVE = 1; - static constexpr char const * POWERD_COOKIE {"567-48-8307"}; - static constexpr char const * POWERD_METHOD_REQUEST_SYS_STATE {"requestSysState"}; - static constexpr char const * POWERD_METHOD_CLEAR_SYS_STATE {"clearSysState"}; - - static constexpr int FIRST_NOTIFY_ID {1000}; - - 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 * METHOD_CLOSE {"CloseNotification"}; - static constexpr char const * METHOD_GET_CAPS {"GetCapabilities"}; - static constexpr char const * METHOD_GET_INFO {"GetServerInformation"}; - static constexpr char const * METHOD_NOTIFY {"Notify"}; - - static constexpr char const * SIGNAL_CLOSED {"NotificationClosed"}; - - static constexpr char const * HINT_TIMEOUT {"x-canonical-snap-decisions-timeout"}; - - static constexpr char const * AS_BUSNAME {"org.freedesktop.Accounts"}; - static constexpr char const * AS_INTERFACE {"com.ubuntu.touch.AccountsService.Sound"}; - static constexpr char const * PROP_OTHER_VIBRATIONS {"OtherVibrate"}; - static constexpr char const * PROP_SILENT_MODE {"SilentMode"}; - - Appointment appt; - Appointment ualarm; - GDBusConnection * system_bus = nullptr; - GDBusConnection * session_bus = nullptr; - DbusTestService * service = nullptr; - DbusTestDbusMock * as_mock = nullptr; - DbusTestDbusMock * notify_mock = nullptr; - DbusTestDbusMock * powerd_mock = nullptr; - DbusTestDbusMock * screen_mock = nullptr; - DbusTestDbusMock * haptic_mock = nullptr; - DbusTestDbusMockObject * as_obj = nullptr; - DbusTestDbusMockObject * notify_obj = nullptr; - DbusTestDbusMockObject * powerd_obj = nullptr; - DbusTestDbusMockObject * screen_obj = nullptr; - DbusTestDbusMockObject * haptic_obj = nullptr; - - void SetUp() override - { - GError * error = nullptr; - char * str = nullptr; - - super::SetUp(); - - // init an Appointment - appt.color = "green"; - appt.summary = "Christmas"; - appt.uid = "D4B57D50247291478ED31DED17FF0A9838DED402"; - appt.type = Appointment::EVENT; - const auto christmas = DateTime::Local(2015,12,25,0,0,0); - appt.begin = christmas.start_of_day(); - appt.end = christmas.end_of_day(); - appt.alarms.push_back(Alarm{"Ho Ho Ho!", "", appt.begin}); - - // init an Ubuntu Alarm - ualarm.color = "red"; - ualarm.summary = "Wakeup"; - ualarm.uid = "E4B57D50247291478ED31DED17FF0A9838DED403"; - ualarm.type = Appointment::UBUNTU_ALARM; - const auto tomorrow = DateTime::NowLocal().add_days(1); - ualarm.begin = tomorrow; - ualarm.end = tomorrow; - ualarm.alarms.push_back(Alarm{"It's Tomorrow!", "", appt.begin}); - - service = dbus_test_service_new(nullptr); - - /// - /// Add the AccountsService mock - /// - - as_mock = dbus_test_dbus_mock_new(AS_BUSNAME); - auto as_path = g_strdup_printf("/org/freedesktop/Accounts/User%lu", (gulong)getuid()); - as_obj = dbus_test_dbus_mock_get_object(as_mock, - as_path, - AS_INTERFACE, - &error); - g_free(as_path); - g_assert_no_error(error); - - // PROP_SILENT_MODE - dbus_test_dbus_mock_object_add_property(as_mock, - as_obj, - PROP_SILENT_MODE, - G_VARIANT_TYPE_BOOLEAN, - g_variant_new_boolean(false), - &error); - g_assert_no_error(error); - - // PROP_OTHER_VIBRATIONS - dbus_test_dbus_mock_object_add_property(as_mock, - as_obj, - PROP_OTHER_VIBRATIONS, - G_VARIANT_TYPE_BOOLEAN, - g_variant_new_boolean(true), - &error); - g_assert_no_error(error); - dbus_test_service_add_task(service, DBUS_TEST_TASK(as_mock)); - - /// - /// Add the Notifications mock - /// - - notify_mock = dbus_test_dbus_mock_new(NOTIFY_BUSNAME); - notify_obj = dbus_test_dbus_mock_get_object(notify_mock, - NOTIFY_PATH, - NOTIFY_INTERFACE, - &error); - g_assert_no_error(error); - - // METHOD_GET_INFO - str = g_strdup("ret = ('mock-notify', 'test vendor', '1.0', '1.1')"); - dbus_test_dbus_mock_object_add_method(notify_mock, - notify_obj, - METHOD_GET_INFO, - nullptr, - G_VARIANT_TYPE("(ssss)"), - str, - &error); - g_assert_no_error (error); - g_free (str); - - // METHOD_NOTIFY - 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(notify_mock, - notify_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(notify_mock, - notify_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(notify_mock)); - - /// - /// Add the powerd mock - /// - - powerd_mock = dbus_test_dbus_mock_new(BUS_POWERD_NAME); - powerd_obj = dbus_test_dbus_mock_get_object(powerd_mock, - BUS_POWERD_PATH, - BUS_POWERD_INTERFACE, - &error); - g_assert_no_error(error); - - str = g_strdup_printf ("ret = '%s'", POWERD_COOKIE); - dbus_test_dbus_mock_object_add_method(powerd_mock, - powerd_obj, - POWERD_METHOD_REQUEST_SYS_STATE, - G_VARIANT_TYPE("(si)"), - G_VARIANT_TYPE("(s)"), - str, - &error); - g_assert_no_error (error); - g_free (str); - - dbus_test_dbus_mock_object_add_method(powerd_mock, - powerd_obj, - POWERD_METHOD_CLEAR_SYS_STATE, - G_VARIANT_TYPE("(s)"), - nullptr, - "", - &error); - g_assert_no_error (error); - - dbus_test_service_add_task(service, DBUS_TEST_TASK(powerd_mock)); - - /// - /// Add the Screen mock - /// - - screen_mock = dbus_test_dbus_mock_new(BUS_SCREEN_NAME); - screen_obj = dbus_test_dbus_mock_get_object(screen_mock, - BUS_SCREEN_PATH, - BUS_SCREEN_INTERFACE, - &error); - g_assert_no_error(error); - - str = g_strdup_printf ("ret = %d", SCREEN_COOKIE); - dbus_test_dbus_mock_object_add_method(screen_mock, - screen_obj, - SCREEN_METHOD_KEEP_DISPLAY_ON, - nullptr, - G_VARIANT_TYPE("(i)"), - str, - &error); - g_assert_no_error (error); - g_free (str); - - dbus_test_dbus_mock_object_add_method(screen_mock, - screen_obj, - SCREEN_METHOD_REMOVE_DISPLAY_ON_REQUEST, - G_VARIANT_TYPE("(i)"), - nullptr, - "", - &error); - g_assert_no_error (error); - dbus_test_service_add_task(service, DBUS_TEST_TASK(screen_mock)); - - /// - /// Add the haptic mock - /// - - haptic_mock = dbus_test_dbus_mock_new(BUS_HAPTIC_NAME); - haptic_obj = dbus_test_dbus_mock_get_object(haptic_mock, - BUS_HAPTIC_PATH, - BUS_HAPTIC_INTERFACE, - &error); - - dbus_test_dbus_mock_object_add_method(haptic_mock, - haptic_obj, - HAPTIC_METHOD_VIBRATE_PATTERN, - G_VARIANT_TYPE("(auu)"), - nullptr, - "", - &error); - g_assert_no_error (error); - dbus_test_service_add_task(service, DBUS_TEST_TASK(haptic_mock)); - - - // start 'em up. - // make the system bus work off the mock bus too, since that's - // where the upower and screen are on the system bus... - - dbus_test_service_start_tasks(service); - g_setenv("DBUS_SYSTEM_BUS_ADDRESS", g_getenv("DBUS_SESSION_BUS_ADDRESS"), TRUE); - - session_bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); - ASSERT_NE(nullptr, session_bus); - g_dbus_connection_set_exit_on_close(session_bus, false); - g_object_add_weak_pointer(G_OBJECT(session_bus), (gpointer *)&session_bus); - - system_bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr); - ASSERT_NE(nullptr, system_bus); - g_dbus_connection_set_exit_on_close(system_bus, FALSE); - g_object_add_weak_pointer(G_OBJECT(system_bus), (gpointer *)&system_bus); - } - - void TearDown() override - { - g_clear_object(&haptic_mock); - g_clear_object(&screen_mock); - g_clear_object(&powerd_mock); - g_clear_object(¬ify_mock); - g_clear_object(&as_mock); - g_clear_object(&service); - g_object_unref(session_bus); - g_object_unref(system_bus); - - // wait a little while for the scaffolding to shut down, - // but don't block on it forever... - unsigned int cleartry = 0; - while (((system_bus != nullptr) || (session_bus != nullptr)) && (cleartry < 50)) - { - g_usleep(100000); - while (g_main_pending()) - g_main_iteration(true); - cleartry++; - } - - super::TearDown(); - } - - void make_interactive() - { - // 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(notify_mock, - notify_obj, - METHOD_GET_CAPS, - nullptr, - G_VARIANT_TYPE_STRING_ARRAY, - "ret = ['actions', 'body']", - &error); - g_assert_no_error (error); - } - - std::shared_ptr<Snap> create_snap(const std::shared_ptr<uin::Engine>& ne, - const std::shared_ptr<uin::SoundBuilder>& sb, - const std::shared_ptr<Settings>& settings) - { - auto snap = std::make_shared<Snap>(ne, sb, settings); - wait_msec(100); // wait a moment for the Snap to finish its async dbus bootstrapping - return snap; - } -}; - /*** **** ***/ @@ -397,7 +55,7 @@ namespace }; } -TEST_F(SnapFixture, InteractiveDuration) +TEST_F(NotificationFixture, InteractiveDuration) { static constexpr int duration_minutes = 120; auto settings = std::make_shared<Settings>(); @@ -450,7 +108,7 @@ TEST_F(SnapFixture, InteractiveDuration) **** ***/ -TEST_F(SnapFixture, InhibitSleep) +TEST_F(NotificationFixture, InhibitSleep) { auto settings = std::make_shared<Settings>(); auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); @@ -506,7 +164,7 @@ TEST_F(SnapFixture, InhibitSleep) **** ***/ -TEST_F(SnapFixture, ForceScreen) +TEST_F(NotificationFixture, ForceScreen) { auto settings = std::make_shared<Settings>(); auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); @@ -588,7 +246,7 @@ std::string path_to_uri(const std::string& path) return uri; } -TEST_F(SnapFixture,DefaultSounds) +TEST_F(NotificationFixture,DefaultSounds) { auto settings = std::make_shared<Settings>(); auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); @@ -619,7 +277,7 @@ TEST_F(SnapFixture,DefaultSounds) **** ***/ -TEST_F(SnapFixture,Notification) +TEST_F(NotificationFixture,Notification) { // Feed different combinations of system settings, // indicator-datetime settings, and event types, diff --git a/tests/test-sound.cpp b/tests/test-sound.cpp new file mode 100644 index 0000000..8e55986 --- /dev/null +++ b/tests/test-sound.cpp @@ -0,0 +1,269 @@ +/* + * Copyright 2014-2016 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 <datetime/appointment.h> +#include <datetime/settings.h> +#include <datetime/snap.h> + +#include "notification-fixture.h" + +using namespace ayatana::indicator::datetime; + +namespace uin = ayatana::indicator::notifications; + +/*** +**** +***/ + +namespace +{ + static constexpr char const * APP_NAME {"indicator-datetime-service"}; +} + + +namespace +{ + gboolean quit_idle (gpointer gloop) + { + g_main_loop_quit(static_cast<GMainLoop*>(gloop)); + return G_SOURCE_REMOVE; + }; +} + +/*** +**** +***/ + +TEST_F(NotificationFixture, InteractiveDuration) +{ + static constexpr int duration_minutes = 120; + auto settings = std::make_shared<Settings>(); + settings->alarm_duration.set(duration_minutes); + auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); + auto sb = std::make_shared<ayatana::indicator::notifications::DefaultSoundBuilder>(); + auto snap = create_snap(ne, sb, settings); + + make_interactive(); + + // call the Snap Decision + auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; + (*snap)(appt, appt.alarms.front(), func, func); + + // confirm that Notify got called once + guint len = 0; + GError * error = nullptr; + const auto calls = dbus_test_dbus_mock_object_get_method_calls (notify_mock, + notify_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); + ne.reset(); +} + +/*** +**** +***/ + +TEST_F(NotificationFixture, InhibitSleep) +{ + auto settings = std::make_shared<Settings>(); + auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); + auto sb = std::make_shared<ayatana::indicator::notifications::DefaultSoundBuilder>(); + auto snap = create_snap(ne, sb, settings); + + make_interactive(); + + // invoke the notification + auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; + (*snap)(appt, appt.alarms.front(), func, func); + + wait_msec(1000); + + // confirm that sleep got inhibited + GError * error = nullptr; + EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (powerd_mock, + powerd_obj, + POWERD_METHOD_REQUEST_SYS_STATE, + g_variant_new("(si)", APP_NAME, POWERD_SYS_STATE_ACTIVE), + &error)); + + // confirm that the screen got forced on + EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (screen_mock, + screen_obj, + SCREEN_METHOD_KEEP_DISPLAY_ON, + nullptr, + &error)); + + // force-close the snap + wait_msec(100); + snap.reset(); + wait_msec(100); + + // confirm that sleep got uninhibted + EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (powerd_mock, + powerd_obj, + POWERD_METHOD_CLEAR_SYS_STATE, + g_variant_new("(s)", POWERD_COOKIE), + &error)); + + // confirm that the screen's no longer forced on + EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (screen_mock, + screen_obj, + SCREEN_METHOD_REMOVE_DISPLAY_ON_REQUEST, + g_variant_new("(i)", SCREEN_COOKIE), + &error)); + + g_assert_no_error (error); +} + +/*** +**** +***/ + +TEST_F(NotificationFixture, ForceScreen) +{ + auto settings = std::make_shared<Settings>(); + auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); + auto sb = std::make_shared<ayatana::indicator::notifications::DefaultSoundBuilder>(); + auto snap = create_snap(ne, sb, settings); + + make_interactive(); + + // invoke the notification + auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; + (*snap)(appt, appt.alarms.front(), func, func); + + wait_msec(1000); + + // confirm that sleep got inhibited + GError * error = nullptr; + EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (powerd_mock, + powerd_obj, + POWERD_METHOD_REQUEST_SYS_STATE, + g_variant_new("(si)", APP_NAME, POWERD_SYS_STATE_ACTIVE), + &error)); + g_assert_no_error(error); + + // force-close the snap + wait_msec(100); + snap.reset(); + wait_msec(100); + + // confirm that sleep got uninhibted + EXPECT_TRUE (dbus_test_dbus_mock_object_check_method_call (powerd_mock, + powerd_obj, + POWERD_METHOD_CLEAR_SYS_STATE, + g_variant_new("(s)", POWERD_COOKIE), + &error)); + g_assert_no_error(error); +} + +/*** +**** +***/ + +/** + * A DefaultSoundBuilder wrapper which remembers the parameters of the last sound created. + */ +class TestSoundBuilder: public uin::SoundBuilder +{ +public: + TestSoundBuilder() =default; + ~TestSoundBuilder() =default; + + virtual std::shared_ptr<uin::Sound> create(const std::string& role, const std::string& uri, unsigned int volume, bool loop) override { + m_role = role; + m_uri = uri; + m_volume = volume; + m_loop = loop; + return m_impl.create(role, uri, volume, loop); + } + + const std::string& role() { return m_role; } + const std::string& uri() { return m_uri; } + unsigned int volume() { return m_volume; } + bool loop() { return m_loop; } + +private: + std::string m_role; + std::string m_uri; + unsigned int m_volume; + bool m_loop; + uin::DefaultSoundBuilder m_impl; +}; + +std::string path_to_uri(const std::string& path) +{ + auto file = g_file_new_for_path(path.c_str()); + auto uri_cstr = g_file_get_uri(file); + std::string uri = uri_cstr; + g_free(uri_cstr); + g_clear_pointer(&file, g_object_unref); + return uri; +} + +TEST_F(NotificationFixture,DefaultSounds) +{ + auto settings = std::make_shared<Settings>(); + auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); + auto sb = std::make_shared<TestSoundBuilder>(); + auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; + + const struct { + Appointment appointment; + std::string expected_role; + std::string expected_uri; + } test_cases[] = { + { ualarm, "alarm", path_to_uri(ALARM_DEFAULT_SOUND) }, + { appt, "alert", path_to_uri(CALENDAR_DEFAULT_SOUND) } + }; + + auto snap = create_snap(ne, sb, settings); + + for(const auto& test_case : test_cases) + { + (*snap)(test_case.appointment, test_case.appointment.alarms.front(), func, func); + wait_msec(100); + EXPECT_EQ(test_case.expected_uri, sb->uri()); + EXPECT_EQ(test_case.expected_role, sb->role()); + } +} |