1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3  * This program is free software: you can redistribute it and/or modify it
4  * under the terms of the GNU Lesser General Public License as published by
5  * the Free Software Foundation.
6  *
7  * This program is distributed in the hope that it will be useful, but
8  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
10  * for more details.
11  *
12  * You should have received a copy of the GNU Lesser General Public License
13  * along with this program. If not, see <http://www.gnu.org/licenses/>.
14  *
15  */
16 
17 #include <stdlib.h>
18 #include <locale.h>
19 #include <libecal/libecal.h>
20 
21 #include "e-test-server-utils.h"
22 #include "test-cal-cache-utils.h"
23 
24 #define NUM_INTERVALS_CLOSED	100
25 #define NUM_INTERVALS_OPEN	100
26 #define NUM_SEARCHES		500
27 #define DELETE_PROBABILITY	0.3
28 #define _TIME_MIN		((time_t) 0)		/* Min valid time_t	*/
29 #define _TIME_MAX		((time_t) INT_MAX)	/* Max valid time_t	*/
30 
31 typedef struct _IntervalData {
32 	gint start;
33 	gint end;
34 	ECalComponent * comp;
35 } IntervalData;
36 
37 static void
interval_data_free(gpointer ptr)38 interval_data_free (gpointer ptr)
39 {
40 	IntervalData *id = ptr;
41 
42 	if (id) {
43 		g_object_unref (id->comp);
44 		g_free (id);
45 	}
46 }
47 
48 static gint
compare_intervals(time_t x_start,time_t x_end,time_t y_start,time_t y_end)49 compare_intervals (time_t x_start,
50 		   time_t x_end,
51 		   time_t y_start,
52 		   time_t y_end)
53 {
54 	/* assumption: x_start <= x_end */
55 	/* assumption: y_start <= y_end */
56 
57 	/* x is left of y */
58 	if (x_end < y_start)
59 		return -1;
60 
61 	/* x is right of y */
62 	if (y_end < x_start)
63 		return 1;
64 
65 	/* x and y overlap */
66 	return 0;
67 }
68 
69 static GHashTable *
search_in_intervals(ETimezoneCache * zone_cache,GSList * intervals,time_t start,time_t end)70 search_in_intervals (ETimezoneCache *zone_cache,
71 		     GSList *intervals,
72 		     time_t start,
73 		     time_t end)
74 {
75 	ECalBackendSExp *sexp;
76 	ICalTime *itt_start, *itt_end;
77 	gchar *expr;
78 	GSList *link;
79 	GHashTable *res;
80 
81 	itt_start = i_cal_time_new_from_timet_with_zone (start, FALSE, NULL);
82 	itt_end = i_cal_time_new_from_timet_with_zone (end, FALSE, NULL);
83 
84 	expr = g_strdup_printf ("(occur-in-time-range? (make-time \"%04d%02d%02dT%02d%02d%02dZ\") (make-time \"%04d%02d%02dT%02d%02d%02dZ\"))",
85 		i_cal_time_get_year (itt_start), i_cal_time_get_month (itt_start), i_cal_time_get_day (itt_start),
86 		i_cal_time_get_hour (itt_start), i_cal_time_get_minute (itt_start), i_cal_time_get_second (itt_start),
87 		i_cal_time_get_year (itt_end), i_cal_time_get_month (itt_end), i_cal_time_get_day (itt_end),
88 		i_cal_time_get_hour (itt_end), i_cal_time_get_minute (itt_end), i_cal_time_get_second (itt_end));
89 
90 	sexp = e_cal_backend_sexp_new (expr);
91 
92 	g_clear_object (&itt_start);
93 	g_clear_object (&itt_end);
94 	g_free (expr);
95 
96 	g_assert_nonnull (sexp);
97 
98 	res = g_hash_table_new_full ((GHashFunc) e_cal_component_id_hash, (GEqualFunc) e_cal_component_id_equal,
99 		(GDestroyNotify) e_cal_component_id_free, g_object_unref);
100 
101 	for (link = intervals; link; link = g_slist_next (link)) {
102 		IntervalData *data = link->data;
103 
104 		if (compare_intervals (start, end, data->start, data->end) == 0 &&
105 		    e_cal_backend_sexp_match_comp (sexp, data->comp, zone_cache)) {
106 			ECalComponentId *id = NULL;
107 
108 			id = e_cal_component_get_id (data->comp);
109 			g_assert_nonnull (id);
110 
111 			g_hash_table_insert (res, id, g_object_ref (data->comp));
112 		}
113 	}
114 
115 	g_object_unref (sexp);
116 
117 	return res;
118 }
119 
120 static void
check_search_results(GSList * ecalcomps,GHashTable * from_intervals)121 check_search_results (GSList *ecalcomps,
122 		      GHashTable *from_intervals)
123 {
124 	GSList *link;
125 
126 	g_assert_cmpint (g_slist_length (ecalcomps), ==, g_hash_table_size (from_intervals));
127 
128 	for (link = ecalcomps; link; link = g_slist_next (link)) {
129 		ECalComponent *comp = link->data;
130 		ECalComponentId *id = NULL;
131 
132 		id = e_cal_component_get_id (comp);
133 		g_assert_nonnull (id);
134 
135 		g_assert (g_hash_table_contains (from_intervals, id));
136 
137 		e_cal_component_id_free (id);
138 	}
139 }
140 
141 static ECalComponent *
create_test_component(time_t start,time_t end)142 create_test_component (time_t start,
143 		       time_t end)
144 {
145 	ECalComponent *comp;
146 	ECalComponentText *summary;
147 	ICalTime *current, *ittstart, *ittend;
148 	gchar *startstr, *endstr, *tmp;
149 
150 	comp = e_cal_component_new ();
151 
152 	e_cal_component_set_new_vtype (comp, E_CAL_COMPONENT_EVENT);
153 
154 	ittstart = i_cal_time_new_from_timet_with_zone (start, 0, NULL);
155 	ittend = i_cal_time_new_from_timet_with_zone (end, 0, NULL);
156 
157 	i_cal_component_set_dtstart (e_cal_component_get_icalcomponent (comp), ittstart);
158 	i_cal_component_set_dtend (e_cal_component_get_icalcomponent (comp), ittend);
159 
160 	startstr = i_cal_time_as_ical_string (ittstart);
161 	endstr = i_cal_time_as_ical_string (ittend);
162 
163 	tmp = g_strdup_printf ("%s - %s", startstr, endstr);
164 	summary = e_cal_component_text_new (tmp, NULL);
165 	g_free (tmp);
166 
167 	g_object_unref (ittstart);
168 	g_object_unref (ittend);
169 	g_free (startstr);
170 	g_free (endstr);
171 
172 	e_cal_component_set_summary (comp, summary);
173 
174 	e_cal_component_text_free (summary);
175 
176 	current = i_cal_time_new_from_timet_with_zone (time (NULL), 0, NULL);
177 	e_cal_component_set_created (comp, current);
178 	e_cal_component_set_last_modified (comp, current);
179 	g_object_unref (current);
180 
181 	return comp;
182 }
183 
184 static void
test_intervals(TCUFixture * fixture,gconstpointer user_data)185 test_intervals (TCUFixture *fixture,
186 		gconstpointer user_data)
187 {
188 	/*
189 	 * outline:
190 	 * 1. create new tree and empty list of intervals
191 	 * 2. insert some intervals into tree and list
192 	 * 3. do various searches, compare results of both structures
193 	 * 4. delete some intervals
194 	 * 5. do various searches, compare results of both structures
195 	 * 6. free memory
196 	 */
197 	GRand *myrand;
198 	IntervalData *interval;
199 	ECalComponent *comp;
200 	ETimezoneCache *zone_cache;
201 	GSList *l1, *intervals = NULL;
202 	GHashTable *from_intervals;
203 	gint num_deleted = 0;
204 	gint ii, start, end;
205 	gboolean success;
206 	GError *error = NULL;
207 
208 	zone_cache = E_TIMEZONE_CACHE (fixture->cal_cache);
209 
210 	myrand = g_rand_new ();
211 
212 	for (ii = 0; ii < NUM_INTERVALS_CLOSED; ii++) {
213 		start = g_rand_int_range (myrand, 0, 1000);
214 		end = g_rand_int_range (myrand, start, 2000);
215 		comp = create_test_component (start, end);
216 		g_assert (comp != NULL);
217 
218 		interval = g_new (IntervalData, 1);
219 		interval->start = start;
220 		interval->end = end;
221 		interval->comp = comp;
222 
223 		intervals = g_slist_prepend (intervals, interval);
224 
225 		success = e_cal_cache_put_component (fixture->cal_cache, comp, NULL, 0, E_CACHE_IS_ONLINE, NULL, &error);
226 		g_assert_no_error (error);
227 		g_assert (success);
228 	}
229 
230 	end = _TIME_MAX;
231 
232 	/* insert open ended intervals */
233 	for (ii = 0; ii < NUM_INTERVALS_OPEN; ii++) {
234 		start = g_rand_int_range (myrand, 0, 1000);
235 		comp = create_test_component (start, end);
236 		g_assert (comp != NULL);
237 
238 		interval = g_new (IntervalData, 1);
239 		interval->start = start;
240 		interval->end = end;
241 		interval->comp = comp;
242 
243 		intervals = g_slist_prepend (intervals, interval);
244 
245 		success = e_cal_cache_put_component (fixture->cal_cache, comp, NULL, 0, E_CACHE_IS_ONLINE, NULL, &error);
246 		g_assert_no_error (error);
247 		g_assert (success);
248 	}
249 
250 	for (ii = 0; ii < NUM_SEARCHES; ii++) {
251 		start = g_rand_int_range (myrand, 0, 1000);
252 		end = g_rand_int_range (myrand, 2000, _TIME_MAX);
253 
254 		l1 = NULL;
255 
256 		success = e_cal_cache_get_components_in_range (fixture->cal_cache, start, end, &l1, NULL, &error);
257 		g_assert_no_error (error);
258 		g_assert (success);
259 
260 		from_intervals = search_in_intervals (zone_cache, intervals, start, end);
261 
262 		check_search_results (l1, from_intervals);
263 
264 		g_slist_free_full (l1, g_object_unref);
265 		g_hash_table_destroy (from_intervals);
266 	}
267 
268 	/* open-ended intervals */
269 	for (ii = 0; ii < 20; ii++) {
270 		start = g_rand_int_range (myrand, 0, 1000);
271 		end = _TIME_MAX;
272 
273 		l1 = NULL;
274 
275 		success = e_cal_cache_get_components_in_range (fixture->cal_cache, start, end, &l1, NULL, &error);
276 		g_assert_no_error (error);
277 		g_assert (success);
278 
279 		from_intervals = search_in_intervals (zone_cache, intervals, start, end);
280 
281 		check_search_results (l1, from_intervals);
282 
283 		g_slist_free_full (l1, g_object_unref);
284 		g_hash_table_destroy (from_intervals);
285 	}
286 
287 	l1 = intervals;
288 
289 	while (l1) {
290 		/* perhaps we will delete l1 */
291 		GSList *next = l1->next;
292 
293 		if (g_rand_double (myrand) < DELETE_PROBABILITY) {
294 			ECalComponent *comp;
295 			ECalComponentId *id;
296 
297 			interval = l1->data;
298 			comp = interval->comp;
299 
300 			id = e_cal_component_get_id (comp);
301 			g_assert (id != NULL);
302 
303 			success = e_cal_cache_remove_component (fixture->cal_cache, e_cal_component_id_get_uid (id),
304 				e_cal_component_id_get_rid (id), 0, E_CACHE_IS_ONLINE, NULL, &error);
305 			g_assert_no_error (error);
306 			g_assert (success);
307 
308 			e_cal_component_id_free (id);
309 
310 			interval_data_free (interval);
311 			intervals = g_slist_delete_link (intervals, l1);
312 
313 			num_deleted++;
314 		}
315 
316 		l1 = next;
317 	}
318 
319 	for (ii = 0; ii < NUM_SEARCHES; ii++) {
320 		start = g_rand_int_range (myrand, 0, 1000);
321 		end = g_rand_int_range (myrand, start + 1, 2000);
322 
323 		l1 = NULL;
324 
325 		success = e_cal_cache_get_components_in_range (fixture->cal_cache, start, end, &l1, NULL, &error);
326 		g_assert_no_error (error);
327 		g_assert (success);
328 
329 		from_intervals = search_in_intervals (zone_cache, intervals, start, end);
330 
331 		check_search_results (l1, from_intervals);
332 
333 		g_slist_free_full (l1, g_object_unref);
334 		g_hash_table_destroy (from_intervals);
335 	}
336 
337 	g_slist_free_full (intervals, interval_data_free);
338 	g_rand_free (myrand);
339 }
340 
341 gint
main(gint argc,gchar ** argv)342 main (gint argc,
343       gchar **argv)
344 {
345 #if !GLIB_CHECK_VERSION (2, 35, 1)
346 	g_type_init ();
347 #endif
348 	g_test_init (&argc, &argv, NULL);
349 	g_test_bug_base ("https://gitlab.gnome.org/GNOME/evolution-data-server/");
350 
351 	tcu_read_args (argc, argv);
352 
353 	/* Ensure that the client and server get the same locale */
354 	g_assert (g_setenv ("LC_ALL", "en_US.UTF-8", TRUE));
355 	setlocale (LC_ALL, "");
356 
357 	g_test_add ("/ECalCache/Intervals", TCUFixture, NULL,
358 		tcu_fixture_setup, test_intervals, tcu_fixture_teardown);
359 
360 	return e_test_server_utils_run_full (argc, argv, 0);
361 }
362