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.
This commit is contained in:
Benjamin Otte 2020-07-05 19:25:15 +02:00
parent 3162e25671
commit bf3382a89e
2 changed files with 480 additions and 0 deletions

View File

@ -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 ();
}

View File

@ -30,6 +30,7 @@ tests = [
['expression'],
['filter'],
['filterlistmodel'],
['filterlistmodel-exhaustive'],
['flattenlistmodel'],
['floating'],
['flowbox'],