object.c 17 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/* Gtk+ object tests
 * Copyright (C) 2007 Imendio AB
 * Authors: Tim Janik
 *
 * 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 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
Javier Jardón's avatar
Javier Jardón committed
16
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
 */
#include <gtk/gtk.h>
#include <string.h>

/* --- helper macros for property value generation --- */
/* dvalue=+0: generate minimum value
 * dvalue=.x: generate value within value range proportional to x.
 * dvalue=+1: generate maximum value
 * dvalue=-1: generate random value within value range
 * dvalue=+2: initialize value from default_value
 */
#define ASSIGN_VALUE(__g_value_set_func, __value, PSPECTYPE, __pspec, __default_value, __minimum, __maximum, __dvalue) do { \
  PSPECTYPE __p = (PSPECTYPE) __pspec; \
  __g_value_set_func (__value, SELECT_VALUE (__dvalue, __p->__default_value, __p->__minimum, __p->__maximum)); \
} while (0)
#define SELECT_VALUE(__dvalue, __default_value, __minimum, __maximum) ( \
  __dvalue >= 0 && __dvalue <= 1 ? __minimum * (1 - __dvalue) + __dvalue * __maximum : \
    __dvalue <= -1 ? g_test_rand_double_range (__minimum, __maximum) : \
      __default_value)
#define SELECT_NAME(__dvalue) ( \
  __dvalue == 0 ? "minimum" : \
    __dvalue == 1 ? "maximum" : \
      __dvalue >= +2 ? "default" : \
        __dvalue == 0.5 ? "medium" : \
          __dvalue > 0 && __dvalue < 1 ? "fractional" : \
            "random")
#define MATCH_ANY_VALUE         ((void*) 0xf1874c23)

