diff --git a/testsuite/gtk/accessor-apis.c b/testsuite/gtk/accessor-apis.c
new file mode 100644
index 0000000000..e45842b6a8
--- /dev/null
+++ b/testsuite/gtk/accessor-apis.c
@@ -0,0 +1,296 @@
+/*
+ * 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 */
+ { "gtk_window_get_display", "gtk_widget_get_display" },
+};
+
+static 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));
+ }
+ }
+
+ 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 333f4347cb..5dfaf65eac 100644
--- a/testsuite/gtk/meson.build
+++ b/testsuite/gtk/meson.build
@@ -10,6 +10,7 @@ endif
tests = [
['accel'],
['accessible'],
+ ['accessor-apis'],
['action'],
['adjustment'],
['bitmask', ['../../gtk/gtkallocatedbitmask.c'], ['-DGTK_COMPILATION', '-UG_ENABLE_DEBUG']],