1 /* Gtk+ object tests
2  * Copyright (C) 2007 Imendio AB
3  * Authors: Tim Janik
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17  */
18 #include <gtk/gtk.h>
19 #include <string.h>
20 
21 /* --- helper macros for property value generation --- */
22 /* dvalue=+0: generate minimum value
23  * dvalue=.x: generate value within value range proportional to x.
24  * dvalue=+1: generate maximum value
25  * dvalue=-1: generate random value within value range
26  * dvalue=+2: initialize value from default_value
27  */
28 #define ASSIGN_VALUE(__g_value_set_func, value__, PSPECTYPE, __pspec, __default_value, __minimum, __maximum, __dvalue) do { \
29   PSPECTYPE __p = (PSPECTYPE) __pspec; \
30   __g_value_set_func (value__, SELECT_VALUE (__dvalue, __p->__default_value, __p->__minimum, __p->__maximum)); \
31 } while (0)
32 #define SELECT_VALUE(__dvalue, __default_value, __minimum, __maximum) ( \
33   __dvalue >= 0 && __dvalue <= 1 ? __minimum * (1 - __dvalue) + __dvalue * __maximum : \
34     __dvalue <= -1 ? g_test_rand_double_range (__minimum, __maximum) : \
35       __default_value)
36 #define SELECT_NAME(__dvalue) ( \
37   __dvalue == 0 ? "minimum" : \
38     __dvalue == 1 ? "maximum" : \
39       __dvalue >= +2 ? "default" : \
40         __dvalue == 0.5 ? "medium" : \
41           __dvalue > 0 && __dvalue < 1 ? "fractional" : \
42             "random")
43 #define MATCH_ANY_VALUE         ((void*) 0xf1874c23)
44 
45 /* --- ignored property names --- */
46 typedef struct {
47   const char   *type_name;
48   const char   *name;
49   gconstpointer value;
50 } IgnoreProperty;
51 static const IgnoreProperty*
list_ignore_properties(gboolean buglist)52 list_ignore_properties (gboolean buglist)
53 {
54   /* currently untestable properties */
55   static const IgnoreProperty ignore_properties[] = {
56     { "GtkWidget",              "parent",               NULL, },                        /* needs working parent widget */
57     { "GtkWidget",              "has-default",          (void*) TRUE, },                /* conflicts with toplevel-less widgets */
58     { "GtkWidget",              "display",              (void*) MATCH_ANY_VALUE },
59     { "GtkCellView",            "background",           (void*) "", },                  /* "" is not a valid background color */
60     { "GtkFileChooserWidget",   "select-multiple",      (void*) 0x1 },                  /* property conflicts */
61     { "GtkFileChooserDialog",   "select-multiple",      (void*) MATCH_ANY_VALUE },      /* property disabled */
62     { "GtkTextView",            "overwrite",            (void*) MATCH_ANY_VALUE },      /* needs text buffer */
63     { "GtkTreeView",            "expander-column",      (void*) MATCH_ANY_VALUE },      /* assertion list != NULL */
64     { "GtkWindow",              "display",              (void*) MATCH_ANY_VALUE },
65     { NULL, NULL, NULL }
66   };
67   /* properties suspected to be Gdk/Gtk+ bugs */
68   static const IgnoreProperty bug_properties[] = {
69     { "GtkComboBox",            "active",               (void*) MATCH_ANY_VALUE },      /* FIXME: triggers NULL model bug */
70     { NULL, NULL, NULL }
71   };
72   if (buglist)
73     return bug_properties;
74   else
75     return ignore_properties;
76 }
77 
78 /* --- test functions --- */
79 static void
pspec_select_value(GParamSpec * pspec,GValue * value,double dvalue)80 pspec_select_value (GParamSpec *pspec,
81                     GValue     *value,
82                     double      dvalue)
83 {
84   /* generate a value suitable for pspec */
85   if (G_IS_PARAM_SPEC_CHAR (pspec))
86     ASSIGN_VALUE (g_value_set_schar, value, GParamSpecChar*, pspec, default_value, minimum, maximum, dvalue);
87   else if (G_IS_PARAM_SPEC_UCHAR (pspec))
88     ASSIGN_VALUE (g_value_set_uchar, value, GParamSpecUChar*, pspec, default_value, minimum, maximum, dvalue);
89   else if (G_IS_PARAM_SPEC_INT (pspec))
90     ASSIGN_VALUE (g_value_set_int, value, GParamSpecInt*, pspec, default_value, minimum, maximum, dvalue);
91   else if (G_IS_PARAM_SPEC_UINT (pspec))
92     ASSIGN_VALUE (g_value_set_uint, value, GParamSpecUInt*, pspec, default_value, minimum, maximum, dvalue);
93   else if (G_IS_PARAM_SPEC_LONG (pspec))
94     ASSIGN_VALUE (g_value_set_long, value, GParamSpecLong*, pspec, default_value, minimum, maximum, dvalue);
95   else if (G_IS_PARAM_SPEC_ULONG (pspec))
96     ASSIGN_VALUE (g_value_set_ulong, value, GParamSpecULong*, pspec, default_value, minimum, maximum, dvalue);
97   else if (G_IS_PARAM_SPEC_INT64 (pspec))
98     ASSIGN_VALUE (g_value_set_int64, value, GParamSpecInt64*, pspec, default_value, minimum, maximum, dvalue);
99   else if (G_IS_PARAM_SPEC_UINT64 (pspec))
100     ASSIGN_VALUE (g_value_set_uint64, value, GParamSpecUInt64*, pspec, default_value, minimum, maximum, dvalue);
101   else if (G_IS_PARAM_SPEC_FLOAT (pspec))
102     ASSIGN_VALUE (g_value_set_float, value, GParamSpecFloat*, pspec, default_value, minimum, maximum, dvalue);
103   else if (G_IS_PARAM_SPEC_DOUBLE (pspec))
104     ASSIGN_VALUE (g_value_set_double, value, GParamSpecDouble*, pspec, default_value, minimum, maximum, dvalue);
105   else if (G_IS_PARAM_SPEC_BOOLEAN (pspec))
106     g_value_set_boolean (value, SELECT_VALUE (dvalue, ((GParamSpecBoolean*) pspec)->default_value, FALSE, TRUE));
107   else if (G_IS_PARAM_SPEC_UNICHAR (pspec))
108     g_value_set_uint (value, SELECT_VALUE (dvalue, ((GParamSpecUnichar*) pspec)->default_value, FALSE, TRUE));
109   else if (G_IS_PARAM_SPEC_GTYPE (pspec))
110     g_value_set_gtype (value, SELECT_VALUE ((int) dvalue, ((GParamSpecGType*) pspec)->is_a_type, 0, GTK_TYPE_WIDGET));
111   else if (G_IS_PARAM_SPEC_STRING (pspec))
112     {
113       GParamSpecString *sspec = (GParamSpecString*) pspec;
114       if (dvalue >= +2)
115         g_value_set_string (value, sspec->default_value);
116       if (dvalue > 0 && sspec->cset_first && sspec->cset_nth)
117         g_value_take_string (value, g_strdup_printf ("%c%c", sspec->cset_first[0], sspec->cset_nth[0]));
118       else /* if (sspec->ensure_non_null) */
119         g_value_set_string (value, "");
120     }
121   else if (G_IS_PARAM_SPEC_ENUM (pspec))
122     {
123       GParamSpecEnum *espec = (GParamSpecEnum*) pspec;
124       if (dvalue >= +2)
125         g_value_set_enum (value, espec->default_value);
126       if (dvalue >= 0 && dvalue <= 1)
127         g_value_set_enum (value, espec->enum_class->values[(int) ((espec->enum_class->n_values - 1) * dvalue)].value);
128       else if (dvalue <= -1)
129         g_value_set_enum (value, espec->enum_class->values[g_test_rand_int_range (0, espec->enum_class->n_values)].value);
130     }
131   else if (G_IS_PARAM_SPEC_FLAGS (pspec))
132     {
133       GParamSpecFlags *fspec = (GParamSpecFlags*) pspec;
134       if (dvalue >= +2)
135         g_value_set_flags (value, fspec->default_value);
136       if (dvalue >= 0 && dvalue <= 1)
137         g_value_set_flags (value, fspec->flags_class->values[(int) ((fspec->flags_class->n_values - 1) * dvalue)].value);
138       else if (dvalue <= -1)
139         g_value_set_flags (value, fspec->flags_class->values[g_test_rand_int_range (0, fspec->flags_class->n_values)].value);
140     }
141   else if (G_IS_PARAM_SPEC_OBJECT (pspec))
142     {
143       gpointer object = NULL;
144       if (!G_TYPE_IS_ABSTRACT (pspec->value_type) &&
145           !G_TYPE_IS_INTERFACE (pspec->value_type))
146         {
147           if (g_type_is_a (pspec->value_type, GDK_TYPE_PIXBUF))
148             object = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 32, 32);
149           else if (g_type_is_a (pspec->value_type, GDK_TYPE_PIXBUF_ANIMATION))
150             object = gdk_pixbuf_simple_anim_new (32, 32, 15);
151           else
152             object = g_object_new (pspec->value_type, NULL);
153           g_object_ref_sink (object);
154           g_value_take_object (value, object);
155         }
156     }
157   /* unimplemented:
158    * G_IS_PARAM_SPEC_PARAM
159    * G_IS_PARAM_SPEC_BOXED
160    * G_IS_PARAM_SPEC_POINTER
161    * G_IS_PARAM_SPEC_VALUE_ARRAY
162    */
163 }
164 
165 static gpointer
value_as_pointer(GValue * value)166 value_as_pointer (GValue *value)
167 {
168   if (g_value_fits_pointer (value))
169     return g_value_peek_pointer (value);
170   if (G_VALUE_HOLDS_BOOLEAN (value))
171     return GINT_TO_POINTER(g_value_get_boolean (value));
172   if (G_VALUE_HOLDS_CHAR (value))
173     return (void*) (gssize) g_value_get_schar (value);
174   if (G_VALUE_HOLDS_UCHAR (value))
175     return (void*) (gsize) g_value_get_uchar (value);
176   if (G_VALUE_HOLDS_INT (value))
177     return GINT_TO_POINTER(g_value_get_int (value));
178   if (G_VALUE_HOLDS_UINT (value))
179     return GUINT_TO_POINTER(g_value_get_uint (value));
180   if (G_VALUE_HOLDS_LONG (value))
181     return GSIZE_TO_POINTER ((gssize) g_value_get_long (value));
182   if (G_VALUE_HOLDS_ULONG (value))
183     return GSIZE_TO_POINTER (g_value_get_ulong (value));
184   if (G_VALUE_HOLDS_FLOAT (value))
185     return (void*) (gssize) g_value_get_float (value);
186   if (G_VALUE_HOLDS_DOUBLE (value))
187     return (void*) (gssize) g_value_get_double (value);
188   if (G_VALUE_HOLDS_ENUM (value))
189     return (void*) (gssize) g_value_get_enum (value);
190   if (G_VALUE_HOLDS_FLAGS (value))
191     return (void*) (gsize) g_value_get_flags (value);
192   return (void*) 0x1373babe;
193 }
194 
195 static void
object_test_property(GObject * object,GParamSpec * pspec,double dvalue)196 object_test_property (GObject           *object,
197                       GParamSpec        *pspec,
198                       double             dvalue)
199 {
200   /* test setting of a normal writable property */
201   if (pspec->flags & G_PARAM_WRITABLE &&
202       !(pspec->flags & (G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY)))
203     {
204       GValue value = G_VALUE_INIT;
205       guint i;
206       const IgnoreProperty *ignore_properties;
207       /* select value to set */
208       g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
209       pspec_select_value (pspec, &value, dvalue);
210       /* ignore untestable properties */
211       ignore_properties = list_ignore_properties (FALSE);
212       for (i = 0; ignore_properties[i].name; i++)
213         if (g_strcmp0 ("", ignore_properties[i].name) ||
214             (g_type_is_a (G_OBJECT_TYPE (object), g_type_from_name (ignore_properties[i].type_name)) &&
215              strcmp (pspec->name, ignore_properties[i].name) == 0 &&
216              (MATCH_ANY_VALUE == ignore_properties[i].value ||
217               value_as_pointer (&value) == ignore_properties[i].value ||
218               (G_VALUE_HOLDS_STRING (&value) &&
219                g_strcmp0 (g_value_get_string (&value), ignore_properties[i].value) == 0))))
220           break;
221       /* ignore known property bugs if not testing thoroughly */
222       if (ignore_properties[i].name == NULL && !g_test_thorough ())
223         {
224           ignore_properties = list_ignore_properties (TRUE);
225           for (i = 0; ignore_properties[i].name; i++)
226             if (g_type_is_a (G_OBJECT_TYPE (object), g_type_from_name (ignore_properties[i].type_name)) &&
227                 strcmp (pspec->name, ignore_properties[i].name) == 0 &&
228                 (MATCH_ANY_VALUE == ignore_properties[i].value ||
229                  value_as_pointer (&value) == ignore_properties[i].value ||
230                  (G_VALUE_HOLDS_STRING (&value) &&
231                   g_strcmp0 (g_value_get_string (&value), ignore_properties[i].value) == 0)))
232               break;
233         }
234       /* assign unignored properties */
235       if (ignore_properties[i].name == NULL)
236         {
237           if (g_test_verbose ())
238             g_print ("PropertyTest: %s::%s := (%s value (%s): %p)\n",
239                      g_type_name (G_OBJECT_TYPE (object)), pspec->name,
240                      SELECT_NAME (dvalue), g_type_name (G_VALUE_TYPE (&value)),
241                      value_as_pointer (&value));
242           g_object_set_property (object, pspec->name, &value);
243         }
244       /* cleanups */
245       g_value_unset (&value);
246     }
247 }
248 
249 static void
widget_test_properties(GtkWidget * widget,double dvalue)250 widget_test_properties (GtkWidget   *widget,
251                         double       dvalue)
252 {
253   /* try setting all possible properties, according to dvalue */
254   guint i, n_pspecs = 0;
255   GParamSpec **pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (widget), &n_pspecs);
256   for (i = 0; i < n_pspecs; i++)
257     {
258       GParamSpec *pspec = pspecs[i];
259       if (pspec->flags & G_PARAM_WRITABLE &&
260           !(pspec->flags & (G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY)))
261         object_test_property (G_OBJECT (widget), pspecs[i], dvalue);
262     }
263   g_free (pspecs);
264 }
265 
266 static void
widget_fixups(GtkWidget * widget)267 widget_fixups (GtkWidget *widget)
268 {
269   /* post-constructor for widgets that need additional settings to work correctly */
270   if (GTK_IS_COMBO_BOX_TEXT (widget))
271     {
272       gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "test text");
273     }
274 }
275 
276 static void
widget_property_tests(gconstpointer test_data)277 widget_property_tests (gconstpointer test_data)
278 {
279   GType wtype = (GType) test_data;
280   /* create widget */
281   GtkWidget *widget = g_object_new (wtype, NULL);
282   g_object_ref_sink (widget);
283   widget_fixups (widget);
284   /* test property values */
285   widget_test_properties (widget,  +2); /* test default_value */
286   widget_test_properties (widget,   0); /* test minimum */
287   widget_test_properties (widget, 0.5); /* test medium */
288   widget_test_properties (widget,   1); /* test maximum */
289   widget_test_properties (widget,  -1); /* test random value */
290   /* cleanup */
291   if (GTK_IS_WINDOW (widget))
292     gtk_window_destroy (GTK_WINDOW (widget));
293   g_object_unref (widget);
294 }
295 
296 /* --- main test program --- */
297 int
main(int argc,char * argv[])298 main (int   argc,
299       char *argv[])
300 {
301   const GType *otypes;
302   guint i;
303 
304   g_setenv ("GSETTINGS_BACKEND", "memory", TRUE);
305 
306   /* initialize test program */
307   gtk_test_init (&argc, &argv);
308   gtk_test_register_all_types ();
309 
310   /* install a property test for each widget type */
311   otypes = gtk_test_list_all_types (NULL);
312   for (i = 0; otypes[i]; i++)
313     if (g_type_is_a (otypes[i], GTK_TYPE_WIDGET) &&
314         G_TYPE_IS_OBJECT (otypes[i]) &&
315         !G_TYPE_IS_ABSTRACT (otypes[i]))
316       {
317         char *testpath = g_strdup_printf ("/properties/%s", g_type_name (otypes[i]));
318         g_test_add_data_func (testpath, (void*) otypes[i], widget_property_tests);
319         g_free (testpath);
320       }
321 
322   return g_test_run ();
323 }
324