/*
* 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;
}