#define GLIB_DISABLE_DEPRECATION_WARNINGS
#include <glib-object.h>
+#include <stdlib.h>
static void
test_param_value (void)
g_value_unset (&dest);
}
+
+/* We create some dummy objects with a simple relationship:
+ *
+ * GObject
+ * / \
+ * TestObjectA TestObjectC
+ * |
+ * TestObjectB
+ *
+ * ie: TestObjectB is a subclass of TestObjectA and TestObjectC is
+ * related to neither.
+ */
+
+static GType test_object_a_get_type (void);
+typedef GObject TestObjectA; typedef GObjectClass TestObjectAClass;
+G_DEFINE_TYPE (TestObjectA, test_object_a, G_TYPE_OBJECT);
+static void test_object_a_class_init (TestObjectAClass *class) { }
+static void test_object_a_init (TestObjectA *a) { }
+
+static GType test_object_b_get_type (void);
+typedef GObject TestObjectB; typedef GObjectClass TestObjectBClass;
+G_DEFINE_TYPE (TestObjectB, test_object_b, test_object_a_get_type ());
+static void test_object_b_class_init (TestObjectBClass *class) { }
+static void test_object_b_init (TestObjectB *b) { }
+
+static GType test_object_c_get_type (void);
+typedef GObject TestObjectC; typedef GObjectClass TestObjectCClass;
+G_DEFINE_TYPE (TestObjectC, test_object_c, G_TYPE_OBJECT);
+static void test_object_c_class_init (TestObjectCClass *class) { }
+static void test_object_c_init (TestObjectC *c) { }
+
+/* We create an interface and programmatically populate it with
+ * properties of each of the above type, with various flag combinations.
+ *
+ * Properties are named like "type-perm" where type is 'a', 'b' or 'c'
+ * and perm is a series of characters, indicating the permissions:
+ *
+ * - 'r': readable
+ * - 'w': writable
+ * - 'c': construct
+ * - 'C': construct-only
+ *
+ * It doesn't make sense to have a property that is neither readable nor
+ * writable. It is also not valid to have construct or construct-only
+ * on read-only params. Finally, it is invalid to have both construct
+ * and construct-only specified, so we do not consider those cases.
+ * That gives us 7 possible permissions:
+ *
+ * 'r', 'w', 'rw', 'wc', 'rwc', 'wC', 'rwC'
+ *
+ * And 9 impossible ones:
+ *
+ * '', 'c', 'rc', 'C', 'rC', 'cC', 'rcC', 'wcC', rwcC'
+ *
+ * For a total of 16 combinations.
+ *
+ * That gives a total of 48 (16 * 3) possible flag/type combinations, of
+ * which 27 (9 * 3) are impossible to install.
+ *
+ * That gives 21 (7 * 3) properties that will be installed.
+ */
+typedef GTypeInterface TestInterfaceInterface;
+static GType test_interface_get_type (void);
+//typedef struct _TestInterface TestInterface;
+G_DEFINE_INTERFACE (TestInterface, test_interface, G_TYPE_OBJECT)
+static void
+test_interface_default_init (TestInterfaceInterface *iface)
+{
+ const gchar *names[] = { "a", "b", "c" };
+ const gchar *perms[] = { NULL, "r", "w", "rw",
+ NULL, NULL, "wc", "rwc",
+ NULL, NULL, "wC", "rwC",
+ NULL, NULL, NULL, NULL };
+ const GType types[] = { test_object_a_get_type (), test_object_b_get_type (), test_object_c_get_type () };
+ guint i, j;
+
+ for (i = 0; i < G_N_ELEMENTS (types); i++)
+ for (j = 0; j < G_N_ELEMENTS (perms); j++)
+ {
+ gchar prop_name[10];
+ GParamSpec *pspec;
+
+ if (perms[j] == NULL)
+ {
+ if (!g_test_undefined ())
+ continue;
+
+ /* we think that this is impossible. make sure. */
+ pspec = g_param_spec_object ("xyz", "xyz", "xyz", types[i], j);
+
+ g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL,
+ "*assertion*pspec->flags*failed*");
+ g_object_interface_install_property (iface, pspec);
+ g_test_assert_expected_messages ();
+
+ g_param_spec_unref (pspec);
+ continue;
+ }
+
+ /* install the property */
+ g_snprintf (prop_name, sizeof prop_name, "%s-%s", names[i], perms[j]);
+ pspec = g_param_spec_object (prop_name, prop_name, prop_name, types[i], j);
+ g_object_interface_install_property (iface, pspec);
+ }
+}
+
+/* We now have 21 properties. Each property may be correctly
+ * implemented with the following types:
+ *
+ * Properties Valid Types Reason
+ *
+ * a-r a, b Read only can provide subclasses
+ * a-w, wc, wC a, GObject Write only can accept superclasses
+ * a-rw, rwc, rwC a Read-write must be exactly equal
+ *
+ * b-r b (as above)
+ * b-w, wc, wC b, a, GObject
+ * b-rw, rwc, rwC b
+ *
+ * c-r c (as above)
+ * c-wo, wc, wC c, GObject
+ * c-rw, rwc, rwC c
+ *
+ * We can express this in a 48-by-4 table where each row represents an
+ * installed property and each column represents a type. The value in
+ * the table represents if it is valid to subclass the row's property
+ * with the type of the column:
+ *
+ * - 0: invalid because the interface property doesn't exist (invalid flags)
+ * - 'v': valid
+ * - '=': invalid because of the type not being exactly equal
+ * - '<': invalid because of the type not being a subclass
+ * - '>': invalid because of the type not being a superclass
+ *
+ * We organise the table by interface property type ('a', 'b', 'c') then
+ * by interface property flags.
+ */
+
+static gint valid_impl_types[48][4] = {
+ /* a b c GObject */
+ /* 'a-' */ { },
+ /* 'a-r' */ { 'v', 'v', '<', '<' },
+ /* 'a-w' */ { 'v', '>', '>', 'v' },
+ /* 'a-rw' */ { 'v', '=', '=', '=' },
+ /* 'a-c */ { },
+ /* 'a-rc' */ { },
+ /* 'a-wc' */ { 'v', '>', '>', 'v' },
+ /* 'a-rwc' */ { 'v', '=', '=', '=' },
+ /* 'a-C */ { },
+ /* 'a-rC' */ { },
+ /* 'a-wC' */ { 'v', '>', '>', 'v' },
+ /* 'a-rwC' */ { 'v', '=', '=', '=' },
+ /* 'a-cC */ { },
+ /* 'a-rcC' */ { },
+ /* 'a-wcC' */ { },
+ /* 'a-rwcC' */ { },
+
+ /* 'b-' */ { },
+ /* 'b-r' */ { '<', 'v', '<', '<' },
+ /* 'b-w' */ { 'v', 'v', '>', 'v' },
+ /* 'b-rw' */ { '=', 'v', '=', '=' },
+ /* 'b-c */ { },
+ /* 'b-rc' */ { },
+ /* 'b-wc' */ { 'v', 'v', '>', 'v' },
+ /* 'b-rwc' */ { '=', 'v', '=', '=' },
+ /* 'b-C */ { },
+ /* 'b-rC' */ { },
+ /* 'b-wC' */ { 'v', 'v', '>', 'v' },
+ /* 'b-rwC' */ { '=', 'v', '=', '=' },
+ /* 'b-cC */ { },
+ /* 'b-rcC' */ { },
+ /* 'b-wcC' */ { },
+ /* 'b-rwcC' */ { },
+
+ /* 'c-' */ { },
+ /* 'c-r' */ { '<', '<', 'v', '<' },
+ /* 'c-w' */ { '>', '>', 'v', 'v' },
+ /* 'c-rw' */ { '=', '=', 'v', '=' },
+ /* 'c-c */ { },
+ /* 'c-rc' */ { },
+ /* 'c-wc' */ { '>', '>', 'v', 'v' },
+ /* 'c-rwc' */ { '=', '=', 'v', '=' },
+ /* 'c-C */ { },
+ /* 'c-rC' */ { },
+ /* 'c-wC' */ { '>', '>', 'v', 'v' },
+ /* 'c-rwC' */ { '=', '=', 'v', '=' },
+ /* 'c-cC */ { },
+ /* 'c-rcC' */ { },
+ /* 'c-wcC' */ { },
+ /* 'c-rwcC' */ { }
+};
+
+/* We also try to change the flags. We must ensure that all
+ * implementations provide all functionality promised by the interface.
+ * We must therefore never remove readability or writability (but we can
+ * add them). Construct-only is a restrictions that applies to
+ * writability, so we can never add it unless writability was never
+ * present in the first place, in which case "writable at construct
+ * only" is still better than "not writable".
+ *
+ * The 'construct' flag is of interest only to the implementation. It
+ * may be changed at any time.
+ *
+ * Properties Valid Access Reason
+ *
+ * *-r r, rw, rwc, rwC Must keep readable, but can restrict newly-added writable
+ * *-w w, rw, rwc Must keep writable unrestricted
+ * *-rw rw, rwc Must not add any restrictions
+ * *-rwc rw, rwc Must not add any restrictions
+ * *-rwC rw, rwc, rwC Can remove 'construct-only' restriction
+ * *-wc rwc, rw, w, wc Can add readability
+ * *-wC rwC, rw, w, wC Can add readability or remove 'construct only' restriction
+ * rwc, wc
+ *
+ * We can represent this with a 16-by-16 table. The rows represent the
+ * flags of the property on the interface. The columns is the flags to
+ * try to use when overriding the property. The cell contents are:
+ *
+ * - 0: invalid because the interface property doesn't exist (invalid flags)
+ * - 'v': valid
+ * - 'i': invalid because the implementation flags are invalid
+ * - 'f': invalid because of the removal of functionality
+ * - 'r': invalid because of the addition of restrictions (ie: construct-only)
+ *
+ * We also ensure that removal of functionality is reported before
+ * addition of restrictions, since this is a more basic problem.
+ */
+static gint valid_impl_flags[16][16] = {
+ /* '' r w rw c rc wc rwc C rC wC rwC cC rcC wcC rwcC */
+ /* '*-' */ { },
+ /* '*-r' */ { 'i', 'v', 'f', 'v', 'i', 'i', 'f', 'v', 'i', 'i', 'f', 'v', 'i', 'i', 'i', 'i' },
+ /* '*-w' */ { 'i', 'f', 'v', 'v', 'i', 'i', 'v', 'v', 'i', 'i', 'r', 'r', 'i', 'i', 'i', 'i' },
+ /* '*-rw' */ { 'i', 'f', 'f', 'v', 'i', 'i', 'f', 'v', 'i', 'i', 'f', 'r', 'i', 'i', 'i', 'i' },
+ /* '*-c */ { },
+ /* '*-rc' */ { },
+ /* '*-wc' */ { 'i', 'f', 'v', 'v', 'i', 'i', 'v', 'v', 'i', 'i', 'r', 'r', 'i', 'i', 'i', 'i' },
+ /* '*-rwc' */ { 'i', 'f', 'f', 'v', 'i', 'i', 'f', 'v', 'i', 'i', 'f', 'r', 'i', 'i', 'i', 'i' },
+ /* '*-C */ { },
+ /* '*-rC' */ { },
+ /* '*-wC' */ { 'i', 'f', 'v', 'v', 'i', 'i', 'v', 'v', 'i', 'i', 'v', 'v', 'i', 'i', 'i', 'i' },
+ /* '*-rwC' */ { 'i', 'f', 'f', 'v', 'i', 'i', 'f', 'v', 'i', 'i', 'f', 'v', 'i', 'i', 'i', 'i' },
+};
+
+static guint change_this_flag;
+static guint change_this_type;
+static guint use_this_flag;
+static guint use_this_type;
+
+typedef GObjectClass TestImplementationClass;
+typedef GObject TestImplementation;
+
+static void test_implementation_init (TestImplementation *impl) { }
+static void test_implementation_iface_init (TestInterfaceInterface *iface) { }
+
+static GType test_implementation_get_type (void);
+G_DEFINE_TYPE_WITH_CODE (TestImplementation, test_implementation, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (test_interface_get_type (), test_implementation_iface_init))
+
+static void test_implementation_class_init (TestImplementationClass *class)
+{
+ const gchar *names[] = { "a", "b", "c" };
+ const gchar *perms[] = { NULL, "r", "w", "rw",
+ NULL, NULL, "wc", "rwc",
+ NULL, NULL, "wC", "rwC",
+ NULL, NULL, NULL, NULL };
+ const GType types[] = { test_object_a_get_type (), test_object_b_get_type (),
+ test_object_c_get_type (), G_TYPE_OBJECT };
+ gchar prop_name[10];
+ GParamSpec *pspec;
+ guint i, j;
+
+ class->get_property = GINT_TO_POINTER (1);
+ class->set_property = GINT_TO_POINTER (1);
+
+ /* Install all of the non-modified properties or else GObject will
+ * complain about non-implemented properties.
+ */
+ for (i = 0; i < 3; i++)
+ for (j = 0; j < G_N_ELEMENTS (perms); j++)
+ {
+ if (i == change_this_type && j == change_this_flag)
+ continue;
+
+ if (perms[j] != NULL)
+ {
+ /* override the property without making changes */
+ g_snprintf (prop_name, sizeof prop_name, "%s-%s", names[i], perms[j]);
+ g_object_class_override_property (class, 1, prop_name);
+ }
+ }
+
+ /* Now try installing our modified property */
+ if (perms[change_this_flag] == NULL)
+ g_error ("Interface property does not exist");
+
+ g_snprintf (prop_name, sizeof prop_name, "%s-%s", names[change_this_type], perms[change_this_flag]);
+ pspec = g_param_spec_object (prop_name, prop_name, prop_name, types[use_this_type], use_this_flag);
+ g_object_class_install_property (class, 1, pspec);
+}
+
+typedef struct {
+ gint change_this_flag;
+ gint change_this_type;
+ gint use_this_flag;
+ gint use_this_type;
+} TestParamImplementData;
+
+static void
+test_param_implement_child (gconstpointer user_data)
+{
+ TestParamImplementData *data = (gpointer) user_data;
+
+ /* GObject oddity: GObjectClass must be initialised before we can
+ * initialise a GTypeInterface.
+ */
+ g_type_class_ref (G_TYPE_OBJECT);
+
+ /* Bring up the interface first. */
+ g_type_default_interface_ref (test_interface_get_type ());
+
+ /* Copy the flags into the global vars so
+ * test_implementation_class_init() will see them.
+ */
+ change_this_flag = data->change_this_flag;
+ change_this_type = data->change_this_type;
+ use_this_flag = data->use_this_flag;
+ use_this_type = data->use_this_type;
+
+ g_type_class_ref (test_implementation_get_type ());
+}
+
+static void
+test_param_implement (void)
+{
+ gchar *test_path;
+
+ for (change_this_flag = 0; change_this_flag < 16; change_this_flag++)
+ for (change_this_type = 0; change_this_type < 3; change_this_type++)
+ for (use_this_flag = 0; use_this_flag < 16; use_this_flag++)
+ for (use_this_type = 0; use_this_type < 4; use_this_type++)
+ {
+ if (!g_test_undefined ())
+ {
+ /* only test the valid (defined) cases, e.g. under valgrind */
+ if (valid_impl_flags[change_this_flag][use_this_flag] != 'v')
+ continue;
+
+ if (valid_impl_types[change_this_type * 16 + change_this_flag][use_this_type] != 'v')
+ continue;
+ }
+
+ test_path = g_strdup_printf ("/param/implement/subprocess/%d-%d-%d-%d",
+ change_this_flag, change_this_type,
+ use_this_flag, use_this_type);
+ g_test_trap_subprocess (test_path, G_TIME_SPAN_SECOND, 0);
+ g_free (test_path);
+
+ /* We want to ensure that any flags mismatch problems are reported first. */
+ switch (valid_impl_flags[change_this_flag][use_this_flag])
+ {
+ case 0:
+ /* make sure the other table agrees */
+ g_assert (valid_impl_types[change_this_type * 16 + change_this_flag][use_this_type] == 0);
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*Interface property does not exist*");
+ continue;
+
+ case 'i':
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*g_object_class_install_property*");
+ continue;
+
+ case 'f':
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*remove functionality*");
+ continue;
+
+ case 'r':
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*introduce additional restrictions*");
+ continue;
+
+ case 'v':
+ break;
+ }
+
+ /* Next, we check if there should have been a type error. */
+ switch (valid_impl_types[change_this_type * 16 + change_this_flag][use_this_type])
+ {
+ case 0:
+ /* this should have been caught above */
+ g_assert_not_reached ();
+
+ case '=':
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*exactly equal*");
+ continue;
+
+ case '<':
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*equal to or more restrictive*");
+ continue;
+
+ case '>':
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*equal to or less restrictive*");
+ continue;
+
+ case 'v':
+ break;
+ }
+
+ g_test_trap_assert_passed ();
+ }
+}
+
+static void
+test_param_default (void)
+{
+ GParamSpec *param;
+ const GValue *def;
+
+ param = g_param_spec_int ("my-int", "My Int", "Blurb", 0, 20, 10, G_PARAM_READWRITE);
+ def = g_param_spec_get_default_value (param);
+
+ g_assert (G_VALUE_HOLDS (def, G_TYPE_INT));
+ g_assert_cmpint (g_value_get_int (def), ==, 10);
+
+ g_param_spec_unref (param);
+}
+
int
main (int argc, char *argv[])
{
- g_type_init ();
+ TestParamImplementData data, *test_data;
+ gchar *test_path;
+
g_test_init (&argc, &argv, NULL);
g_test_add_func ("/param/value", test_param_value);
g_test_add_func ("/param/qdata", test_param_qdata);
g_test_add_func ("/param/validate", test_param_validate);
g_test_add_func ("/param/convert", test_param_convert);
+
+ g_test_add_func ("/param/implement", test_param_implement);
+ for (data.change_this_flag = 0; data.change_this_flag < 16; data.change_this_flag++)
+ for (data.change_this_type = 0; data.change_this_type < 3; data.change_this_type++)
+ for (data.use_this_flag = 0; data.use_this_flag < 16; data.use_this_flag++)
+ for (data.use_this_type = 0; data.use_this_type < 4; data.use_this_type++)
+ {
+ test_path = g_strdup_printf ("/param/implement/subprocess/%d-%d-%d-%d",
+ data.change_this_flag, data.change_this_type,
+ data.use_this_flag, data.use_this_type);
+ test_data = g_memdup (&data, sizeof (TestParamImplementData));
+ g_test_add_data_func_full (test_path, test_data, test_param_implement_child, g_free);
+ g_free (test_path);
+ }
+
g_test_add_func ("/value/transform", test_value_transform);
+ g_test_add_func ("/param/default", test_param_default);
return g_test_run ();
}