summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Otte <otte@redhat.com>2020-07-05 19:25:15 +0200
committerBenjamin Otte <otte@redhat.com>2020-07-06 02:52:13 +0200
commit0628b4a43e3bdee3fc6056590912e813d60b4a7d (patch)
tree8c4504373221587806fdcab9e57b860b5081ca54
parentb51bfae6d849642119717cd4d4314f8904d295d8 (diff)
downloadgtk+-wip/otte/whatever.tar.gz
testsuite: Add more filterlistmodel testswip/otte/whatever
These ones try to be exhaustive and randomly catch weird cases. As such, the tests are more complicated and harder to grasp. Sorry.
-rw-r--r--testsuite/gtk/filterlistmodel-exhaustive.c479
-rw-r--r--testsuite/gtk/meson.build1
2 files changed, 480 insertions, 0 deletions
diff --git a/testsuite/gtk/filterlistmodel-exhaustive.c b/testsuite/gtk/filterlistmodel-exhaustive.c
new file mode 100644
index 0000000000..83b74b7c70
--- /dev/null
+++ b/testsuite/gtk/filterlistmodel-exhaustive.c
@@ -0,0 +1,479 @@
+/* GtkRBTree tests.
+ *
+ * Copyright (C) 2011, Red Hat, Inc.
+ * Authors: Benjamin Otte <otte@gnome.org>
+ *
+ * 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_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 = g_list_model_get_item (model, i);
+
+ if (i > 0)
+ g_string_append (string, ", ");
+ g_string_append (string, gtk_string_object_get_string (item));
+ g_object_unref (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);
+
+ /* 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 GtkFilterListModel *
+filter_list_model_new (GListModel *source,
+ GtkFilter *filter)
+{
+ GtkFilterListModel *model;
+ GListStore *check;
+ guint i;
+
+ model = gtk_filter_list_model_new (source, filter);
+ 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 GtkFilterListModel *
+create_filter_list_model (gconstpointer model_id,
+ GListModel *source,
+ GtkFilter *filter)
+{
+ GtkFilterListModel *model;
+ guint id = GPOINTER_TO_UINT (model_id);
+
+ model = filter_list_model_new (id & 1 ? NULL : source, id & 2 ? NULL : filter);
+
+ switch (id >> 2)
+ {
+ case 0:
+ break;
+
+ case 1:
+ gtk_filter_list_model_set_incremental (model, TRUE);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ if (id & 1)
+ gtk_filter_list_model_set_model (model, source);
+ if (id & 2)
+ gtk_filter_list_model_set_filter (model, filter);
+
+ return model;
+}
+
+static GListModel *
+create_source_model (guint min_size, guint max_size)
+{
+ GtkStringList *list;
+ guint i, size;
+
+ size = g_random_int_range (min_size, max_size + 1);
+ list = gtk_string_list_new (NULL);
+
+ for (i = 0; i < size; i++)
+ gtk_string_list_append (list, g_random_boolean () ? "A" : "B");
+
+ return G_LIST_MODEL (list);
+}
+
+#define N_FILTERS 5
+
+static GtkFilter *
+create_filter (gsize id)
+{
+ GtkFilter *filter;
+ GtkExpression *expr;
+
+ switch (id)
+ {
+ case 0:
+ /* GTK_FILTER_MATCH_ALL */
+ return gtk_string_filter_new ();
+
+ case 1:
+ /* GTK_FILTER_MATCH_NONE */
+ filter = gtk_string_filter_new ();
+ gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "does not matter, because no expression");
+ return filter;
+
+ case 2:
+ case 3:
+ case 4:
+ /* match all As, Bs and nothing */
+ filter = gtk_string_filter_new ();
+ expr = gtk_property_expression_new (GTK_TYPE_STRING_OBJECT, NULL, "string");
+ gtk_string_filter_set_expression (GTK_STRING_FILTER (filter), expr);
+ gtk_expression_unref (expr);
+ if (id == 2)
+ gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "A");
+ else if (id == 3)
+ gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "B");
+ else
+ gtk_string_filter_set_search (GTK_STRING_FILTER (filter), "does-not-match");
+ return filter;
+
+ default:
+ g_assert_not_reached ();
+ return NULL;
+ }
+}
+
+static GtkFilter *
+create_random_filter (gboolean allow_null)
+{
+ guint n;
+
+ if (allow_null)
+ n = g_random_int_range (0, N_FILTERS + 1);
+ else
+ n = g_random_int_range (0, N_FILTERS);
+
+ if (n >= N_FILTERS)
+ return NULL;
+
+ return create_filter (n);
+}
+
+static void
+test_no_filter (gconstpointer model_id)
+{
+ GtkFilterListModel *model;
+ GListModel *source;
+ GtkFilter *filter;
+
+ source = create_source_model (10, 10);
+ model = create_filter_list_model (model_id, source, NULL);
+ ensure_updated ();
+ assert_model_equal (G_LIST_MODEL (model), source);
+
+ filter = create_random_filter (FALSE);
+ gtk_filter_list_model_set_filter (model, filter);
+ g_object_unref (filter);
+ gtk_filter_list_model_set_filter (model, NULL);
+ ensure_updated ();
+ assert_model_equal (G_LIST_MODEL (model), source);
+
+ g_object_unref (model);
+ g_object_unref (source);
+}
+
+/* Compare this:
+ * source => filter1 => filter2
+ * with:
+ * source => multifilter(filter1, filter2)
+ * and randomly change the source and filters and see if the
+ * two continue agreeing.
+ */
+static void
+test_two_filters (gconstpointer model_id)
+{
+ GtkFilterListModel *compare;
+ GtkFilterListModel *model1, *model2;
+ GListModel *source;
+ GtkFilter *every, *filter;
+ guint i, j, k;
+
+ source = create_source_model (10, 10);
+ model1 = create_filter_list_model (model_id, source, NULL);
+ model2 = create_filter_list_model (model_id, G_LIST_MODEL (model1), NULL);
+ every = gtk_every_filter_new ();
+ compare = create_filter_list_model (model_id, source, every);
+ g_object_unref (every);
+ g_object_unref (source);
+
+ for (i = 0; i < N_FILTERS; i++)
+ {
+ filter = create_filter (i);
+ gtk_filter_list_model_set_filter (model1, filter);
+ gtk_multi_filter_append (GTK_MULTI_FILTER (every), filter);
+
+ for (j = 0; j < N_FILTERS; j++)
+ {
+ filter = create_filter (i);
+ gtk_filter_list_model_set_filter (model2, filter);
+ gtk_multi_filter_append (GTK_MULTI_FILTER (every), filter);
+
+ ensure_updated ();
+ assert_model_equal (G_LIST_MODEL (model2), G_LIST_MODEL (compare));
+
+ for (k = 0; k < 10; k++)
+ {
+ source = create_source_model (0, 20);
+ gtk_filter_list_model_set_model (compare, source);
+ gtk_filter_list_model_set_model (model1, source);
+ g_object_unref (source);
+
+ ensure_updated ();
+ assert_model_equal (G_LIST_MODEL (model2), G_LIST_MODEL (compare));
+ }
+
+ gtk_multi_filter_remove (GTK_MULTI_FILTER (every), 1);
+ }
+
+ gtk_multi_filter_remove (GTK_MULTI_FILTER (every), 0);
+ }
+
+ g_object_unref (compare);
+ g_object_unref (model2);
+ g_object_unref (model1);
+}
+
+/* Compare this:
+ * (source => filter) * => flatten
+ * with:
+ * source * => flatten => filter
+ * and randomly add/remove sources and change the filters and
+ * see if the two agree.
+ *
+ * We use a multifilter for the top chain so that changing the filter
+ * is easy.
+ */
+static void
+test_model_changes (gconstpointer model_id)
+{
+ GListStore *store1, *store2;
+ GtkFlattenListModel *flatten1, *flatten2;
+ GtkFilterListModel *model2;
+ GtkFilter *multi, *filter;
+ gsize i;
+
+ filter = create_random_filter (TRUE);
+ multi = gtk_every_filter_new ();
+ if (filter)
+ gtk_multi_filter_append (GTK_MULTI_FILTER (multi), filter);
+
+ store1 = g_list_store_new (G_TYPE_OBJECT);
+ store2 = g_list_store_new (G_TYPE_OBJECT);
+ flatten1 = gtk_flatten_list_model_new (G_LIST_MODEL (store1));
+ flatten2 = gtk_flatten_list_model_new (G_LIST_MODEL (store2));
+ model2 = create_filter_list_model (model_id, G_LIST_MODEL (flatten2), filter);
+
+ for (i = 0; i < 500; i++)
+ {
+ gboolean add = FALSE, remove = FALSE;
+ guint position;
+
+ switch (g_random_int_range (0, 4))
+ {
+ case 0:
+ /* change the filter */
+ filter = create_random_filter (TRUE);
+ gtk_multi_filter_remove (GTK_MULTI_FILTER (multi), 0); /* no-op if no filter */
+ if (filter)
+ gtk_multi_filter_append (GTK_MULTI_FILTER (multi), filter);
+ gtk_filter_list_model_set_filter (model2, filter);
+ 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_random_int_range (0, g_list_model_get_n_items (G_LIST_MODEL (store1)) + 1);
+ if (g_list_model_get_n_items (G_LIST_MODEL (store1)) == position)
+ remove = FALSE;
+
+ if (add)
+ {
+ /* We want at least one element, otherwise the filters will see no changes */
+ GListModel *source = create_source_model (1, 20);
+ GtkFilterListModel *model1 = create_filter_list_model (model_id, source, multi);
+ g_list_store_splice (store1,
+ position,
+ remove ? 1 : 0,
+ (gpointer *) &model1, 1);
+ g_list_store_splice (store2,
+ position,
+ remove ? 1 : 0,
+ (gpointer *) &source, 1);
+ g_object_unref (source);
+ }
+ else if (remove)
+ {
+ g_list_store_remove (store1, position);
+ g_list_store_remove (store2, position);
+ }
+
+ if (g_random_boolean ())
+ {
+ ensure_updated ();
+ assert_model_equal (G_LIST_MODEL (flatten1), G_LIST_MODEL (model2));
+ }
+ }
+
+ g_object_unref (model2);
+ g_object_unref (flatten2);
+ g_object_unref (flatten1);
+ g_object_unref (store2);
+ g_object_unref (store1);
+ g_object_unref (multi);
+}
+
+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 ("/filterlistmodel/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 ("no-filter", test_no_filter);
+ add_test_for_all_models ("two-filters", test_two_filters);
+ add_test_for_all_models ("model-changes", test_model_changes);
+
+ return g_test_run ();
+}
diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build
index 6bdab866de..ca1f8c28ea 100644
--- a/testsuite/gtk/meson.build
+++ b/testsuite/gtk/meson.build
@@ -30,6 +30,7 @@ tests = [
['expression'],
['filter'],
['filterlistmodel'],
+ ['filterlistmodel-exhaustive'],
['flattenlistmodel'],
['floating'],
['flowbox'],