[evolution-data-server/wip/offline-cache] Add range search test for ECalCache



commit 1a03bcfcbf4a0f8a95893307e03dbc003ac1e71d
Author: Milan Crha <mcrha redhat com>
Date:   Mon Feb 20 19:31:00 2017 +0100

    Add range search test for ECalCache

 src/calendar/libecal/e-cal-util.c             |   30 ++-
 src/calendar/libedata-cal/e-cal-cache.c       |   65 ++++-
 tests/libedata-cal/CMakeLists.txt             |    1 +
 tests/libedata-cal/test-cal-cache-intervals.c |  321 +++++++++++++++++++++++++
 4 files changed, 393 insertions(+), 24 deletions(-)
---
diff --git a/src/calendar/libecal/e-cal-util.c b/src/calendar/libecal/e-cal-util.c
index bd10022..689fab6 100644
--- a/src/calendar/libecal/e-cal-util.c
+++ b/src/calendar/libecal/e-cal-util.c
@@ -1578,6 +1578,8 @@ e_cal_util_get_component_occur_times (ECalComponent *comp,
        } else {
                /* ALARMS, EVENTS: DTEND and reccurences */
 
+               time_t may_end = _TIME_MIN;
+
                if (e_cal_component_has_recurrences (comp)) {
                        GSList *rrules = NULL;
                        GSList *exrules = NULL;
@@ -1600,9 +1602,9 @@ e_cal_util_get_component_occur_times (ECalComponent *comp,
                                        &ir, prop, utc_zone, TRUE);
 
                                if (rule_end == -1) /* repeats forever */
-                                       *end = _TIME_MAX;
-                               else if (rule_end > *end) /* new maximum */
-                                       *end = rule_end;
+                                       may_end = _TIME_MAX;
+                               else if (rule_end > may_end) /* new maximum */
+                                       may_end = rule_end;
                        }
 
                        /* Do the EXRULEs. */
@@ -1617,9 +1619,9 @@ e_cal_util_get_component_occur_times (ECalComponent *comp,
                                        &ir, prop, utc_zone, TRUE);
 
                                if (rule_end == -1) /* repeats forever */
-                                       *end = _TIME_MAX;
-                               else if (rule_end > *end)
-                                       *end = rule_end;
+                                       may_end = _TIME_MAX;
+                               else if (rule_end > may_end)
+                                       may_end = rule_end;
                        }
 
                        /* Do the RDATEs */
@@ -1639,12 +1641,14 @@ e_cal_util_get_component_occur_times (ECalComponent *comp,
                                        rdate_end = icaltime_as_timet (p->u.end);
 
                                if (rdate_end == -1) /* repeats forever */
-                                       *end = _TIME_MAX;
-                               else if (rdate_end > *end)
-                                       *end = rdate_end;
+                                       may_end = _TIME_MAX;
+                               else if (rdate_end > may_end)
+                                       may_end = rdate_end;
                        }
 
                        e_cal_component_free_period_list (rdates);
+               } else if (*start != _TIME_MIN) {
+                       may_end = *start;
                }
 
                /* Get dtend of the component and convert it to UTC */
@@ -1656,11 +1660,15 @@ e_cal_util_get_component_occur_times (ECalComponent *comp,
                        dtend_time = componenttime_to_utc_timet (
                                &dt_end, tz_cb, tz_cb_data, default_timezone);
 
-                       if (dtend_time == -1 || (dtend_time > *end))
-                               *end = dtend_time;
+                       if (dtend_time == -1 || (dtend_time > may_end))
+                               may_end = dtend_time;
+               } else {
+                       may_end = _TIME_MAX;
                }
 
                e_cal_component_free_datetime (&dt_end);
+
+               *end = may_end == _TIME_MIN ? _TIME_MAX : may_end;
        }
 }
 
