summaryrefslogtreecommitdiff
path: root/testsuite
diff options
context:
space:
mode:
authorBenjamin Otte <otte@redhat.com>2023-03-19 23:24:42 +0100
committerBenjamin Otte <otte@redhat.com>2023-03-21 14:10:17 +0100
commitfb2a17a97223e6402ab6697e3a4f3166b1926775 (patch)
tree6304e7e3809c325eea906634a8376f96213b30ae /testsuite
parent1c64438b122870e085ffc19d914fe7a274871c5d (diff)
downloadgtk+-fb2a17a97223e6402ab6697e3a4f3166b1926775.tar.gz
testsuite: Add a treelistmodel test
This one exhaustively tests reusing the same model as a child model for many nodes. This tracks that multiple items-changed signals emitted at the same time (or multiple handlers for one such signal) doesn't put the treelistmodel in an inconsistent state while it is handling all of them. I'm not sure this (ab)use of treelistmodel should be officially supported, but it works today, so let's test it to see if we can keep it working.
Diffstat (limited to 'testsuite')
-rw-r--r--testsuite/gtk/treelistmodel.c191
1 files changed, 188 insertions, 3 deletions
diff --git a/testsuite/gtk/treelistmodel.c b/testsuite/gtk/treelistmodel.c
index 941ac3d306..c3ce3dea05 100644
--- a/testsuite/gtk/treelistmodel.c
+++ b/testsuite/gtk/treelistmodel.c
@@ -76,6 +76,88 @@ prepend (GListStore *store,
g_object_unref (object);
}
+static void
+assert_items_changed_correctly (GListModel *model,
+ guint position,
+ guint removed,
+ guint added,
+ GListModel *compare)
+{
+ guint i, n_items;
+
+ //sanity check that we got all notifies
+ g_assert_cmpuint (g_list_model_get_n_items (compare), ==, GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (compare), "last-notified-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);
+ }
+
+ /*doesn't hold with passthrough, because we reuse the same children */
+#if 0
+ /* 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);
+ }
+#endif
+
+ /* 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 void
+assert_n_items_notified_properly (GListModel *model,
+ GParamSpec *pspec,
+ GListModel *compare)
+{
+ g_assert_cmpuint (g_list_model_get_n_items (model), !=, GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (compare), "last-notified-n-items")));
+
+ /* These should hve been updated in items-changed, which should have been emitted first */
+ g_assert_cmpuint (g_list_model_get_n_items (model), ==, g_list_model_get_n_items (compare));
+
+ g_object_set_data (G_OBJECT (compare),
+ "last-notified-n-items",
+ GUINT_TO_POINTER (g_list_model_get_n_items (model)));
+}
+
#define assert_model(model, expected) G_STMT_START{ \
char *s = model_to_string (G_LIST_MODEL (model)); \
if (!g_str_equal (s, expected)) \
@@ -92,6 +174,38 @@ prepend (GListStore *store,
g_string_set_size (changes, 0); \
}G_STMT_END
+static void
+check_model_changes (GListModel *model)
+{
+ GListStore *check;
+ gsize i;
+
+ check = g_list_store_new (g_list_model_get_item_type (model));
+
+ for (i = 0; i < g_list_model_get_n_items (model); i++)
+ {
+ gpointer item = g_list_model_get_item (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);
+
+ g_object_set_data (G_OBJECT (check),
+ "last-notified-n-items",
+ GUINT_TO_POINTER (g_list_model_get_n_items (G_LIST_MODEL (check))));
+ g_signal_connect_data (model,
+ "notify::n-items",
+ G_CALLBACK (assert_n_items_notified_properly),
+ g_object_ref (check),
+ (GClosureNotify) g_object_unref,
+ 0);
+}
+
static GListStore *
new_empty_store (void)
{
@@ -337,8 +451,8 @@ create_model (void)
}
static GListModel *
-model_children (gpointer item,
- gpointer unused)
+demo_node_children (gpointer item,
+ gpointer unused)
{
GListStore *children;
@@ -363,7 +477,7 @@ test_collapse_change (void)
treemodel = gtk_tree_list_model_new (G_LIST_MODEL (model),
FALSE,
FALSE,
- model_children,
+ demo_node_children,
NULL,
NULL);
@@ -380,6 +494,76 @@ test_collapse_change (void)
g_object_unref (a);
}
+static void
+test_same_child_model (void)
+{
+#define N_MODELS 4
+ static const guint N_ITEMS_PER_MODEL = 3;
+ static const guint N_RUNS = 100;
+
+ GListStore *models[N_MODELS];
+ GtkTreeListModel *treelist;
+ DemoNode *d;
+ guint i, j;
+
+ i = N_MODELS;
+ while (i --> 0)
+ {
+ models[i] = g_list_store_new (demo_node_get_type ());
+ for (j = 0; j < N_ITEMS_PER_MODEL; j++)
+ {
+ d = demo_node_new ("A" + j, i + 1 < N_MODELS ? models[i + 1] : NULL);
+ g_list_store_append (models[i], d);
+ }
+ }
+
+ treelist = gtk_tree_list_model_new (g_object_ref (G_LIST_MODEL (models[0])),
+ g_test_rand_bit (), TRUE,
+ demo_node_children,
+ NULL, NULL);
+ check_model_changes (G_LIST_MODEL (treelist));
+
+ for (i = 0; i < N_RUNS; i++)
+ {
+ guint model_id = g_test_rand_int_range (0, N_MODELS);
+ GListStore *model = models[model_id];
+ guint n_items = g_list_model_get_n_items (G_LIST_MODEL (model));
+
+ switch (g_test_rand_int_range (0, 3))
+ {
+ case 0:
+ if (n_items < 10)
+ {
+ d = demo_node_new ("A" + (i % 26), model_id + 1 < N_MODELS ? models[model_id + 1] : NULL);
+ g_list_store_insert (model,
+ g_test_rand_int_range (0, n_items + 1),
+ d);
+ g_object_unref (d);
+ }
+ break;
+ case 1:
+ if (n_items > 0)
+ g_list_store_remove (model, g_test_rand_int_range (0, n_items));
+ break;
+ case 2:
+ d = demo_node_new ("A" + (i % 26), model_id + 1 < N_MODELS ? models[model_id + 1] : NULL);
+ g_list_store_splice (model,
+ n_items ? g_test_rand_int_range (0, n_items) : 0,
+ n_items ? 1 : 0,
+ (gpointer[1]) { d }, 1);
+ g_object_unref (d);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+
+ g_object_unref (treelist);
+ for (i = 0; i < G_N_ELEMENTS (models); i++)
+ g_object_unref (models[i]);
+}
+
int
main (int argc, char *argv[])
{
@@ -392,6 +576,7 @@ main (int argc, char *argv[])
g_test_add_func ("/treelistmodel/expand", test_expand);
g_test_add_func ("/treelistmodel/remove_some", test_remove_some);
g_test_add_func ("/treelistmodel/collapse-change", test_collapse_change);
+ g_test_add_func ("/treelistmodel/same-child-model", test_same_child_model);
return g_test_run ();
}