1 /*
2  * This library is free software: you can redistribute it and/or modify it
3  * under the terms of the GNU Lesser General Public License as published by
4  * the Free Software Foundation.
5  *
6  * This library is distributed in the hope that it will be useful, but
7  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
8  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
9  * for more details.
10  *
11  * You should have received a copy of the GNU Lesser General Public License
12  * along with this library. If not, see <http://www.gnu.org/licenses/>.
13  */
14 
15 /* folder/index testing */
16 
17 #include <string.h>
18 
19 #include "camel-test.h"
20 #include "camel-test-provider.h"
21 #include "messages.h"
22 #include "folders.h"
23 #include "session.h"
24 
25 static void
test_folder_search_sub(CamelFolder * folder,const gchar * expr,gint expected)26 test_folder_search_sub (CamelFolder *folder,
27                         const gchar *expr,
28                         gint expected)
29 {
30 	GPtrArray *uids;
31 	GHashTable *hash;
32 	gint i;
33 	GError *error = NULL;
34 
35 	uids = camel_folder_search_by_expression (folder, expr, NULL, &error);
36 	check (uids != NULL);
37 	check_msg (uids->len == expected, "search %s expected %d got %d", expr, expected, uids->len);
38 	check_msg (error == NULL, "%s", error->message);
39 	g_clear_error (&error);
40 
41 	/* check the uid's are actually unique, too */
42 	hash = g_hash_table_new (g_str_hash, g_str_equal);
43 	for (i = 0; i < uids->len; i++) {
44 		check (g_hash_table_lookup (hash, uids->pdata[i]) == NULL);
45 		g_hash_table_insert (hash, uids->pdata[i], uids->pdata[i]);
46 	}
47 	g_hash_table_destroy (hash);
48 
49 	camel_folder_search_free (folder, uids);
50 }
51 
52 static void
test_folder_search(CamelFolder * folder,const gchar * expr,gint expected)53 test_folder_search (CamelFolder *folder,
54                     const gchar *expr,
55                     gint expected)
56 {
57 	gchar *matchall;
58 
59 #if 0
60 	/* FIXME: ??? */
61 	camel_test_nonfatal ("most searches require match-all construct");
62 	push ("Testing search: %s", expr);
63 	test_folder_search_sub (folder, expr, expected);
64 	pull ();
65 	camel_test_fatal ();
66 #endif
67 
68 	matchall = g_strdup_printf ("(match-all %s)", expr);
69 	push ("Testing search: %s", matchall);
70 	test_folder_search_sub (folder, matchall, expected);
71 	test_free (matchall);
72 	pull ();
73 }
74 
75 static struct {
76 	gint counts[3];
77 	const gchar *expr;
78 } searches[] = {
79 	{ { 1, 1, 0 }, "(header-matches \"subject\" \"Test1 message99 subject\")" },
80 
81 	{ { 100, 50, 0 }, "(header-contains \"subject\" \"subject\")" },
82 	{ { 100, 50, 0 }, "(header-contains \"subject\" \"Subject\")" },
83 
84 	{ { 100, 50, 0 }, "(body-contains \"content\")" },
85 	{ { 100, 50, 0 }, "(body-contains \"Content\")" },
86 
87 	{ { 0, 0, 0 }, "(user-flag \"every7\")" },
88 	{ { 100 / 13 + 1, 50 / 13 + 1, 0 }, "(user-flag \"every13\")" },
89 	{ { 1, 1, 0 }, "(= \"7tag1\" (user-tag \"every7\"))" },
90 	{ { 100 / 11 + 1, 50 / 11 + 1, 0 }, "(= \"11tag\" (user-tag \"every11\"))" },
91 
92 	{ { 100 / 13 + 100 / 17 + 1, 50 / 13 + 50 / 17 + 2, 0 }, "(user-flag \"every13\" \"every17\")" },
93 	{ { 100 / 13 + 100 / 17 + 1, 50 / 13 + 50 / 17 + 2, 0 }, "(or (user-flag \"every13\") (user-flag \"every17\"))" },
94 	{ { 1, 0, 0 }, "(and (user-flag \"every13\") (user-flag \"every17\"))" },
95 
96 	{ { 0, 0, 0 }, "(and (header-contains \"subject\" \"Test1\") (header-contains \"subject\" \"Test2\"))" },
97 	/* we get 11 here as the header-contains is a substring match */
98 	{ { 11, 6, 0 }, "(and (header-contains \"subject\" \"Test1\") (header-contains \"subject\" \"subject\"))" },
99 	{ { 1, 1, 0 }, "(and (header-contains \"subject\" \"Test19\") (header-contains \"subject\" \"subject\"))" },
100 	{ { 0, 0, 0 }, "(and (header-contains \"subject\" \"Test191\") (header-contains \"subject\" \"subject\"))" },
101 	{ { 1, 1, 0 }, "(and (header-contains \"subject\" \"Test1\") (header-contains \"subject\" \"message99\"))" },
102 
103 	{ { 22, 11, 0 }, "(or (header-contains \"subject\" \"Test1\") (header-contains \"subject\" \"Test2\"))" },
104 	{ { 2, 1, 0 }, "(or (header-contains \"subject\" \"Test16\") (header-contains \"subject\" \"Test99\"))" },
105 	{ { 1, 1, 0 }, "(or (header-contains \"subject\" \"Test123\") (header-contains \"subject\" \"Test99\"))" },
106 	{ { 100, 50, 0 }, "(or (header-contains \"subject\" \"Test1\") (header-contains \"subject\" \"subject\"))" },
107 	{ { 11, 6, 0 }, "(or (header-contains \"subject\" \"Test1\") (header-contains \"subject\" \"message99\"))" },
108 
109 	/* 72000 is 24*60*100 == half the 'sent date' of the messages */
110 	{ { 100 / 2, 50 / 2, 0 }, "(> 72000 (get-sent-date))" },
111 	{ { 100 / 2 - 1, 50 / 2, 0 }, "(< 72000 (get-sent-date))" },
112 	{ { 1, 0, 0 }, "(= 72000 (get-sent-date))" },
113 	{ { 0, 0, 0 }, "(= 72001 (get-sent-date))" },
114 
115 	{ { (100 / 2 - 1) / 17 + 1, (50 / 2 - 1) / 17 + 1, 0 }, "(and (user-flag \"every17\") (< 72000 (get-sent-date)))" },
116 	{ { (100 / 2 - 1) / 17 + 1, (50 / 2 - 1) / 17, 0 }, "(and (user-flag \"every17\") (> 72000 (get-sent-date)))" },
117 	{ { (100 / 2 - 1) / 13 + 1, (50 / 2 - 1) / 13 + 1, 0 }, "(and (user-flag \"every13\") (< 72000 (get-sent-date)))" },
118 	{ { (100 / 2 - 1) / 13 + 1, (50 / 2 - 1) / 13 + 1, 0 }, "(and (user-flag \"every13\") (> 72000 (get-sent-date)))" },
119 
120 	{ { 100 / 2 + 100 / 2 / 17, 50 / 2 + 50 / 2 / 17, 0 }, "(or (user-flag \"every17\") (< 72000 (get-sent-date)))" },
121 	{ { 100 / 2 + 100 / 2 / 17 + 1, 50 / 2 + 50 / 2 / 17 + 1, 0 }, "(or (user-flag \"every17\") (> 72000 (get-sent-date)))" },
122 	{ { 100 / 2 + 100 / 2 / 13, 50 / 2 + 50 / 2 / 13 + 1, 0 }, "(or (user-flag \"every13\") (< 72000 (get-sent-date)))" },
123 	{ { 100 / 2 + 100 / 2 / 13 + 1, 50 / 2 + 50 / 2 / 13 + 1, 0 }, "(or (user-flag \"every13\") (> 72000 (get-sent-date)))" },
124 };
125 
126 static void
run_search(CamelFolder * folder,gint m)127 run_search (CamelFolder *folder,
128             gint m)
129 {
130 	gint i, j = 0;
131 
132 	check (m == 50 || m == 100 || m == 0);
133 
134 	/* *shrug* messy, but it'll do */
135 	if (m == 50)
136 		j = 1;
137 	else if (m == 0)
138 		j = 2;
139 
140 	push ("performing searches, expected %d", m);
141 	for (i = 0; i < G_N_ELEMENTS (searches); i++) {
142 		push ("running search %d: %s", i, searches[i].expr);
143 		test_folder_search (folder, searches[i].expr, searches[i].counts[j]);
144 		pull ();
145 	}
146 	pull ();
147 }
148 
149 static const gchar *local_drivers[] = { "local" };
150 
151 static const gchar *stores[] = {
152 	"mbox:///tmp/camel-test/mbox",
153 	"mh:///tmp/camel-test/mh",
154 	"maildir:///tmp/camel-test/maildir"
155 };
156 
157 gint
main(gint argc,gchar ** argv)158 main (gint argc,
159       gchar **argv)
160 {
161 	CamelService *service;
162 	CamelSession *session;
163 	CamelStore *store;
164 	CamelFolder *folder;
165 	CamelMimeMessage *msg;
166 	gint i, j;
167 	gint indexed;
168 	GPtrArray *uids;
169 	GError *error = NULL;
170 
171 	camel_test_init (argc, argv);
172 	camel_test_provider_init (1, local_drivers);
173 
174 	/* clear out any camel-test data */
175 	system ("/bin/rm -rf /tmp/camel-test");
176 
177 	session = camel_test_session_new ("/tmp/camel-test");
178 
179 	/* todo: cross-check everything with folder_info checks as well */
180 	/* todo: work out how to do imap/pop/nntp tests */
181 
182 	/* we iterate over all stores we want to test, with indexing or indexing turned on or off */
183 	for (i = 0; i < G_N_ELEMENTS (stores); i++) {
184 		const gchar *name = stores[i];
185 		for (indexed = 0; indexed < 2; indexed++) {
186 			gchar *what = g_strdup_printf ("folder search: %s (%sindexed)", name, indexed?"":"non-");
187 			gchar *uid;
188 			gint flags;
189 
190 			camel_test_start (what);
191 			test_free (what);
192 
193 			push ("getting store");
194 			uid = g_strdup_printf ("test-uid-%d", i);
195 			service = camel_session_add_service (
196 				session, uid, stores[i],
197 				CAMEL_PROVIDER_STORE, &error);
198 			g_free (uid);
199 			check_msg (error == NULL, "adding store: %s", error->message);
200 			check (CAMEL_IS_STORE (service));
201 			store = CAMEL_STORE (service);
202 			g_clear_error (&error);
203 			pull ();
204 
205 			push ("creating %sindexed folder", indexed?"":"non-");
206 			if (indexed)
207 				flags = CAMEL_STORE_FOLDER_CREATE | CAMEL_STORE_FOLDER_BODY_INDEX;
208 			else
209 				flags = CAMEL_STORE_FOLDER_CREATE;
210 			folder = camel_store_get_folder_sync (
211 				store, "testbox", flags, NULL, &error);
212 			check_msg (error == NULL, "%s", error->message);
213 			check (folder != NULL);
214 
215 			/* we need an empty folder for this to work */
216 			test_folder_counts (folder, 0, 0);
217 			g_clear_error (&error);
218 			pull ();
219 
220 			/* append a bunch of messages with specific content */
221 			push ("appending 100 test messages");
222 			for (j = 0; j < 100; j++) {
223 				gchar *content, *subject;
224 
225 				push ("creating test message");
226 				msg = test_message_create_simple ();
227 				content = g_strdup_printf ("data%d content\n", j);
228 				test_message_set_content_simple (
229 					(CamelMimePart *) msg, 0, "text/plain",
230 								content, strlen (content));
231 				test_free (content);
232 				subject = g_strdup_printf ("Test%d message%d subject", j, 100 - j);
233 				camel_mime_message_set_subject (msg, subject);
234 
235 				camel_mime_message_set_date (msg, j * 60 * 24, 0);
236 				pull ();
237 
238 				push ("appending simple message %d", j);
239 				camel_folder_append_message_sync (
240 					folder, msg, NULL, NULL, NULL, &error);
241 				check_msg (error == NULL, "%s", error->message);
242 				g_clear_error (&error);
243 				pull ();
244 
245 				test_free (subject);
246 
247 				check_unref (msg, 1);
248 			}
249 			pull ();
250 
251 			push ("Setting up some flags &c");
252 			uids = camel_folder_get_uids (folder);
253 			check (uids->len == 100);
254 			for (j = 0; j < 100; j++) {
255 				gchar *uid = uids->pdata[j];
256 
257 				if ((j / 13) * 13 == j) {
258 					camel_folder_set_message_user_flag (folder, uid, "every13", TRUE);
259 				}
260 				if ((j / 17) * 17 == j) {
261 					camel_folder_set_message_user_flag (folder, uid, "every17", TRUE);
262 				}
263 				if ((j / 7) * 7 == j) {
264 					gchar *tag = g_strdup_printf ("7tag%d", j / 7);
265 					camel_folder_set_message_user_tag (folder, uid, "every7", tag);
266 					test_free (tag);
267 				}
268 				if ((j / 11) * 11 == j) {
269 					camel_folder_set_message_user_tag (folder, uid, "every11", "11tag");
270 				}
271 			}
272 			camel_folder_free_uids (folder, uids);
273 			pull ();
274 
275 			camel_test_nonfatal ("Index not guaranteed to be accurate before sync: should be fixed eventually");
276 			push ("Search before sync");
277 			run_search (folder, 100);
278 			pull ();
279 			camel_test_fatal ();
280 
281 			push ("syncing folder, searching");
282 			camel_folder_synchronize_sync (
283 				folder, FALSE, NULL, NULL);
284 			run_search (folder, 100);
285 			pull ();
286 
287 			push ("syncing wiht expunge, search");
288 			camel_folder_synchronize_sync (
289 				folder, TRUE, NULL, NULL);
290 			run_search (folder, 100);
291 			pull ();
292 
293 			push ("deleting every 2nd message");
294 			uids = camel_folder_get_uids (folder);
295 			check (uids->len == 100);
296 			for (j = 0; j < uids->len; j+=2) {
297 				camel_folder_delete_message (folder, uids->pdata[j]);
298 			}
299 			camel_folder_free_uids (folder, uids);
300 			run_search (folder, 100);
301 
302 			push ("syncing");
303 			camel_folder_synchronize_sync (
304 				folder, FALSE, NULL, &error);
305 			check_msg (error == NULL, "%s", error->message);
306 			run_search (folder, 100);
307 			g_clear_error (&error);
308 			pull ();
309 
310 			push ("expunging");
311 			camel_folder_expunge_sync (folder, NULL, &error);
312 			check_msg (error == NULL, "%s", error->message);
313 			run_search (folder, 50);
314 			g_clear_error (&error);
315 			pull ();
316 
317 			pull ();
318 
319 			push ("closing and re-opening folder");
320 			check_unref (folder, 1);
321 			folder = camel_store_get_folder_sync (
322 				store, "testbox",
323 				flags & ~(CAMEL_STORE_FOLDER_CREATE),
324 				NULL, &error);
325 			check_msg (error == NULL, "%s", error->message);
326 			check (folder != NULL);
327 			g_clear_error (&error);
328 
329 			push ("deleting remaining messages");
330 			uids = camel_folder_get_uids (folder);
331 			check (uids->len == 50);
332 			for (j = 0; j < uids->len; j++) {
333 				camel_folder_delete_message (folder, uids->pdata[j]);
334 			}
335 			camel_folder_free_uids (folder, uids);
336 			run_search (folder, 50);
337 
338 			push ("syncing");
339 			camel_folder_synchronize_sync (
340 				folder, FALSE, NULL, &error);
341 			check_msg (error == NULL, "%s", error->message);
342 			run_search (folder, 50);
343 			g_clear_error (&error);
344 			pull ();
345 
346 			push ("expunging");
347 			camel_folder_expunge_sync (folder, NULL, &error);
348 			check_msg (error == NULL, "%s", error->message);
349 			run_search (folder, 0);
350 			g_clear_error (&error);
351 			pull ();
352 
353 			pull ();
354 
355 			check_unref (folder, 1);
356 			pull ();
357 
358 			push ("deleting test folder, with no messages in it");
359 			camel_store_delete_folder_sync (
360 				store, "testbox", NULL, &error);
361 			check_msg (error == NULL, "%s", error->message);
362 			g_clear_error (&error);
363 			pull ();
364 
365 			check_unref (store, 1);
366 			camel_test_end ();
367 		}
368 	}
369 
370 	check_unref (session, 1);
371 
372 	return 0;
373 }
374