[gtk/wip/otte/sortlistmodel: 149/154] testsuite: Add exhaustive sortlistmodel test
- From: Benjamin Otte <otte src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gtk/wip/otte/sortlistmodel: 149/154] testsuite: Add exhaustive sortlistmodel test
- Date: Sun, 12 Jul 2020 20:25:17 +0000 (UTC)
commit 53495c4a524a849c98c4655ae8a7204602eb1bbe
Author: Benjamin Otte <otte redhat com>
Date: Sun Jul 12 06:53:06 2020 +0200
testsuite: Add exhaustive sortlistmodel test
This is basically a copy/paste from the filterlistmodel test, but
adapted for sorting.
testsuite/gtk/meson.build | 1 +
testsuite/gtk/sortlistmodel-exhaustive.c | 433 +++++++++++++++++++++++++++++++
2 files changed, 434 insertions(+)
---
diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build
index a44d901c13..c01419e2b9 100644
--- a/testsuite/gtk/meson.build
+++ b/testsuite/gtk/meson.build
@@ -92,6 +92,7 @@ tests = [
{ 'name': 'slicelistmodel' },
{ 'name': 'sorter' },
{ 'name': 'sortlistmodel' },
+ { 'name': 'sortlistmodel-exhaustive' },
{ 'name': 'sort-performance' },
{ 'name': 'spinbutton' },
{ 'name': 'stringlist' },
diff --git a/testsuite/gtk/sortlistmodel-exhaustive.c b/testsuite/gtk/sortlistmodel-exhaustive.c
new file mode 100644
index 0000000000..bad08c2ae8
--- /dev/null
+++ b/testsuite/gtk/sortlistmodel-exhaustive.c
@@ -0,0 +1,433 @@
+/*
+ * Copyright © 2020 Benjamin Otte
+ *
+ * This library 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; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <locale.h>
+
+#include <gtk/gtk.h>
+
+#define ensure_updated() G_STMT_START{ \
+ while (g_main_context_pending (NULL)) \
+ g_main_context_iteration (NULL, TRUE); \
+}G_STMT_END
+
+#define assert_model_equal(model1, model2) G_STMT_START{ \
+ guint _i, _n; \
+ g_assert_cmpint (g_list_model_get_n_items (model1), ==, g_list_model_get_n_items (model2)); \
+ _n = g_list_model_get_n_items (model1); \
+ for (_i = 0; _i < _n; _i++) \
+ { \
+ gpointer o1 = g_list_model_get_item (model1, _i); \
+ gpointer o2 = g_list_model_get_item (model2, _i); \
+\
+ if (o1 != o2) \
+ { \
+ char *_s = g_strdup_printf ("Objects differ at index %u out of %u", _i, _n); \
+ g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, _s); \
+ g_free (_s); \
+ } \
+\
+ g_object_unref (o1); \
+ g_object_unref (o2); \
+ } \
+}G_STMT_END
+
+G_GNUC_UNUSED static char *
+model_to_string (GListModel *model)
+{
+ GString *string;
+ guint i, n;
+
+ n = g_list_model_get_n_items (model);
+ string = g_string_new (NULL);
+
+ /* Check that all unchanged items are indeed unchanged */
+ for (i = 0; i < n; i++)
+ {
+ gpointer item, model_item = g_list_model_get_item (model, i);
+ if (GTK_IS_TREE_LIST_ROW (model_item))
+ item = gtk_tree_list_row_get_item (model_item);
+ else
+ item = model_item;
+
+ if (i > 0)
+ g_string_append (string, ", ");
+ if (G_IS_LIST_MODEL (item))
+ g_string_append (string, "*");
+ else
+ g_string_append (string, gtk_string_object_get_string (item));
+ g_object_unref (model_item);
+ }
+
+ return g_string_free (string, FALSE);
+}
+
+static void
+assert_items_changed_correctly (GListModel *model,
+ guint position,
+ guint removed,
+ guint added,
+ GListModel *compare)
+{
+ guint i, n_items;
+
+ //g_print ("%s => %u -%u +%u => %s\n", model_to_string (compare), position, removed, added,
model_to_string (model));
+
+ g_assert_cmpint (g_list_model_get_n_items (model), ==, g_list_model_get_n_items (compare) - removed +
added);
+ n_items = g_list_model_get_n_items (model);
+
+ if (position != 0 || removed != n_items)
+ {
+ /* Check that all unchanged items are indeed unchanged */
+ for (i = 0; i < position; i++)
+ {
+ gpointer o1 = g_list_model_get_item (model, i);
+ gpointer o2 = g_list_model_get_item (compare, i);
+ g_assert_cmphex (GPOINTER_TO_SIZE (o1), ==, GPOINTER_TO_SIZE (o2));
+ g_object_unref (o1);
+ g_object_unref (o2);
+ }
+ for (i = position + added; i < n_items; i++)
+ {
+ gpointer o1 = g_list_model_get_item (model, i);
+ gpointer o2 = g_list_model_get_item (compare, i - added + removed);
+ g_assert_cmphex (GPOINTER_TO_SIZE (o1), ==, GPOINTER_TO_SIZE (o2));
+ g_object_unref (o1);
+ g_object_unref (o2);
+ }
+
+ /* Check that the first and last added item are different from
+ * first and last removed item.
+ * Otherwise we could have kept them as-is
+ */
+ if (removed > 0 && added > 0)
+ {
+ gpointer o1 = g_list_model_get_item (model, position);
+ gpointer o2 = g_list_model_get_item (compare, position);
+ g_assert_cmphex (GPOINTER_TO_SIZE (o1), !=, GPOINTER_TO_SIZE (o2));
+ g_object_unref (o1);
+ g_object_unref (o2);
+
+ o1 = g_list_model_get_item (model, position + added - 1);
+ o2 = g_list_model_get_item (compare, position + removed - 1);
+ g_assert_cmphex (GPOINTER_TO_SIZE (o1), !=, GPOINTER_TO_SIZE (o2));
+ g_object_unref (o1);
+ g_object_unref (o2);
+ }
+ }
+
+ /* Finally, perform the same change as the signal indicates */
+ g_list_store_splice (G_LIST_STORE (compare), position, removed, NULL, 0);
+ for (i = position; i < position + added; i++)
+ {
+ gpointer item = g_list_model_get_item (G_LIST_MODEL (model), i);
+ g_list_store_insert (G_LIST_STORE (compare), i, item);
+ g_object_unref (item);
+ }
+}
+
+static GtkTim2SortModel *
+tim2_sort_model_new (GListModel *source,
+ GtkSorter *sorter)
+{
+ GtkTim2SortModel *model;
+ GListStore *check;
+ guint i;
+
+ model = gtk_tim2_sort_model_new (source, sorter);
+ check = g_list_store_new (G_TYPE_OBJECT);
+ for (i = 0; i < g_list_model_get_n_items (G_LIST_MODEL (model)); i++)
+ {
+ gpointer item = g_list_model_get_item (G_LIST_MODEL (model), i);
+ g_list_store_append (check, item);
+ g_object_unref (item);
+ }
+ g_signal_connect_data (model,
+ "items-changed",
+ G_CALLBACK (assert_items_changed_correctly),
+ check,
+ (GClosureNotify) g_object_unref,
+ 0);
+
+ return model;
+}
+
+#define N_MODELS 8
+
+static GtkTim2SortModel *
+create_tim2_sort_model (gconstpointer model_id,
+ GListModel *source,
+ GtkSorter *sorter)
+{
+ GtkTim2SortModel *model;
+ guint id = GPOINTER_TO_UINT (model_id);
+
+ model = tim2_sort_model_new (id & 1 ? NULL : source, id & 2 ? NULL : sorter);
+
+ switch (id >> 2)
+ {
+ case 0:
+ break;
+
+ case 1:
+ gtk_tim2_sort_model_set_incremental (model, TRUE);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ if (id & 1)
+ gtk_tim2_sort_model_set_model (model, source);
+ if (id & 2)
+ gtk_tim2_sort_model_set_sorter (model, sorter);
+
+ return model;
+}
+
+static GListModel *
+create_source_model (guint min_size, guint max_size)
+{
+ const char *strings[] = { "A", "a", "B", "b" };
+ GtkStringList *list;
+ guint i, size;
+
+ size = g_test_rand_int_range (min_size, max_size + 1);
+ list = gtk_string_list_new (NULL);
+
+ for (i = 0; i < size; i++)
+ gtk_string_list_append (list, strings[g_test_rand_int_range (0, G_N_ELEMENTS (strings))]);
+
+ return G_LIST_MODEL (list);
+}
+
+#define N_SORTERS 3
+
+static GtkSorter *
+create_sorter (gsize id)
+{
+ GtkSorter *sorter;
+
+ switch (id)
+ {
+ case 0:
+ return gtk_string_sorter_new (NULL);
+
+ case 1:
+ case 2:
+ /* match all As, Bs and nothing */
+ sorter = gtk_string_sorter_new (gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string"));
+ if (id == 1)
+ gtk_string_sorter_set_ignore_case (GTK_STRING_SORTER (sorter), TRUE);
+ return sorter;
+
+ default:
+ g_assert_not_reached ();
+ return NULL;
+ }
+}
+
+static GtkSorter *
+create_random_sorter (gboolean allow_null)
+{
+ guint n;
+
+ if (allow_null)
+ n = g_test_rand_int_range (0, N_SORTERS + 1);
+ else
+ n = g_test_rand_int_range (0, N_SORTERS);
+
+ if (n >= N_SORTERS)
+ return NULL;
+
+ return create_sorter (n);
+}
+
+/* Compare this:
+ * source => sorter1 => sorter2
+ * with:
+ * source => multisorter(sorter1, sorter2)
+ * and randomly change the source and sorters and see if the
+ * two continue agreeing.
+ */
+static void
+test_two_sorters (gconstpointer model_id)
+{
+ GtkTim2SortModel *compare;
+ GtkTim2SortModel *model1, *model2;
+ GListModel *source;
+ GtkSorter *every, *sorter;
+ guint i, j, k;
+
+ source = create_source_model (10, 10);
+ model2 = create_tim2_sort_model (model_id, source, NULL);
+ model1 = create_tim2_sort_model (model_id, G_LIST_MODEL (model2), NULL);
+ every = gtk_multi_sorter_new ();
+ compare = create_tim2_sort_model (model_id, source, every);
+ g_object_unref (every);
+ g_object_unref (source);
+
+ for (i = 0; i < N_SORTERS; i++)
+ {
+ sorter = create_sorter (i);
+ gtk_tim2_sort_model_set_sorter (model1, sorter);
+ gtk_multi_sorter_append (GTK_MULTI_SORTER (every), sorter);
+
+ for (j = 0; j < N_SORTERS; j++)
+ {
+ sorter = create_sorter (i);
+ gtk_tim2_sort_model_set_sorter (model2, sorter);
+ gtk_multi_sorter_append (GTK_MULTI_SORTER (every), sorter);
+
+ ensure_updated ();
+ assert_model_equal (G_LIST_MODEL (model2), G_LIST_MODEL (compare));
+
+ for (k = 0; k < 10; k++)
+ {
+ source = create_source_model (0, 1000);
+ gtk_tim2_sort_model_set_model (compare, source);
+ gtk_tim2_sort_model_set_model (model2, source);
+ g_object_unref (source);
+
+ ensure_updated ();
+ assert_model_equal (G_LIST_MODEL (model1), G_LIST_MODEL (compare));
+ }
+
+ gtk_multi_sorter_remove (GTK_MULTI_SORTER (every), 1);
+ }
+
+ gtk_multi_sorter_remove (GTK_MULTI_SORTER (every), 0);
+ }
+
+ g_object_unref (compare);
+ g_object_unref (model2);
+ g_object_unref (model1);
+}
+
+/* Run:
+ * source => sorter1 => sorter2
+ * and randomly add/remove sources and change the sorters and
+ * see if the two sorters stay identical
+ */
+static void
+test_chained_sort (gconstpointer model_id)
+{
+ GListStore *store;
+ GtkFlattenListModel *flatten;
+ GtkTim2SortModel *sort1, *sort2;
+ GtkSorter *sorter;
+ gsize i;
+
+ sorter = create_random_sorter (TRUE);
+
+ store = g_list_store_new (G_TYPE_OBJECT);
+ flatten = gtk_flatten_list_model_new (G_LIST_MODEL (store));
+ sort1 = create_tim2_sort_model (model_id, G_LIST_MODEL (flatten), sorter);
+ sort2 = create_tim2_sort_model (model_id, G_LIST_MODEL (sort1), sorter);
+
+ for (i = 0; i < 500; i++)
+ {
+ gboolean add = FALSE, remove = FALSE;
+ guint position;
+
+ switch (g_test_rand_int_range (0, 4))
+ {
+ case 0:
+ /* change the sorter */
+ sorter = create_random_sorter (TRUE);
+ gtk_tim2_sort_model_set_sorter (sort1, sorter);
+ gtk_tim2_sort_model_set_sorter (sort2, sorter);
+ break;
+
+ case 1:
+ /* remove a model */
+ remove = TRUE;
+ break;
+
+ case 2:
+ /* add a model */
+ add = TRUE;
+ break;
+
+ case 3:
+ /* replace a model */
+ remove = TRUE;
+ add = TRUE;
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ position = g_test_rand_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store)) + 1);
+ if (g_list_model_get_n_items (G_LIST_MODEL (store)) == position)
+ remove = FALSE;
+
+ if (add)
+ {
+ /* We want at least one element, otherwise the sorters will see no changes */
+ GListModel *source = create_source_model (1, 50);
+ g_list_store_splice (store,
+ position,
+ remove ? 1 : 0,
+ (gpointer *) &source, 1);
+ g_object_unref (source);
+ }
+ else if (remove)
+ {
+ g_list_store_remove (store, position);
+ }
+
+ if (g_test_rand_bit ())
+ {
+ ensure_updated ();
+ assert_model_equal (G_LIST_MODEL (sort1), G_LIST_MODEL (sort2));
+ }
+ }
+
+ g_object_unref (sort2);
+ g_object_unref (sort1);
+ g_object_unref (flatten);
+ g_object_unref (store);
+}
+
+static void
+add_test_for_all_models (const char *name,
+ GTestDataFunc test_func)
+{
+ guint i;
+
+ for (i = 0; i < N_MODELS; i++)
+ {
+ char *path = g_strdup_printf ("/sorterlistmodel/model%u/%s", i, name);
+ g_test_add_data_func (path, GUINT_TO_POINTER (i), test_func);
+ g_free (path);
+ }
+}
+
+int
+main (int argc, char *argv[])
+{
+ g_test_init (&argc, &argv, NULL);
+ setlocale (LC_ALL, "C");
+
+ add_test_for_all_models ("two-sorters", test_two_sorters);
+ add_test_for_all_models ("chained-sort", test_chained_sort);
+
+ return g_test_run ();
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]