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 | |
24 | static GQuark number_quark; |
25 | static GQuark changes_quark; |
26 | |
27 | static guint |
28 | get (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 | |
39 | static char * |
40 | model_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 | |
55 | static void |
56 | splice (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 | |
79 | static void |
80 | add (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 | |
94 | static void |
95 | insert (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 | |
131 | static GListStore * |
132 | new_empty_store (void) |
133 | { |
134 | return g_list_store_new (G_TYPE_OBJECT); |
135 | } |
136 | |
137 | static GListStore * |
138 | new_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 | |
149 | static void |
150 | items_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 | |
179 | static void |
180 | free_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 | |
190 | static int |
191 | compare_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 | |
201 | static int |
202 | compare (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 | |
210 | static GtkSortListModel * |
211 | new_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 | |
235 | static void |
236 | test_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 | |
247 | static void |
248 | test_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 | |
265 | static void |
266 | test_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 | |
303 | static void |
304 | test_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 | |
335 | static void |
336 | test_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 | |
375 | static void |
376 | test_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 | |
415 | static void |
416 | test_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 | |
437 | static GListStore * |
438 | new_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 | */ |
454 | static void |
455 | test_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 | |
520 | static void |
521 | test_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 | |
537 | int |
538 | main (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 | |