/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License 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 warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see .
*
*/
#include
#include
#include
#include "e-test-server-utils.h"
static ETestServerClosure test_closure = { E_TEST_SERVER_CALENDAR, NULL, E_CAL_CLIENT_SOURCE_TYPE_EVENTS, FALSE, NULL, FALSE };
static ICalComponent *
create_component (const gchar *tz_location)
{
const gchar *comp_str =
"BEGIN:VEVENT\r\n"
"SUMMARY:recurs\r\n"
"UID:recurs-id\r\n"
"DTSTART%s:20190107T080000%s\r\n"
"DTEND%s:20190107T090000%s\r\n"
"DTSTAMP:20190101T050000Z\r\n"
"CREATED:20190101T050000Z\r\n"
"LAST-MODIFIED:20190101T050000Z\r\n"
"RRULE:FREQ=DAILY;COUNT=5\r\n"
"END:VEVENT\r\n";
gchar *tzref = NULL, tzsuffix[2] = { 0, 0};
gchar *str;
ICalComponent *icomp;
ICalTimezone *zone = NULL;
ICalTime *itt;
if (tz_location) {
if (g_ascii_strcasecmp (tz_location, "UTC") == 0) {
tzsuffix[0] = 'Z';
zone = i_cal_timezone_get_utc_timezone ();
} else {
const gchar *tzid;
zone = i_cal_timezone_get_builtin_timezone (tz_location);
g_assert_nonnull (zone);
tzid = i_cal_timezone_get_tzid (zone);
g_assert_nonnull (tzid);
tzref = g_strconcat (";TZID=", tzid, NULL);
}
}
str = g_strdup_printf (comp_str, tzref ? tzref : "", tzsuffix, tzref ? tzref : "", tzsuffix);
icomp = i_cal_component_new_from_string (str);
g_assert_nonnull (icomp);
g_free (tzref);
g_free (str);
itt = i_cal_component_get_dtstart (icomp);
g_assert_nonnull (itt);
g_assert_true (i_cal_time_get_timezone (itt) == zone);
g_object_unref (itt);
itt = i_cal_component_get_dtend (icomp);
g_assert_nonnull (itt);
g_assert_true (i_cal_time_get_timezone (itt) == zone);
g_object_unref (itt);
return icomp;
}
static void
setup_cal (ECalClient *cal_client,
const gchar *tz_location)
{
ICalComponent *icomp;
gboolean success;
gchar *uid = NULL;
GError *error = NULL;
icomp = create_component (tz_location);
/* Ignore errors, it'll fail the first time, but will succeed all other times */
e_cal_client_remove_object_sync (cal_client, i_cal_component_get_uid (icomp), NULL, E_CAL_OBJ_MOD_ALL, E_CAL_OPERATION_FLAG_NONE, NULL, NULL);
success = e_cal_client_create_object_sync (cal_client, icomp, E_CAL_OPERATION_FLAG_NONE, &uid, NULL, &error);
g_assert_no_error (error);
g_assert_true (success);
g_assert_nonnull (uid);
g_object_unref (icomp);
g_free (uid);
}
typedef struct _Instance {
ICalTime *start;
ICalTime *end;
ICalComponent *icomp;
} Instance;
static Instance *
instance_new (ICalTime *start,
ICalTime *end,
ICalComponent *icomp)
{
Instance *ins;
ins = g_new0 (Instance, 1);
ins->start = i_cal_time_clone (start);
ins->end = i_cal_time_clone (end);
ins->icomp = i_cal_component_clone (icomp);
return ins;
}
static void
instance_free (gpointer ptr)
{
Instance *ins = ptr;
if (ins) {
g_clear_object (&ins->start);
g_clear_object (&ins->end);
g_clear_object (&ins->icomp);
g_free (ins);
}
}
static guint
instance_hash (gconstpointer ptr)
{
/* Place everything into a single basket */
return 0;
}
static gboolean
instance_equal (gconstpointer ptr1,
gconstpointer ptr2)
{
Instance *ins1 = (Instance *) ptr1;
Instance *ins2 = (Instance *) ptr2;
if (!ins1 || !ins2)
return ins1 == ins2;
return i_cal_time_compare (ins1->start, ins2->start) == 0;
}
static void
verify_received_instances (GHashTable *instances,
ICalTimezone *comp_zone)
{
const gchar *expected_times[] = {
"20190107T080000",
"20190108T080000",
"20190109T080000",
"20190110T080000",
"20190111T080000"
};
gint ii;
g_assert_nonnull (instances);
for (ii = 0; ii < G_N_ELEMENTS (expected_times); ii++) {
ICalTime *expected_start;
Instance ins = { 0, };
expected_start = i_cal_time_new_from_string (expected_times[ii]);
g_assert_nonnull (expected_start);
ins.start = expected_start;
g_assert_true (g_hash_table_remove (instances, &ins));
g_object_unref (expected_start);
}
g_assert_cmpint (g_hash_table_size (instances), ==, 0);
}
typedef struct _RecurData {
GHashTable *instances;
} RecurData;
static gboolean
recur_instance_cb (ICalComponent *icomp,
ICalTime *instance_start,
ICalTime *instance_end,
gpointer user_data,
GCancellable *cancellable,
GError **error)
{
RecurData *rd = user_data;
Instance *ins;
gsize pre;
g_assert_nonnull (rd);
ins = instance_new (instance_start, instance_end, icomp);
pre = g_hash_table_size (rd->instances);
g_hash_table_insert (rd->instances, ins, NULL);
g_assert_cmpint (pre + 1, ==, g_hash_table_size (rd->instances));
return TRUE;
}
static void
test_recur_plain_run (ECalClient *client,
const gchar *default_tz,
const gchar *comp_tz)
{
ICalTimezone *default_zone, *comp_zone;
ICalComponent *icomp;
ICalTime *start, *end;
RecurData rd;
gboolean success;
GError *error = NULL;
default_zone = default_tz ? i_cal_timezone_get_builtin_timezone (default_tz) : NULL;
if (default_tz)
g_assert_nonnull (default_zone);
comp_zone = comp_tz ? i_cal_timezone_get_builtin_timezone (comp_tz) : NULL;
if (comp_tz)
g_assert_nonnull (comp_zone);
icomp = create_component (comp_tz);
start = i_cal_time_new_from_string ("20190103T080000Z");
end = i_cal_time_new_from_string ("20190115T080000Z");
rd.instances = g_hash_table_new_full (instance_hash, instance_equal, instance_free, NULL);
success = e_cal_recur_generate_instances_sync (icomp, start, end,
recur_instance_cb, &rd,
e_cal_client_tzlookup_cb, client,
default_zone, NULL, &error);
g_assert_no_error (error);
g_assert_true (success);
g_assert_cmpint (g_hash_table_size (rd.instances), ==, 5);
verify_received_instances (rd.instances, comp_zone);
g_hash_table_destroy (rd.instances);
g_clear_object (&icomp);
g_clear_object (&start);
g_clear_object (&end);
}
static void
test_recur_client_run (ECalClient *client,
const gchar *default_tz,
const gchar *comp_tz)
{
ICalTimezone *default_zone, *comp_zone;
ICalTime *start, *end;
ICalComponent *icomp;
RecurData rd;
default_zone = default_tz ? i_cal_timezone_get_builtin_timezone (default_tz) : NULL;
if (default_tz)
g_assert_nonnull (default_zone);
comp_zone = comp_tz ? i_cal_timezone_get_builtin_timezone (comp_tz) : NULL;
if (comp_tz)
g_assert_nonnull (comp_zone);
e_cal_client_set_default_timezone (client, default_zone);
setup_cal (client, comp_tz);
start = i_cal_time_new_from_string ("20190103T080000Z");
end = i_cal_time_new_from_string ("20190115T080000Z");
rd.instances = g_hash_table_new_full (instance_hash, instance_equal, instance_free, NULL);
e_cal_client_generate_instances_sync (client,
i_cal_time_as_timet (start),
i_cal_time_as_timet (end),
NULL, /* GCancellable * */
recur_instance_cb, &rd);
g_assert_cmpint (g_hash_table_size (rd.instances), ==, 5);
verify_received_instances (rd.instances, comp_zone);
g_hash_table_destroy (rd.instances);
icomp = create_component (comp_tz);
rd.instances = g_hash_table_new_full (instance_hash, instance_equal, instance_free, NULL);
e_cal_client_generate_instances_for_object_sync (client,
icomp,
i_cal_time_as_timet (start),
i_cal_time_as_timet (end),
NULL, /* GCancellable * */
recur_instance_cb, &rd);
g_assert_cmpint (g_hash_table_size (rd.instances), ==, 5);
verify_received_instances (rd.instances, comp_zone);
g_hash_table_destroy (rd.instances);
g_clear_object (&icomp);
g_clear_object (&start);
g_clear_object (&end);
}
static const gchar *timezones[] = {
NULL, /* floating time */
"Pacific/Midway", /* UTC-11 */
"Pacific/Honolulu", /* UTC-10 */
"America/Adak", /* UTC-9 */
"America/Yakutat", /* UTC-8 */
"America/Vancouver", /* UTC-7 */
"America/Boise", /* UTC-6 */
"America/Cancun", /* UTC-5 */
"America/New_York", /* UTC-4 */
"Atlantic/Bermuda", /* UTC-3 */
"America/Noronha", /* UTC-2 */
"Atlantic/Cape_Verde", /* UTC-1 */
"Atlantic/Azores", /* UTC */
"UTC", /* UTC */
"Africa/Casablanca", /* UTC+1 */
"Europe/Malta", /* UTC+2 */
"Europe/Athens", /* UTC+3 */
"Asia/Baku", /* UTC+4 */
"Asia/Qyzylorda", /* UTC+5 */
"Asia/Urumqi", /* UTC+6 */
"Asia/Hovd", /* UTC+7 */
"Asia/Irkutsk", /* UTC+8 */
"Asia/Seoul", /* UTC+9 */
"Asia/Vladivostok", /* UTC+10 */
"Asia/Sakhalin", /* UTC+11 */
"Asia/Kamchatka", /* UTC+12 */
"Pacific/Enderbury", /* UTC+13 */
"Pacific/Kiritimati" /* UTC+14 */
};
static void
test_recur_plain (ETestServerFixture *fixture,
gconstpointer user_data)
{
ECalClient *cal;
gint deftz_ii, comptz_ii;
cal = E_TEST_SERVER_UTILS_SERVICE (fixture, ECalClient);
for (deftz_ii = 0; deftz_ii < G_N_ELEMENTS (timezones); deftz_ii++) {
for (comptz_ii = 0; comptz_ii < G_N_ELEMENTS (timezones); comptz_ii++) {
test_recur_plain_run (cal, timezones[deftz_ii], timezones[comptz_ii]);
}
}
}
static ICalComponent *
create_component_midnight (const gchar *tz_location)
{
const gchar *comp_str =
"BEGIN:VEVENT\r\n"
"SUMMARY:recurs\r\n"
"UID:recurs-id\r\n"
"DTSTART%s:20190107T000000%s\r\n"
"DTEND%s:20190107T003000%s\r\n"
"DTSTAMP:20190101T050000Z\r\n"
"CREATED:20190101T050000Z\r\n"
"LAST-MODIFIED:20190101T050000Z\r\n"
"RRULE:FREQ=DAILY;UNTIL=20190109\r\n"
"END:VEVENT\r\n";
gchar *tzref = NULL, tzsuffix[2] = { 0, 0 };
gchar *str;
ICalComponent *icomp;
ICalTimezone *zone = NULL;
ICalTime *itt;
if (tz_location) {
if (g_ascii_strcasecmp (tz_location, "UTC") == 0) {
tzsuffix[0] = 'Z';
zone = i_cal_timezone_get_utc_timezone ();
} else {
const gchar *tzid;
zone = i_cal_timezone_get_builtin_timezone (tz_location);
g_assert_nonnull (zone);
tzid = i_cal_timezone_get_tzid (zone);
g_assert_nonnull (tzid);
tzref = g_strconcat (";TZID=", tzid, NULL);
}
}
str = g_strdup_printf (comp_str, tzref ? tzref : "", tzsuffix, tzref ? tzref : "", tzsuffix);
icomp = i_cal_component_new_from_string (str);
g_assert_nonnull (icomp);
g_free (tzref);
g_free (str);
itt = i_cal_component_get_dtstart (icomp);
g_assert_nonnull (itt);
g_assert_true (i_cal_time_get_timezone (itt) == zone);
g_object_unref (itt);
itt = i_cal_component_get_dtend (icomp);
g_assert_nonnull (itt);
g_assert_true (i_cal_time_get_timezone (itt) == zone);
g_object_unref (itt);
return icomp;
}
static void
setup_cal_midnight (ECalClient *cal_client,
const gchar *tz_location)
{
ICalComponent *icomp;
gboolean success;
gchar *uid = NULL;
GError *error = NULL;
icomp = create_component_midnight (tz_location);
if (!e_cal_client_remove_object_sync (cal_client, i_cal_component_get_uid (icomp), NULL, E_CAL_OBJ_MOD_ALL, E_CAL_OPERATION_FLAG_NONE, NULL, &error)) {
g_assert_error (error, E_CAL_CLIENT_ERROR, E_CAL_CLIENT_ERROR_OBJECT_NOT_FOUND);
g_clear_error (&error);
} else {
g_assert_no_error (error);
}
success = e_cal_client_create_object_sync (cal_client, icomp, E_CAL_OPERATION_FLAG_NONE, &uid, NULL, &error);
g_assert_no_error (error);
g_assert_true (success);
g_assert_nonnull (uid);
g_object_unref (icomp);
g_free (uid);
}
static gboolean
recur_instance_midnight_cb (ICalComponent *icomp,
ICalTime *instance_start,
ICalTime *instance_end,
gpointer user_data,
GCancellable *cancellable,
GError **error)
{
GSList **listp = user_data;
*listp = g_slist_append (*listp, i_cal_time_as_ical_string (instance_start));
return TRUE;
}
static void
test_recur_midnight_for_zone (ECalClient *client,
const gchar *tz_location)
{
ICalTime *start, *end;
GSList *list = NULL, *last;
setup_cal_midnight (client, tz_location);
start = i_cal_time_new_from_string ("20190101T000000Z");
end = i_cal_time_new_from_string ("20190131T000000Z");
e_cal_client_generate_instances_sync (client,
i_cal_time_as_timet (start),
i_cal_time_as_timet (end),
NULL, /* GCancellable * */
recur_instance_midnight_cb, &list);
last = g_slist_last (list);
g_assert_nonnull (last);
if (g_ascii_strcasecmp (tz_location, "UTC") == 0)
g_assert_cmpstr (last->data, ==, "20190109T000000Z");
else
g_assert_cmpstr (last->data, ==, "20190109T000000");
g_assert_cmpint (g_slist_length (list), ==, 3);
g_slist_free_full (list, g_free);
g_clear_object (&start);
g_clear_object (&end);
}
static void
test_recur_midnight (ETestServerFixture *fixture,
gconstpointer user_data)
{
ECalClient *client;
client = E_TEST_SERVER_UTILS_SERVICE (fixture, ECalClient);
e_cal_client_set_default_timezone (client, i_cal_timezone_get_builtin_timezone ("UTC"));
test_recur_midnight_for_zone (client, "UTC");
test_recur_midnight_for_zone (client, "America/New_York");
test_recur_midnight_for_zone (client, "Europe/Berlin");
}
static void
test_recur_client (ETestServerFixture *fixture,
gconstpointer user_data)
{
ECalClient *cal;
gint deftz_ii, comptz_ii;
cal = E_TEST_SERVER_UTILS_SERVICE (fixture, ECalClient);
for (deftz_ii = 0; deftz_ii < G_N_ELEMENTS (timezones); deftz_ii++) {
for (comptz_ii = 0; comptz_ii < G_N_ELEMENTS (timezones); comptz_ii++) {
/* Skip the floating time for the client's default timezone */
if (timezones[deftz_ii])
test_recur_client_run (cal, timezones[deftz_ii], timezones[comptz_ii]);
}
}
}
static gboolean
got_instance_cb (ICalComponent *icomp,
ICalTime *instance_start,
ICalTime *instance_end,
gpointer user_data,
GCancellable *cancellable,
GError **error)
{
gint *pfound = user_data;
(*pfound)++;
return TRUE;
}
static ICalTimezone *
lookup_tzid_cb (const gchar *tzid,
gpointer lookup_data,
GCancellable *cancellable,
GError **error)
{
return i_cal_timezone_get_builtin_timezone (tzid);
}
static void
test_recur_exdate_component (const gchar *comp_str)
{
ICalComponent *comp;
ICalTime *start, *end;
gint found = 0;
gboolean success;
GError *error = NULL;
comp = i_cal_component_new_from_string (comp_str);
g_assert_nonnull (comp);
start = i_cal_time_new_from_string ("20191001T000000Z");
end = i_cal_time_new_from_string ("20191031T235959Z");
g_assert_nonnull (start);
g_assert_nonnull (end);
success = e_cal_recur_generate_instances_sync (comp, start, end,
got_instance_cb, &found,
lookup_tzid_cb, NULL,
i_cal_timezone_get_builtin_timezone ("Europe/Berlin"),
NULL, &error);
g_assert_no_error (error);
g_assert_true (success);
g_assert_cmpint (found, ==, 2);
found = 0;
success = e_cal_recur_generate_instances_sync (comp, start, end,
got_instance_cb, &found,
lookup_tzid_cb, NULL,
i_cal_timezone_get_builtin_timezone ("America/New_York"),
NULL, &error);
g_assert_no_error (error);
g_assert_true (success);
g_assert_cmpint (found, ==, 2);
g_object_unref (start);
g_object_unref (end);
g_object_unref (comp);
}
static void
test_recur_exdate (ETestServerFixture *fixture,
gconstpointer user_data)
{
test_recur_exdate_component (
"BEGIN:VEVENT\r\n"
"UID:007\r\n"
"DTSTART;TZID=Europe/Amsterdam:20191010T120000\r\n"
"DTEND;TZID=Europe/Amsterdam:20191010T170000\r\n"
"SUMMARY:Test\r\n"
"RRULE:FREQ=DAILY;COUNT=4\r\n"
"EXDATE;VALUE=DATE:20191011\r\n"
"EXDATE:20191012T100000Z\r\n"
"END:VEVENT\r\n"
);
test_recur_exdate_component (
"BEGIN:VEVENT\r\n"
"UID:007\r\n"
"DTSTART:20191010T120000\r\n"
"DTEND:20191010T170000\r\n"
"SUMMARY:Test\r\n"
"RRULE:FREQ=DAILY;COUNT=4\r\n"
"EXDATE;VALUE=DATE:20191011\r\n"
"EXDATE:20191012T120000\r\n"
"END:VEVENT\r\n"
);
}
typedef struct _DurationData {
gint n_found;
gint expected_duration;
} DurationData;
static gboolean
duration_got_instance_cb (ICalComponent *icomp,
ICalTime *instance_start,
ICalTime *instance_end,
gpointer user_data,
GCancellable *cancellable,
GError **error)
{
DurationData *dd = user_data;
ICalDuration *dur;
dd->n_found++;
dur = i_cal_component_get_duration (icomp);
g_assert_nonnull (dur);
g_assert_cmpint (i_cal_duration_as_int (dur), ==, dd->expected_duration);
g_assert_cmpint (i_cal_time_as_timet (instance_end) - i_cal_time_as_timet (instance_start), ==, dd->expected_duration);
g_object_unref (dur);
return TRUE;
}
static void
test_recur_duration (ETestServerFixture *fixture,
gconstpointer user_data)
{
ICalComponent *comp;
ICalDuration *dur;
ICalTime *start, *end;
DurationData dd;
gboolean success;
GError *error = NULL;
comp = i_cal_component_new_from_string (
"BEGIN:VEVENT\n"
"UID:1\n"
"DTSTART;TZID=Australia/Melbourne:20200212T100000\n"
"DURATION:PT30M\n"
"CREATED:20200212T111400Z\n"
"DTSTAMP:20200212T111400Z\n"
"SUMMARY:With duration\n"
"RRULE:FREQ=WEEKLY\n"
"END:VEVENT\n"
);
g_assert_nonnull (comp);
start = i_cal_time_new_from_string ("20200201T000000Z");
end = i_cal_time_new_from_string ("20200331T235959Z");
g_assert_nonnull (start);
g_assert_nonnull (end);
dur = i_cal_component_get_duration (comp);
g_assert_nonnull (dur);
dd.expected_duration = i_cal_duration_as_int (dur);
g_object_unref (dur);
g_assert_cmpint (dd.expected_duration, ==, 30 * 60);
dd.n_found = 0;
success = e_cal_recur_generate_instances_sync (comp, start, end,
duration_got_instance_cb, &dd,
lookup_tzid_cb, NULL,
i_cal_timezone_get_builtin_timezone ("Australia/Melbourne"),
NULL, &error);
g_assert_no_error (error);
g_assert_true (success);
g_assert_cmpint (dd.n_found, ==, 8);
dd.n_found = 0;
success = e_cal_recur_generate_instances_sync (comp, start, end,
duration_got_instance_cb, &dd,
lookup_tzid_cb, NULL,
i_cal_timezone_get_builtin_timezone ("Europe/Berlin"),
NULL, &error);
g_assert_no_error (error);
g_assert_true (success);
g_assert_cmpint (dd.n_found, ==, 8);
dd.n_found = 0;
success = e_cal_recur_generate_instances_sync (comp, start, end,
duration_got_instance_cb, &dd,
lookup_tzid_cb, NULL,
i_cal_timezone_get_builtin_timezone ("America/New_York"),
NULL, &error);
g_assert_no_error (error);
g_assert_true (success);
g_assert_cmpint (dd.n_found, ==, 8);
g_object_unref (start);
g_object_unref (end);
g_object_unref (comp);
}
gint
main (gint argc,
gchar **argv)
{
g_test_init (&argc, &argv, NULL);
g_test_bug_base ("http://bugzilla.gnome.org/");
g_test_add (
"/ECalRecur/Plain",
ETestServerFixture,
&test_closure,
e_test_server_utils_setup,
test_recur_plain,
e_test_server_utils_teardown);
g_test_add (
"/ECalRecur/Client",
ETestServerFixture,
&test_closure,
e_test_server_utils_setup,
test_recur_client,
e_test_server_utils_teardown);
g_test_add (
"/ECalRecur/Exdate",
ETestServerFixture,
&test_closure,
e_test_server_utils_setup,
test_recur_exdate,
e_test_server_utils_teardown);
g_test_add (
"/ECalRecur/Duration",
ETestServerFixture,
&test_closure,
e_test_server_utils_setup,
test_recur_duration,
e_test_server_utils_teardown);
g_test_add (
"/ECalRecur/Midnight",
ETestServerFixture,
&test_closure,
e_test_server_utils_setup,
test_recur_midnight,
e_test_server_utils_teardown);
return e_test_server_utils_run (argc, argv);
}