/* --- property blacklists --- */
typedef struct {
  const char   *type_name;
  const char   *name;
  gconstpointer value;
} IgnoreProperty;
static const IgnoreProperty*
list_ignore_properties (gboolean buglist)
{
  /* currently untestable properties */
  static const IgnoreProperty ignore_properties[] = {
    { "GtkContainer",           "child",                NULL, },                        /* needs working child widget */
    { "GtkRadioMenuItem",       "group",                NULL, },                        /* needs working sibling */
    { "GtkWidget",              "parent",               NULL, },                        /* needs working parent widget */
    { "GtkCList",               "selection-mode",       (void*) GTK_SELECTION_NONE, },
    { "GtkWidget",              "has-default",          (void*) TRUE, },                /* conflicts with toplevel-less widgets */
    { "GtkWidget",              "screen",               NULL, },
    { "GtkWindow",              "type-hint",            (void*) GDK_WINDOW_TYPE_HINT_DND, }, /* conflicts with ::visible=TRUE */
63
    { "GtkCellView",            "background",           (void*) "", },                  /* "" is not a valid background color */
64 65
    { "GtkColorButton",         "color",                (void*) NULL, },                /* not a valid boxed color */
    { "GtkInputDialog",         "has-separator",        (void*) MATCH_ANY_VALUE, },     /* property disabled */
66
    { "GtkInvisible",           "screen",               (void*) MATCH_ANY_VALUE },      /* cannot create GdkScreen */
67 68 69 70 71 72 73 74 75 76 77 78
    { "GtkMessageDialog",       "has-separator",        (void*) MATCH_ANY_VALUE, },     /* property disabled */
    { "GtkFontSelectionDialog", "has-separator",        (void*) MATCH_ANY_VALUE, },     /* property disabled */
    { "GtkColorSelectionDialog","has-separator",        (void*) MATCH_ANY_VALUE, },     /* property disabled */
    { "GtkColorSelection",      "child",                NULL, },
    { "GtkColorSelection",      "current-color",        (void*) NULL, },                /* not a valid boxed color */
    { "GtkComboBox",            "row-span-column",      (void*) MATCH_ANY_VALUE },      /* GtkComboBoxEntry needs a tree model for this */
    { "GtkComboBox",            "column-span-column",   (void*) MATCH_ANY_VALUE },      /* GtkComboBoxEntry needs a tree model for this */
    { "GtkFileChooserButton",   "select-multiple",      (void*) MATCH_ANY_VALUE },      /* property disabled */
    { "GtkFileChooserButton",   "action",               (void*) GTK_FILE_CHOOSER_ACTION_SAVE },
    { "GtkFileChooserButton",   "action",               (void*) GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER },
    { "GtkFileChooserWidget",   "select-multiple",      (void*) 0x1 },                  /* property conflicts */
    { "GtkFileChooserDialog",   "select-multiple",      (void*) MATCH_ANY_VALUE },      /* property disabled */
Matthias Clasen's avatar
Matthias Clasen committed
79 80
    { "GtkMenu",                "accel-path",           (void*) MATCH_ANY_VALUE },      /* has odd restrictions in the setter */
    { "GtkMenuItem",            "accel-path",           (void*) MATCH_ANY_VALUE },      /* has odd restrictions in the setter */
81 82 83
    { "GtkRecentChooserMenu",   "select-multiple",      (void*) MATCH_ANY_VALUE },      /* property disabled */
    { "GtkTextView",            "overwrite",            (void*) MATCH_ANY_VALUE },      /* needs text buffer */
    { "GtkToolbar",             "icon-size",            (void*) GTK_ICON_SIZE_INVALID },
84 85
    { "GtkTreeView",            "expander-column",      (void*) MATCH_ANY_VALUE },      /* assertion list != NULL */
    { "GtkWindow",              "screen",               (void*) MATCH_ANY_VALUE },      /* cannot create GdkScreen */
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
    { NULL, NULL, NULL }
  };
  /* properties suspected to be Gdk/Gtk+ bugs */
  static const IgnoreProperty bug_properties[] = {
    { "GtkComboBox",            "active",               (void*) MATCH_ANY_VALUE },      /* FIXME: triggers NULL model bug */
    { "GtkCTree",               "spacing",              (void*) MATCH_ANY_VALUE },      /* FIXME: triggers signedness bug */
    { "GtkFileChooserButton",   "local-only",           (void*) MATCH_ANY_VALUE },      /* FIXME: triggers NULL path assertion */
    { "GtkFileChooserDialog",   "local-only",           (void*) MATCH_ANY_VALUE },      /* FIXME: triggers NULL path assertion */
    { "GtkFileChooserWidget",   "local-only",           (void*) MATCH_ANY_VALUE },      /* FIXME: triggers NULL path assertion */
    { "GtkMenu",                "tearoff-state",        (void*) MATCH_ANY_VALUE },      /* FIXME: triggers NULL widget cast */
    { "GtkText",                "text-position",        (void*) MATCH_ANY_VALUE },      /* FIXME: segfaults, fix property minimum/maximum */
    { NULL, NULL, NULL }
  };
  if (buglist)
    return bug_properties;
  else
    return ignore_properties;
}