diff --git a/src/calendar/libedata-cal/e-cal-cache.c b/src/calendar/libedata-cal/e-cal-cache.c
index cc82c34..d6eecb2 100644
--- a/src/calendar/libedata-cal/e-cal-cache.c
+++ b/src/calendar/libedata-cal/e-cal-cache.c
@@ -766,6 +766,40 @@ ecc_fill_other_columns (ECalCache *cal_cache,
        add_value (ECC_COLUMN_CATEGORIES, ecc_extract_categories (comp));
 }
 
+static gchar *
+ecc_range_as_where_clause (const gchar *start_str,
+                          const gchar *end_str)
+{
+       GString *stmt;
+
+       if (!start_str && !end_str)
+               return NULL;
+
+       stmt = g_string_new ("");
+
+       if (start_str) {
+               e_cache_sqlite_stmt_append_printf (stmt,
+                       "(" ECC_COLUMN_OCCUR_END " IS NULL OR " ECC_COLUMN_OCCUR_END ">=%Q)",
+                       start_str);
+       }
+
+       if (end_str) {
+               if (start_str) {
+                       g_string_prepend (stmt, "(");
+                       g_string_append (stmt, " AND ");
+               }
+
+               e_cache_sqlite_stmt_append_printf (stmt,
+                       "(" ECC_COLUMN_OCCUR_START " IS NULL OR " ECC_COLUMN_OCCUR_START "<=%Q)",
+                       end_str);
+
+               if (start_str)
+                       g_string_append (stmt, ")");
+       }
+
+       return g_string_free (stmt, FALSE);
+}
+
 typedef struct _SExpToSqlContext {
        ECalCache *cal_cache;
        gint sexp_id;
@@ -931,10 +965,10 @@ ecc_sexp_func_occur_in_time_range (ESExp *esexp,
        end_str = ecc_encode_itt_to_sql (itt_end);
 
        result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
-       result->value.string = g_strdup_printf (
-               "((" ECC_COLUMN_OCCUR_START " IS NULL OR " ECC_COLUMN_OCCUR_START "<'%s')"
-               " AND (" ECC_COLUMN_OCCUR_END " IS NULL OR " ECC_COLUMN_OCCUR_END ">'%s'))",
-               start_str, end_str);
+       result->value.string = ecc_range_as_where_clause (start_str, end_str);
+
+       if (!result->value.string)
+               result->value.string = g_strdup ("1==1");
 
        g_free (start_str);
        g_free (end_str);
@@ -2256,7 +2290,8 @@ e_cal_cache_get_components_in_range_as_strings (ECalCache *cal_cache,
                                                GCancellable *cancellable,
                                                GError **error)
 {
-       gchar *stmt, *range_start_str, *range_end_str;
+       GString *stmt;
+       gchar *range_start_str, *range_end_str, *range_where_clause;
        gboolean success;
 
        g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
@@ -2266,18 +2301,22 @@ e_cal_cache_get_components_in_range_as_strings (ECalCache *cal_cache,
 
        range_start_str = ecc_encode_timet_to_sql (cal_cache, range_start);
        range_end_str = ecc_encode_timet_to_sql (cal_cache, range_end);
+       range_where_clause = ecc_range_as_where_clause (range_start_str, range_end_str);
 
        /* Using 'ORDER BY' to get the master object first */
-       stmt = e_cache_sqlite_stmt_printf (
-               "SELECT " E_CACHE_COLUMN_OBJECT " FROM " E_CACHE_TABLE_OBJECTS
-               " WHERE (" ECC_COLUMN_OCCUR_START " IS NULL OR " ECC_COLUMN_OCCUR_START "<%Q)"
-               " AND (" ECC_COLUMN_OCCUR_END " IS NULL OR " ECC_COLUMN_OCCUR_END ">%Q)"
-               " ORDER BY " E_CACHE_COLUMN_UID,
-               range_end, range_start);
+       stmt = g_string_new ("SELECT " E_CACHE_COLUMN_OBJECT " FROM " E_CACHE_TABLE_OBJECTS);
 
-       success = e_cache_sqlite_select (E_CACHE (cal_cache), stmt, e_cal_cache_get_strings, out_icalstrings, 
cancellable, error);
+       if (range_where_clause) {
+               g_string_append (stmt, " WHERE ");
+               g_string_append (stmt, range_where_clause);
+       }
 
-       e_cache_sqlite_stmt_free (stmt);
+       g_string_append (stmt, " ORDER BY " E_CACHE_COLUMN_UID);
+
+       success = e_cache_sqlite_select (E_CACHE (cal_cache), stmt->str, e_cal_cache_get_strings, 
out_icalstrings, cancellable, error);
+
+       g_string_free (stmt, TRUE);
+       g_free (range_where_clause);
        g_free (range_start_str);
        g_free (range_end_str);
 
diff --git a/tests/libedata-cal/CMakeLists.txt b/tests/libedata-cal/CMakeLists.txt
index 06659d4..eb70d3c 100644
--- a/tests/libedata-cal/CMakeLists.txt
+++ b/tests/libedata-cal/CMakeLists.txt
@@ -79,6 +79,7 @@ set(TESTS
        test-cal-backend-sexp
        test-intervaltree
        test-cal-cache-offline
+       test-cal-cache-intervals
 )
 
 foreach(_test ${TESTS})
diff --git a/tests/libedata-cal/test-cal-cache-intervals.c b/tests/libedata-cal/test-cal-cache-intervals.c
new file mode 100644
index 0000000..151e328
--- /dev/null
+++ b/tests/libedata-cal/test-cal-cache-intervals.c
@@ -0,0 +1,321 @@
+/* -*- 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 <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <stdlib.h>
+#include <locale.h>
+#include <libecal/libecal.h>
+
+#include "test-cal-cache-utils.h"
+
+#define NUM_INTERVALS_CLOSED   100
+#define NUM_INTERVALS_OPEN     100
+#define NUM_SEARCHES           500
+#define DELETE_PROBABILITY     0.3
+#define _TIME_MIN              ((time_t) 0)            /* Min valid time_t     */
+#define _TIME_MAX              ((time_t) INT_MAX)      /* Max valid time_t     */
+
+typedef struct _IntervalData {
+       gint start;
+       gint end;
+       ECalComponent * comp;
+} IntervalData;
+
+static void
+interval_data_free (gpointer ptr)
+{
+       IntervalData *id = ptr;
+
+       if (id) {
+               g_object_unref (id->comp);
+               g_free (id);
+       }
+}
+
+static gint
+compare_intervals (time_t x_start,
+                  time_t x_end,
+                  time_t y_start,
+                  time_t y_end)
+{
+       /* assumption: x_start <= x_end */
+       /* assumption: y_start <= y_end */
+
+       /* x is left of y */
+       if (x_end < y_start)
+               return -1;
+
+       /* x is right of y */
+       if (y_end < x_start)
+               return 1;
+
+       /* x and y overlap */
+       return 0;
+}
+
+static GHashTable *
+search_in_intervals (GSList *intervals,
+                    time_t start,
+                    time_t end)
+{
+       GSList *link;
+       GHashTable *res;
+
+       res = g_hash_table_new_full ((GHashFunc) e_cal_component_id_hash, (GEqualFunc) 
e_cal_component_id_equal,
+               (GDestroyNotify) e_cal_component_free_id, g_object_unref);
+
+       for (link = intervals; link; link = g_slist_next (link)) {
+               IntervalData *data = link->data;
+
+               if (compare_intervals (start, end, data->start, data->end) == 0) {
+                       ECalComponentId *id = NULL;
+
+                       id = e_cal_component_get_id (data->comp);
+                       g_assert_nonnull (id);
+
+                       g_hash_table_insert (res, id, g_object_ref (data->comp));
+               }
+       }
+
+       return res;
+}
+
+static void
+check_search_results (GSList *ecalcomps,
+                     GHashTable *from_intervals)
+{
+       GSList *link;
+
+       g_assert_cmpint (g_slist_length (ecalcomps), ==, g_hash_table_size (from_intervals));
+
+       for (link = ecalcomps; link; link = g_slist_next (link)) {
+               ECalComponent *comp = link->data;
+               ECalComponentId *id = NULL;
+
+               id = e_cal_component_get_id (comp);
+               g_assert_nonnull (id);
+
+               g_assert (g_hash_table_contains (from_intervals, id));
+
+               e_cal_component_free_id (id);
+       }
+}
+
+static ECalComponent *
+create_test_component (time_t start,
+                      time_t end)
+{
+       ECalComponent *comp;
+       ECalComponentText summary;
+       struct icaltimetype current, ittstart, ittend;
+
+       comp = e_cal_component_new ();
+
+       e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
+
+       ittstart = icaltime_from_timet_with_zone (start, 0, NULL);
+       ittend = icaltime_from_timet_with_zone (end, 0, NULL);
+
+       icalcomponent_set_dtstart (e_cal_component_get_icalcomponent (comp), ittstart);
+       if (end != _TIME_MAX)
+               icalcomponent_set_dtend (e_cal_component_get_icalcomponent (comp), ittend);
+
+       summary.value = g_strdup_printf ("%s - %s", icaltime_as_ical_string (ittstart), 
icaltime_as_ical_string (ittend));
+       summary.altrep = NULL;
+
+       e_cal_component_set_summary (comp, &summary);
+
+       g_free ((gchar *) summary.value);
+
+       current = icaltime_from_timet_with_zone (time (NULL), 0, NULL);
+       e_cal_component_set_created (comp, &current);
+       e_cal_component_set_last_modified (comp, &current);
+
+       e_cal_component_rescan (comp);
+
+       return comp;
+}
+
+static void
+test_intervals (TCUFixture *fixture,
+               gconstpointer user_data)
+{
+       /*
+        * outline:
+        * 1. create new tree and empty list of intervals
+        * 2. insert some intervals into tree and list
+        * 3. do various searches, compare results of both structures
+        * 4. delete some intervals
+        * 5. do various searches, compare results of both structures
+        * 6. free memory
+        */
+       GRand *myrand;
+       IntervalData *interval;
+       ECalComponent *comp;
+       GSList *l1, *intervals = NULL;
+       GHashTable *from_intervals;
+       gint num_deleted = 0;
+       gint ii, start, end;
+       gboolean success;
+       GError *error = NULL;
+
+       myrand = g_rand_new ();
+
+       for (ii = 0; ii < NUM_INTERVALS_CLOSED; ii++) {
+               start = g_rand_int_range (myrand, 0, 1000);
+               end = g_rand_int_range (myrand, start, 2000);
+               comp = create_test_component (start, end);
+               g_assert (comp != NULL);
+
+               interval = g_new (IntervalData, 1);
+               interval->start = start;
+               interval->end = end;
+               interval->comp = comp;
+
+               intervals = g_slist_prepend (intervals, interval);
+
+               success = e_cal_cache_put_component (fixture->cal_cache, comp, NULL, E_OFFLINE_STATE_SYNCED, 
NULL, &error);
+               g_assert_no_error (error);
+               g_assert (success);
+       }
+
+       end = _TIME_MAX;
+
+       /* insert open ended intervals */
+       for (ii = 0; ii < NUM_INTERVALS_OPEN; ii++) {
+               start = g_rand_int_range (myrand, 0, 1000);
+               comp = create_test_component (start, end);
+               g_assert (comp != NULL);
+
+               interval = g_new (IntervalData, 1);
+               interval->start = start;
+               interval->end = end;
+               interval->comp = comp;
+
+               intervals = g_slist_prepend (intervals, interval);
+
+               success = e_cal_cache_put_component (fixture->cal_cache, comp, NULL, E_OFFLINE_STATE_SYNCED, 
NULL, &error);
+               g_assert_no_error (error);
+               g_assert (success);
+       }
+
+       for (ii = 0; ii < NUM_SEARCHES; ii++) {
+               start = g_rand_int_range (myrand, 0, 1000);
+               end = g_rand_int_range (myrand, 2000, _TIME_MAX);
+
+               l1 = NULL;
+
+               success = e_cal_cache_get_components_in_range (fixture->cal_cache, start, end, &l1, NULL, 
&error);
+               g_assert_no_error (error);
+               g_assert (success);
+
+               from_intervals = search_in_intervals (intervals, start, end);
+
+               check_search_results (l1, from_intervals);
+
+               g_slist_free_full (l1, g_object_unref);
+               g_hash_table_destroy (from_intervals);
+       }
+
+       /* open-ended intervals */
+       for (ii = 0; ii < 20; ii++) {
+               start = g_rand_int_range (myrand, 0, 1000);
+               end = _TIME_MAX;
+
+               l1 = NULL;
+
+               success = e_cal_cache_get_components_in_range (fixture->cal_cache, start, end, &l1, NULL, 
&error);
+               g_assert_no_error (error);
+               g_assert (success);
+
+               from_intervals = search_in_intervals (intervals, start, end);
+
+               check_search_results (l1, from_intervals);
+
+               g_slist_free_full (l1, g_object_unref);
+               g_hash_table_destroy (from_intervals);
+       }
+
+       l1 = intervals;
+
+       while (l1) {
+               /* perhaps we will delete l1 */
+               GSList *next = l1->next;
+
+               if (g_rand_double (myrand) < DELETE_PROBABILITY) {
+                       ECalComponent *comp;
+                       ECalComponentId *id;
+
+                       interval = l1->data;
+                       comp = interval->comp;
+
+                       id = e_cal_component_get_id (comp);
+                       g_assert (id != NULL);
+
+                       success = e_cal_cache_remove_component (fixture->cal_cache, id->uid, id->rid, 
E_OFFLINE_STATE_SYNCED, NULL, &error);
+                       g_assert_no_error (error);
+                       g_assert (success);
+
+                       e_cal_component_free_id (id);
+
+                       interval_data_free (interval);
+                       intervals = g_slist_delete_link (intervals, l1);
+
+                       num_deleted++;
+               }
+
+               l1 = next;
+       }
+
+       for (ii = 0; ii < NUM_SEARCHES; ii++) {
+               start = g_rand_int_range (myrand, 0, 1000);
+               end = g_rand_int_range (myrand, start, 2000);
+
+               l1 = NULL;
+
+               success = e_cal_cache_get_components_in_range (fixture->cal_cache, start, end, &l1, NULL, 
&error);
+               g_assert_no_error (error);
+               g_assert (success);
+
+               from_intervals = search_in_intervals (intervals, start, end);
+
+               check_search_results (l1, from_intervals);
+
+               g_slist_free_full (l1, g_object_unref);
+               g_hash_table_destroy (from_intervals);
+       }
+
+       g_slist_free_full (intervals, interval_data_free);
+       g_rand_free (myrand);
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+#if !GLIB_CHECK_VERSION (2, 35, 1)
+       g_type_init ();
+#endif
+       g_test_init (&argc, &argv, NULL);
+
+       /* Ensure that the client and server get the same locale */
+       g_assert (g_setenv ("LC_ALL", "en_US.UTF-8", TRUE));
+       setlocale (LC_ALL, "");
+
+       g_test_add ("/ECalCache/Intervals", TCUFixture, NULL,
+               tcu_fixture_setup, test_intervals, tcu_fixture_teardown);
+
+       return g_test_run ();
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]