[gtk/wip/otte/filterlistmodel: 13/13] testsuite: Add more filterlistmodel tests



commit bf3382a89ed7e0e446f256d83514135162dd321d
Author: Benjamin Otte <otte redhat com>
Date:   Sun Jul 5 19:25:15 2020 +0200

    testsuite: Add more filterlistmodel tests
    
    These ones try to be exhaustive and randomly catch weird cases.
    
    As such, the tests are more complicated and harder to grasp.
    Sorry.

 testsuite/gtk/filterlistmodel-exhaustive.c | 479 +++++++++++++++++++++++++++++
 testsuite/gtk/meson.build                  |   1 +
 2 files changed, 480 insertions(+)
---
diff --git a/testsuite/gtk/filterlistmodel-exhaustive.c b/testsuite/gtk/filterlistmodel-exhaustive.c
new file mode 100644
index 0000000000..117430b68f
--- /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_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, g_test_rand_bit () ? "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_test_rand_int_range (0, N_FILTERS + 1);
+  else
+    n = g_test_rand_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_test_rand_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_test_rand_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_test_rand_bit ())
+        {
+          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 cee96704db..a93540dd80 100644
--- a/testsuite/gtk/meson.build
+++ b/testsuite/gtk/meson.build
@@ -30,6 +30,7 @@ tests = [
   ['expression'],
   ['filter'],
   ['filterlistmodel'],
+  ['filterlistmodel-exhaustive'],
   ['flattenlistmodel'],
   ['floating'],
   ['flowbox'],


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]