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* |
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 |
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, v_string: sspec->default_value); |
116 | if (dvalue > 0 && sspec->cset_first && sspec->cset_nth) |
117 | g_value_take_string (value, v_string: g_strdup_printf (format: "%c%c" , sspec->cset_first[0], sspec->cset_nth[0])); |
118 | else /* if (sspec->ensure_non_null) */ |
119 | g_value_set_string (value, v_string: "" ); |
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, v_enum: espec->default_value); |
126 | if (dvalue >= 0 && dvalue <= 1) |
127 | g_value_set_enum (value, v_enum: espec->enum_class->values[(int) ((espec->enum_class->n_values - 1) * dvalue)].value); |
128 | else if (dvalue <= -1) |
129 | g_value_set_enum (value, v_enum: espec->enum_class->values[g_test_rand_int_range (begin: 0, end: 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, v_flags: fspec->default_value); |
136 | if (dvalue >= 0 && dvalue <= 1) |
137 | g_value_set_flags (value, v_flags: fspec->flags_class->values[(int) ((fspec->flags_class->n_values - 1) * dvalue)].value); |
138 | else if (dvalue <= -1) |
139 | g_value_set_flags (value, v_flags: fspec->flags_class->values[g_test_rand_int_range (begin: 0, end: 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 (type: pspec->value_type, GDK_TYPE_PIXBUF)) |
148 | object = gdk_pixbuf_new (colorspace: GDK_COLORSPACE_RGB, FALSE, bits_per_sample: 8, width: 32, height: 32); |
149 | else if (g_type_is_a (type: pspec->value_type, GDK_TYPE_PIXBUF_ANIMATION)) |
150 | object = gdk_pixbuf_simple_anim_new (width: 32, height: 32, rate: 15); |
151 | else |
152 | object = g_object_new (object_type: pspec->value_type, NULL); |
153 | g_object_ref_sink (object); |
154 | g_value_take_object (value, v_object: 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 |
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 |
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: &value, G_PARAM_SPEC_VALUE_TYPE (pspec)); |
209 | pspec_select_value (pspec, value: &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 (str1: "" , str2: ignore_properties[i].name) || |
214 | (g_type_is_a (G_OBJECT_TYPE (object), is_a_type: g_type_from_name (name: ignore_properties[i].type_name)) && |
215 | strcmp (s1: pspec->name, s2: ignore_properties[i].name) == 0 && |
216 | (MATCH_ANY_VALUE == ignore_properties[i].value || |
217 | value_as_pointer (value: &value) == ignore_properties[i].value || |
218 | (G_VALUE_HOLDS_STRING (&value) && |
219 | g_strcmp0 (str1: g_value_get_string (value: &value), str2: 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), is_a_type: g_type_from_name (name: ignore_properties[i].type_name)) && |
227 | strcmp (s1: pspec->name, s2: ignore_properties[i].name) == 0 && |
228 | (MATCH_ANY_VALUE == ignore_properties[i].value || |
229 | value_as_pointer (value: &value) == ignore_properties[i].value || |
230 | (G_VALUE_HOLDS_STRING (&value) && |
231 | g_strcmp0 (str1: g_value_get_string (value: &value), str2: 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 (format: "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: &value)); |
242 | g_object_set_property (object, property_name: pspec->name, value: &value); |
243 | } |
244 | /* cleanups */ |
245 | g_value_unset (value: &value); |
246 | } |
247 | } |
248 | |
249 | static void |
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_properties: &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), pspec: pspecs[i], dvalue); |
262 | } |
263 | g_free (mem: pspecs); |
264 | } |
265 | |
266 | static void |
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), text: "test text" ); |
273 | } |
274 | } |
275 | |
276 | static void |
277 | widget_property_tests (gconstpointer test_data) |
278 | { |
279 | GType wtype = (GType) test_data; |
280 | /* create widget */ |
281 | GtkWidget *widget = g_object_new (object_type: wtype, NULL); |
282 | g_object_ref_sink (widget); |
283 | widget_fixups (widget); |
284 | /* test property values */ |
285 | widget_test_properties (widget, dvalue: +2); /* test default_value */ |
286 | widget_test_properties (widget, dvalue: 0); /* test minimum */ |
287 | widget_test_properties (widget, dvalue: 0.5); /* test medium */ |
288 | widget_test_properties (widget, dvalue: 1); /* test maximum */ |
289 | widget_test_properties (widget, dvalue: -1); /* test random value */ |
290 | /* cleanup */ |
291 | if (GTK_IS_WINDOW (widget)) |
292 | gtk_window_destroy (GTK_WINDOW (widget)); |
293 | g_object_unref (object: widget); |
294 | } |
295 | |
296 | /* --- main test program --- */ |
297 | int |
298 | main (int argc, |
299 | char *argv[]) |
300 | { |
301 | const GType *otypes; |
302 | guint i; |
303 | |
304 | g_setenv (variable: "GSETTINGS_BACKEND" , value: "memory" , TRUE); |
305 | |
306 | /* initialize test program */ |
307 | gtk_test_init (argcp: &argc, argvp: &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 (type: 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 (format: "/properties/%s" , g_type_name (type: otypes[i])); |
318 | g_test_add_data_func (testpath, test_data: (void*) otypes[i], test_func: widget_property_tests); |
319 | g_free (mem: testpath); |
320 | } |
321 | |
322 | return g_test_run (); |
323 | } |
324 | |