/* --- test functions --- */
static void
pspec_select_value (GParamSpec *pspec,
                    GValue     *value,
                    double      dvalue)
{
  /* generate a value suitable for pspec */
  if (G_IS_PARAM_SPEC_CHAR (pspec))
113
    ASSIGN_VALUE (g_value_set_schar, value, GParamSpecChar*, pspec, default_value, minimum, maximum, dvalue);
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
  else if (G_IS_PARAM_SPEC_UCHAR (pspec))
    ASSIGN_VALUE (g_value_set_uchar, value, GParamSpecUChar*, pspec, default_value, minimum, maximum, dvalue);
  else if (G_IS_PARAM_SPEC_INT (pspec))
    ASSIGN_VALUE (g_value_set_int, value, GParamSpecInt*, pspec, default_value, minimum, maximum, dvalue);
  else if (G_IS_PARAM_SPEC_UINT (pspec))
    ASSIGN_VALUE (g_value_set_uint, value, GParamSpecUInt*, pspec, default_value, minimum, maximum, dvalue);
  else if (G_IS_PARAM_SPEC_LONG (pspec))
    ASSIGN_VALUE (g_value_set_long, value, GParamSpecLong*, pspec, default_value, minimum, maximum, dvalue);
  else if (G_IS_PARAM_SPEC_ULONG (pspec))
    ASSIGN_VALUE (g_value_set_ulong, value, GParamSpecULong*, pspec, default_value, minimum, maximum, dvalue);
  else if (G_IS_PARAM_SPEC_INT64 (pspec))
    ASSIGN_VALUE (g_value_set_int64, value, GParamSpecInt64*, pspec, default_value, minimum, maximum, dvalue);
  else if (G_IS_PARAM_SPEC_UINT64 (pspec))
    ASSIGN_VALUE (g_value_set_uint64, value, GParamSpecUInt64*, pspec, default_value, minimum, maximum, dvalue);
  else if (G_IS_PARAM_SPEC_FLOAT (pspec))
    ASSIGN_VALUE (g_value_set_float, value, GParamSpecFloat*, pspec, default_value, minimum, maximum, dvalue);
  else if (G_IS_PARAM_SPEC_DOUBLE (pspec))
    ASSIGN_VALUE (g_value_set_double, value, GParamSpecDouble*, pspec, default_value, minimum, maximum, dvalue);
  else if (G_IS_PARAM_SPEC_BOOLEAN (pspec))
    g_value_set_boolean (value, SELECT_VALUE (dvalue, ((GParamSpecBoolean*) pspec)->default_value, FALSE, TRUE));
  else if (G_IS_PARAM_SPEC_UNICHAR (pspec))
    g_value_set_uint (value, SELECT_VALUE (dvalue, ((GParamSpecUnichar*) pspec)->default_value, FALSE, TRUE));
  else if (G_IS_PARAM_SPEC_GTYPE (pspec))
    g_value_set_gtype (value, SELECT_VALUE ((int) dvalue, ((GParamSpecGType*) pspec)->is_a_type, 0, GTK_TYPE_WIDGET));
  else if (G_IS_PARAM_SPEC_STRING (pspec))
    {
      GParamSpecString *sspec = (GParamSpecString*) pspec;
      if (dvalue >= +2)
        g_value_set_string (value, sspec->default_value);
      if (dvalue > 0 && sspec->cset_first && sspec->cset_nth)
        g_value_take_string (value, g_strdup_printf ("%c%c", sspec->cset_first[0], sspec->cset_nth[0]));
      else /* if (sspec->ensure_non_null) */
        g_value_set_string (value, "");
    }
  else if (G_IS_PARAM_SPEC_ENUM (pspec))
    {
      GParamSpecEnum *espec = (GParamSpecEnum*) pspec;
      if (dvalue >= +2)
        g_value_set_enum (value, espec->default_value);
      if (dvalue >= 0 && dvalue <= 1)
        g_value_set_enum (value, espec->enum_class->values[(int) ((espec->enum_class->n_values - 1) * dvalue)].value);
      else if (dvalue <= -1)
        g_value_set_enum (value, espec->enum_class->values[g_test_rand_int_range (0, espec->enum_class->n_values)].value);
    }
  else if (G_IS_PARAM_SPEC_FLAGS (pspec))
    {
      GParamSpecFlags *fspec = (GParamSpecFlags*) pspec;
      if (dvalue >= +2)
        g_value_set_flags (value, fspec->default_value);
      if (dvalue >= 0 && dvalue <= 1)
        g_value_set_flags (value, fspec->flags_class->values[(int) ((fspec->flags_class->n_values - 1) * dvalue)].value);
      else if (dvalue <= -1)
        g_value_set_flags (value, fspec->flags_class->values[g_test_rand_int_range (0, fspec->flags_class->n_values)].value);
    }
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
  else if (G_IS_PARAM_SPEC_OBJECT (pspec))
    {
      gpointer object = NULL;
      if (!G_TYPE_IS_ABSTRACT (pspec->value_type) &&
          !G_TYPE_IS_INTERFACE (pspec->value_type))
        {
          if (g_type_is_a (pspec->value_type, GDK_TYPE_PIXBUF))
            object = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, 32, 32);
          else if (g_type_is_a (pspec->value_type, GDK_TYPE_PIXBUF_ANIMATION))
            object = gdk_pixbuf_simple_anim_new (32, 32, 15);
          else
            object = g_object_new (pspec->value_type, NULL);
          g_object_ref_sink (object);
          g_value_take_object (value, object);
        }
    }
