diff --git a/testsuite/gtk/accessor-apis.c b/testsuite/gtk/accessor-apis.c new file mode 100644 index 0000000000..7d10391b41 --- /dev/null +++ b/testsuite/gtk/accessor-apis.c @@ -0,0 +1,336 @@ +/* + * 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.1 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 . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include + +static struct +{ + const char *expected; + const char *alternative; +} exceptions[] = { + /* keep this sorted please */ + { "gdk_device_get_tool", "gdk_device_get_device_tool" }, + { "gdk_display_get_input_shapes", "gdk_display_supports_input_shapes" }, + { "gtk_constraint_guide_get_max_height", "gtk_constraint_guide_get_max_size" }, + { "gtk_constraint_guide_get_max_width", "gtk_constraint_guide_get_max_size" }, + { "gtk_constraint_guide_get_min_height", "gtk_constraint_guide_get_min_size" }, + { "gtk_constraint_guide_get_min_width", "gtk_constraint_guide_get_min_size" }, + { "gtk_constraint_guide_get_nat_height", "gtk_constraint_guide_get_nat_size" }, + { "gtk_constraint_guide_get_nat_width", "gtk_constraint_guide_get_nat_size" }, + { "gtk_constraint_guide_set_max_height", "gtk_constraint_guide_set_max_size" }, + { "gtk_constraint_guide_set_max_width", "gtk_constraint_guide_set_max_size" }, + { "gtk_constraint_guide_set_min_height", "gtk_constraint_guide_set_min_size" }, + { "gtk_constraint_guide_set_min_width", "gtk_constraint_guide_set_min_size" }, + { "gtk_constraint_guide_set_nat_height", "gtk_constraint_guide_set_nat_size" }, + { "gtk_constraint_guide_set_nat_width", "gtk_constraint_guide_set_nat_size" }, + { "gtk_tree_view_get_enable_grid_lines", "gtk_tree_view_get_grid_lines" }, + { "gtk_tree_view_set_enable_grid_lines", "gtk_tree_view_set_grid_lines" }, + { "gtk_widget_get_height_request", "gtk_widget_get_size_request" }, + { "gtk_widget_get_width_request", "gtk_widget_get_size_request" }, + { "gtk_widget_set_height_request", "gtk_widget_set_size_request" }, + { "gtk_widget_set_width_request", "gtk_widget_set_size_request" }, + { "gtk_window_get_default_height", "gtk_window_get_default_size" }, + { "gtk_window_get_default_width", "gtk_window_get_default_size" }, + { "gtk_window_set_default_height", "gtk_window_set_default_size" }, + { "gtk_window_set_default_width", "gtk_window_set_default_size" }, + { "gtk_window_get_display", "gtk_widget_get_display" }, + { "gtk_window_get_focus_widget", "gtk_window_get_focus" }, + { "gtk_window_set_focus_widget", "gtk_window_set_focus" }, +}; + +static const char *type_exceptions[] = { + "GtkCellRenderer", + "GtkSettings", + "GtkTextTag", +}; + +static GModule *module; + +static gboolean +function_exists (const char *function_name) +{ + gpointer func; + guint i; + + if (g_module_symbol (module, function_name, &func) && func) + return TRUE; + + for (i = 0; i < G_N_ELEMENTS(exceptions); i++) + { + if (g_str_equal (function_name, exceptions[i].expected)) + { + if (exceptions[i].alternative) + return function_exists (exceptions[i].alternative); + else + return TRUE; + } + } + + return FALSE; +} + +/* Keep in sync with gtkbuilder.c ! */ +static char * +type_name_mangle (const char *name, + gboolean split_first_cap) +{ + GString *symbol_name = g_string_new (""); + gint i; + + for (i = 0; name[i] != '\0'; i++) + { + /* skip if uppercase, first or previous is uppercase */ + if ((name[i] == g_ascii_toupper (name[i]) && + ((i > 0 && name[i-1] != g_ascii_toupper (name[i-1])) || + (i == 1 && name[0] == g_ascii_toupper (name[0]) && split_first_cap))) || + (i > 2 && name[i] == g_ascii_toupper (name[i]) && + name[i-1] == g_ascii_toupper (name[i-1]) && + name[i-2] == g_ascii_toupper (name[i-2]))) + g_string_append_c (symbol_name, '_'); + g_string_append_c (symbol_name, g_ascii_tolower (name[i])); + } + + return g_string_free (symbol_name, FALSE); +} + +static void +property_name_mangle (GString *symbol_name, + const char *name) +{ + guint i; + + for (i = 0; name[i] != '\0'; i++) + { + if (g_ascii_isalnum (name[i])) + g_string_append_c (symbol_name, g_ascii_tolower (name[i])); + else + g_string_append_c (symbol_name, '_'); + } +} + +const char *getters[] = { "get", "is", "ref" }; +const char *setters[] = { "set" }; + +static char ** +get_potential_names (GType type, + gboolean get, + const char *property_name) +{ + GPtrArray *options; + char *type_name_options[2]; + guint n_type_name_options, n_verbs; + const char **verbs; + guint i, j; + + if (get) + { + verbs = getters; + n_verbs = G_N_ELEMENTS (getters); + } + else + { + verbs = setters; + n_verbs = G_N_ELEMENTS (setters); + } + + type_name_options[0] = type_name_mangle (g_type_name (type), FALSE); + type_name_options[1] = type_name_mangle (g_type_name (type), TRUE); + if (g_str_equal (type_name_options[0], type_name_options[1])) + n_type_name_options = 1; + else + n_type_name_options = 2; + + options = g_ptr_array_new (); + + for (i = 0; i < n_type_name_options; i++) + { + for (j = 0; j < n_verbs; j++) + { + GString *str; + + str = g_string_new (type_name_options[i]); + g_string_append_c (str, '_'); + g_string_append (str, verbs[j]); + g_string_append_c (str, '_'); + property_name_mangle (str, property_name); + + g_ptr_array_add (options, g_string_free (str, FALSE)); + } + + if (g_str_has_prefix (property_name, "is-") || + g_str_has_prefix (property_name, "has-") || + g_str_has_prefix (property_name, "contains-")) + { + GString *str; + + /* try without a verb */ + str = g_string_new (type_name_options[i]); + g_string_append_c (str, '_'); + property_name_mangle (str, property_name); + + g_ptr_array_add (options, g_string_free (str, FALSE)); + } + } + + g_free (type_name_options[0]); + g_free (type_name_options[1]); + + g_ptr_array_add (options, NULL); + + return (char **) g_ptr_array_free (options, FALSE); +} + +static void +check_function_name (GType type, + gboolean get, + const char *property_name) +{ + guint i; + char **names; + + names = get_potential_names (type, get, property_name); + + for (i = 0; names[i] != NULL; i++) + { + if (function_exists (names[i])) + { + g_strfreev (names); + return; + } + } + + g_test_message ("No %s for property %s::%s", get ? "getter" : "setter", g_type_name (type), property_name); + for (i = 0; names[i] != NULL; i++) + { + g_test_message (" %s", names[i]); + } + + g_test_fail (); + + g_strfreev (names); +} + +static void +check_property (GParamSpec *pspec) +{ + if (pspec->flags & G_PARAM_READABLE) + { + check_function_name (pspec->owner_type, + TRUE, + pspec->name); + } + if (pspec->flags & G_PARAM_WRITABLE && + !(pspec->flags & G_PARAM_CONSTRUCT_ONLY)) + { + check_function_name (pspec->owner_type, + FALSE, + pspec->name); + } +} + +static void +test_accessors (gconstpointer data) +{ + GType type = GPOINTER_TO_SIZE (data); + GObjectClass *klass; + GParamSpec **pspecs; + guint i, n_pspecs; + + klass = g_type_class_ref (type); + pspecs = g_object_class_list_properties (klass, &n_pspecs); + + for (i = 0; i < n_pspecs; ++i) + { + GParamSpec *pspec = pspecs[i]; + + if (pspec->owner_type != type) + continue; + + check_property (pspec); + } + + g_free (pspecs); + g_type_class_unref (klass); +} + +static gboolean +type_is_whitelisted (GType type) +{ + guint i; + + if (!G_TYPE_IS_INSTANTIATABLE (type) || + !g_type_is_a (type, G_TYPE_OBJECT)) + return TRUE; + + for (i = 0; i < G_N_ELEMENTS (type_exceptions); i++) + { + GType exception = g_type_from_name (type_exceptions[i]); + + /* type hasn't been registered yet */ + if (exception == G_TYPE_INVALID) + continue; + + if (g_type_is_a (type, exception)) + return TRUE; + } + + return FALSE; +} + +int +main (int argc, char **argv) +{ + const GType *all_types; + guint n_types = 0, i; + gint result; + + /* These must be set before before gtk_test_init */ + g_setenv ("GIO_USE_VFS", "local", TRUE); + g_setenv ("GSETTINGS_BACKEND", "memory", TRUE); + + /* initialize test program */ + gtk_test_init (&argc, &argv); + gtk_test_register_all_types (); + + module = g_module_open (NULL, G_MODULE_BIND_LAZY); + + all_types = gtk_test_list_all_types (&n_types); + + for (i = 0; i < n_types; i++) + { + char *test_path; + + if (type_is_whitelisted (all_types[i])) + continue; + + test_path = g_strdup_printf ("/accessor-apis/%s", g_type_name (all_types[i])); + + g_test_add_data_func (test_path, GSIZE_TO_POINTER (all_types[i]), test_accessors); + + g_free (test_path); + } + + result = g_test_run(); + + g_module_close (module); + + return result; +} diff --git a/testsuite/gtk/meson.build b/testsuite/gtk/meson.build index d31481bd3e..11509fa5bc 100644 --- a/testsuite/gtk/meson.build +++ b/testsuite/gtk/meson.build @@ -21,6 +21,8 @@ endif # - 'suites': (array): additional test suites tests = [ { 'name': 'accel' }, +# sadly, mesons xfail support seems busted +# { 'name': 'accessor-apis' }, { 'name': 'action' }, { 'name': 'adjustment' }, { 'name': 'bitset' }, @@ -132,6 +134,8 @@ tests = [ # Tests that are expected to fail xfail = [ + # we are still missing some accessors + 'accessor-apis', # one of the window resizing tests fails after # the GdkToplevel refactoring, and needs a big # gtkwindow.c configure request cleanup