/* * Copyright 2014 Canonical Ltd. * * Authors: * Charles Kerr * * 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 . */ #include #include #include #include #include #include #include #include #include // getuid() #include // getuid() using namespace ayatana::indicator::datetime; #include "glib-fixture.h" /*** **** ***/ namespace { static constexpr char const * APP_NAME {"indicator-datetime-service"}; } 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; 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 the Appointment appt.color = "green"; appt.summary = "Alarm"; 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{"Alarm Text", "", 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); } }; /*** **** ***/ namespace { gboolean quit_idle (gpointer gloop) { g_main_loop_quit(static_cast(gloop)); return G_SOURCE_REMOVE; }; } TEST_F(SnapFixture, InteractiveDuration) { static constexpr int duration_minutes = 120; auto settings = std::make_shared(); settings->alarm_duration.set(duration_minutes); auto ne = std::make_shared(APP_NAME); Snap snap (ne, 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(duration).count(), i32); g_variant_unref(hints); ne.reset(); } /*** **** ***/ TEST_F(SnapFixture, InhibitSleep) { auto settings = std::make_shared(); auto ne = std::make_shared(APP_NAME); auto snap = new Snap (ne, 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); delete snap; 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(SnapFixture, ForceScreen) { auto settings = std::make_shared(); auto ne = std::make_shared(APP_NAME); auto snap = new Snap (ne, 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); delete snap; 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); } /*** **** ***/ TEST_F(SnapFixture,Vibrate) { auto settings = std::make_shared(); auto ne = std::make_shared(APP_NAME); auto func = [this](const Appointment&, const Alarm&){g_idle_add(quit_idle, loop);}; GError * error = nullptr; struct { bool other_vibrations; // the com.ubuntu.touch.AccountsService.Sound "other vibrations" setting const char* haptic_mode; // supported values: "none", "pulse" bool expected_vibrate_called; // do we expect the phone to vibrate? } test_cases[] = { { false, "none", false }, { true, "none", false }, { false, "pulse", false }, { true, "pulse", true } }; auto snap = std::make_shared(ne, settings); for(const auto& test_case : test_cases) { // clear out any previous iterations' noise dbus_test_dbus_mock_object_clear_method_calls(haptic_mock, haptic_obj, &error); // set the properties to match the test case settings->alarm_haptic.set(test_case.haptic_mode); dbus_test_dbus_mock_object_update_property(as_mock, as_obj, PROP_OTHER_VIBRATIONS, g_variant_new_boolean(test_case.other_vibrations), &error); g_assert_no_error(error); wait_msec(100); // run the test (*snap)(appt, appt.alarms.front(), func, func); wait_msec(100); 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(test_case.expected_vibrate_called, vibrate_called); } }