184 185 186 187 188 189 190 191 192 193 194 195 196 197
  /* unimplemented:
   * G_IS_PARAM_SPEC_PARAM
   * G_IS_PARAM_SPEC_BOXED
   * G_IS_PARAM_SPEC_POINTER
   * G_IS_PARAM_SPEC_VALUE_ARRAY
   */
}

static gpointer
value_as_pointer (GValue *value)
{
  if (g_value_fits_pointer (value))
    return g_value_peek_pointer (value);
  if (G_VALUE_HOLDS_BOOLEAN (value))
198
    return GINT_TO_POINTER(g_value_get_boolean (value));
199
  if (G_VALUE_HOLDS_CHAR (value))
200
    return (void*) (gssize) g_value_get_schar (value);
201 202 203
  if (G_VALUE_HOLDS_UCHAR (value))
    return (void*) (gsize) g_value_get_uchar (value);
  if (G_VALUE_HOLDS_INT (value))
204
    return GINT_TO_POINTER(g_value_get_int (value));
205
  if (G_VALUE_HOLDS_UINT (value))
206
    return GUINT_TO_POINTER(g_value_get_uint (value));
207
  if (G_VALUE_HOLDS_LONG (value))
208
    return GSIZE_TO_POINTER ((gssize) g_value_get_long (value));
209
  if (G_VALUE_HOLDS_ULONG (value))
210
    return GSIZE_TO_POINTER (g_value_get_ulong (value));
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
  if (G_VALUE_HOLDS_FLOAT (value))
    return (void*) (gssize) g_value_get_float (value);
  if (G_VALUE_HOLDS_DOUBLE (value))
    return (void*) (gssize) g_value_get_double (value);
  if (G_VALUE_HOLDS_ENUM (value))
    return (void*) (gssize) g_value_get_enum (value);
  if (G_VALUE_HOLDS_FLAGS (value))
    return (void*) (gsize) g_value_get_flags (value);
  return (void*) 0x1373babe;
}

static void
object_test_property (GObject           *object,
                      GParamSpec        *pspec,
                      double             dvalue)
{
  /* test setting of a normal writable property */
  if (pspec->flags & G_PARAM_WRITABLE &&
      !(pspec->flags & (G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY)))
    {
Javier Jardón's avatar
Javier Jardón committed
231
      GValue value = G_VALUE_INIT;
232 233 234 235 236 237 238 239
      guint i;
      const IgnoreProperty *ignore_properties;
      /* select value to set */
      g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
      pspec_select_value (pspec, &value, dvalue);
      /* ignore untestable properties */
      ignore_properties = list_ignore_properties (FALSE);
      for (i = 0; ignore_properties[i].name; i++)
240
        if (g_strcmp0 ("", ignore_properties[i].name) ||
241 242 243 244 245
            (g_type_is_a (G_OBJECT_TYPE (object), g_type_from_name (ignore_properties[i].type_name)) &&
             strcmp (pspec->name, ignore_properties[i].name) == 0 &&
             (MATCH_ANY_VALUE == ignore_properties[i].value ||
              value_as_pointer (&value) == ignore_properties[i].value ||
              (G_VALUE_HOLDS_STRING (&value) &&
246
               g_strcmp0 (g_value_get_string (&value), ignore_properties[i].value) == 0))))
247 248
          break;
      /* ignore known property bugs if not testing thoroughly */
249
      if (ignore_properties[i].name == NULL && !g_test_thorough ())
250 251 252 253 254 255
        {
          ignore_properties = list_ignore_properties (TRUE);
          for (i = 0; ignore_properties[i].name; i++)
            if (g_type_is_a (G_OBJECT_TYPE (object), g_type_from_name (ignore_properties[i].type_name)) &&
                strcmp (pspec->name, ignore_properties[i].name) == 0 &&
                (MATCH_ANY_VALUE == ignore_properties[i].value ||
256 257
                 value_as_pointer (&value) == ignore_properties[i].value ||
                 (G_VALUE_HOLDS_STRING (&value) &&
258
                  g_strcmp0 (g_value_get_string (&value), ignore_properties[i].value) == 0)))
259 260 261 262 263
              break;
        }
      /* assign unignored properties */
      if (ignore_properties[i].name == NULL)
        {
264
          if (g_test_verbose ())
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
            g_print ("PropertyTest: %s::%s := (%s value (%s): %p)\n",
                     g_type_name (G_OBJECT_TYPE (object)), pspec->name,
                     SELECT_NAME (dvalue), g_type_name (G_VALUE_TYPE (&value)),
                     value_as_pointer (&value));
          g_object_set_property (object, pspec->name, &value);
        }
      /* cleanups */
      g_value_unset (&value);
    }
}

