diff options
Diffstat (limited to 'tests')
34 files changed, 2918 insertions, 760 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 5206259..4a38890 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,11 +1,6 @@ -# build libgtest -add_library (gtest STATIC - ${GTEST_SOURCE_DIR}/gtest-all.cc - ${GTEST_SOURCE_DIR}/gtest_main.cc) -set_target_properties (gtest PROPERTIES INCLUDE_DIRECTORIES ${INCLUDE_DIRECTORIES} ${GTEST_INCLUDE_DIR}) -set_target_properties (gtest PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -w") +find_package(GMock REQUIRED) -SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -g ${COMPILE_FLAGS}") +SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 ${COMPILE_FLAGS}") # dbustest pkg_check_modules(DBUSTEST REQUIRED @@ -42,13 +37,17 @@ add_definitions (-DG_LOG_DOMAIN="ayatana-indicator-datetime") function(add_test_by_name name) set (TEST_NAME ${name}) + set (COVERAGE_TEST_TARGETS ${COVERAGE_TEST_TARGETS} ${TEST_NAME} PARENT_SCOPE) add_executable (${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled) + target_link_options(${TEST_NAME} PRIVATE -no-pie) add_test (${TEST_NAME} ${TEST_NAME}) - target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS} ${URLDISPATCHER_LIBRARIES}) + target_link_libraries (${TEST_NAME} indicatordatetimeservice ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) 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) +add_test_by_name(test-notification-response) endif() add_test_by_name(test-actions) add_test_by_name(test-alarm-queue) @@ -58,6 +57,7 @@ add_test_by_name(test-exporter) add_test_by_name(test-formatter) add_test_by_name(test-live-actions) add_test_by_name(test-locations) +add_test_by_name(test-menu-appointments) add_test_by_name(test-menus) add_test_by_name(test-planner) add_test_by_name(test-settings) @@ -66,8 +66,10 @@ add_test_by_name(test-utils) if(HAVE_UT_ACCTSERVICE_SYSTEMSOUND_SETTINGS) set (TEST_NAME manual-test-snap) + set (COVERAGE_TEST_TARGETS ${COVERAGE_TEST_TARGETS} ${TEST_NAME}) add_executable (${TEST_NAME} ${TEST_NAME}.cpp) - target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) + target_link_options(${TEST_NAME} PRIVATE -no-pie) + target_link_libraries (${TEST_NAME} indicatordatetimeservice ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) endif() ## @@ -78,17 +80,20 @@ find_program(DBUS_RUNNER dbus-test-runner) function(add_eds_ics_test_by_name name) set (TEST_NAME ${name}) + set (COVERAGE_TEST_TARGETS ${COVERAGE_TEST_TARGETS} ${TEST_NAME} PARENT_SCOPE) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/${TEST_NAME}.ics.in" "${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}.ics") add_executable(${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled) - target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBS}) + target_link_options(${TEST_NAME} PRIVATE -no-pie) + target_link_libraries (${TEST_NAME} indicatordatetimeservice ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) add_test (${TEST_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/run-eds-ics-test.sh ${DBUS_RUNNER} # arg1: dbus-test-runner exec ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME} # arg2: test executable path ${TEST_NAME} # arg3: test name ${CMAKE_CURRENT_SOURCE_DIR}/test-eds-ics-config-files # arg4: base directory for config file template - ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}.ics) # arg5: the ical file for this test + ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}.ics # arg5: the ical file for this test + ${CMAKE_CURRENT_SOURCE_DIR}/accounts.db) # arg6: online accounts database endfunction() add_eds_ics_test_by_name(test-eds-ics-all-day-events) add_eds_ics_test_by_name(test-eds-ics-repeating-events) @@ -97,15 +102,25 @@ add_eds_ics_test_by_name(test-eds-ics-repeating-valarms) add_eds_ics_test_by_name(test-eds-ics-missing-trigger) add_eds_ics_test_by_name(test-eds-ics-tzids) add_eds_ics_test_by_name(test-eds-ics-tzids-2) +add_eds_ics_test_by_name(test-eds-ics-tzids-utc) +add_eds_ics_test_by_name(test-eds-ics-non-attending-alarms) +add_eds_ics_test_by_name(test-eds-ics-repeating-events-with-individual-change) # disabling the timezone unit tests because they require # https://code.launchpad.net/~ted/dbus-test-runner/multi-interface-test/+merge/199724 # which hasn't landed yet. These can be re-enabled as soon as that lands. #function(add_dbusmock_test_by_name name) # set (TEST_NAME ${name}) +# set (COVERAGE_TEST_TARGETS ${COVERAGE_TEST_TARGETS} ${TEST_NAME} PARENT_SCOPE) # add_executable (${TEST_NAME} ${TEST_NAME}.cpp gschemas.compiled) # add_test (${TEST_NAME} ${TEST_NAME}) -# target_link_libraries (${TEST_NAME} indicatordatetimeservice gtest ${SERVICE_DEPS_LIBRARIES} ${DBUSTEST_LIBRARIES} ${GTEST_LIBS}) +# target_link_libraries (${TEST_NAME} indicatordatetimeservice ${SERVICE_DEPS_LIBRARIES} ${DBUSTEST_LIBRARIES} ${GTEST_LIBRARIES} ${GMOCK_LIBRARIES}) #endfunction() #add_dbusmock_test_by_name(test-timezone-geoclue) #add_dbusmock_test_by_name(test-timezones) + +set( + COVERAGE_TEST_TARGETS + ${COVERAGE_TEST_TARGETS} + PARENT_SCOPE +) diff --git a/tests/accounts.db b/tests/accounts.db Binary files differnew file mode 100644 index 0000000..ece5b2f --- /dev/null +++ b/tests/accounts.db diff --git a/tests/actions-mock.h b/tests/actions-mock.h index 59a0912..a02a7e2 100644 --- a/tests/actions-mock.h +++ b/tests/actions-mock.h @@ -34,14 +34,10 @@ public: explicit MockActions(const std::shared_ptr<State>& state_in): Actions(state_in) {} ~MockActions() =default; - enum Action { DesktopOpenAlarmApp, - DesktopOpenAppt, - DesktopOpenCalendarApp, - DesktopOpenSettingsApp, - PhoneOpenAlarmApp, - PhoneOpenAppt, - PhoneOpenCalendarApp, - PhoneOpenSettingsApp, + enum Action { OpenAlarmApp, + OpenAppt, + OpenCalendarApp, + OpenSettingsApp, SetLocation }; const std::vector<Action>& history() const { return m_history; } @@ -54,34 +50,20 @@ public: bool desktop_has_calendar_app() const { return m_desktop_has_calendar_app; } - void desktop_open_alarm_app() { - m_history.push_back(DesktopOpenAlarmApp); + void open_alarm_app() { + m_history.push_back(OpenAlarmApp); } - void desktop_open_appointment(const Appointment& appt) { + void open_appointment(const Appointment& appt, const DateTime& dt) { m_appt = appt; - m_history.push_back(DesktopOpenAppt); - } - void desktop_open_calendar_app(const DateTime& dt) { m_date_time = dt; - m_history.push_back(DesktopOpenCalendarApp); - } - void desktop_open_settings_app() { - m_history.push_back(DesktopOpenSettingsApp); - } - - void phone_open_alarm_app() { - m_history.push_back(PhoneOpenAlarmApp); - } - void phone_open_appointment(const Appointment& appt) { - m_appt = appt; - m_history.push_back(PhoneOpenAppt); + m_history.push_back(OpenAppt); } - void phone_open_calendar_app(const DateTime& dt) { + void open_calendar_app(const DateTime& dt) { m_date_time = dt; - m_history.push_back(PhoneOpenCalendarApp); + m_history.push_back(OpenCalendarApp); } - void phone_open_settings_app() { - m_history.push_back(PhoneOpenSettingsApp); + void open_settings_app() { + m_history.push_back(OpenSettingsApp); } void set_location(const std::string& zone_, const std::string& name_) { diff --git a/tests/glib-fixture.h b/tests/glib-fixture.h index 4d309e6..88ee384 100644 --- a/tests/glib-fixture.h +++ b/tests/glib-fixture.h @@ -1,5 +1,5 @@ /* - * Copyright 2013 Canonical Ltd. + * Copyright 2013-2016 Canonical Ltd. * * Authors: * Charles Kerr <charles.kerr@canonical.com> @@ -17,10 +17,12 @@ * with this program. If not, see <http://www.gnu.org/licenses/>. */ -#ifndef INDICATOR_DATETIME_TESTS_GLIB_FIXTURE_H -#define INDICATOR_DATETIME_TESTS_GLIB_FIXTURE_H +#pragma once +#include <chrono> +#include <functional> // std::function #include <map> +#include <memory> // std::shared_ptr #include <glib.h> #include <glib/gstdio.h> @@ -109,7 +111,115 @@ class GlibFixture : public ::testing::Test g_source_remove(id); } - GMainLoop * loop; + bool wait_for(std::function<bool()> test_function, guint timeout_msec=1000) + { + auto timer = std::shared_ptr<GTimer>(g_timer_new(), [](GTimer* t){g_timer_destroy(t);}); + const auto timeout_sec = timeout_msec / 1000.0; + for (;;) { + if (test_function()) + return true; + //g_message("%f ... %f", g_timer_elapsed(timer.get(), nullptr), timeout_sec); + if (g_timer_elapsed(timer.get(), nullptr) >= timeout_sec) + return false; + wait_msec(); + } + } + + bool wait_for_name_owned(GDBusConnection* connection, + const gchar* name, + guint timeout_msec=1000, + GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + struct Data { + GMainLoop* loop = nullptr; + bool owned = false; + }; + Data data; + + auto on_name_appeared = [](GDBusConnection* /*connection*/, + const gchar* /*name_*/, + const gchar* name_owner, + gpointer gdata) + { + if (name_owner == nullptr) + return; + auto tmp = static_cast<Data*>(gdata); + tmp->owned = true; + g_main_loop_quit(tmp->loop); + }; + + const auto timeout_id = g_timeout_add(timeout_msec, wait_msec__timeout, loop); + data.loop = loop; + const auto watch_id = g_bus_watch_name_on_connection(connection, + name, + flags, + on_name_appeared, + nullptr, /* name_vanished */ + &data, + nullptr); /* user_data_free_func */ + g_main_loop_run(loop); + + g_bus_unwatch_name(watch_id); + g_source_remove(timeout_id); + + return data.owned; + } + + void EXPECT_NAME_OWNED_EVENTUALLY(GDBusConnection* connection, + const gchar* name, + guint timeout_msec=1000, + GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + EXPECT_TRUE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; + } + + void EXPECT_NAME_NOT_OWNED_EVENTUALLY(GDBusConnection* connection, + const gchar* name, + guint timeout_msec=1000, + GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + EXPECT_FALSE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; + } + + void ASSERT_NAME_OWNED_EVENTUALLY(GDBusConnection* connection, + const gchar* name, + guint timeout_msec=1000, + GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + ASSERT_TRUE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; + } + + void ASSERT_NAME_NOT_OWNED_EVENTUALLY(GDBusConnection* connection, + const gchar* name, + guint timeout_msec=1000, + GBusNameWatcherFlags flags=G_BUS_NAME_WATCHER_FLAGS_AUTO_START) + { + ASSERT_FALSE(wait_for_name_owned(connection, name, timeout_msec, flags)) << "name: " << name; + } + + using source_func = std::function<gboolean()>; + + guint idle_add(source_func&& func) + { + return g_idle_add_full( + G_PRIORITY_DEFAULT_IDLE, + [](gpointer gf){return (*static_cast<source_func*>(gf))();}, + new std::function<gboolean()>(func), + [](gpointer gf){delete static_cast<source_func*>(gf);} + ); + } + + guint timeout_add(source_func&& func, std::chrono::milliseconds msec) + { + return g_timeout_add_full( + G_PRIORITY_DEFAULT, + msec.count(), + [](gpointer gf){return (*static_cast<source_func*>(gf))();}, + new std::function<gboolean()>(func), + [](gpointer gf){delete static_cast<source_func*>(gf);} + ); + } + + GMainLoop* loop {}; }; -#endif /* INDICATOR_DATETIME_TESTS_GLIB_FIXTURE_H */ diff --git a/tests/libdbusmock-fixture.h b/tests/libdbusmock-fixture.h new file mode 100644 index 0000000..7301042 --- /dev/null +++ b/tests/libdbusmock-fixture.h @@ -0,0 +1,148 @@ +/* + * 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 <libdbustest/dbus-test.h> + +/*** +**** +***/ + +class LibdbusmockFixture: public GlibFixture +{ +private: + + typedef GlibFixture super; + +protected: + + GDBusConnection * system_bus {}; + GDBusConnection * session_bus {}; + DbusTestService * service {}; + + void SetUp() override + { + + super::SetUp(); + + service = dbus_test_service_new(nullptr); + } + + void startDbusMock() + { + // 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(&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... + wait_for([this](){return system_bus==nullptr && session_bus==nullptr;}, 5000); + + super::TearDown(); + } + + bool wait_for_method_call(DbusTestDbusMock* mock, + DbusTestDbusMockObject* obj, + const gchar* method, + GVariant* params=nullptr, + guint timeout_msec=100) + { + if (params != nullptr) + g_variant_ref_sink(params); + + auto test_function = [mock, obj, method, params]() { + GError* error {}; + const auto called = dbus_test_dbus_mock_object_check_method_call(mock, + obj, + method, + params, + &error); + if (error != nullptr) { + g_critical("Error looking for method call '%s': %s", method, error->message); + g_clear_error(&error); + } + + return called; + }; + + const auto ret = wait_for(test_function, timeout_msec); + g_clear_pointer(¶ms, g_variant_unref); + return ret; + } + + void EXPECT_METHOD_CALLED_EVENTUALLY(DbusTestDbusMock* mock, + DbusTestDbusMockObject* obj, + const gchar* method, + GVariant* params=nullptr, + guint timeout_msec=1000) + { + EXPECT_TRUE(wait_for_method_call(mock, obj, method, params, timeout_msec)) << "method: " << method; + } + + void EXPECT_METHOD_NOT_CALLED_EVENTUALLY(DbusTestDbusMock* mock, + DbusTestDbusMockObject* obj, + const gchar* method, + GVariant* params=nullptr, + guint timeout_msec=1000) + { + EXPECT_FALSE(wait_for_method_call(mock, obj, method, params, timeout_msec)) << "method: " << method; + } + + void ASSERT_METHOD_CALLED_EVENTUALLY(DbusTestDbusMock* mock, + DbusTestDbusMockObject* obj, + const gchar* method, + GVariant* params=nullptr, + guint timeout_msec=1000) + { + ASSERT_TRUE(wait_for_method_call(mock, obj, method, params, timeout_msec)) << "method: " << method; + } + + void ASSERT_METHOD_NOT_CALLED_EVENTUALLY(DbusTestDbusMock* mock, + DbusTestDbusMockObject* obj, + const gchar* method, + GVariant* params=nullptr, + guint timeout_msec=1000) + { + ASSERT_FALSE(wait_for_method_call(mock, obj, method, params, timeout_msec)) << "method: " << method; + } +}; + diff --git a/tests/manual-test-snap.cpp b/tests/manual-test-snap.cpp index a0f80f2..72fd374 100644 --- a/tests/manual-test-snap.cpp +++ b/tests/manual-test-snap.cpp @@ -74,12 +74,14 @@ int main(int argc, const char* argv[]) a.alarms.push_back(Alarm{"Alarm Text", "", a.begin}); auto loop = g_main_loop_new(nullptr, false); - auto on_snooze = [loop](const Appointment& appt, const Alarm&){ - g_message("You clicked 'Snooze' for appt url '%s'", appt.summary.c_str()); - g_idle_add(quit_idle, loop); - }; - auto on_ok = [loop](const Appointment&, const Alarm&){ - g_message("You clicked 'OK'"); + auto on_response = [loop](const Appointment& appt, const Alarm&, const Snap::Response& response){ + const char* str {""}; + switch(response) { + case Snap::Response::ShowApp: str = "show-app"; break; + case Snap::Response::Snooze: str = "snooze"; break; + case Snap::Response::None: str = "no-action"; break; + }; + g_message("You clicked '%s' for appt url '%s'", str, appt.summary.c_str()); g_idle_add(quit_idle, loop); }; @@ -92,10 +94,13 @@ int main(int argc, const char* argv[]) settings->alarm_volume.set(volume); auto notification_engine = std::make_shared<ain::Engine>("ayatana-indicator-datetime-service"); - Snap snap (notification_engine, settings); - snap(a, a.alarms.front(), on_snooze, on_ok); + auto sound_builder = std::make_shared<ain::DefaultSoundBuilder>(); + auto system_bus = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr); + Snap snap (notification_engine, sound_builder, settings, system_bus); + snap(a, a.alarms.front(), on_response); g_main_loop_run(loop); g_main_loop_unref(loop); + g_clear_object(&system_bus); return 0; } diff --git a/tests/test-snap.cpp b/tests/notification-fixture.h index afee297..cbce9ff 100644 --- a/tests/test-snap.cpp +++ b/tests/notification-fixture.h @@ -1,8 +1,5 @@ /* - * Copyright 2014 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> + * 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 @@ -15,8 +12,15 @@ * * 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 "libdbusmock-fixture.h" + #include <datetime/appointment.h> #include <datetime/dbus-shared.h> #include <datetime/settings.h> @@ -27,38 +31,25 @@ #include <libdbustest/dbus-test.h> -#include <glib.h> - #include <unistd.h> // getuid() #include <sys/types.h> // getuid() -using namespace ayatana::indicator::datetime; - -#include "glib-fixture.h" - /*** **** ***/ -namespace -{ - static constexpr char const * APP_NAME {"ayatana-indicator-datetime-service"}; -} - -using namespace ayatana::indicator::datetime; - -class SnapFixture: public GlibFixture +class NotificationFixture: public LibdbusmockFixture { private: - typedef GlibFixture super; + typedef LibdbusmockFixture super; + +protected: 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}; @@ -87,15 +78,13 @@ protected: 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 * AS_INTERFACE {"com.lomiri.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; + ayatana::indicator::datetime::Appointment appt; + ayatana::indicator::datetime::Appointment ualarm; + DbusTestDbusMock * as_mock = nullptr; DbusTestDbusMock * notify_mock = nullptr; DbusTestDbusMock * powerd_mock = nullptr; @@ -118,23 +107,21 @@ protected: 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.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(Alarm{"Ho Ho Ho!", "", appt.begin}); + appt.alarms.push_back(ayatana::indicator::datetime::Alarm{"Ho Ho Ho!", CALENDAR_DEFAULT_SOUND, appt.begin}); - // init an Ubuntu Alarm + // init a Lomiri Alarm ualarm.color = "red"; ualarm.summary = "Wakeup"; ualarm.uid = "E4B57D50247291478ED31DED17FF0A9838DED403"; - ualarm.type = Appointment::UBUNTU_ALARM; - const auto tomorrow = DateTime::NowLocal().add_days(1); + 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(Alarm{"It's Tomorrow!", "", appt.begin}); - - service = dbus_test_service_new(nullptr); + ualarm.alarms.push_back(ayatana::indicator::datetime::Alarm{"It's Tomorrow!", "", appt.begin}); /// /// Add the AccountsService mock @@ -311,23 +298,7 @@ protected: 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); + startDbusMock(); } void TearDown() override @@ -337,20 +308,6 @@ protected: 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_context_pending(nullptr)) - g_main_context_iteration(nullptr, true); - cleartry++; - } super::TearDown(); } @@ -371,213 +328,15 @@ protected: &error); g_assert_no_error (error); } -}; -/*** -**** -***/ - -namespace -{ - gboolean quit_idle (gpointer gloop) + 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) { - 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 ne = std::make_shared<ayatana::indicator::notifications::Engine>(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<std::chrono::milliseconds>(duration).count(), i32); - g_variant_unref(hints); - ne.reset(); -} - -/*** -**** -***/ - -TEST_F(SnapFixture, InhibitSleep) -{ - auto settings = std::make_shared<Settings>(); - auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(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<Settings>(); - auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(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<Settings>(); - auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(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<Snap>(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); + auto snap = std::make_shared<ayatana::indicator::datetime::Snap>(ne, sb, settings, system_bus); + wait_msec(100); // wait a moment for the Snap to finish its async dbus bootstrapping + return snap; } -} +}; + diff --git a/tests/print-to.h b/tests/print-to.h index 19367ac..652da52 100644 --- a/tests/print-to.h +++ b/tests/print-to.h @@ -21,6 +21,7 @@ #define INDICATOR_DATETIME_TESTS_PRINT_TO #include <algorithm> +#include <vector> #include <datetime/appointment.h> @@ -71,6 +72,15 @@ PrintTo(const Appointment& appointment, std::ostream* os) *os << '}'; } +void +PrintTo(const std::vector<Appointment>& appointments, std::ostream* os) +{ + *os << '{'; + for (const auto& appointment : appointments) + PrintTo(appointment, os); + *os << '}'; +} + } // namespace datetime } // namespace indicator } // namespace ayatana diff --git a/tests/run-eds-ics-test.sh b/tests/run-eds-ics-test.sh index 13c1617..b38fe77 100755 --- a/tests/run-eds-ics-test.sh +++ b/tests/run-eds-ics-test.sh @@ -6,6 +6,7 @@ TEST_EXEC=$2 # full executable path of test app TEST_NAME=$3 # test name CONFIG_DIR=$4 # config files ICS_FILE=$5 # ical file holding test data +ACCOUNTS_DB=$6 # online account database echo "this script: ${SELF}" echo "test-runner: ${TEST_RUNNER}" @@ -55,6 +56,13 @@ if [ -e ${ICS_FILE} ]; then cp --verbose --archive ${ICS_FILE} ${XDG_DATA_HOME}/evolution/tasks/system/tasks.ics fi +# prepare online accounts database +if [ -e ${ACCOUNTS_DB} ]; then + echo "copying ${ACCOUNTS_DB} into $HOME" + mkdir -p ${XDG_CONFIG_HOME}/libaccounts-glib/ + cp --verbose --archive ${ACCOUNTS_DB} ${XDG_CONFIG_HOME}/libaccounts-glib/accounts.db +fi + # run the test ${TEST_RUNNER} --keep-env --max-wait=90 --task ${TEST_EXEC} --task-name ${TEST_NAME} --wait-until-complete rv=$? diff --git a/tests/test-actions.cpp b/tests/test-actions.cpp index aa608a8..a01fb83 100644 --- a/tests/test-actions.cpp +++ b/tests/test-actions.cpp @@ -116,7 +116,7 @@ protected: m_mock_state->mock_range_planner->appointments().set(appointments); // activate the action - auto v = g_variant_new_string(appointments[0].uid.c_str()); + auto v = g_variant_new("(sx)", appointments[0].uid.c_str(), 0); g_action_group_activate_action(action_group, action_name, v); // test the results @@ -134,7 +134,7 @@ protected: EXPECT_TRUE(m_mock_actions->history().empty()); // activate the action - v = g_variant_new_string("this-uid-is-not-one-that-we-have"); + v = g_variant_new("(sx)", "this-uid-is-not-one-that-we-have", 0); g_action_group_activate_action(action_group, action_name, v); // test the results @@ -176,25 +176,25 @@ TEST_F(ActionsFixture, ActionsExist) TEST_F(ActionsFixture, DesktopOpenAlarmApp) { test_action_with_no_args("desktop.open-alarm-app", - MockActions::DesktopOpenAlarmApp); + MockActions::OpenAlarmApp); } TEST_F(ActionsFixture, DesktopOpenAppointment) { test_action_with_appt_arg("desktop.open-appointment", - MockActions::DesktopOpenAppt); + MockActions::OpenAppt); } TEST_F(ActionsFixture, DesktopOpenCalendarApp) { test_action_with_time_arg("desktop.open-calendar-app", - MockActions::DesktopOpenCalendarApp); + MockActions::OpenCalendarApp); } TEST_F(ActionsFixture, DesktopOpenSettingsApp) { test_action_with_no_args("desktop.open-settings-app", - MockActions::DesktopOpenSettingsApp); + MockActions::OpenSettingsApp); } /*** @@ -204,25 +204,25 @@ TEST_F(ActionsFixture, DesktopOpenSettingsApp) TEST_F(ActionsFixture, PhoneOpenAlarmApp) { test_action_with_no_args("phone.open-alarm-app", - MockActions::PhoneOpenAlarmApp); + MockActions::OpenAlarmApp); } TEST_F(ActionsFixture, PhoneOpenAppointment) { test_action_with_appt_arg("phone.open-appointment", - MockActions::PhoneOpenAppt); + MockActions::OpenAppt); } TEST_F(ActionsFixture, PhoneOpenCalendarApp) { test_action_with_time_arg("phone.open-calendar-app", - MockActions::PhoneOpenCalendarApp); + MockActions::OpenCalendarApp); } TEST_F(ActionsFixture, PhoneOpenSettingsApp) { test_action_with_no_args("phone.open-settings-app", - MockActions::PhoneOpenSettingsApp); + MockActions::OpenSettingsApp); } /*** diff --git a/tests/test-eds-ics-all-day-events.cpp b/tests/test-eds-ics-all-day-events.cpp index 68a3c95..5d7cdc6 100644 --- a/tests/test-eds-ics-all-day-events.cpp +++ b/tests/test-eds-ics-all-day-events.cpp @@ -24,6 +24,7 @@ #include <datetime/alarm-queue-simple.h> #include <datetime/clock-mock.h> #include <datetime/engine-eds.h> +#include <datetime/myself.h> #include <datetime/planner-range.h> #include <gtest/gtest.h> @@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture; TEST_F(VAlarmFixture, MultipleAppointments) { // start the EDS engine - auto engine = std::make_shared<EdsEngine>(); + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); // we need a consistent timezone for the planner and our local DateTimes constexpr char const * zone_str {"America/Chicago"}; diff --git a/tests/test-eds-ics-missing-trigger.cpp b/tests/test-eds-ics-missing-trigger.cpp index 0aa00c6..70bbccb 100644 --- a/tests/test-eds-ics-missing-trigger.cpp +++ b/tests/test-eds-ics-missing-trigger.cpp @@ -21,9 +21,10 @@ #include <algorithm> -#include <datetime/engine-eds.h> #include <datetime/alarm-queue-simple.h> #include <datetime/clock-mock.h> +#include <datetime/engine-eds.h> +#include <datetime/myself.h> #include <datetime/planner-range.h> #include <gtest/gtest.h> @@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture; TEST_F(VAlarmFixture, MissingTriggers) { // start the EDS engine - auto engine = std::make_shared<EdsEngine>(); + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); // we need a consistent timezone for the planner and our local DateTimes constexpr char const * zone_str {"America/Chicago"}; @@ -62,7 +63,7 @@ TEST_F(VAlarmFixture, MissingTriggers) // make a planner that looks at the first half of 2015 in EDS auto planner = std::make_shared<SimpleRangePlanner>(engine, tz); const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0}; - const DateTime range_end {gtz, 2015,6,30,23,59,59.5}; + const DateTime range_end {gtz, 2015,7,1,23,59,59.5}; planner->range().set(std::make_pair(range_begin, range_end)); // give EDS a moment to load diff --git a/tests/test-eds-ics-non-attending-alarms.cpp b/tests/test-eds-ics-non-attending-alarms.cpp new file mode 100644 index 0000000..bfa3ac3 --- /dev/null +++ b/tests/test-eds-ics-non-attending-alarms.cpp @@ -0,0 +1,89 @@ +/* + * Copyright 2015 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 <algorithm> + +#include <datetime/alarm-queue-simple.h> +#include <datetime/clock-mock.h> +#include <datetime/engine-eds.h> +#include <datetime/myself.h> +#include <datetime/planner-range.h> + +#include <gtest/gtest.h> + +#include "glib-fixture.h" +#include "print-to.h" +#include "timezone-mock.h" +#include "wakeup-timer-mock.h" + +using namespace ayatana::indicator::datetime; +using VAlarmFixture = GlibFixture; + +/*** +**** +***/ + +TEST_F(VAlarmFixture, NonAttendingEvent) +{ + // start the EDS engine + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); + + // we need a consistent timezone for the planner and our local DateTimes + constexpr char const * zone_str {"America/Recife"}; + auto tz = std::make_shared<MockTimezone>(zone_str); + + #if GLIB_CHECK_VERSION(2, 68, 0) + auto gtz = g_time_zone_new_identifier(zone_str); + + if (gtz == NULL) + { + gtz = g_time_zone_new_utc(); + } + #else + auto gtz = g_time_zone_new(zone_str); + #endif + + // make a planner that looks at the first half of 2016 in EDS + auto planner = std::make_shared<SimpleRangePlanner>(engine, tz); + const DateTime range_begin {gtz, 2016,1, 1, 0, 0, 0.0}; + const DateTime range_end {gtz, 2016,6,30,23,59,59.5}; + planner->range().set(std::make_pair(range_begin, range_end)); + + // give EDS a moment to load + if (planner->appointments().get().empty()) { + g_message("waiting a moment for EDS to load..."); + auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){ + g_message("ah, they loaded"); + if (!appointments.empty()) + g_main_loop_quit(loop); + }; + core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); + constexpr int max_wait_sec = 10; + wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); + } + + // the planner should match what we've got in the calendar.ics file + const auto appts = planner->appointments().get(); + EXPECT_EQ(2, appts.size()); + EXPECT_EQ(appts[0].begin, DateTime(gtz, 2016, 4, 4, 16, 0, 0)); + EXPECT_EQ(appts[1].begin, DateTime(gtz, 2016, 4, 6, 16, 0, 0)); + + // cleanup + g_time_zone_unref(gtz); +} diff --git a/tests/test-eds-ics-non-attending-alarms.ics.in b/tests/test-eds-ics-non-attending-alarms.ics.in new file mode 100644 index 0000000..7adc8ab --- /dev/null +++ b/tests/test-eds-ics-non-attending-alarms.ics.in @@ -0,0 +1,53 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +X-EVOLUTION-DATA-REVISION:2015-04-05T21:32:47.354433Z(2) +BEGIN:VEVENT +STATUS:CONFIRMED +DTSTAMP:20160405T152128Z +CREATED:20160405T152128Z +UID:ddtvl069dn2cquo8dhg3j9c360@google.com +SEQUENCE:1 +TRANSP:OPAQUE +SUMMARY:Every day at 4PM +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160404T160000 +RRULE:FREQ=DAILY;UNTIL=20160406T190000Z +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160404T170000 +ATTENDEE;CN=Uphablet;PARTSTAT=NEEDS-ACTION;ROLE=REQ-PARTICIPANT; + CUTYPE=INDIVIDUAL:mailto:uphablet@lomiri.com +LAST-MODIFIED:20160405T152128Z +BEGIN:VALARM +TRIGGER;VALUE=DURATION:-PT30M +ACTION:EMAIL +DESCRIPTION:This is an event reminder +X-EVOLUTION-ALARM-UID:20160405T152128Z-2848-32011-1844-65@lomiri-phablet +END:VALARM +END:VEVENT +BEGIN:VEVENT +STATUS:CONFIRMED +DTSTAMP:20160405T152128Z +CREATED:20160405T151054Z +UID:ddtvl069dn2cquo8dhg3j9c360@google.com +SEQUENCE:1 +TRANSP:OPAQUE +SUMMARY::Every day at 4PM +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Fortaleza: + 20160405T160000 +RECURRENCE-ID;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160405T160000 +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Fortaleza: + 20160405T170000 +ATTENDEE;CN=Uphablet;PARTSTAT=DECLINED;ROLE=REQ-PARTICIPANT; + CUTYPE=INDIVIDUAL:mailto:uphablet@lomiri.com +LAST-MODIFIED:20160405T152128Z +BEGIN:VALARM +TRIGGER;VALUE=DURATION:-PT30M +ACTION:EMAIL +DESCRIPTION:This is an event reminder +X-EVOLUTION-ALARM-UID:20160405T152128Z-2848-32011-1844-66@lomiri-phablet +END:VALARM +END:VEVENT +END:VCALENDAR diff --git a/tests/test-eds-ics-non-selected-source.cpp b/tests/test-eds-ics-non-selected-source.cpp new file mode 100644 index 0000000..5101b32 --- /dev/null +++ b/tests/test-eds-ics-non-selected-source.cpp @@ -0,0 +1,94 @@ +/* + * Copyright 2015 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: + * Renato Araujo Oliveira Filh <renato.filho@canonical.com> + */ + +#include <algorithm> + +#include <datetime/engine-eds.h> +#include <datetime/myself.h> +#include <datetime/planner-range.h> + +#include <libedataserver/libedataserver.h> + +#include <gtest/gtest.h> + +#include "glib-fixture.h" +#include "timezone-mock.h" + +using namespace ayatana::indicator::datetime; +using VAlarmFixture = GlibFixture; + +/*** +**** +***/ + +TEST_F(VAlarmFixture, NonSelectedSources) +{ + // start the EDS engine + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); + + // we need a consistent timezone for the planner and our local DateTimes + constexpr char const * zone_str {"America/Chicago"}; + auto tz = std::make_shared<MockTimezone>(zone_str); + auto gtz = g_time_zone_new(zone_str); + + // make a planner that looks at the first half of 2015 in EDS + auto planner = std::make_shared<SimpleRangePlanner>(engine, tz); + const DateTime range_begin {gtz, 2015,1, 1, 0, 0, 0.0}; + const DateTime range_end {gtz, 2015,6,31,23,59,59.5}; + planner->range().set(std::make_pair(range_begin, range_end)); + + // give EDS a moment to load + if (planner->appointments().get().empty()) { + g_message("waiting a moment for EDS to load..."); + auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){ + g_message("ah, they loaded"); + if (!appointments.empty()) + g_main_loop_quit(loop); + }; + core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); + constexpr int max_wait_sec = 10; + wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); + } + + // appointmes are visible + auto appts = planner->appointments().get(); + EXPECT_TRUE(appts.size() > 0); + + // Unselect all sources + auto registry = e_source_registry_new_sync(NULL, NULL); + auto sources = e_source_registry_list_sources(registry, E_SOURCE_EXTENSION_TASK_LIST); + for (auto l=sources; l!=nullptr; l=l->next) { + auto source = static_cast<ESource*>(l->data); + auto extension = e_source_get_extension(source, E_SOURCE_EXTENSION_CALENDAR); + e_source_selectable_set_selected(E_SOURCE_SELECTABLE(extension), FALSE); + e_source_write_sync(source, NULL, NULL); + } + + g_list_free_full(sources, g_object_unref); + g_object_unref(registry); + + // give some time to planner update + wait_msec(5 * G_TIME_SPAN_MILLISECOND); + + // the planner should be empty at this point + appts = planner->appointments().get(); + EXPECT_TRUE(appts.size() == 0); + // cleanup + g_time_zone_unref(gtz); +} diff --git a/tests/test-eds-ics-non-selected-source.ics.in b/tests/test-eds-ics-non-selected-source.ics.in new file mode 100644 index 0000000..19f93d7 --- /dev/null +++ b/tests/test-eds-ics-non-selected-source.ics.in @@ -0,0 +1,28 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +X-EVOLUTION-DATA-REVISION:2015-05-07T21:14:49.315443Z(0) +BEGIN:VTODO +UID:20150507T211449Z-4262-32011-1418-1@lomiri-phablet +DTSTAMP:20150508T211449Z +DTSTART:20150508T164000 +RRULE:FREQ=WEEKLY;BYDAY=FR +SUMMARY:Alarm +CATEGORIES:x-lomiri-alarm +CREATED:20150507T211449Z +LAST-MODIFIED:20150507T211449Z +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-2@lomiri-phablet +ACTION:AUDIO +ATTACH:file://@ALARM_DEFAULT_SOUND@ +TRIGGER;VALUE=DURATION;RELATED=START:PT0S +END:VALARM +BEGIN:VALARM +X-EVOLUTION-ALARM-UID:20150507T211449Z-4262-32011-1418-3@lomiri-phablet +ACTION:DISPLAY +DESCRIPTION:Alarm +TRIGGER;VALUE=DURATION;RELATED=START:PT0S +END:VALARM +END:VTODO +END:VCALENDAR diff --git a/tests/test-eds-ics-nonrepeating-events.cpp b/tests/test-eds-ics-nonrepeating-events.cpp index e79ab1a..8aa2b82 100644 --- a/tests/test-eds-ics-nonrepeating-events.cpp +++ b/tests/test-eds-ics-nonrepeating-events.cpp @@ -24,6 +24,7 @@ #include <datetime/alarm-queue-simple.h> #include <datetime/clock-mock.h> #include <datetime/engine-eds.h> +#include <datetime/myself.h> #include <datetime/planner-range.h> #include <gtest/gtest.h> @@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture; TEST_F(VAlarmFixture, MultipleAppointments) { // start the EDS engine - auto engine = std::make_shared<EdsEngine>(); + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); // we need a consistent timezone for the planner and our local DateTimes constexpr char const * zone_str {"America/Chicago"}; @@ -80,7 +81,7 @@ TEST_F(VAlarmFixture, MultipleAppointments) // what we expect to get... Appointment expected_appt; - expected_appt.uid = "20150520T000726Z-3878-32011-1770-81@ubuntu-phablet"; + expected_appt.uid = "20150520T000726Z-3878-32011-1770-81@lomiri-phablet"; expected_appt.color = "#becedd"; expected_appt.summary = "Alarm"; std::array<Alarm,1> expected_alarms = { diff --git a/tests/test-eds-ics-repeating-events-with-individual-change.cpp b/tests/test-eds-ics-repeating-events-with-individual-change.cpp new file mode 100644 index 0000000..a4a85c0 --- /dev/null +++ b/tests/test-eds-ics-repeating-events-with-individual-change.cpp @@ -0,0 +1,110 @@ +/* + * Copyright 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: + * Renato Araujo Oliveira Filho <renato.filho@canonical.com> + */ + +#include <algorithm> + +#include <datetime/alarm-queue-simple.h> +#include <datetime/clock-mock.h> +#include <datetime/engine-eds.h> +#include <datetime/myself.h> +#include <datetime/planner-range.h> + +#include <gtest/gtest.h> + +#include "glib-fixture.h" +#include "print-to.h" +#include "timezone-mock.h" +#include "wakeup-timer-mock.h" + +using namespace ayatana::indicator::datetime; +using VAlarmFixture = GlibFixture; + +/*** +**** +***/ + +TEST_F(VAlarmFixture, RepeatingEventsWithIndividualChange) +{ + // start the EDS engine + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); + + // we need a consistent timezone for the planner and our local DateTimes + constexpr char const * zone_str {"America/Recife"}; + auto tz = std::make_shared<MockTimezone>(zone_str); + #if GLIB_CHECK_VERSION(2, 68, 0) + auto gtz = g_time_zone_new_identifier(zone_str); + + if (gtz == NULL) + { + gtz = g_time_zone_new_utc(); + } + #else + auto gtz = g_time_zone_new(zone_str); + #endif + + // make a planner that looks at the year of 2016 in EDS + auto planner = std::make_shared<SimpleRangePlanner>(engine, tz); + const DateTime range_begin {gtz, 2016,1, 1, 0, 0, 0.0}; + const DateTime range_end {gtz, 2016,12,31,23,59,59.5}; + planner->range().set(std::make_pair(range_begin, range_end)); + + // give EDS a moment to load + if (planner->appointments().get().empty()) { + g_message("waiting a moment for EDS to load..."); + auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){ + g_message("ah, they loaded"); + if (!appointments.empty()) + g_main_loop_quit(loop); + }; + core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); + constexpr int max_wait_sec = 10; + wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); + } + + // what we expect to get... + Appointment expected_appt; + expected_appt.summary = "Alarm"; + std::array<DateTime,10> expected_times = { + DateTime(gtz,2016,6, 20,10,00,0), + DateTime(gtz,2016,6, 21,10,00,0), + DateTime(gtz,2016,6, 22,10,00,0), + DateTime(gtz,2016,6, 23,10,00,0), + DateTime(gtz,2016,6, 24,20,00,0), + DateTime(gtz,2016,6, 25,10,00,0), + DateTime(gtz,2016,6, 26,10,00,0), + DateTime(gtz,2016,6, 27,10,00,0), + DateTime(gtz,2016,6, 28,10,00,0), + DateTime(gtz,2016,6, 29,10,00,0) + }; + + // compare it to what we actually loaded... + const auto appts = planner->appointments().get(); + EXPECT_EQ(expected_times.size(), appts.size()); + for (size_t i=0, n=expected_times.size(); i<n; i++) { + const auto& appt = appts[i]; + if (i != 4) + EXPECT_EQ("Every day and every night", appt.summary); + else + EXPECT_EQ("At night", appt.summary); + EXPECT_EQ(expected_times[i], appt.begin); + } + + // cleanup + g_time_zone_unref(gtz); +} diff --git a/tests/test-eds-ics-repeating-events-with-individual-change.ics.in b/tests/test-eds-ics-repeating-events-with-individual-change.ics.in new file mode 100644 index 0000000..3723399 --- /dev/null +++ b/tests/test-eds-ics-repeating-events-with-individual-change.ics.in @@ -0,0 +1,969 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +X-EVOLUTION-DATA-REVISION:2016-06-21T21:42:08.507434Z(7) +BEGIN:VTIMEZONE +TZID:/freeassociation.sourceforge.net/Tzfile/America/Recife +X-LIC-LOCATION:America/Recife +BEGIN:STANDARD +TZNAME:BRT +DTSTART:19680301T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:19851102T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:19860315T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:19861025T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:19870214T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:19871025T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:19880207T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:19881016T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:19890129T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:19891015T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:19900211T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:19991003T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:20000227T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:20001008T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:20001015T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:BRST +DTSTART:20011014T000000 +TZOFFSETFROM:-0300 +TZOFFSETTO:-0200 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:BRT +DTSTART:20020217T000000 +TZOFFSETFROM:-0200 +TZOFFSETTO:-0300 +END:STANDARD +END:VTIMEZONE +BEGIN:VTIMEZONE +TZID:/freeassociation.sourceforge.net/Tzfile/America/New_York +X-LIC-LOCATION:America/New_York +BEGIN:STANDARD +TZNAME:EST +DTSTART:19691026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19700426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19701025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19710425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19711031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19720430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19721029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19730429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19731028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19740106T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19741027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19750223T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19751026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19760425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19761031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19770424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19771030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19780430T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19781029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19790429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19791028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19800427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19801026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19810426T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19811025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19820425T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19821031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19830424T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19831030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19840429T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19841028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19850428T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19851027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19860427T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19861026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19870405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19871025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19880403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19881030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19890402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19891029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19900401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19901028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19910407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19911027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19920405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19921025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19930404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19931031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19940403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19941030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19950402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19951029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19960407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19961027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19970406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19971026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19980405T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19981025T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:19990404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:19991031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20000402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20001029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20010401T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20011028T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20020407T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20021027T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20030406T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20031026T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20040404T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20041031T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20050403T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20051030T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20060402T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20061029T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20070311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20071104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20080309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20081102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20090308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20091101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20100314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20101107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20110313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20111106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20120311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20121104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20130310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20131103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20140309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20141102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20150308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20151101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20160313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20161106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20170312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20171105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20180311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20181104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20190310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20191103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20200308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20201101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20210314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20211107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20220313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20221106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20230312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20231105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20240310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20241103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20250309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20251102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20260308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20261101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20270314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20271107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20280312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20281105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20290311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20291104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20300310T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20301103T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20310309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20311102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20320314T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20321107T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20330313T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20331106T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20340312T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20341105T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20350311T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20351104T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20360309T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20361102T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +BEGIN:DAYLIGHT +TZNAME:EDT +DTSTART:20370308T020000 +TZOFFSETFROM:-0500 +TZOFFSETTO:-0400 +END:DAYLIGHT +BEGIN:STANDARD +TZNAME:EST +DTSTART:20371101T020000 +TZOFFSETFROM:-0400 +TZOFFSETTO:-0500 +END:STANDARD +END:VTIMEZONE +BEGIN:VEVENT +UID:20160621T214054Z-31488-1000-4151-27@rento-lomiri +DTSTAMP:20160621T123718Z +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160620T100000 +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160620T103000 +TRANSP:OPAQUE +SEQUENCE:2 +SUMMARY:Every day and every night +CLASS:PUBLIC +RRULE;X-EVOLUTION-ENDDATE=20160629T130000Z:FREQ=DAILY;COUNT=10 +CREATED:20160621T214124Z +LAST-MODIFIED:20160621T214124Z +END:VEVENT +BEGIN:VEVENT +UID:20160621T214054Z-31488-1000-4151-27@rento-lomiri +DTSTAMP:20160621T123718Z +DTSTART;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160624T200000 +DTEND;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160624T203000 +TRANSP:OPAQUE +SEQUENCE:3 +SUMMARY:At night +CLASS:PUBLIC +CREATED:20160621T214124Z +LAST-MODIFIED:20160621T214208Z +RECURRENCE-ID;TZID=/freeassociation.sourceforge.net/Tzfile/America/Recife: + 20160624T100000 +END:VEVENT +END:VCALENDAR diff --git a/tests/test-eds-ics-repeating-events.cpp b/tests/test-eds-ics-repeating-events.cpp index d4f0026..4125623 100644 --- a/tests/test-eds-ics-repeating-events.cpp +++ b/tests/test-eds-ics-repeating-events.cpp @@ -24,6 +24,7 @@ #include <datetime/alarm-queue-simple.h> #include <datetime/clock-mock.h> #include <datetime/engine-eds.h> +#include <datetime/myself.h> #include <datetime/planner-range.h> #include <gtest/gtest.h> @@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture; TEST_F(VAlarmFixture, MultipleAppointments) { // start the EDS engine - auto engine = std::make_shared<EdsEngine>(); + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); // we need a consistent timezone for the planner and our local DateTimes constexpr char const * zone_str {"America/Chicago"}; @@ -80,7 +81,7 @@ TEST_F(VAlarmFixture, MultipleAppointments) // what we expect to get... Appointment expected_appt; - expected_appt.uid = "20150507T211449Z-4262-32011-1418-1@ubuntu-phablet"; + expected_appt.uid = "20150507T211449Z-4262-32011-1418-1@lomiri-phablet"; expected_appt.color = "#becedd"; expected_appt.summary = "Alarm"; std::array<Alarm,8> expected_alarms = { diff --git a/tests/test-eds-ics-repeating-valarms.cpp b/tests/test-eds-ics-repeating-valarms.cpp index 2132b71..1584983 100644 --- a/tests/test-eds-ics-repeating-valarms.cpp +++ b/tests/test-eds-ics-repeating-valarms.cpp @@ -24,6 +24,7 @@ #include <datetime/alarm-queue-simple.h> #include <datetime/clock-mock.h> #include <datetime/engine-eds.h> +#include <datetime/myself.h> #include <datetime/planner-range.h> #include <gtest/gtest.h> @@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture; TEST_F(VAlarmFixture, MultipleAppointments) { // start the EDS engine - auto engine = std::make_shared<EdsEngine>(); + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); // we need a consistent timezone for the planner and our local DateTimes constexpr char const * zone_str {"America/Chicago"}; diff --git a/tests/test-eds-ics-tzids-2.cpp b/tests/test-eds-ics-tzids-2.cpp index c8b0370..a1d2f5a 100644 --- a/tests/test-eds-ics-tzids-2.cpp +++ b/tests/test-eds-ics-tzids-2.cpp @@ -24,6 +24,7 @@ #include <datetime/alarm-queue-simple.h> #include <datetime/clock-mock.h> #include <datetime/engine-eds.h> +#include <datetime/myself.h> #include <datetime/planner-range.h> #include <gtest/gtest.h> @@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture; TEST_F(VAlarmFixture, MultipleAppointments) { // start the EDS engine - auto engine = std::make_shared<EdsEngine>(); + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); // we need a consistent timezone for the planner and our local DateTimes constexpr char const * zone_str {"America/Los_Angeles"}; diff --git a/tests/test-eds-ics-tzids-utc.cpp b/tests/test-eds-ics-tzids-utc.cpp new file mode 100644 index 0000000..f79bf3e --- /dev/null +++ b/tests/test-eds-ics-tzids-utc.cpp @@ -0,0 +1,98 @@ +/* + * Copyright 2015 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 <algorithm> + +#include <datetime/alarm-queue-simple.h> +#include <datetime/clock-mock.h> +#include <datetime/engine-eds.h> +#include <datetime/myself.h> +#include <datetime/planner-range.h> + +#include <gtest/gtest.h> + +#include "glib-fixture.h" +#include "print-to.h" +#include "timezone-mock.h" +#include "wakeup-timer-mock.h" + +using namespace ayatana::indicator::datetime; +using VAlarmFixture = GlibFixture; + +/*** +**** +***/ + +TEST_F(VAlarmFixture, UTCAppointments) +{ + // start the EDS engine + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); + + // we need a consistent timezone for the planner and our local DateTimes + constexpr char const * zone_str {"America/Recife"}; + auto tz = std::make_shared<MockTimezone>(zone_str); + + #if GLIB_CHECK_VERSION(2, 68, 0) + auto gtz = g_time_zone_new_identifier(zone_str); + + if (gtz == NULL) + { + gtz = g_time_zone_new_utc(); + } + #else + auto gtz = g_time_zone_new(zone_str); + #endif + + // make a planner that looks at the first half of 2015 in EDS + auto planner = std::make_shared<SimpleRangePlanner>(engine, tz); + const DateTime range_begin {gtz, 2016,1, 1, 0, 0, 0.0}; + const DateTime range_end {gtz, 2017,1, 1, 0, 0, 0.0}; + planner->range().set(std::make_pair(range_begin, range_end)); + + // give EDS a moment to load + if (planner->appointments().get().empty()) { + g_message("waiting a moment for EDS to load..."); + auto on_appointments_changed = [this](const std::vector<Appointment>& appointments){ + g_message("ah, they loaded"); + if (!appointments.empty()) + g_main_loop_quit(loop); + }; + core::ScopedConnection conn(planner->appointments().changed().connect(on_appointments_changed)); + constexpr int max_wait_sec = 10; + wait_msec(max_wait_sec * G_TIME_SPAN_MILLISECOND); + } + + // what we expect to get... + std::array<Appointment,1> expected_appts; + auto appt = &expected_appts[0]; + appt->uid = "20160322T132738Z"; + appt->color = "#becedd"; + appt->summary = "UTC event"; + appt->begin = DateTime{gtz,2016,3,22,15,0,0}; + appt->end = DateTime{gtz,2016,3,22,16,0,0}; + + // compare it to what we actually loaded... + const auto appts = planner->appointments().get(); + EXPECT_EQ(expected_appts.size(), appts.size()); + for (size_t i=0, n=std::min(appts.size(),expected_appts.size()); i<n; i++) + EXPECT_EQ(expected_appts[i], appts[i]); + + // cleanup + g_time_zone_unref(gtz); +} diff --git a/tests/test-eds-ics-tzids-utc.ics.in b/tests/test-eds-ics-tzids-utc.ics.in new file mode 100644 index 0000000..da607d6 --- /dev/null +++ b/tests/test-eds-ics-tzids-utc.ics.in @@ -0,0 +1,15 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +PRODID:-//Ximian//NONSGML Evolution Calendar//EN +VERSION:2.0 +X-EVOLUTION-DATA-REVISION:2016-03-22T16:13:53.714952Z(2) +BEGIN:VEVENT +UID:20160322T132738Z +DTSTAMP:20160322T133534Z +DTSTART:20160322T180000Z +DTEND:20160322T190000Z +SUMMARY:UTC event +SEQUENCE:1 +LAST-MODIFIED:20160322T133534Z +END:VEVENT +END:VCALENDAR diff --git a/tests/test-eds-ics-tzids.cpp b/tests/test-eds-ics-tzids.cpp index c80feb2..11d44b7 100644 --- a/tests/test-eds-ics-tzids.cpp +++ b/tests/test-eds-ics-tzids.cpp @@ -24,6 +24,7 @@ #include <datetime/alarm-queue-simple.h> #include <datetime/clock-mock.h> #include <datetime/engine-eds.h> +#include <datetime/myself.h> #include <datetime/planner-range.h> #include <gtest/gtest.h> @@ -43,7 +44,7 @@ using VAlarmFixture = GlibFixture; TEST_F(VAlarmFixture, MultipleAppointments) { // start the EDS engine - auto engine = std::make_shared<EdsEngine>(); + auto engine = std::make_shared<EdsEngine>(std::make_shared<Myself>()); // we need a consistent timezone for the planner and our local DateTimes constexpr char const * zone_str {"Europe/Berlin"}; diff --git a/tests/test-formatter.cpp b/tests/test-formatter.cpp index 855d4c5..87c6475 100644 --- a/tests/test-formatter.cpp +++ b/tests/test-formatter.cpp @@ -66,15 +66,14 @@ class FormatterFixture: public GlibFixture bool SetLocale(const char* expected_locale, const char* name) { - setlocale(LC_TIME, expected_locale); - const auto actual_locale = setlocale(LC_TIME, nullptr); - if (!g_strcmp0(expected_locale, actual_locale)) + if (setlocale(LC_TIME, expected_locale) != nullptr) { return true; } else { - g_message("Unable to set locale to %s, actual locale is %s; skipping %s locale tests.", expected_locale, actual_locale, name); + g_warning("Unable to set locale to %s; skipping %s locale tests. (Current LC_TIME: %s)", + expected_locale, name, setlocale(LC_TIME, nullptr)); return false; } } @@ -87,7 +86,7 @@ class FormatterFixture: public GlibFixture /** * Test the phone header format */ -TEST_F(FormatterFixture, TestPhoneHeader) +TEST_F(FormatterFixture, DISABLED_TestPhoneHeader) { auto now = DateTime::Local(2020, 10, 31, 18, 30, 59); auto clock = std::make_shared<MockClock>(now); @@ -114,7 +113,7 @@ TEST_F(FormatterFixture, TestPhoneHeader) /** * Test the default values of the desktop header format */ -TEST_F(FormatterFixture, TestDesktopHeader) +TEST_F(FormatterFixture, DISABLED_TestDesktopHeader) { struct { bool is_12h; diff --git a/tests/test-live-actions.cpp b/tests/test-live-actions.cpp index 9f17001..2d6ac9b 100644 --- a/tests/test-live-actions.cpp +++ b/tests/test-live-actions.cpp @@ -17,18 +17,72 @@ * Charles Kerr <charles.kerr@canonical.com> */ +#include "state-mock.h" #include "timedated-fixture.h" +#include <datetime/actions-live.h> + +using namespace ayatana::indicator::datetime; + +class MockLiveActions: public LiveActions +{ +public: + std::string last_cmd; + std::string last_url; + + explicit MockLiveActions(const std::shared_ptr<State>& state_in): LiveActions(state_in) {} + ~MockLiveActions() {} +}; + +class TestLiveActionsFixture: public TimedatedFixture +{ +private: + + using super = TimedatedFixture; + +protected: + + std::shared_ptr<MockState> m_mock_state; + std::shared_ptr<State> m_state; + std::shared_ptr<MockLiveActions> m_live_actions; + std::shared_ptr<Actions> m_actions; + + void SetUp() override + { + super::SetUp(); + + // create the State and Actions + m_mock_state.reset(new MockState); + m_mock_state->settings.reset(new Settings); + m_state = std::dynamic_pointer_cast<State>(m_mock_state); + m_live_actions.reset(new MockLiveActions(m_state)); + m_actions = std::dynamic_pointer_cast<Actions>(m_live_actions); + + // start the timedate1 dbusmock + start_timedate1("Etc/Utc"); + } + + void TearDown() override + { + m_actions.reset(); + m_live_actions.reset(); + m_state.reset(); + m_mock_state.reset(); + + super::TearDown(); + } +}; + /*** **** ***/ -TEST_F(TimedateFixture, HelloWorld) +TEST_F(TestLiveActionsFixture, HelloWorld) { EXPECT_TRUE(true); } -TEST_F(TimedateFixture, SetLocation) +TEST_F(TestLiveActionsFixture, SetLocation) { const std::string tzid = "America/Chicago"; const std::string name = "Oklahoma City"; @@ -36,49 +90,50 @@ TEST_F(TimedateFixture, SetLocation) EXPECT_NE(expected, m_state->settings->timezone_name.get()); - m_actions->set_location(tzid, name); + std::string new_name; m_state->settings->timezone_name.changed().connect( - [this](const std::string&){ - g_main_loop_quit(loop); - }); - g_main_loop_run(loop); - EXPECT_EQ(attempted_tzid, tzid); - wait_msec(); + [&new_name](const std::string& n){new_name = n;} + ); + + m_actions->set_location(tzid, name); + EXPECT_TRUE(wait_for([&new_name](){return !new_name.empty();})); + EXPECT_EQ(expected, new_name); EXPECT_EQ(expected, m_state->settings->timezone_name.get()); + EXPECT_EQ(tzid, get_timedate1_timezone()); } /*** **** ***/ -TEST_F(TimedateFixture, DesktopOpenAlarmApp) +TEST_F(TestLiveActionsFixture, DesktopOpenAlarmApp) { - m_actions->desktop_open_alarm_app(); + m_actions->open_alarm_app(); const std::string expected = "evolution -c calendar"; EXPECT_EQ(expected, m_live_actions->last_cmd); } -TEST_F(TimedateFixture, DesktopOpenAppointment) +TEST_F(TestLiveActionsFixture, DesktopOpenAppointment) { Appointment a; a.uid = "some-uid"; a.begin = DateTime::NowLocal(); - m_actions->desktop_open_appointment(a); + m_actions->open_appointment(a, a.begin); const std::string expected_substr = "evolution \"calendar:///?startdate="; EXPECT_NE(m_live_actions->last_cmd.find(expected_substr), std::string::npos); } -TEST_F(TimedateFixture, DesktopOpenCalendarApp) +TEST_F(TestLiveActionsFixture, DesktopOpenCalendarApp) { - m_actions->desktop_open_calendar_app(DateTime::NowLocal()); + m_actions->open_calendar_app(DateTime::NowLocal()); const std::string expected_substr = "evolution \"calendar:///?startdate="; EXPECT_NE(m_live_actions->last_cmd.find(expected_substr), std::string::npos); } -TEST_F(TimedateFixture, DesktopOpenSettingsApp) +TEST_F(TestLiveActionsFixture, DesktopOpenSettingsApp) { - m_actions->desktop_open_settings_app(); + m_actions->open_settings_app(); const std::string expected_substr = "control-center"; EXPECT_NE(m_live_actions->last_cmd.find(expected_substr), std::string::npos); } @@ -89,42 +144,45 @@ TEST_F(TimedateFixture, DesktopOpenSettingsApp) namespace { - const std::string clock_app_url = "appid://com.ubuntu.clock/clock/current-user-version"; - - const std::string calendar_app_url = "appid://com.ubuntu.calendar/calendar/current-user-version"; + const std::string clock_app_url = "alarm://"; } -TEST_F(TimedateFixture, PhoneOpenAlarmApp) +TEST_F(TestLiveActionsFixture, PhoneOpenAlarmApp) { - m_actions->phone_open_alarm_app(); + m_actions->open_alarm_app(); EXPECT_EQ(clock_app_url, m_live_actions->last_url); } -TEST_F(TimedateFixture, PhoneOpenAppointment) +TEST_F(TestLiveActionsFixture, PhoneOpenAppointment) { Appointment a; - a.uid = "some-uid"; + a.uid = "event-uid"; + a.source_uid = "source-uid"; a.begin = DateTime::NowLocal(); a.type = Appointment::EVENT; - m_actions->phone_open_appointment(a); - EXPECT_EQ(calendar_app_url, m_live_actions->last_url); + auto ocurrenceDate = DateTime::Local(2014, 1, 1, 0, 0, 0); + m_actions->open_appointment(a, ocurrenceDate); + const std::string appointment_app_url = ocurrenceDate.to_timezone("UTC").format("calendar://startdate=%Y-%m-%dT%H:%M:%S+00:00"); + EXPECT_EQ(appointment_app_url, m_live_actions->last_url); a.type = Appointment::UBUNTU_ALARM; - m_actions->phone_open_appointment(a); + m_actions->open_appointment(a, a.begin); EXPECT_EQ(clock_app_url, m_live_actions->last_url); } -TEST_F(TimedateFixture, PhoneOpenCalendarApp) +TEST_F(TestLiveActionsFixture, PhoneOpenCalendarApp) { - m_actions->phone_open_calendar_app(DateTime::NowLocal()); - const std::string expected = "appid://com.ubuntu.calendar/calendar/current-user-version"; + auto now = DateTime::NowLocal(); + m_actions->open_calendar_app(now); + const std::string expected = now.to_timezone("UTC").format("calendar://startdate=%Y-%m-%dT%H:%M:%S+00:00"); EXPECT_EQ(expected, m_live_actions->last_url); } -TEST_F(TimedateFixture, PhoneOpenSettingsApp) + +TEST_F(TestLiveActionsFixture, PhoneOpenSettingsApp) { - m_actions->phone_open_settings_app(); + m_actions->open_settings_app(); const std::string expected = "settings:///system/time-date"; EXPECT_EQ(expected, m_live_actions->last_url); } @@ -133,7 +191,7 @@ TEST_F(TimedateFixture, PhoneOpenSettingsApp) **** ***/ -TEST_F(TimedateFixture, CalendarState) +TEST_F(TestLiveActionsFixture, CalendarState) { // init the clock auto now = DateTime::Local(2014, 1, 1, 0, 0, 0); diff --git a/tests/test-menu-appointments.cpp b/tests/test-menu-appointments.cpp new file mode 100644 index 0000000..91a4b42 --- /dev/null +++ b/tests/test-menu-appointments.cpp @@ -0,0 +1,263 @@ +/* + * Copyright 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 "glib-fixture.h" +#include "print-to.h" + +#include <datetime/appointment.h> +#include <datetime/menu.h> + +#include <vector> + +using MenuAppointmentFixture = GlibFixture; + +using namespace ayatana::indicator::datetime; + +namespace +{ + Appointment create_appointment( + const Appointment::Type& type, + const std::string& uid, + const std::string& summary, + const DateTime& begin, + const DateTime& end) + { + Appointment a; + a.type = type; + a.uid = uid; + a.summary = summary; + a.begin = begin; + a.end = end; + return a; + } +} + +TEST_F(MenuAppointmentFixture, DisplayEvents) +{ + const auto airport = create_appointment( + Appointment::UBUNTU_ALARM, + "uid-airport", + "Pick Aunt Mabel up at the airport", + DateTime::Local(2016,12,24,10,0,0), + DateTime::Local(2016,12,24,10,0,0) + ); + + const auto christmas_eve_candle_service = create_appointment( + Appointment::EVENT, + "uid-christmas-eve-candle-service", + "Christmas Eve Candle Service", + DateTime::Local(2016,12,24,22,0,0), + DateTime::Local(2016,12,24,23,0,0) + ); + + const auto christmas = create_appointment( + Appointment::EVENT, + "uid-christmas", + "Christmas", + DateTime::Local(2016,12,25,0,0,0), + DateTime::Local(2016,12,26,0,0,0) + ); + + const auto santa = create_appointment( + Appointment::UBUNTU_ALARM, + "uid-santa", + "Time to set out cookies and milk for Santa", + DateTime::Local(2016,12,25,1,0,0), + DateTime::Local(2016,12,25,1,0,0) + ); + + const auto bike = create_appointment( + Appointment::UBUNTU_ALARM, + "uid-bike", + "Remember to put out the bike, it's in the garage", + DateTime::Local(2016,12,25,1,0,0), + DateTime::Local(2016,12,25,1,0,0) + ); + + + const auto christmas_lunch = create_appointment( + Appointment::EVENT, + "uid-christmas-lunch", + "Christmas Lunch at Grandma's", + DateTime::Local(2016,12,25,12,0,0), + DateTime::Local(2016,12,25,14,0,0) + ); + + const auto boxing_day = create_appointment( + Appointment::EVENT, + "uid-boxing-day", + "Boxing Day", + DateTime::Local(2016,12,26,0,0,0), + DateTime::Local(2016,12,27,0,0,0) + ); + + const auto new_years_eve = create_appointment( + Appointment::EVENT, + "uid-new-years-eve", + "New Years' Eve", + DateTime::Local(2016,12,31,0,0,0), + DateTime::Local(2017,1,1,0,0,0) + ); + + const auto nye_party = create_appointment( + Appointment::EVENT, + "uid-new-years-party", + "New Year Party at Ted's", + DateTime::Local(2016,12,31,19,0,0), + DateTime::Local(2017, 1, 1, 2,0,0) + ); + + const auto new_years_day = create_appointment( + Appointment::EVENT, + "uid-new-years-day", + "New Years' Day", + DateTime::Local(2017,1,1,0,0,0), + DateTime::Local(2017,1,2,0,0,0) + ); + + const auto weekday_wakeup_alarm = create_appointment( + Appointment::UBUNTU_ALARM, + "wakeup-alarm", + "Wake Up", + DateTime::Local(2017,1,3,6,0,0), + DateTime::Local(2017,1,3,6,0,0) + ); + + const auto dentist_appointment = create_appointment( + Appointment::EVENT, + "dentist", + "Dentist", + DateTime::Local(2017,1,5,14,0,0), + DateTime::Local(2017,1,5,15,0,0) + ); + + const auto marcus_birthday = create_appointment( + Appointment::EVENT, + "uid-mlk", + "Marcus' Birthday", + DateTime::Local(2017,1,8,0,0,0), + DateTime::Local(2017,1,9,0,0,0) + ); + + const auto mlk_day = create_appointment( + Appointment::EVENT, + "uid-mlk", + "Martin Luther King Day", + DateTime::Local(2017,1,16,0,0,0), + DateTime::Local(2017,1,17,0,0,0) + ); + + const auto rodney_party = create_appointment( + Appointment::EVENT, + "uid-rodney", + "Rodney's Party", + DateTime::Local(2017,1,30,19,0,0), + DateTime::Local(2017,1,30,23,0,0) + ); + + const auto pub_with_pawel = create_appointment( + Appointment::UBUNTU_ALARM, + "uid-pawel", + "Meet Pawel at the Pub", + DateTime::Local(2017,2,4,19,0,0), + DateTime::Local(2017,2,4,19,0,0) + ); + + const struct + { + const char* const description; + DateTime start; + std::vector<Appointment> appointments; + std::vector<Appointment> expected_display_appointments; + int max_items; + } + tests[] = + { + { + "test presentation order: full-day events come before part-day events", + DateTime::Local(2016,12,25,6,0,0), + std::vector<Appointment>({christmas, christmas_lunch, boxing_day, new_years_eve}), + std::vector<Appointment>({christmas, christmas_lunch, boxing_day, new_years_eve}), + 5 + }, + { + "test presentation order: part-day events in chronological order", + DateTime::Local(2016,12,24,0,0,0), + std::vector<Appointment>({airport, christmas_eve_candle_service, christmas, santa, christmas_lunch}), + std::vector<Appointment>({airport, christmas_eve_candle_service, christmas, santa, christmas_lunch}), + 5 + }, + { + "test presentation order: multiple events with the same start+end sorted alphabetically", + DateTime::Local(2016,12,25,0,0,0), + std::vector<Appointment>({christmas, bike, santa, christmas_lunch}), + std::vector<Appointment>({christmas, bike, santa, christmas_lunch}), + 5 + }, + { + "test culling priority: today's part-day events outrank today's full-day events", + DateTime::Local(2016,12,25,1,0,0), + std::vector<Appointment>({christmas, santa, christmas_lunch}), + std::vector<Appointment>({santa, christmas_lunch}), + 2 + }, + { + "test culling priority: events later today outrank both tomorrow's full-day and part-day events", + DateTime::Local(2016,12,24,0,0,0), + std::vector<Appointment>({christmas_eve_candle_service, christmas, santa}), + std::vector<Appointment>({christmas_eve_candle_service}), + 1 + }, + { + "test edge cases: confirm <max events works ok", + DateTime::Local(2016,12,24,0,0,0), + std::vector<Appointment>({christmas, christmas_lunch}), + std::vector<Appointment>({christmas, christmas_lunch}), + 5 + }, + { + "test edge cases: confirm 0 events works ok", + DateTime::Local(2016,12,24,0,0,0), + std::vector<Appointment>({}), + std::vector<Appointment>({}), + 5 + }, + { + "test edge cases: confirm max-events of 0 doesn't crash", + DateTime::Local(2016,12,24,0,0,0), + std::vector<Appointment>({christmas, bike, santa, christmas_lunch}), + std::vector<Appointment>({}), + 0 + } + }; + + for (const auto& test : tests) + { + g_debug("running test: %s", test.description); + + // run the test... + ASSERT_EQ(test.expected_display_appointments, Menu::get_display_appointments(test.appointments, test.start, test.max_items)); + + // ...and again with a reversed vector to confirm input order doesn't matter + auto reversed = test.appointments; + std::reverse(reversed.begin(), reversed.end()); + ASSERT_EQ(test.expected_display_appointments, Menu::get_display_appointments(reversed, test.start, test.max_items)); + } +} + diff --git a/tests/test-notification-response.cpp b/tests/test-notification-response.cpp new file mode 100644 index 0000000..fd40ed8 --- /dev/null +++ b/tests/test-notification-response.cpp @@ -0,0 +1,144 @@ +/* + * 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,Response) +{ + // create the world + make_interactive(); + auto ne = std::make_shared<ayatana::indicator::notifications::Engine>(APP_NAME); + auto sb = std::make_shared<ayatana::indicator::notifications::DefaultSoundBuilder>(); + auto settings = std::make_shared<Settings>(); + int next_notification_id {FIRST_NOTIFY_ID}; + + // set a response callback that remembers what response we got + bool on_response_called {}; + Snap::Response response {}; + auto on_response = [this, &on_response_called, &response] + (const Appointment&, const Alarm&, const Snap::Response& r){ + on_response_called = true; + response = r; + g_idle_add(quit_idle, loop); + }; + + // our tests! + const struct { + Appointment appt; + std::vector<std::string> expected_actions; + std::string action; + Snap::Response expected_response; + } tests[] = { + { appt, {"show-app"}, "show-app", Snap::Response::ShowApp }, + { ualarm, {"none", "snooze"}, "snooze", Snap::Response::Snooze }, + { ualarm, {"none", "snooze"}, "none", Snap::Response::None } + }; + + + settings->cal_notification_enabled.set(true); + settings->cal_notification_sounds.set(true); + settings->cal_notification_vibrations.set(true); + settings->cal_notification_bubbles.set(true); + settings->cal_notification_list.set(true); + + // walk through the tests + for (const auto& test : tests) + { + // wait for previous iterations' bus noise to finish and reset the counters + GError* error {}; + wait_msec(500); + dbus_test_dbus_mock_object_clear_method_calls(notify_mock, notify_obj, &error); + g_assert_no_error(error); + on_response_called = false; + + // create a snap decision + auto snap = create_snap(ne, sb, settings); + (*snap)(test.appt, test.appt.alarms.front(), on_response); + + // wait for the notification to show up + EXPECT_METHOD_CALLED_EVENTUALLY(notify_mock, notify_obj, METHOD_NOTIFY); + + // test that Notify was called the right number of times + static constexpr int expected_num_notify_calls {1}; + guint num_notify_calls {}; + const auto notify_calls = dbus_test_dbus_mock_object_get_method_calls( + notify_mock, + notify_obj, + METHOD_NOTIFY, + &num_notify_calls, + &error); + g_assert_no_error(error); + EXPECT_EQ(expected_num_notify_calls, num_notify_calls); + + // test that Notify was called with the correct list of actions + if (num_notify_calls > 0) { + std::vector<std::string> actions; + const gchar** as {nullptr}; + g_variant_get_child(notify_calls[0].params, 5, "^a&s", &as); + for (int i=0; as && as[i]; i+=2) // actions are in pairs of (name, i18n), skip the i18n + actions.push_back(as[i]); + EXPECT_EQ(test.expected_actions, actions); + } + + // make the notification mock tell the world that the user invoked an action + const auto notification_id = next_notification_id++; + idle_add([this, notification_id, test](){ + GError* err {}; + dbus_test_dbus_mock_object_emit_signal(notify_mock, notify_obj, "ActionInvoked", + G_VARIANT_TYPE("(us)"), + g_variant_new("(us)", guint(notification_id), test.action.c_str()), + &err); + dbus_test_dbus_mock_object_emit_signal(notify_mock, notify_obj, "NotificationClosed", + G_VARIANT_TYPE("(uu)"), + g_variant_new("(uu)", guint(notification_id), guint(NOTIFICATION_CLOSED_DISMISSED)), + &err); + g_assert_no_error(err); + return G_SOURCE_REMOVE; + }); + + // confirm that the response callback got the right response + EXPECT_TRUE(wait_for([&on_response_called](){return on_response_called;})); + EXPECT_EQ(int(test.expected_response), int(response)) << "notification_id " << notification_id; + } +} diff --git a/tests/test-notification.cpp b/tests/test-notification.cpp new file mode 100644 index 0000000..83a021f --- /dev/null +++ b/tests/test-notification.cpp @@ -0,0 +1,198 @@ +/* + * 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&, const Snap::Response&){g_idle_add(quit_idle, loop);}; + + // combinatorial factor #1: event type + struct { + Appointment appt; + const char* icon_name; + const char* prefix; + bool expected_notify_called; + bool expected_vibrate_called; + } test_appts[] = { + { appt, "calendar-app", "Event", true, true }, + { ualarm, "alarm-clock", "Alarm", 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 disabled + struct { + bool cal_notification_enabled; // calendar app can trigger notifications + std::set<Appointment::Type> expected_notify_called; // do we expect the notification to show? + std::set<Appointment::Type> expected_vibrate_called; // do we expect the phone to vibrate? + } test_cal_disabled[] = { + { true, std::set<Appointment::Type>{ Appointment::Type::UBUNTU_ALARM, Appointment::Type::EVENT }, + std::set<Appointment::Type>{ Appointment::Type::UBUNTU_ALARM, Appointment::Type::EVENT } }, + { false, std::set<Appointment::Type>{ Appointment::Type::UBUNTU_ALARM }, + std::set<Appointment::Type>{ Appointment::Type::UBUNTU_ALARM } } + }; + + 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_disabled : test_cal_disabled) + { + const bool expected_notify_called = test_appt.expected_notify_called + && test_vibes.expected_notify_called + && (test_disabled.expected_notify_called.count(test_appt.appt.type) > 0) + && test_haptic.expected_notify_called; + + const bool expected_vibrate_called = test_appt.expected_vibrate_called + && test_vibes.expected_vibrate_called + && (test_disabled.expected_vibrate_called.count(test_appt.appt.type) > 0) + && test_haptic.expected_vibrate_called; + + // set test case properties: cal_notification_enabled + settings->cal_notification_enabled.set(test_disabled.cal_notification_enabled); + settings->cal_notification_sounds.set(test_disabled.cal_notification_enabled); + settings->cal_notification_vibrations.set(test_disabled.cal_notification_enabled); + settings->cal_notification_bubbles.set(test_disabled.cal_notification_enabled); + settings->cal_notification_list.set(test_disabled.cal_notification_enabled); + + // set test case properties: haptic mode + settings->alarm_haptic.set(test_haptic.haptic_mode); + + // set test case properties: other-vibrations flag + // (and wait for the PropertiesChanged signal so we know the dbusmock got it) + GError * error {}; + 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 for previous iterations' bus noise to finish and reset the counters + wait_msec(500); + dbus_test_dbus_mock_object_clear_method_calls(haptic_mock, haptic_obj, &error); + dbus_test_dbus_mock_object_clear_method_calls(notify_mock, notify_obj, &error); + g_assert_no_error(error); + + // run the test + auto snap = create_snap(ne, sb, settings); + (*snap)(test_appt.appt, appt.alarms.front(), func); + + // confirm that the notification was as expected + if (expected_notify_called) { + EXPECT_METHOD_CALLED_EVENTUALLY(notify_mock, notify_obj, METHOD_NOTIFY); + } else { + EXPECT_METHOD_NOT_CALLED_EVENTUALLY(notify_mock, notify_obj, METHOD_NOTIFY); + } + + // confirm that the vibration was as expected + if (expected_vibrate_called) { + EXPECT_METHOD_CALLED_EVENTUALLY(haptic_mock, haptic_obj, HAPTIC_METHOD_VIBRATE_PATTERN); + } else { + EXPECT_METHOD_NOT_CALLED_EVENTUALLY(haptic_mock, haptic_obj, HAPTIC_METHOD_VIBRATE_PATTERN); + } + + // confirm that the notification was as expected + guint num_notify_calls = 0; + const auto notify_calls = dbus_test_dbus_mock_object_get_method_calls(notify_mock, + notify_obj, + METHOD_NOTIFY, + &num_notify_calls, + &error); + g_assert_no_error(error); + if (num_notify_calls > 0) + { + // test that Notify was called with the app_name + const gchar* app_name {nullptr}; + g_variant_get_child(notify_calls[0].params, 0, "&s", &app_name); + ASSERT_STREQ(APP_NAME, app_name); + + // test that Notify was called with the type-appropriate icon + const gchar* icon_name {nullptr}; + g_variant_get_child(notify_calls[0].params, 2, "&s", &icon_name); + ASSERT_STREQ(test_appt.icon_name, icon_name); + + // test that the Notification title has the correct prefix + const gchar* title {nullptr}; + g_variant_get_child(notify_calls[0].params, 3, "&s", &title); + ASSERT_TRUE(g_str_has_prefix(title, test_appt.prefix)); + + // test that Notify was called with the appointment's body + const gchar* body {nullptr}; + g_variant_get_child(notify_calls[0].params, 4, "&s", &body); + ASSERT_STREQ(test_appt.appt.summary.c_str(), body); + } + } + } + } + } +} diff --git a/tests/test-settings.cpp b/tests/test-settings.cpp index 3af9eab..b9658f4 100644 --- a/tests/test-settings.cpp +++ b/tests/test-settings.cpp @@ -22,6 +22,11 @@ #include <datetime/settings-live.h> #include <datetime/settings-shared.h> +extern "C" +{ + #include <ayatana/common/utils.h> +} + using namespace ayatana::indicator::datetime; /*** @@ -37,19 +42,23 @@ protected: std::shared_ptr<LiveSettings> m_live; std::shared_ptr<Settings> m_settings; - GSettings * m_gsettings; + GSettings * m_gsettings {}; + GSettings * m_gsettings_cal_notification {}; void SetUp() override { super::SetUp(); m_gsettings = g_settings_new(SETTINGS_INTERFACE); + m_gsettings_cal_notification = g_settings_new_with_path(SETTINGS_NOTIFY_SCHEMA_ID, SETTINGS_NOTIFY_CALENDAR_PATH); + m_live.reset(new LiveSettings); m_settings = std::dynamic_pointer_cast<Settings>(m_live); } void TearDown() override { + g_clear_object(&m_gsettings_cal_notification); g_clear_object(&m_gsettings); m_settings.reset(); m_live.reset(); @@ -57,62 +66,62 @@ protected: super::TearDown(); } - void TestBoolProperty(core::Property<bool>& property, const gchar* key) + void TestBoolProperty(GSettings* gsettings, core::Property<bool>& property, const gchar* key) { - EXPECT_EQ(g_settings_get_boolean(m_gsettings, key), property.get()); - g_settings_set_boolean(m_gsettings, key, false); + EXPECT_EQ(g_settings_get_boolean(gsettings, key), property.get()); + g_settings_set_boolean(gsettings, key, false); EXPECT_FALSE(property.get()); - g_settings_set_boolean(m_gsettings, key, true); + g_settings_set_boolean(gsettings, key, true); EXPECT_TRUE(property.get()); property.set(false); - EXPECT_FALSE(g_settings_get_boolean(m_gsettings, key)); + EXPECT_FALSE(g_settings_get_boolean(gsettings, key)); property.set(true); - EXPECT_TRUE(g_settings_get_boolean(m_gsettings, key)); + EXPECT_TRUE(g_settings_get_boolean(gsettings, key)); } - void TestStringProperty(core::Property<std::string>& property, const gchar* key) + void TestStringProperty(GSettings* gsettings, core::Property<std::string>& property, const gchar* key) { gchar* tmp; std::string str; - tmp = g_settings_get_string(m_gsettings, key); + tmp = g_settings_get_string(gsettings, key); EXPECT_EQ(tmp, property.get()); g_clear_pointer(&tmp, g_free); str = "a"; - g_settings_set_string(m_gsettings, key, str.c_str()); + g_settings_set_string(gsettings, key, str.c_str()); EXPECT_EQ(str, property.get()); str = "b"; - g_settings_set_string(m_gsettings, key, str.c_str()); + g_settings_set_string(gsettings, key, str.c_str()); EXPECT_EQ(str, property.get()); str = "a"; property.set(str); - tmp = g_settings_get_string(m_gsettings, key); + tmp = g_settings_get_string(gsettings, key); EXPECT_EQ(str, tmp); g_clear_pointer(&tmp, g_free); str = "b"; property.set(str); - tmp = g_settings_get_string(m_gsettings, key); + tmp = g_settings_get_string(gsettings, key); EXPECT_EQ(str, tmp); g_clear_pointer(&tmp, g_free); } - void TestUIntProperty(core::Property<unsigned int>& property, const gchar* key) + void TestUIntProperty(GSettings* gsettings, core::Property<unsigned int>& property, const gchar* key) { - EXPECT_EQ(g_settings_get_uint(m_gsettings, key), property.get()); + EXPECT_EQ(g_settings_get_uint(gsettings, key), property.get()); unsigned int expected_values[] = { 1, 2, 3 }; // modify GSettings and confirm that the new value is propagated for(const auto& expected_value : expected_values) { - g_settings_set_uint(m_gsettings, key, expected_value); + g_settings_set_uint(gsettings, key, expected_value); EXPECT_EQ(expected_value, property.get()); - EXPECT_EQ(expected_value, g_settings_get_uint(m_gsettings, key)); + EXPECT_EQ(expected_value, g_settings_get_uint(gsettings, key)); } // modify the property and confirm that the new value is propagated @@ -120,7 +129,7 @@ protected: { property.set(expected_value); EXPECT_EQ(expected_value, property.get()); - EXPECT_EQ(expected_value, g_settings_get_uint(m_gsettings, key)); + EXPECT_EQ(expected_value, g_settings_get_uint(gsettings, key)); } } }; @@ -136,31 +145,31 @@ TEST_F(SettingsFixture, HelloWorld) TEST_F(SettingsFixture, BoolProperties) { - TestBoolProperty(m_settings->show_seconds, SETTINGS_SHOW_SECONDS_S); - TestBoolProperty(m_settings->show_calendar, SETTINGS_SHOW_CALENDAR_S); - TestBoolProperty(m_settings->show_date, SETTINGS_SHOW_DATE_S); - TestBoolProperty(m_settings->show_day, SETTINGS_SHOW_DAY_S); - TestBoolProperty(m_settings->show_detected_location, SETTINGS_SHOW_DETECTED_S); - TestBoolProperty(m_settings->show_events, SETTINGS_SHOW_EVENTS_S); - TestBoolProperty(m_settings->show_locations, SETTINGS_SHOW_LOCATIONS_S); - TestBoolProperty(m_settings->show_week_numbers, SETTINGS_SHOW_WEEK_NUMBERS_S); - TestBoolProperty(m_settings->show_year, SETTINGS_SHOW_YEAR_S); + TestBoolProperty(m_gsettings, m_settings->show_seconds, SETTINGS_SHOW_SECONDS_S); + TestBoolProperty(m_gsettings, m_settings->show_calendar, SETTINGS_SHOW_CALENDAR_S); + TestBoolProperty(m_gsettings, m_settings->show_date, SETTINGS_SHOW_DATE_S); + TestBoolProperty(m_gsettings, m_settings->show_day, SETTINGS_SHOW_DAY_S); + TestBoolProperty(m_gsettings, m_settings->show_detected_location, SETTINGS_SHOW_DETECTED_S); + TestBoolProperty(m_gsettings, m_settings->show_events, SETTINGS_SHOW_EVENTS_S); + TestBoolProperty(m_gsettings, m_settings->show_locations, SETTINGS_SHOW_LOCATIONS_S); + TestBoolProperty(m_gsettings, m_settings->show_week_numbers, SETTINGS_SHOW_WEEK_NUMBERS_S); + TestBoolProperty(m_gsettings, m_settings->show_year, SETTINGS_SHOW_YEAR_S); } TEST_F(SettingsFixture, UIntProperties) { - TestUIntProperty(m_settings->alarm_duration, SETTINGS_ALARM_DURATION_S); - TestUIntProperty(m_settings->alarm_volume, SETTINGS_ALARM_VOLUME_S); - TestUIntProperty(m_settings->snooze_duration, SETTINGS_SNOOZE_DURATION_S); + TestUIntProperty(m_gsettings, m_settings->alarm_duration, SETTINGS_ALARM_DURATION_S); + TestUIntProperty(m_gsettings, m_settings->alarm_volume, SETTINGS_ALARM_VOLUME_S); + TestUIntProperty(m_gsettings, m_settings->snooze_duration, SETTINGS_SNOOZE_DURATION_S); } TEST_F(SettingsFixture, StringProperties) { - TestStringProperty(m_settings->custom_time_format, SETTINGS_CUSTOM_TIME_FORMAT_S); - TestStringProperty(m_settings->timezone_name, SETTINGS_TIMEZONE_NAME_S); - TestStringProperty(m_settings->alarm_sound, SETTINGS_ALARM_SOUND_S); - TestStringProperty(m_settings->calendar_sound, SETTINGS_CALENDAR_SOUND_S); - TestStringProperty(m_settings->alarm_haptic, SETTINGS_ALARM_HAPTIC_S); + TestStringProperty(m_gsettings, m_settings->custom_time_format, SETTINGS_CUSTOM_TIME_FORMAT_S); + TestStringProperty(m_gsettings, m_settings->timezone_name, SETTINGS_TIMEZONE_NAME_S); + TestStringProperty(m_gsettings, m_settings->alarm_sound, SETTINGS_ALARM_SOUND_S); + TestStringProperty(m_gsettings, m_settings->calendar_sound, SETTINGS_CALENDAR_SOUND_S); + TestStringProperty(m_gsettings, m_settings->alarm_haptic, SETTINGS_ALARM_HAPTIC_S); } TEST_F(SettingsFixture, TimeFormatMode) @@ -221,3 +230,16 @@ TEST_F(SettingsFixture, Locations) g_strfreev(tmp); EXPECT_EQ(bv, vtmp); } + +TEST_F(SettingsFixture, MutedApps) +{ + if (!m_gsettings_cal_notification) { + return; + } + + TestBoolProperty(m_gsettings_cal_notification, m_settings->cal_notification_enabled, SETTINGS_NOTIFY_ENABLED_KEY); + TestBoolProperty(m_gsettings_cal_notification, m_settings->cal_notification_sounds, SETTINGS_NOTIFY_SOUNDS_KEY); + TestBoolProperty(m_gsettings_cal_notification, m_settings->cal_notification_vibrations, SETTINGS_NOTIFY_VIBRATIONS_KEY); + TestBoolProperty(m_gsettings_cal_notification, m_settings->cal_notification_bubbles, SETTINGS_NOTIFY_BUBBLES_KEY); + TestBoolProperty(m_gsettings_cal_notification, m_settings->cal_notification_list, SETTINGS_NOTIFY_LIST_KEY); +} diff --git a/tests/test-sound.cpp b/tests/test-sound.cpp new file mode 100644 index 0000000..f808db6 --- /dev/null +++ b/tests/test-sound.cpp @@ -0,0 +1,182 @@ +/* + * 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); + + settings->cal_notification_enabled.set(true); + settings->cal_notification_sounds.set(true); + settings->cal_notification_vibrations.set(true); + settings->cal_notification_bubbles.set(true); + settings->cal_notification_list.set(true); + + make_interactive(); + + // call the Snap Decision + auto func = [this](const Appointment&, const Alarm&, const Snap::Response&){g_idle_add(quit_idle, loop);}; + (*snap)(appt, appt.alarms.front(), 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("calendar-app", 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(); +} + +/*** +**** +***/ + +/** + * 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&, const Snap::Response&){g_idle_add(quit_idle, loop);}; + + settings->cal_notification_enabled.set(true); + settings->cal_notification_sounds.set(true); + settings->cal_notification_vibrations.set(true); + settings->cal_notification_bubbles.set(true); + settings->cal_notification_list.set(true); + + const struct { + Appointment appointment; + std::string expected_role; + std::string expected_uri; + } test_cases[] = { + { ualarm, "alarm", path_to_uri(ALARM_DEFAULT_SOUND) } + // No sound for appointments + // { 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); + EXPECT_EQ(test_case.expected_uri, sb->uri()); + EXPECT_EQ(test_case.expected_role, sb->role()); + } +} diff --git a/tests/test-timezone-timedated.cpp b/tests/test-timezone-timedated.cpp index 2fdec12..c686c39 100644 --- a/tests/test-timezone-timedated.cpp +++ b/tests/test-timezone-timedated.cpp @@ -1,9 +1,5 @@ - /* - * Copyright 2013 Canonical Ltd. - * - * Authors: - * Charles Kerr <charles.kerr@canonical.com> + * 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 @@ -16,127 +12,76 @@ * * 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> + * Ted Gould <ted.gould@canonical.com> */ #include "timedated-fixture.h" #include <datetime/timezone-timedated.h> -using ayatana::indicator::datetime::TimedatedTimezone; +using namespace ayatana::indicator::datetime; + +using TestTimedatedFixture = TimedatedFixture; /*** **** ***/ -#define TIMEZONE_FILE (SANDBOX"/timezone") - -class TimezoneFixture: public TimedateFixture +TEST_F(TestTimedatedFixture, HelloWorld) { - private: - - typedef TimedateFixture super; - - protected: - - void SetUp() override - { - super::SetUp(); - } - - void TearDown() override - { - super::TearDown(); - } - - public: - - /* convenience func to set the timezone file */ - void set_file(const std::string& text) - { - g_debug("set_file %s %s", TIMEZONE_FILE, text.c_str()); - auto fp = fopen(TIMEZONE_FILE, "w+"); - fprintf(fp, "%s\n", text.c_str()); - fclose(fp); - sync(); - } -}; +} /** - * Test that timezone-timedated warns, but doesn't crash, if the timezone file doesn't exist + * Test that the tzid is right if timedated isn't available */ -TEST_F(TimezoneFixture, NoFile) +TEST_F(TestTimedatedFixture, DefaultTimezone) { - remove(TIMEZONE_FILE); - ASSERT_FALSE(g_file_test(TIMEZONE_FILE, G_FILE_TEST_EXISTS)); + const std::string expected_tzid{"Etc/Utc"}; - expectLogMessage(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*No such file or directory*"); - TimedatedTimezone tz(TIMEZONE_FILE); + TimedatedTimezone tmp {m_bus}; + EXPECT_TZID(expected_tzid, tmp); } /** - * Test that timezone-timedated gives a default of UTC if the file doesn't exist + * Test that the tzid is right if timedated shows BEFORE we start */ -TEST_F(TimezoneFixture, DefaultValueNoFile) +TEST_F(TestTimedatedFixture, Timedate1First) { - const std::string expected_timezone = "Etc/Utc"; - remove(TIMEZONE_FILE); - ASSERT_FALSE(g_file_test(TIMEZONE_FILE, G_FILE_TEST_EXISTS)); + const std::string expected_tzid{"America/Chicago"}; - expectLogMessage(G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, "*No such file or directory*"); - TimedatedTimezone tz(TIMEZONE_FILE); - ASSERT_EQ(expected_timezone, tz.timezone.get()); + start_timedate1(expected_tzid); + TimedatedTimezone tmp {m_bus}; + EXPECT_TZID(expected_tzid, tmp); } /** - * Test that timezone-timedated picks up the initial value + * Test that the tzid is right if timedated shows AFTER we start */ -TEST_F(TimezoneFixture, InitialValue) +TEST_F(TestTimedatedFixture, Timedate1Last) { - const std::string expected_timezone = "America/Chicago"; - set_file(expected_timezone); - TimedatedTimezone tz(TIMEZONE_FILE); + const std::string expected_tzid("America/Los_Angeles"); + + TimedatedTimezone tmp {m_bus}; + start_timedate1(expected_tzid); + EXPECT_TZID(expected_tzid, tmp); } /** - * Test that changing the tz after we are running works. + * Test that the tzid is right if timedated's property changes */ -TEST_F(TimezoneFixture, ChangedValue) +TEST_F(TestTimedatedFixture, TimezoneChange) { - const std::string initial_timezone = "America/Chicago"; - const std::string changed_timezone = "America/New_York"; - - set_file(initial_timezone); - - TimedatedTimezone tz(TIMEZONE_FILE); - ASSERT_EQ(initial_timezone, tz.timezone.get()); - - bool changed = false; - tz.timezone.changed().connect( - [&changed, this](const std::string& s){ - g_message("timezone changed to %s", s.c_str()); - changed = true; - g_main_loop_quit(loop); - }); + const std::vector<std::string> expected_tzids{"America/Los_Angeles", "America/Chicago", "Etc/Utc"}; - g_idle_add([](gpointer gself){ - static_cast<TimedateFixture*>(gself)->set_timezone("America/New_York"); - return G_SOURCE_REMOVE; - }, this); + TimedatedTimezone tmp {m_bus}; + start_timedate1("America/New_York"); - g_main_loop_run(loop); - - ASSERT_TRUE(changed); - ASSERT_EQ(changed_timezone, tz.timezone.get()); -} - -/** - * Test that timezone-timedated picks up the initial value - */ -TEST_F(TimezoneFixture, IgnoreComments) -{ - const std::string comment = "# Created by cloud-init v. 0.7.5 on Thu, 24 Apr 2014 14:03:29 +0000"; - const std::string expected_timezone = "Europe/Berlin"; - set_file(comment + "\n" + expected_timezone); - TimedatedTimezone tz(TIMEZONE_FILE); - ASSERT_EQ(expected_timezone, tz.timezone.get()); + for(const auto& expected_tzid : expected_tzids) + { + set_timedate1_timezone(expected_tzid); + EXPECT_TZID(expected_tzid, tmp); + } } diff --git a/tests/timedated-fixture.h b/tests/timedated-fixture.h index 00269e0..3aff986 100644 --- a/tests/timedated-fixture.h +++ b/tests/timedated-fixture.h @@ -17,285 +17,132 @@ * Charles Kerr <charles.kerr@canonical.com> */ -#ifndef INDICATOR_DATETIME_TESTS_TIMEDATED_FIXTURE_H -#define INDICATOR_DATETIME_TESTS_TIMEDATED_FIXTURE_H +#pragma once #include <datetime/actions-live.h> -#include "state-mock.h" #include "glib-fixture.h" -using namespace ayatana::indicator::datetime; - -class MockLiveActions: public LiveActions -{ -public: - std::string last_cmd; - std::string last_url; - explicit MockLiveActions(const std::shared_ptr<State>& state_in): LiveActions(state_in) {} - ~MockLiveActions() {} - -protected: - void dispatch_url(const std::string& url) override { last_url = url; } - void execute_command(const std::string& cmd) override { last_cmd = cmd; } -}; +#include <datetime/dbus-shared.h> +#include <datetime/timezone.h> /*** **** ***/ -using namespace ayatana::indicator::datetime; - -class TimedateFixture: public GlibFixture +struct TimedatedFixture: public GlibFixture { private: - typedef GlibFixture super; - - static GVariant * timedate1_get_properties (GDBusConnection * /*connection*/ , - const gchar * /*sender*/, - const gchar * /*object_path*/, - const gchar * /*interface_name*/, - const gchar *property_name, - GError ** /*error*/, - gpointer gself) + using super = GlibFixture; - { - auto self = static_cast<TimedateFixture*>(gself); - g_debug("get_properties called"); - if (g_strcmp0(property_name, "Timezone") == 0) - { - g_debug("timezone requested, giving '%s'", - self->attempted_tzid.c_str()); - return g_variant_new_string(self->attempted_tzid.c_str()); - } - return nullptr; - } +protected: + GDBusConnection* m_bus {}; + GTestDBus* m_test_bus {}; - static void on_bus_acquired(GDBusConnection* conn, - const gchar* name, - gpointer gself) + virtual void SetUp() override { - auto self = static_cast<TimedateFixture*>(gself); - g_debug("bus acquired: %s, connection is %p", name, conn); - - /* Set up a fake timedated which handles setting and getting the - ** timezone - */ - static const GDBusInterfaceVTable vtable = { - timedate1_handle_method_call, - timedate1_get_properties, /* GetProperty */ - nullptr, /* SetProperty */ - }; + super::SetUp(); - self->connection = G_DBUS_CONNECTION(g_object_ref(G_OBJECT(conn))); + // use a fake bus + m_test_bus = g_test_dbus_new(G_TEST_DBUS_NONE); + g_test_dbus_up(m_test_bus); + const char * address = g_test_dbus_get_bus_address(m_test_bus); + g_setenv("DBUS_SYSTEM_BUS_ADDRESS", address, true); + g_setenv("DBUS_SESSION_BUS_ADDRESS", address, true); + g_debug("test_dbus's address is %s", address); - GError* error = nullptr; - self->object_register_id = g_dbus_connection_register_object( - conn, - "/org/freedesktop/timedate1", - self->node_info->interfaces[0], - &vtable, - self, - nullptr, - &error); - g_assert_no_error(error); + // get the bus + m_bus = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr); + g_dbus_connection_set_exit_on_close(m_bus, FALSE); + g_object_add_weak_pointer(G_OBJECT(m_bus), (gpointer*)&m_bus); } - static void on_name_acquired(GDBusConnection* conn, - const gchar* name, - gpointer gself) + virtual void TearDown() override { - g_debug("on_name_acquired"); - auto self = static_cast<TimedateFixture*>(gself); - self->name_acquired = true; - self->proxy = g_dbus_proxy_new_sync(conn, - G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES, - nullptr, - name, - "/org/freedesktop/timedate1", - "org.freedesktop.timedate1", - nullptr, - nullptr); - g_main_loop_quit(self->loop); - } + g_clear_object(&m_bus); + g_clear_object(&m_test_bus); - static void on_name_lost(GDBusConnection* /*conn*/, - const gchar* /*name*/, - gpointer gself) - { - g_debug("on_name_lost"); - auto self = static_cast<TimedateFixture*>(gself); - self->name_acquired = false; + super::TearDown(); } - static void on_bus_closed(GObject* /*object*/, - GAsyncResult* res, - gpointer gself) + void start_timedate1(const std::string& tzid) { - g_debug("on_bus_closed"); - auto self = static_cast<TimedateFixture*>(gself); - GError* err = nullptr; - g_dbus_connection_close_finish(self->connection, res, &err); - g_assert_no_error(err); - g_main_loop_quit(self->loop); + // start dbusmock with the timedated template + auto json_parameters = g_strdup_printf("{\"Timezone\": \"%s\"}", tzid.c_str()); + const gchar* child_argv[] = { + "python3", "-m", "dbusmock", + "--template", "timedated", + "--parameters", json_parameters, + nullptr + }; + GError* error = nullptr; + g_spawn_async(nullptr, (gchar**)child_argv, nullptr, G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, &error); + g_assert_no_error(error); + g_free(json_parameters); + + // wait for it to appear on the bus + wait_for_name_owned(m_bus, Bus::Timedate1::BUSNAME); } - static void - timedate1_handle_method_call(GDBusConnection * connection, - const gchar * /*sender*/, - const gchar * object_path, - const gchar * interface_name, - const gchar * method_name, - GVariant * parameters, - GDBusMethodInvocation * invocation, - gpointer gself) + bool wait_for_tzid(const std::string& tzid, ayatana::indicator::datetime::Timezone& tz) { - g_assert(!g_strcmp0(method_name, "SetTimezone")); - g_assert(g_variant_is_of_type(parameters, G_VARIANT_TYPE_TUPLE)); - g_assert(2 == g_variant_n_children(parameters)); - - auto child = g_variant_get_child_value(parameters, 0); - g_assert(g_variant_is_of_type(child, G_VARIANT_TYPE_STRING)); - auto self = static_cast<TimedateFixture*>(gself); - self->attempted_tzid = g_variant_get_string(child, nullptr); - g_debug("set tz (dbus side): '%s'", self->attempted_tzid.c_str()); - g_dbus_method_invocation_return_value(invocation, nullptr); - - /* Send PropertiesChanged */ - GError * local_error = nullptr; - auto builder = g_variant_builder_new (G_VARIANT_TYPE_ARRAY); - g_variant_builder_add (builder, - "{sv}", - "Timezone", - g_variant_new_string( - self->attempted_tzid.c_str())); - g_dbus_connection_emit_signal (connection, - NULL, - object_path, - "org.freedesktop.DBus.Properties", - "PropertiesChanged", - g_variant_new ("(sa{sv}as)", - interface_name, - builder, - NULL), - &local_error); - g_assert_no_error (local_error); - g_variant_unref(child); + return wait_for([&tzid, &tz](){return tzid == tz.timezone.get();}); } -protected: - - std::shared_ptr<MockState> m_mock_state; - std::shared_ptr<State> m_state; - std::shared_ptr<MockLiveActions> m_live_actions; - std::shared_ptr<Actions> m_actions; - - bool name_acquired; - std::string attempted_tzid; - - GTestDBus* bus; - guint own_name; - GDBusConnection* connection; - GDBusNodeInfo* node_info; - int object_register_id; - GDBusProxy *proxy; - - void SetUp() + void set_timedate1_timezone(const std::string& tzid) { - super::SetUp(); - g_debug("SetUp"); - - name_acquired = false; - attempted_tzid.clear(); - connection = nullptr; - node_info = nullptr; - object_register_id = 0; - own_name = 0; - proxy = nullptr; - - // bring up the test bus - bus = g_test_dbus_new(G_TEST_DBUS_NONE); - g_test_dbus_up(bus); - const auto address = g_test_dbus_get_bus_address(bus); - g_setenv("DBUS_SYSTEM_BUS_ADDRESS", address, true); - g_setenv("DBUS_SESSION_BUS_ADDRESS", address, true); - g_debug("test_dbus's address is %s", address); - - // parse the org.freedesktop.timedate1 interface - const gchar introspection_xml[] = - "<node>" - " <interface name='org.freedesktop.timedate1'>" - " <property name='Timezone' type='s' access='read' />" - " <method name='SetTimezone'>" - " <arg name='timezone' type='s' direction='in'/>" - " <arg name='user_interaction' type='b' direction='in'/>" - " </method>" - " </interface>" - "</node>"; - node_info = g_dbus_node_info_new_for_xml(introspection_xml, nullptr); - ASSERT_TRUE(node_info != nullptr); - ASSERT_TRUE(node_info->interfaces != nullptr); - ASSERT_TRUE(node_info->interfaces[0] != nullptr); - ASSERT_TRUE(node_info->interfaces[1] == nullptr); - ASSERT_STREQ("org.freedesktop.timedate1", node_info->interfaces[0]->name); - - // own the bus - own_name = g_bus_own_name(G_BUS_TYPE_SYSTEM, - "org.freedesktop.timedate1", - G_BUS_NAME_OWNER_FLAGS_NONE, - on_bus_acquired, on_name_acquired, on_name_lost, - this, nullptr); - ASSERT_TRUE(object_register_id == 0); - ASSERT_FALSE(name_acquired); - ASSERT_TRUE(connection == nullptr); - g_main_loop_run(loop); - ASSERT_TRUE(object_register_id != 0); - ASSERT_TRUE(name_acquired); - ASSERT_TRUE(G_IS_DBUS_CONNECTION(connection)); + GError* error {}; + auto v = g_dbus_connection_call_sync( + m_bus, + Bus::Timedate1::BUSNAME, + Bus::Timedate1::ADDR, + Bus::Timedate1::IFACE, + Bus::Timedate1::Methods::SET_TIMEZONE, + g_variant_new("(sb)", tzid.c_str(), FALSE), + nullptr, + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + &error); + g_assert_no_error(error); - // create the State and Actions - m_mock_state.reset(new MockState); - m_mock_state->settings.reset(new Settings); - m_state = std::dynamic_pointer_cast<State>(m_mock_state); - m_live_actions.reset(new MockLiveActions(m_state)); - m_actions = std::dynamic_pointer_cast<Actions>(m_live_actions); + g_clear_pointer(&v, g_variant_unref); } - void TearDown() + std::string get_timedate1_timezone() { - g_debug("TearDown"); - m_actions.reset(); - m_live_actions.reset(); - m_state.reset(); - m_mock_state.reset(); - g_dbus_connection_unregister_object(connection, object_register_id); - g_object_unref(proxy); - g_dbus_node_info_unref(node_info); - g_bus_unown_name(own_name); - g_dbus_connection_close(connection, nullptr, on_bus_closed, this); - g_main_loop_run(loop); - g_clear_object(&connection); - g_test_dbus_down(bus); - g_clear_object(&bus); + GError* error {}; + auto v = g_dbus_connection_call_sync( + m_bus, + Bus::Timedate1::BUSNAME, + Bus::Timedate1::ADDR, + Bus::Properties::IFACE, + Bus::Properties::Methods::GET, + g_variant_new("(ss)", Bus::Timedate1::IFACE, Bus::Timedate1::Properties::TIMEZONE), + G_VARIANT_TYPE("(v)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + nullptr, + &error); + g_assert_no_error(error); - super::TearDown(); - } -public: - void set_timezone(std::string tz) - { - g_debug("set_timezone: '%s'", tz.c_str()); - g_dbus_proxy_call_sync(proxy, - "SetTimezone", - g_variant_new("(sb)", - tz.c_str(), - FALSE), - G_DBUS_CALL_FLAGS_NONE, - 500, - nullptr, - nullptr); + GVariant* tzv {}; + g_variant_get(v, "(v)", &tzv); + std::string tzid; + const char* tz = g_variant_get_string(tzv, nullptr); + if (tz != nullptr) + tzid = tz; + + g_clear_pointer(&tzv, g_variant_unref); + g_clear_pointer(&v, g_variant_unref); + return tzid; } }; -#endif +#define EXPECT_TZID(expected_tzid, tmp) \ + EXPECT_TRUE(wait_for_tzid(expected_tzid, tmp)) \ + << "expected " << expected_tzid \ + << " got " << tmp.timezone.get(); + |