1/* GtkRBTree tests.
2 *
3 * Copyright (C) 2011, Red Hat, Inc.
4 * Authors: Benjamin Otte <otte@gnome.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <locale.h>
21
22#include <gtk/gtk.h>
23
24static GQuark number_quark;
25static GQuark changes_quark;
26
27static guint
28get (GListModel *model,
29 guint position)
30{
31 GObject *object = g_list_model_get_item (list: model, position);
32 guint number;
33 g_assert_nonnull (object);
34 number = GPOINTER_TO_UINT (g_object_get_qdata (object, number_quark));
35 g_object_unref (object);
36 return number;
37}
38
39static char *
40model_to_string (GListModel *model)
41{
42 GString *string = g_string_new (NULL);
43 guint i;
44
45 for (i = 0; i < g_list_model_get_n_items (list: model); i++)
46 {
47 if (i > 0)
48 g_string_append (string, val: " ");
49 g_string_append_printf (string, format: "%u", get (model, position: i));
50 }
51
52 return g_string_free (string, FALSE);
53}
54
55static void
56splice (GListStore *store,
57 guint pos,
58 guint removed,
59 guint *numbers,
60 guint added)
61{
62 GObject **objects = g_newa (GObject *, added);
63 guint i;
64
65 for (i = 0; i < added; i++)
66 {
67 /* 0 cannot be differentiated from NULL, so don't use it */
68 g_assert_cmpint (numbers[i], !=, 0);
69 objects[i] = g_object_new (G_TYPE_OBJECT, NULL);
70 g_object_set_qdata (object: objects[i], quark: number_quark, GUINT_TO_POINTER (numbers[i]));
71 }
72
73 g_list_store_splice (store, position: pos, n_removals: removed, additions: (gpointer *) objects, n_additions: added);
74
75 for (i = 0; i < added; i++)
76 g_object_unref (object: objects[i]);
77}
78
79static void
80add (GListStore *store,
81 guint number)
82{
83 GObject *object;
84
85 /* 0 cannot be differentiated from NULL, so don't use it */
86 g_assert_cmpint (number, !=, 0);
87
88 object = g_object_new (G_TYPE_OBJECT, NULL);
89 g_object_set_qdata (object, quark: number_quark, GUINT_TO_POINTER (number));
90 g_list_store_append (store, item: object);
91 g_object_unref (object);
92}
93
94static void
95insert (GListStore *store,
96 guint position,
97 guint number)
98{
99 GObject *object;
100
101 /* 0 cannot be differentiated from NULL, so don't use it */
102 g_assert_cmpint (number, !=, 0);
103
104 object = g_object_new (G_TYPE_OBJECT, NULL);
105 g_object_set_qdata (object, quark: number_quark, GUINT_TO_POINTER (number));
106 g_list_store_insert (store, position, item: object);
107 g_object_unref (object);
108}
109
110#define assert_model(model, expected) G_STMT_START{ \
111 char *s = model_to_string (G_LIST_MODEL (model)); \
112 if (!g_str_equal (s, expected)) \
113 g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
114 #model " == " #expected, s, "==", expected); \
115 g_free (s); \
116}G_STMT_END
117
118#define assert_changes(model, expected) G_STMT_START{ \
119 GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \
120 if (!g_str_equal (changes->str, expected)) \
121 g_assertion_message_cmpstr (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \
122 #model " == " #expected, changes->str, "==", expected); \
123 g_string_set_size (changes, 0); \
124}G_STMT_END
125
126#define ignore_changes(model) G_STMT_START{ \
127 GString *changes = g_object_get_qdata (G_OBJECT (model), changes_quark); \
128 g_string_set_size (changes, 0); \
129}G_STMT_END
130
131static GListStore *
132new_empty_store (void)
133{
134 return g_list_store_new (G_TYPE_OBJECT);
135}
136
137static GListStore *
138new_store (guint *numbers)
139{
140 GListStore *store = new_empty_store ();
141 guint i;
142
143 for (i = 0; numbers[i] != 0; i++)
144 add (store, number: numbers[i]);
145
146 return store;
147}
148
149static void
150items_changed (GListModel *model,
151 guint position,
152 guint removed,
153 guint added,
154 GString *changes)
155{
156 g_assert_true (removed != 0 || added != 0);
157
158 if (changes->len)
159 g_string_append (string: changes, val: ", ");
160
161 if (removed == 1 && added == 0)
162 {
163 g_string_append_printf (string: changes, format: "-%u", position);
164 }
165 else if (removed == 0 && added == 1)
166 {
167 g_string_append_printf (string: changes, format: "+%u", position);
168 }
169 else
170 {
171 g_string_append_printf (string: changes, format: "%u", position);
172 if (removed > 0)
173 g_string_append_printf (string: changes, format: "-%u", removed);
174 if (added > 0)
175 g_string_append_printf (string: changes, format: "+%u", added);
176 }
177}
178
179static void
180free_changes (gpointer data)
181{
182 GString *changes = data;
183
184 /* all changes must have been checked via assert_changes() before */
185 g_assert_cmpstr (changes->str, ==, "");
186
187 g_string_free (string: changes, TRUE);
188}
189
190static int
191compare_modulo (gconstpointer first,
192 gconstpointer second,
193 gpointer modulo)
194{
195 guint mod = GPOINTER_TO_UINT (modulo);
196
197 return (GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (first), number_quark)) % mod)
198 - (GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (second), number_quark)) % mod);
199}
200
201static int
202compare (gconstpointer first,
203 gconstpointer second,
204 gpointer unused)
205{
206 return GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (first), number_quark))
207 - GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (second), number_quark));
208}
209
210static GtkSortListModel *
211new_model (gpointer model)
212{
213 GtkSortListModel *result;
214 GString *changes;
215
216 g_assert_true (model == NULL || G_IS_LIST_MODEL (model));
217
218 if (model)
219 {
220 GtkSorter *sorter;
221
222 sorter = GTK_SORTER (ptr: gtk_custom_sorter_new (sort_func: compare, NULL, NULL));
223 result = gtk_sort_list_model_new (g_object_ref (model), sorter);
224 }
225 else
226 result = gtk_sort_list_model_new (NULL, NULL);
227
228 changes = g_string_new (init: "");
229 g_object_set_qdata_full (G_OBJECT(result), quark: changes_quark, data: changes, destroy: free_changes);
230 g_signal_connect (result, "items-changed", G_CALLBACK (items_changed), changes);
231
232 return result;
233}
234
235static void
236test_create_empty (void)
237{
238 GtkSortListModel *sort;
239
240 sort = new_model (NULL);
241 assert_model (sort, "");
242 assert_changes (sort, "");
243
244 g_object_unref (object: sort);
245}
246
247static void
248test_create (void)
249{
250 GtkSortListModel *sort;
251 GListStore *store;
252
253 store = new_store (numbers: (guint[]) { 4, 8, 2, 6, 10, 0 });
254 sort = new_model (model: store);
255 assert_model (sort, "2 4 6 8 10");
256 assert_changes (sort, "");
257
258 g_object_unref (object: store);
259 assert_model (sort, "2 4 6 8 10");
260 assert_changes (sort, "");
261
262 g_object_unref (object: sort);
263}
264
265static void
266test_set_model (void)
267{
268 GtkSortListModel *sort;
269 GListStore *store;
270
271 sort = new_model (NULL);
272 assert_model (sort, "");
273 assert_changes (sort, "");
274
275 store = new_store (numbers: (guint[]) { 4, 8, 2, 6, 10, 0 });
276 gtk_sort_list_model_set_model (self: sort, model: G_LIST_MODEL (ptr: store));
277 assert_model (sort, "4 8 2 6 10");
278 assert_changes (sort, "0+5");
279
280 gtk_sort_list_model_set_model (self: sort, NULL);
281 assert_model (sort, "");
282 assert_changes (sort, "0-5");
283
284 g_object_unref (object: sort);
285
286
287 sort = new_model (model: store);
288 assert_model (sort, "2 4 6 8 10");
289 assert_changes (sort, "");
290
291 gtk_sort_list_model_set_model (self: sort, NULL);
292 assert_model (sort, "");
293 assert_changes (sort, "0-5");
294
295 gtk_sort_list_model_set_model (self: sort, model: G_LIST_MODEL (ptr: store));
296 assert_model (sort, "2 4 6 8 10");
297 assert_changes (sort, "0+5");
298
299 g_object_unref (object: store);
300 g_object_unref (object: sort);
301}
302
303static void
304test_set_sorter (void)
305{
306 GtkSortListModel *sort;
307 GtkSorter *sorter;
308 GListStore *store;
309
310 store = new_store (numbers: (guint[]) { 4, 8, 2, 6, 10, 0 });
311 sort = new_model (model: store);
312 assert_model (sort, "2 4 6 8 10");
313 assert_changes (sort, "");
314
315 sorter = GTK_SORTER (ptr: gtk_custom_sorter_new (sort_func: compare_modulo, GUINT_TO_POINTER (5), NULL));
316 gtk_sort_list_model_set_sorter (self: sort, sorter);
317 g_object_unref (object: sorter);
318 assert_model (sort, "10 6 2 8 4");
319 assert_changes (sort, "0-5+5");
320
321 gtk_sort_list_model_set_sorter (self: sort, NULL);
322 assert_model (sort, "4 8 2 6 10");
323 assert_changes (sort, "0-5+5");
324
325 sorter = GTK_SORTER (ptr: gtk_custom_sorter_new (sort_func: compare, NULL, NULL));
326 gtk_sort_list_model_set_sorter (self: sort, sorter);
327 g_object_unref (object: sorter);
328 assert_model (sort, "2 4 6 8 10");
329 assert_changes (sort, "0-4+4");
330
331 g_object_unref (object: store);
332 g_object_unref (object: sort);
333}
334
335static void
336test_add_items (void)
337{
338 GtkSortListModel *sort;
339 GListStore *store;
340
341 /* add beginning */
342 store = new_store (numbers: (guint[]) { 51, 99, 100, 49, 50, 0 });
343 sort = new_model (model: store);
344 assert_model (sort, "49 50 51 99 100");
345 assert_changes (sort, "");
346 splice (store, pos: 4, removed: 0, numbers: (guint[]) { 1, 2 }, added: 2);
347 assert_model (sort, "1 2 49 50 51 99 100");
348 assert_changes (sort, "0+2");
349 g_object_unref (object: store);
350 g_object_unref (object: sort);
351
352 /* add middle */
353 store = new_store (numbers: (guint[]) { 99, 100, 1, 2, 0 });
354 sort = new_model (model: store);
355 assert_model (sort, "1 2 99 100");
356 assert_changes (sort, "");
357 splice (store, pos: 2, removed: 0, numbers: (guint[]) { 49, 50, 51 }, added: 3);
358 assert_model (sort, "1 2 49 50 51 99 100");
359 assert_changes (sort, "2+3");
360 g_object_unref (object: store);
361 g_object_unref (object: sort);
362
363 /* add end */
364 store = new_store (numbers: (guint[]) { 51, 49, 1, 2, 50, 0 });
365 sort = new_model (model: store);
366 assert_model (sort, "1 2 49 50 51");
367 assert_changes (sort, "");
368 splice (store, pos: 1, removed: 0, numbers: (guint[]) { 99, 100 }, added: 2);
369 assert_model (sort, "1 2 49 50 51 99 100");
370 assert_changes (sort, "5+2");
371 g_object_unref (object: store);
372 g_object_unref (object: sort);
373}
374
375static void
376test_remove_items (void)
377{
378 GtkSortListModel *sort;
379 GListStore *store;
380
381 /* remove beginning */
382 store = new_store (numbers: (guint[]) { 51, 99, 100, 49, 1, 2, 50, 0 });
383 sort = new_model (model: store);
384 assert_model (sort, "1 2 49 50 51 99 100");
385 assert_changes (sort, "");
386 splice (store, pos: 4, removed: 2, NULL, added: 0);
387 assert_model (sort, "49 50 51 99 100");
388 assert_changes (sort, "0-2");
389 g_object_unref (object: store);
390 g_object_unref (object: sort);
391
392 /* remove middle */
393 store = new_store (numbers: (guint[]) { 99, 100, 51, 49, 50, 1, 2, 0 });
394 sort = new_model (model: store);
395 assert_model (sort, "1 2 49 50 51 99 100");
396 assert_changes (sort, "");
397 splice (store, pos: 2, removed: 3, NULL, added: 0);
398 assert_model (sort, "1 2 99 100");
399 assert_changes (sort, "2-3");
400 g_object_unref (object: store);
401 g_object_unref (object: sort);
402
403 /* remove end */
404 store = new_store (numbers: (guint[]) { 51, 99, 100, 49, 1, 2, 50, 0 });
405 sort = new_model (model: store);
406 assert_model (sort, "1 2 49 50 51 99 100");
407 assert_changes (sort, "");
408 splice (store, pos: 1, removed: 2, NULL, added: 0);
409 assert_model (sort, "1 2 49 50 51");
410 assert_changes (sort, "5-2");
411 g_object_unref (object: store);
412 g_object_unref (object: sort);
413}
414
415static void
416test_stability (void)
417{
418 GtkSortListModel *sort;
419 GListStore *store;
420 GtkSorter *sorter;
421
422 store = new_store (numbers: (guint[]) { 11, 31, 21, 1, 0 });
423 sort = new_model (model: store);
424 assert_model (sort, "1 11 21 31");
425 assert_changes (sort, "");
426
427 sorter = GTK_SORTER (ptr: gtk_custom_sorter_new (sort_func: compare_modulo, GUINT_TO_POINTER (5), NULL));
428 gtk_sort_list_model_set_sorter (self: sort, sorter);
429 g_object_unref (object: sorter);
430 assert_model (sort, "11 31 21 1");
431 assert_changes (sort, "0-4+4");
432
433 g_object_unref (object: store);
434 g_object_unref (object: sort);
435}
436
437static GListStore *
438new_shuffled_store (guint size)
439{
440 GListStore *store = new_empty_store ();
441 guint i;
442
443 add (store, number: 1);
444
445 for (i = 1; i < size; i++)
446 insert (store, position: g_random_int_range (begin: 0, end: i), number: i + 1);
447
448 return store;
449}
450
451/* Test that we don't crash when things are removed from the
452 * model while it is incrementally sorting.
453 */
454static void
455test_incremental_remove (void)
456{
457 GListStore *store;
458 GtkSortListModel *model;
459 GtkSorter *sorter;
460 guint i;
461 GListStore *removed;
462 const guint n_items = 100000;
463
464 store = new_shuffled_store (size: n_items);
465 model = new_model (NULL);
466 gtk_sort_list_model_set_incremental (self: model, TRUE);
467
468 gtk_sort_list_model_set_model (self: model, model: G_LIST_MODEL (ptr: store));
469
470 sorter = GTK_SORTER (ptr: gtk_custom_sorter_new (sort_func: compare, NULL, NULL));
471 gtk_sort_list_model_set_sorter (self: model, sorter);
472 g_object_unref (object: sorter);
473
474 removed = g_list_store_new (G_TYPE_OBJECT);
475
476 while (gtk_sort_list_model_get_pending (self: model) != 0)
477 {
478 g_main_context_iteration (NULL, TRUE);
479
480 /* randomly remove items while the sort is ongoing */
481 if (g_list_model_get_n_items (list: G_LIST_MODEL (ptr: removed)) < 100)
482 {
483 guint position;
484
485 position = g_random_int_range (begin: 0, end: g_list_model_get_n_items (list: G_LIST_MODEL (ptr: store)) - 10);
486 for (i = 0; i < 10; i++)
487 {
488 GObject *item = g_list_model_get_item (list: G_LIST_MODEL (ptr: store), position: position + i);
489 g_list_store_append (store: removed, item);
490 g_object_unref (object: item);
491 }
492 g_list_store_splice (store, position, n_removals: 10, NULL, n_additions: 0);
493 }
494 }
495
496 g_assert_cmpuint (gtk_sort_list_model_get_pending (model), ==, 0);
497
498 gtk_sort_list_model_set_incremental (self: model, FALSE);
499
500 /* add them back */
501 for (i = 0; i < g_list_model_get_n_items (list: G_LIST_MODEL (ptr: removed)); i++)
502 {
503 GObject *item = g_list_model_get_item (list: G_LIST_MODEL (ptr: removed), position: i);
504 g_list_store_append (store, item);
505 g_object_unref (object: item);
506 }
507
508 g_assert_cmpuint (g_list_model_get_n_items (G_LIST_MODEL (model)), ==, n_items);
509
510 for (i = 0; i < g_list_model_get_n_items (list: G_LIST_MODEL (ptr: model)); i++)
511 g_assert_cmpuint (i + 1, ==, get (G_LIST_MODEL (model), i));
512
513 ignore_changes (model);
514
515 g_object_unref (object: store);
516 g_object_unref (object: model);
517 g_object_unref (object: removed);
518}
519
520static void
521test_out_of_bounds_access (void)
522{
523 GtkSortListModel *sort;
524 GListStore *store;
525 gpointer item;
526
527 store = new_store (numbers: (guint[]) { 4, 8, 2, 6, 10, 0 });
528 sort = new_model (model: store);
529
530 item = g_list_model_get_item (list: G_LIST_MODEL (ptr: sort), GTK_INVALID_LIST_POSITION);
531 g_assert_null (item);
532
533 g_object_unref (object: store);
534 g_object_unref (object: sort);
535}
536
537int
538main (int argc, char *argv[])
539{
540 (g_test_init) (argc: &argc, argv: &argv, NULL);
541 setlocale (LC_ALL, locale: "C");
542
543 number_quark = g_quark_from_static_string (string: "Hell and fire was spawned to be released.");
544 changes_quark = g_quark_from_static_string (string: "What did I see? Can I believe what I saw?");
545
546 g_test_add_func (testpath: "/sortlistmodel/create_empty", test_func: test_create_empty);
547 g_test_add_func (testpath: "/sortlistmodel/create", test_func: test_create);
548 g_test_add_func (testpath: "/sortlistmodel/set-model", test_func: test_set_model);
549 g_test_add_func (testpath: "/sortlistmodel/set-sorter", test_func: test_set_sorter);
550#if GLIB_CHECK_VERSION (2, 58, 0) /* g_list_store_splice() is broken before 2.58 */
551 g_test_add_func (testpath: "/sortlistmodel/add_items", test_func: test_add_items);
552 g_test_add_func (testpath: "/sortlistmodel/remove_items", test_func: test_remove_items);
553#endif
554 g_test_add_func (testpath: "/sortlistmodel/stability", test_func: test_stability);
555 g_test_add_func (testpath: "/sortlistmodel/incremental/remove", test_func: test_incremental_remove);
556 g_test_add_func (testpath: "/sortlistmodel/oob-access", test_func: test_out_of_bounds_access);
557
558 return g_test_run ();
559}
560

source code of gtk/testsuite/gtk/sortlistmodel.c