static void
widget_test_properties (GtkWidget   *widget,
                        double       dvalue)
{
  /* try setting all possible properties, according to dvalue */
  guint i, n_pspecs = 0;
  GParamSpec **pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (widget), &n_pspecs);
  for (i = 0; i < n_pspecs; i++)
    {
      GParamSpec *pspec = pspecs[i];
      if (pspec->flags & G_PARAM_WRITABLE &&
          !(pspec->flags & (G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY)))
        object_test_property (G_OBJECT (widget), pspecs[i], dvalue);
    }
  g_free (pspecs);
}

static void
widget_fixups (GtkWidget *widget)
{
  /* post-constructor for widgets that need additional settings to work correctly */
Matthias Clasen's avatar
Matthias Clasen committed
297
  if (GTK_IS_COMBO_BOX_TEXT (widget))
298
    {
Matthias Clasen's avatar
Matthias Clasen committed
299
      gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (widget), "test text");
300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328
    }
}

static void
widget_property_tests (gconstpointer test_data)
{
  GType wtype = (GType) test_data;
  /* create widget */
  GtkWidget *widget = gtk_widget_new (wtype, NULL);
  g_object_ref_sink (widget);
  widget_fixups (widget);
  /* test property values */
  widget_test_properties (widget,  +2); /* test default_value */
  widget_test_properties (widget,   0); /* test minimum */
  widget_test_properties (widget, 0.5); /* test medium */
  widget_test_properties (widget,   1); /* test maximum */
  widget_test_properties (widget,  -1); /* test random value */
  /* cleanup */
  gtk_widget_destroy (widget);
  g_object_unref (widget);
}

/* --- main test program --- */
int
main (int   argc,
      char *argv[])
{
  const GType *otypes;
  guint i;
329 330 331 332
  gchar *schema_dir;

  g_setenv ("GSETTINGS_BACKEND", "memory", TRUE);

333 334
  /* initialize test program */
  gtk_test_init (&argc, &argv);
335
  gtk_test_register_all_types ();
336 337 338 339 340

  /* g_test_build_filename must be called after gtk_test_init */
  schema_dir = g_test_build_filename (G_TEST_BUILT, "", NULL);
  g_setenv ("GSETTINGS_SCHEMA_DIR", schema_dir, TRUE);

341 342 343 344 345 346 347 348 349 350 351
  /* install a property test for each widget type */
  otypes = gtk_test_list_all_types (NULL);
  for (i = 0; otypes[i]; i++)
    if (g_type_is_a (otypes[i], GTK_TYPE_WIDGET) &&
        G_TYPE_IS_OBJECT (otypes[i]) &&
        !G_TYPE_IS_ABSTRACT (otypes[i]))
      {
        gchar *testpath = g_strdup_printf ("/properties/%s", g_type_name (otypes[i]));
        g_test_add_data_func (testpath, (void*) otypes[i], widget_property_tests);
        g_free (testpath);
      }
352 353

  g_free (schema_dir);
354
  return g_test_run ();
355
}