From db40a593b3557e86e8da8ecd594887334b728bca Mon Sep 17 00:00:00 2001 From: George Kiagiadakis <george.kiagiadakis@collabora.com> Date: Fri, 1 May 2020 17:51:12 +0300 Subject: [PATCH] lib: implement WpObjectInterest --- lib/wp/meson.build | 2 + lib/wp/object-interest.c | 830 ++++++++++++++++++++++++++++++++++++ lib/wp/object-interest.h | 105 +++++ lib/wp/wp.h | 1 + tests/wp/meson.build | 7 + tests/wp/object-interest.c | 843 +++++++++++++++++++++++++++++++++++++ 6 files changed, 1788 insertions(+) create mode 100644 lib/wp/object-interest.c create mode 100644 lib/wp/object-interest.h create mode 100644 tests/wp/object-interest.c diff --git a/lib/wp/meson.build b/lib/wp/meson.build index 53351e12..7de804a2 100644 --- a/lib/wp/meson.build +++ b/lib/wp/meson.build @@ -14,6 +14,7 @@ wp_lib_sources = files( 'link.c', 'module.c', 'node.c', + 'object-interest.c', 'object-manager.c', 'policy.c', 'port.c', @@ -46,6 +47,7 @@ wp_lib_headers = files( 'link.h', 'module.h', 'node.h', + 'object-interest.h', 'object-manager.h', 'policy.h', 'port.h', diff --git a/lib/wp/object-interest.c b/lib/wp/object-interest.c new file mode 100644 index 00000000..e8fee86d --- /dev/null +++ b/lib/wp/object-interest.c @@ -0,0 +1,830 @@ +/* WirePlumber + * + * Copyright © 2020 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +/** + * SECTION: object-interest + * @title: Object Interest + */ + +#define G_LOG_DOMAIN "wp-object-interest" + +#include "object-interest.h" +#include "debug.h" +#include "error.h" +#include "private.h" + +struct constraint +{ + WpConstraintType type; + WpConstraintVerb verb; + gchar subject_type; /* a basic GVariantType as a single char */ + gchar *subject; + GVariant *value; +}; + +struct _WpObjectInterest +{ + gboolean valid; + GType gtype; + struct pw_array constraints; +}; + +/** + * WpObjectInterest: + * An object interest is a helper that is used in #WpObjectManager to + * declare interest in certain kinds of objects. + * + * An interest is defined by a #GType and a set of constraints on the object's + * properties. An object "matches" the interest if it is of the specified + * #GType (either the same type or a descendant of it) and all the constraints + * are satisfied. + */ +G_DEFINE_BOXED_TYPE (WpObjectInterest, wp_object_interest, + wp_object_interest_copy, wp_object_interest_free) + +/** + * wp_object_interest_new: + * @gtype: the type of the object to declare interest in + * @...: a set of constraints, terminated with %NULL + * + * Creates a new interest that declares interest in objects of the specified + * @gtype, with the constraints specified in the variable arguments. + * + * The variable arguments should be a list of constraints terminated with %NULL, + * where each constraint consists of the following arguments: + * - a #WpConstraintType: the constraint type + * - a `const gchar *`: the subject name + * - a `const gchar *`: the format string + * - 0 or more arguments according to the format string + * + * The format string is interpreted as follows: + * - the first character is the constraint verb: + * - `=`: %WP_CONSTRAINT_VERB_EQUALS + * - `c`: %WP_CONSTRAINT_VERB_IN_LIST + * - `~`: %WP_CONSTRAINT_VERB_IN_RANGE + * - `#`: %WP_CONSTRAINT_VERB_MATCHES + * - `+`: %WP_CONSTRAINT_VERB_IS_PRESENT + * - `-`: %WP_CONSTRAINT_VERB_IS_ABSENT + * - the rest of the characters are interpreted as a #GVariant format string, + * as it would be used in g_variant_new() + * + * The rest of this function's arguments up to the start of the next constraint + * depend on the #GVariant format part of the format string and are used to + * construct a #GVariant for the constraint's value argument. + * + * For further reading on the constraint's arguments, see + * wp_object_interest_add_constraint() + * + * For example, this interest matches objects that are descendands of #WpProxy + * with a "bound-id" between 0 and 100 (inclusive), with a pipewire property + * called "format.dsp" that contains the string "audio" somewhere in the value + * and with a pipewire property "port.name" being present (with any value): + * |[ + * interest = wp_object_interest_new (WP_TYPE_PROXY, + * WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "~(uu)", 0, 100, + * WP_CONSTRAINT_TYPE_PW_PROPERTY, "format.dsp", "#s", "*audio*", + * WP_CONSTRAINT_TYPE_PW_PROPERTY, "port.name", "+", + * NULL); + * ]| + * + * Returns: (transfer full): the new object interest + */ +WpObjectInterest * +wp_object_interest_new (GType gtype, ...) +{ + WpObjectInterest *self; + va_list args; + va_start (args, gtype); + self = wp_object_interest_new_valist (gtype, &args); + va_end (args); + return self; +} + +/** + * wp_object_interest_new_valist: + * @gtype: the type of the object to declare interest in + * @args: pointer to va_list containing the constraints + * + * va_list version of wp_object_interest_new() + * + * Returns: (transfer full): the new object interest + */ +WpObjectInterest * +wp_object_interest_new_valist (GType gtype, va_list *args) +{ + WpObjectInterest *self = wp_object_interest_new_type (gtype); + WpConstraintType type; + + g_return_val_if_fail (self != NULL, NULL); + + for (type = va_arg (*args, WpConstraintType); + type != WP_CONSTRAINT_TYPE_NONE; + type = va_arg (*args, WpConstraintType)) + { + const gchar *subject, *format; + WpConstraintVerb verb = 0; + GVariant *value = NULL; + + subject = va_arg (*args, const gchar *); + g_return_val_if_fail (subject != NULL, NULL); + + format = va_arg (*args, const gchar *); + g_return_val_if_fail (format != NULL, NULL); + + verb = format[0]; + if (verb != 0 && format[1] != '\0') + value = g_variant_new_va (format + 1, NULL, args); + + wp_object_interest_add_constraint (self, type, subject, verb, value); + } + return self; +} + +/** + * wp_object_interest_new_type: (rename-to wp_object_interest_new) + * @gtype: the type of the object to declare interest in + * + * Creates a new interest that declares interest in objects of the specified + * @gtype, without any property constraints. To add property constraints, + * you can call wp_object_interest_add_constraint() afterwards. + * + * Returns: (transfer full): the new object interest + */ +WpObjectInterest * +wp_object_interest_new_type (GType gtype) +{ + WpObjectInterest *self = g_slice_new0 (WpObjectInterest); + g_return_val_if_fail (self != NULL, NULL); + self->gtype = gtype; + pw_array_init (&self->constraints, sizeof (struct constraint)); + return self; +} + +/** + * wp_object_interest_add_constraint: + * @self: the object interest + * @type: the constraint type + * @subject: the subject that the constraint applies to + * @verb: the operation that is performed to check the constraint + * @value: (transfer floating)(nullable): the value to check for + * + * Adds a constraint to this interest. Constraints consist of a @type, + * a @subject, a @verb and, depending on the @verb, a @value. + * + * Constraints are almost like a spoken language sentence that declare a + * condition that must be true in order to consider that an object can match + * this interest. For instance, a constraint can be "pipewire property + * 'object.id' equals 10". This would be translated to: + * |[ + * wp_object_interest_add_constraint (i, + * WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", + * WP_CONSTRAINT_VERB_EQUALS, g_variant_new_int (10)); + * ]| + * + * Some verbs require a @value and some others do not. For those that do, + * the @value must be of a specific type: + * - %WP_CONSTRAINT_VERB_EQUALS: @value can be a string, a (u)int32, + * a (u)int64, a double or a boolean. The @subject value must equal this + * value for the constraint to be satisfied + * - %WP_CONSTRAINT_VERB_IN_LIST: @value must be a tuple that contains any + * number of items of the same type; the items can be string, (u)int32, + * (u)int64 or double. These items make a list that the @subject's value + * will be checked against. If any of the items equals the @subject value, + * the constraint is satisfied + * - %WP_CONSTRAINT_VERB_IN_RANGE: @value must be a tuple that contains exactly + * 2 numbers of the same type ((u)int32, (u)int64 or double), meaning the + * minimum and maximum (inclusive) of the range. If the @subject value is a + * number within this range, the constraint is satisfied + * - %WP_CONSTRAINT_VERB_MATCHES: @value must be a string that defines a + * pattern usable with #GPatternSpec. If the @subject value matches this + * pattern, the constraint is satisfied + * + * In case the type of the @subject value is not the same type as the one + * requested by the type of the @value, the @subject value is converted. + * For #GObject properties, this conversion is done using g_value_transform(), + * so limitations of this function apply. In the case of PipeWire properties, + * which are *always* strings, conversion is done as follows: + * - to boolean: `"true"` or `"1"` means %TRUE, `"false"` or `"0"` means %FALSE + * - to int / uint / int64 / uint64: One of the `strtol()` family of functions + * is used to convert, using base 10 + * - to double: `strtod()` is used + * + * This method does not fail if invalid arguments are given. However, + * wp_object_interest_validate() should be called after adding all the + * constraints on an interest in order to catch errors. + */ +void +wp_object_interest_add_constraint (WpObjectInterest * self, + WpConstraintType type, const gchar * subject, + WpConstraintVerb verb, GVariant * value) +{ + struct constraint *c; + + g_return_if_fail (self != NULL); + + c = pw_array_add (&self->constraints, sizeof (struct constraint)); + g_return_if_fail (c != NULL); + c->type = type; + c->verb = verb; + /* subject_type is filled in by _validate() */ + c->subject_type = '\0'; + c->subject = g_strdup (subject); + c->value = value ? g_variant_ref_sink (value) : NULL; + + /* mark as invalid to force validation */ + self->valid = FALSE; +} + +/** + * wp_object_interest_copy: + * @self: the object interest to copy + * + * Returns: (transfer full): a deep copy of @self + */ +WpObjectInterest * +wp_object_interest_copy (WpObjectInterest * self) +{ + WpObjectInterest *copy; + struct constraint *c, *cc; + + g_return_val_if_fail (self != NULL, NULL); + + copy = wp_object_interest_new_type (self->gtype); + g_return_val_if_fail (copy != NULL, NULL); + + pw_array_ensure_size (©->constraints, self->constraints.size); + pw_array_for_each (c, &self->constraints) { + cc = pw_array_add (&self->constraints, sizeof (struct constraint)); + g_return_val_if_fail (cc != NULL, NULL); + cc->type = c->type; + cc->verb = c->verb; + cc->subject_type = c->subject_type; + cc->subject = g_strdup (c->subject); + cc->value = c->value ? g_variant_ref (c->value) : NULL; + } + copy->valid = self->valid; + + return copy; +} + +/** + * wp_object_interest_free: + * @self: (transfer full): the object interest to free + * + * Releases @self and all the memory associated with it + */ +void +wp_object_interest_free (WpObjectInterest * self) +{ + struct constraint *c; + + g_return_if_fail (self != NULL); + + pw_array_for_each (c, &self->constraints) { + g_clear_pointer (&c->subject, g_free); + g_clear_pointer (&c->value, g_variant_unref); + } + pw_array_clear (&self->constraints); + g_slice_free (WpObjectInterest, self); +} + +/** + * wp_object_interest_validate: + * @self: the object interest to validate + * @error: (out) (optional): the error, in case validation failed + * + * Validates the interest, ensuring that the interest #GType is a valid + * object and that all the constraints have been expressed properly. + * + * Returns: %TRUE if the interest is valid and can be used in a match, + * %FALSE otherwise + */ +gboolean +wp_object_interest_validate (WpObjectInterest * self, GError ** error) +{ + struct constraint *c; + gboolean is_proxy; + + g_return_val_if_fail (self != NULL, FALSE); + + /* if already validated, we are done */ + if (self->valid) + return TRUE; + + if (!G_TYPE_IS_OBJECT (self->gtype)) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "type '%s' is not a GObject", g_type_name (self->gtype)); + return FALSE; + } + + is_proxy = g_type_is_a (self->gtype, WP_TYPE_PROXY); + + pw_array_for_each (c, &self->constraints) { + const GVariantType *value_type = NULL; + + if (c->type <= WP_CONSTRAINT_TYPE_NONE || + c->type > WP_CONSTRAINT_TYPE_G_PROPERTY) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "invalid constraint type %d", c->type); + return FALSE; + } + + if (!is_proxy && (c->type == WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY || + c->type == WP_CONSTRAINT_TYPE_PW_PROPERTY)) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "constraint type %d cannot apply to non-WpProxy type '%s'", + c->type, g_type_name (self->gtype)); + return FALSE; + } + + if (!c->subject) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "constraint subject cannot be NULL"); + return FALSE; + } + + switch (c->verb) { + case WP_CONSTRAINT_VERB_EQUALS: + case WP_CONSTRAINT_VERB_IN_LIST: + case WP_CONSTRAINT_VERB_IN_RANGE: + case WP_CONSTRAINT_VERB_MATCHES: + if (!c->value) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "verb %d (%c) requires a value", c->verb, (gchar) c->verb); + return FALSE; + } + value_type = g_variant_get_type (c->value); + break; + + case WP_CONSTRAINT_VERB_IS_PRESENT: + case WP_CONSTRAINT_VERB_IS_ABSENT: + if (c->value) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "verb %d (%c) should not have a value", c->verb, (gchar) c->verb); + return FALSE; + } + break; + + default: + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "invalid constraint verb %d (%c)", c->verb, (gchar) c->verb); + return FALSE; + } + + switch (c->verb) { + case WP_CONSTRAINT_VERB_EQUALS: + if (!g_variant_type_equal (value_type, G_VARIANT_TYPE_STRING) && + !g_variant_type_equal (value_type, G_VARIANT_TYPE_BOOLEAN) && + !g_variant_type_equal (value_type, G_VARIANT_TYPE_INT32) && + !g_variant_type_equal (value_type, G_VARIANT_TYPE_UINT32) && + !g_variant_type_equal (value_type, G_VARIANT_TYPE_INT64) && + !g_variant_type_equal (value_type, G_VARIANT_TYPE_UINT64) && + !g_variant_type_equal (value_type, G_VARIANT_TYPE_DOUBLE)) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "WP_CONSTRAINT_VERB_EQUALS requires a basic GVariant type" + " (actual type was '%s')", g_variant_get_type_string (c->value)); + return FALSE; + } + + break; + case WP_CONSTRAINT_VERB_IN_LIST: { + const GVariantType *tuple_type; + + if (!g_variant_type_is_definite (value_type) || + !g_variant_type_is_tuple (value_type)) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "WP_CONSTRAINT_VERB_IN_LIST requires a tuple GVariant type" + " (actual type was '%s')", g_variant_get_type_string (c->value)); + return FALSE; + } + + for (tuple_type = value_type = g_variant_type_first (value_type); + tuple_type != NULL; + tuple_type = g_variant_type_next (tuple_type)) { + if (!g_variant_type_equal (tuple_type, value_type)) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "tuple must contain children of the same type" + " (mismatching type was '%s' at '%.*s')", + g_variant_get_type_string (c->value), + (int) g_variant_type_get_string_length (tuple_type), + g_variant_type_peek_string (tuple_type)); + return FALSE; + } + } + + if (!g_variant_type_equal (value_type, G_VARIANT_TYPE_STRING) && + !g_variant_type_equal (value_type, G_VARIANT_TYPE_INT32) && + !g_variant_type_equal (value_type, G_VARIANT_TYPE_UINT32) && + !g_variant_type_equal (value_type, G_VARIANT_TYPE_INT64) && + !g_variant_type_equal (value_type, G_VARIANT_TYPE_UINT64) && + !g_variant_type_equal (value_type, G_VARIANT_TYPE_DOUBLE)) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "list tuple must contain string, (u)int32, (u)int64 or double" + " (mismatching type was '%s' at '%.*s')", + g_variant_get_type_string (c->value), + (int) g_variant_type_get_string_length (value_type), + g_variant_type_peek_string (value_type)); + return FALSE; + } + + break; + } + case WP_CONSTRAINT_VERB_IN_RANGE: { + const GVariantType *tuple_type; + + if (!g_variant_type_is_definite (value_type) || + !g_variant_type_is_tuple (value_type)) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "range requires a tuple GVariant type (actual type was '%s')", + g_variant_get_type_string (c->value)); + return FALSE; + } + + tuple_type = value_type = g_variant_type_first (value_type); + if (!tuple_type) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "range requires a non-empty tuple (actual type was '%s')", + g_variant_get_type_string (c->value)); + return FALSE; + } + + if (!g_variant_type_equal (tuple_type, G_VARIANT_TYPE_INT32) && + !g_variant_type_equal (tuple_type, G_VARIANT_TYPE_UINT32) && + !g_variant_type_equal (tuple_type, G_VARIANT_TYPE_INT64) && + !g_variant_type_equal (tuple_type, G_VARIANT_TYPE_UINT64) && + !g_variant_type_equal (tuple_type, G_VARIANT_TYPE_DOUBLE)) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "range tuple must contain (u)int32, (u)int64 or double" + " (mismatching type was '%s' at '%.*s')", + g_variant_get_type_string (c->value), + (int) g_variant_type_get_string_length (tuple_type), + g_variant_type_peek_string (tuple_type)); + return FALSE; + } + + tuple_type = g_variant_type_next (tuple_type); + if (!tuple_type || !g_variant_type_equal (tuple_type, value_type)) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "range tuple must contain 2 children of the same type" + " (mismatching type was '%s' at '%.*s')", + g_variant_get_type_string (c->value), + (int) g_variant_type_get_string_length (tuple_type), + g_variant_type_peek_string (tuple_type)); + return FALSE; + } + + tuple_type = g_variant_type_next (tuple_type); + if (tuple_type) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "range tuple must contain exactly 2 children, not more" + " (mismatching type was '%s')", + g_variant_get_type_string (c->value)); + return FALSE; + } + + break; + } + case WP_CONSTRAINT_VERB_MATCHES: + if (!g_variant_type_equal (value_type, G_VARIANT_TYPE_STRING)) { + g_set_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT, + "WP_CONSTRAINT_VERB_MATCHES requires a string GVariant" + " (actual type was '%s')", g_variant_get_type_string (c->value)); + return FALSE; + } + + break; + case WP_CONSTRAINT_VERB_IS_PRESENT: + case WP_CONSTRAINT_VERB_IS_ABSENT: + break; + default: + g_return_val_if_reached (FALSE); + } + + /* cache the type that the property must have */ + if (value_type) + c->subject_type = *g_variant_type_peek_string (value_type); + } + + return (self->valid = TRUE); +} + +G_GNUC_CONST static GType +subject_type_to_gtype (gchar type) +{ + switch (type) { + case 'b': return G_TYPE_BOOLEAN; + case 'i': return G_TYPE_INT; + case 'u': return G_TYPE_UINT; + case 'x': return G_TYPE_INT64; + case 't': return G_TYPE_UINT64; + case 'd': return G_TYPE_DOUBLE; + case 's': return G_TYPE_STRING; + default: g_return_val_if_reached (G_TYPE_INVALID); + } +} + +static inline gboolean +property_string_to_gvalue (gchar subj_type, const gchar * str, GValue * val) +{ + g_value_init (val, subject_type_to_gtype (subj_type)); + + switch (subj_type) { + case 'b': + if (!strcmp (str, "true") || !strcmp (str, "1")) + g_value_set_boolean (val, TRUE); + else if (!strcmp (str, "false") || !strcmp (str, "0")) + g_value_set_boolean (val, FALSE); + else { + wp_trace ("failed to convert '%s' to boolean", str); + return FALSE; + } + break; + case 's': + g_value_set_static_string (val, str); + break; + +#define CASE_NUMBER(l, T, convert) \ + case l: { \ + g##T number; \ + errno = 0; \ + number = convert; \ + if (errno != 0) { \ + wp_trace ("failed to convert '%s' to " #T, str); \ + return FALSE; \ + } \ + g_value_set_##T (val, number); \ + break; \ + } + CASE_NUMBER ('i', int, strtol (str, NULL, 10)) + CASE_NUMBER ('u', uint, strtoul (str, NULL, 10)) + CASE_NUMBER ('x', int64, strtoll (str, NULL, 10)) + CASE_NUMBER ('t', uint64, strtoull (str, NULL, 10)) + CASE_NUMBER ('d', double, strtod (str, NULL)) +#undef CASE_NUMBER + default: + g_return_val_if_reached (FALSE); + } + return TRUE; +} + +static inline gboolean +constraint_verb_equals (gchar subj_type, const GValue * subj_val, + GVariant * check_val) +{ + switch (subj_type) { + case 'd': { + gdouble a = g_value_get_double (subj_val); + gdouble b = g_variant_get_double (check_val); + return G_APPROX_VALUE (a, b, FLT_EPSILON); + } + case 's': + return !g_strcmp0 (g_value_get_string (subj_val), + g_variant_get_string (check_val, NULL)); +#define CASE_BASIC(l, T, R) \ + case l: \ + return (g_value_get_##T (subj_val) == g_variant_get_##R (check_val)); + CASE_BASIC ('b', boolean, boolean) + CASE_BASIC ('i', int, int32) + CASE_BASIC ('u', uint, uint32) + CASE_BASIC ('x', int64, int64) + CASE_BASIC ('t', uint64, uint64) +#undef CASE_BASIC + default: + g_return_val_if_reached (FALSE); + } +} + +static inline gboolean +constraint_verb_matches (gchar subj_type, const GValue * subj_val, + GVariant * check_val) +{ + switch (subj_type) { + case 's': + return g_pattern_match_simple (g_variant_get_string (check_val, NULL), + g_value_get_string (subj_val)); + default: + g_return_val_if_reached (FALSE); + } + return TRUE; +} + +static inline gboolean +constraint_verb_in_list (gchar subj_type, const GValue * subj_val, + GVariant * check_val) +{ + GVariantIter iter; + g_autoptr (GVariant) child = NULL; + + g_variant_iter_init (&iter, check_val); + while ((child = g_variant_iter_next_value (&iter))) { + if (constraint_verb_equals (subj_type, subj_val, child)) + return TRUE; + g_variant_unref (child); + } + return FALSE; +} + +static inline gboolean +constraint_verb_in_range (gchar subj_type, const GValue * subj_val, + GVariant * check_val) +{ + switch (subj_type) { +#define CASE_RANGE(l, t, T) \ + case l: { \ + g##T val, min, max; \ + g_variant_get (check_val, "("#t#t")", &min, &max); \ + val = g_value_get_##T (subj_val); \ + if (val < min || val > max) \ + return FALSE; \ + break; \ + } + CASE_RANGE('i', i, int) + CASE_RANGE('u', u, uint) + CASE_RANGE('x', x, int64) + CASE_RANGE('t', t, uint64) + CASE_RANGE('d', d, double) +#undef CASE_RANGE + default: + g_return_val_if_reached (FALSE); + } + return TRUE; +} + +/** + * wp_object_interest_matches: + * @self: the object interest + * @object: the target object to check for a match + * + * Checks if the specified @object matches the type and all the constraints + * that are described in @self + * + * Equivalent to `wp_object_interest_matches_full (self, + * G_OBJECT_TYPE (object), object, NULL, NULL)` + * + * Returns: %TRUE if the object matches, %FALSE otherwise + */ +gboolean +wp_object_interest_matches (WpObjectInterest * self, gpointer object) +{ + return wp_object_interest_matches_full (self, G_OBJECT_TYPE (object), + object, NULL, NULL); +} + +/** + * wp_object_interest_matches_full: + * @self: the object interest + * @object_type: the type to be checked against the interest's type + * @object: (type GObject)(transfer none)(nullable): the object to be used for + * checking constraints of type %WP_CONSTRAINT_TYPE_G_PROPERTY + * @pw_props: (transfer none)(nullable): the properties to be used for + * checking constraints of type %WP_CONSTRAINT_TYPE_PW_PROPERTY + * @pw_global_props: (transfer none)(nullable): the properties to be used for + * checking constraints of type %WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY + * + * A low-level version of wp_object_interest_matches(). In this version, + * the object's type is directly given in @object_type and is not inferred + * from the @object. @object is only used to check for constraints against + * #GObject properties. + * + * @pw_props and @pw_global_props are used to check constraints against + * PipeWire object properties and global properties, respectively. + * + * @object, @pw_props and @pw_global_props may be %NULL, but in case there + * are any constraints that require them, the match will fail. + * As a special case, if @object is not %NULL and is a subclass of #WpProxy, + * then @pw_props and @pw_global_props, if required, will be internally + * retrieved from @object by calling wp_proxy_get_properties() and + * wp_proxy_get_global_properties() respectively. + * + * Returns: %TRUE if the the type matches this interest and the properties + * match the constraints, %FALSE otherwise + */ +gboolean +wp_object_interest_matches_full (WpObjectInterest * self, + GType object_type, gpointer object, WpProperties * pw_props, + WpProperties * pw_global_props) +{ + g_autoptr (WpProperties) props = NULL; + g_autoptr (WpProperties) global_props = NULL; + g_autoptr (GError) error = NULL; + struct constraint *c; + + g_return_val_if_fail (self != NULL, FALSE); + + if (G_UNLIKELY (!wp_object_interest_validate (self, &error))) { + wp_critical_boxed (WP_TYPE_OBJECT_INTEREST, self, "validation failed: %s", + error->message); + return FALSE; + } + + /* check if the GType matches */ + if (!g_type_is_a (object_type, self->gtype)) + return FALSE; + + /* prepare for constraint lookups on proxy properties */ + if (object && g_type_is_a (object_type, WP_TYPE_PROXY)) { + WpProxy *p = WP_PROXY (object); + + if (!pw_global_props) + pw_global_props = global_props = wp_proxy_get_global_properties (p); + + if (!pw_props && wp_proxy_get_features (p) & WP_PROXY_FEATURE_INFO) + pw_props = props = wp_proxy_get_properties (p); + } + + /* check all constraints; if any of them fails at any point, fail the match */ + pw_array_for_each (c, &self->constraints) { + WpProperties *lookup_props = pw_global_props; + g_auto (GValue) value = G_VALUE_INIT; + gboolean exists = FALSE; + + /* collect, check & convert the subject property */ + switch (c->type) { + case WP_CONSTRAINT_TYPE_PW_PROPERTY: + lookup_props = pw_props; + G_GNUC_FALLTHROUGH; + + case WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY: { + const gchar *lookup_str = NULL; + + if (lookup_props) + exists = !!(lookup_str = wp_properties_get (lookup_props, c->subject)); + + if (exists && c->subject_type) + property_string_to_gvalue (c->subject_type, lookup_str, &value); + break; + } + case WP_CONSTRAINT_TYPE_G_PROPERTY: { + GType value_type; + + if (object) + exists = !!g_object_class_find_property (G_OBJECT_GET_CLASS (object), c->subject); + + if (exists && c->subject_type) { + g_object_get_property (object, c->subject, &value); + value_type = G_VALUE_TYPE (&value); + + /* transform if not compatible */ + if (value_type != subject_type_to_gtype (c->subject_type)) { + if (g_value_type_transformable (value_type, + subject_type_to_gtype (c->subject_type))) { + g_auto (GValue) orig = G_VALUE_INIT; + g_value_init (&orig, value_type); + g_value_copy (&value, &orig); + g_value_unset (&value); + g_value_init (&value, subject_type_to_gtype (c->subject_type)); + g_value_transform (&orig, &value); + } + else + return FALSE; + } + } + + break; + } + default: + g_return_val_if_reached (FALSE); + } + + /* match the subject to the constraint's value, + according to the operation defined by the verb */ + switch (c->verb) { + case WP_CONSTRAINT_VERB_EQUALS: + if (!exists || + !constraint_verb_equals (c->subject_type, &value, c->value)) + return FALSE; + break; + case WP_CONSTRAINT_VERB_MATCHES: + if (!exists || + !constraint_verb_matches (c->subject_type, &value, c->value)) + return FALSE; + break; + case WP_CONSTRAINT_VERB_IN_LIST: + if (!exists || + !constraint_verb_in_list (c->subject_type, &value, c->value)) + return FALSE; + break; + case WP_CONSTRAINT_VERB_IN_RANGE: + if (!exists || + !constraint_verb_in_range (c->subject_type, &value, c->value)) + return FALSE; + break; + case WP_CONSTRAINT_VERB_IS_PRESENT: + if (!exists) + return FALSE; + break; + case WP_CONSTRAINT_VERB_IS_ABSENT: + if (exists) + return FALSE; + break; + default: + g_return_val_if_reached (FALSE); + } + } + return TRUE; +} diff --git a/lib/wp/object-interest.h b/lib/wp/object-interest.h new file mode 100644 index 00000000..b52853c7 --- /dev/null +++ b/lib/wp/object-interest.h @@ -0,0 +1,105 @@ +/* WirePlumber + * + * Copyright © 2020 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +#ifndef __WIREPLUMBER_OBJECT_INTEREST_H__ +#define __WIREPLUMBER_OBJECT_INTEREST_H__ + +#include <glib-object.h> +#include "defs.h" +#include "properties.h" + +G_BEGIN_DECLS + +/** + * WpConstraintType: + * @WP_CONSTRAINT_TYPE_NONE: invalid constraint type + * @WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY: constraint applies + * to a PipeWire global property of the object (the ones returned by + * wp_proxy_get_global_properties()) + * @WP_CONSTRAINT_TYPE_PW_PROPERTY: constraint applies + * to a PipeWire property of the object (the ones returned by + * wp_proxy_get_properties()) + * @WP_CONSTRAINT_TYPE_G_PROPERTY: constraint applies to a #GObject + * property of the object + */ +typedef enum { + WP_CONSTRAINT_TYPE_NONE = 0, + WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, + WP_CONSTRAINT_TYPE_G_PROPERTY, +} WpConstraintType; + +/** + * WpConstraintVerb: + * @WP_CONSTRAINT_VERB_EQUALS: `=` the subject's value must equal the + * constraint's value + * @WP_CONSTRAINT_VERB_IN_LIST: `c` the subject's value must equal at least + * one of the values in the list given as the constraint's value + * @WP_CONSTRAINT_VERB_IN_RANGE: `~` the subject's value must be a number + * in the range defined by the constraint's value + * @WP_CONSTRAINT_VERB_MATCHES: `#` the subject's value must match the + * pattern specified in the constraint's value + * @WP_CONSTRAINT_VERB_IS_PRESENT: `+` the subject property must exist + * @WP_CONSTRAINT_VERB_IS_ABSENT: `-` the subject property must not exist + */ +typedef enum { + WP_CONSTRAINT_VERB_EQUALS = '=', + WP_CONSTRAINT_VERB_IN_LIST = 'c', + WP_CONSTRAINT_VERB_IN_RANGE = '~', + WP_CONSTRAINT_VERB_MATCHES = '#', + WP_CONSTRAINT_VERB_IS_PRESENT = '+', + WP_CONSTRAINT_VERB_IS_ABSENT = '-', +} WpConstraintVerb; + +/** + * WP_TYPE_OBJECT_INTEREST: + * + * The #WpObjectInterest #GType + */ +#define WP_TYPE_OBJECT_INTEREST (wp_object_interest_get_type ()) +WP_API +GType wp_object_interest_get_type (void) G_GNUC_CONST; + +typedef struct _WpObjectInterest WpObjectInterest; + +WP_API +WpObjectInterest * wp_object_interest_new (GType gtype, ...) G_GNUC_NULL_TERMINATED; + +WP_API +WpObjectInterest * wp_object_interest_new_valist (GType gtype, va_list * args); + +WP_API +WpObjectInterest * wp_object_interest_new_type (GType gtype); + +WP_API +void wp_object_interest_add_constraint (WpObjectInterest * self, + WpConstraintType type, const gchar * subject, + WpConstraintVerb verb, GVariant * value); + +WP_API +WpObjectInterest * wp_object_interest_copy (WpObjectInterest * self); + +WP_API +void wp_object_interest_free (WpObjectInterest * self); + +WP_API +gboolean wp_object_interest_validate (WpObjectInterest * self, GError ** error); + +WP_API +gboolean wp_object_interest_matches (WpObjectInterest * self, gpointer object); + +WP_API +gboolean wp_object_interest_matches_full (WpObjectInterest * self, + GType object_type, gpointer object, WpProperties * pw_props, + WpProperties * pw_global_props); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (WpObjectInterest, wp_object_interest_free) + +G_END_DECLS + +#endif diff --git a/lib/wp/wp.h b/lib/wp/wp.h index d9c377d8..1a678cda 100644 --- a/lib/wp/wp.h +++ b/lib/wp/wp.h @@ -21,6 +21,7 @@ #include "link.h" #include "module.h" #include "node.h" +#include "object-interest.h" #include "object-manager.h" #include "policy.h" #include "port.h" diff --git a/tests/wp/meson.build b/tests/wp/meson.build index a6e30e16..61323ee3 100644 --- a/tests/wp/meson.build +++ b/tests/wp/meson.build @@ -16,6 +16,13 @@ test( env: common_env, ) +test( + 'test-object-interest', + executable('test-object-interest', 'object-interest.c', + dependencies: common_deps, c_args: common_args), + env: common_env, +) + test( 'test-properties', executable('test-properties', 'properties.c', diff --git a/tests/wp/object-interest.c b/tests/wp/object-interest.c new file mode 100644 index 00000000..9a14bc06 --- /dev/null +++ b/tests/wp/object-interest.c @@ -0,0 +1,843 @@ +/* WirePlumber + * + * Copyright © 2020 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +#include <wp/wp.h> + +enum { + PROP_0, + PROP_TEST_STRING, + PROP_TEST_INT, + PROP_TEST_UINT, + PROP_TEST_INT64, + PROP_TEST_UINT64, + PROP_TEST_FLOAT, + PROP_TEST_DOUBLE, + PROP_TEST_BOOLEAN, +}; + +struct _TestObjA +{ + GObject parent; + gchar *test_string; + gint test_int; + guint test_uint; + gint64 test_int64; + guint64 test_uint64; + gfloat test_float; + gdouble test_double; + gboolean test_boolean; +}; + +#define TEST_TYPE_A (test_obj_a_get_type ()) +G_DECLARE_FINAL_TYPE (TestObjA, test_obj_a, TEST, OBJ_A, GObject) +G_DEFINE_TYPE (TestObjA, test_obj_a, G_TYPE_OBJECT) + +static void +test_obj_a_init (TestObjA * self) +{ +} + +static void +test_obj_a_finalize (GObject * object) +{ + TestObjA *self = TEST_OBJ_A (object); + g_free (self->test_string); + G_OBJECT_CLASS (test_obj_a_parent_class)->finalize (object); +} + +static void +test_obj_a_get_property (GObject * object, guint id, GValue * value, + GParamSpec * pspec) +{ + TestObjA *self = TEST_OBJ_A (object); + + switch (id) { + case PROP_TEST_STRING: + g_value_set_string (value, self->test_string); + break; + case PROP_TEST_INT: + g_value_set_int (value, self->test_int); + break; + case PROP_TEST_UINT: + g_value_set_uint (value, self->test_uint); + break; + case PROP_TEST_INT64: + g_value_set_int64 (value, self->test_int64); + break; + case PROP_TEST_UINT64: + g_value_set_uint64 (value, self->test_uint64); + break; + case PROP_TEST_FLOAT: + g_value_set_float (value, self->test_float); + break; + case PROP_TEST_DOUBLE: + g_value_set_double (value, self->test_double); + break; + case PROP_TEST_BOOLEAN: + g_value_set_boolean (value, self->test_boolean); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec); + break; + } +} + +static void +test_obj_a_set_property (GObject * object, guint id, const GValue * value, + GParamSpec * pspec) +{ + TestObjA *self = TEST_OBJ_A (object); + + switch (id) { + case PROP_TEST_STRING: + self->test_string = g_value_dup_string (value); + break; + case PROP_TEST_INT: + self->test_int = g_value_get_int (value); + break; + case PROP_TEST_UINT: + self->test_uint = g_value_get_uint (value); + break; + case PROP_TEST_INT64: + self->test_int64 = g_value_get_int64 (value); + break; + case PROP_TEST_UINT64: + self->test_uint64 = g_value_get_uint64 (value); + break; + case PROP_TEST_FLOAT: + self->test_float = g_value_get_float (value); + break; + case PROP_TEST_DOUBLE: + self->test_double = g_value_get_double (value); + break; + case PROP_TEST_BOOLEAN: + self->test_boolean = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, id, pspec); + break; + } +} + +static void +test_obj_a_class_init (TestObjAClass * klass) +{ + GObjectClass *obj_class = (GObjectClass *) klass; + + obj_class->finalize = test_obj_a_finalize; + obj_class->get_property = test_obj_a_get_property; + obj_class->set_property = test_obj_a_set_property; + + g_object_class_install_property (obj_class, PROP_TEST_STRING, + g_param_spec_string ("test-string", "test-string", "blurb", NULL, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (obj_class, PROP_TEST_INT, + g_param_spec_int ("test-int", "test-int", "blurb", + G_MININT, G_MAXINT, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (obj_class, PROP_TEST_UINT, + g_param_spec_uint ("test-uint", "test-uint", "blurb", + 0, G_MAXUINT, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (obj_class, PROP_TEST_INT64, + g_param_spec_int64 ("test-int64", "test-int64", "blurb", + G_MININT64, G_MAXINT64, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (obj_class, PROP_TEST_UINT64, + g_param_spec_uint64 ("test-uint64", "test-uint64", "blurb", + 0, G_MAXUINT64, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (obj_class, PROP_TEST_FLOAT, + g_param_spec_float ("test-float", "test-float", "blurb", + -20.0f, 20.0f, 0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (obj_class, PROP_TEST_DOUBLE, + g_param_spec_double ("test-double", "test-double", "blurb", + -20.0, 20.0, 0.0, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (obj_class, PROP_TEST_BOOLEAN, + g_param_spec_boolean ("test-boolean", "test-boolean", "blurb", FALSE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +struct _TestObjB +{ + TestObjA parent; +}; + +#define TEST_TYPE_B (test_obj_b_get_type ()) +G_DECLARE_FINAL_TYPE (TestObjB, test_obj_b, TEST, OBJ_B, TestObjA) +G_DEFINE_TYPE (TestObjB, test_obj_b, TEST_TYPE_A) + +static void +test_obj_b_init (TestObjB * self) +{ +} + +static void +test_obj_b_class_init (TestObjBClass * klass) +{ +} + +typedef struct { + GObject *object; +} TestFixture; + +static void +test_object_interest_setup (TestFixture * f, gconstpointer data) +{ + f->object = g_object_new (TEST_TYPE_B, + "test-string", "toast", + "test-int", -30, + "test-uint", 50, + "test-int64", G_GINT64_CONSTANT (-0x1d636b02300a7aa7), + "test-uint64", G_GUINT64_CONSTANT (0x1d636b02300a7aa7), + "test-float", 3.14f, + "test-double", 3.1415926545897932384626433, + "test-boolean", TRUE, + NULL); + g_assert_nonnull (f->object); +} + +static void +test_object_interest_teardown (TestFixture * f, gconstpointer data) +{ + g_clear_object (&f->object); +} + +#define TEST_EXPECT_MATCH(interest) \ + G_STMT_START { \ + g_autoptr (GError) error = NULL; \ + gboolean ret; \ + \ + g_assert_nonnull (interest); \ + \ + ret = wp_object_interest_validate (interest, &error); \ + g_assert_no_error (error); \ + g_assert_true (ret); \ + \ + g_assert_true (wp_object_interest_matches (interest, f->object)); \ + \ + g_clear_pointer (&interest, wp_object_interest_free); \ + } G_STMT_END + +#define TEST_EXPECT_NO_MATCH(interest) \ + G_STMT_START { \ + g_autoptr (GError) error = NULL; \ + gboolean ret; \ + \ + g_assert_nonnull (interest); \ + \ + ret = wp_object_interest_validate (interest, &error); \ + g_assert_no_error (error); \ + g_assert_true (ret); \ + \ + g_assert_false (wp_object_interest_matches (interest, f->object)); \ + \ + g_clear_pointer (&interest, wp_object_interest_free); \ + } G_STMT_END + +#define TEST_EXPECT_MATCH_WP_PROPS(interest, props, global_props) \ + G_STMT_START { \ + g_autoptr (GError) error = NULL; \ + gboolean ret; \ + \ + g_assert_nonnull (interest); \ + \ + ret = wp_object_interest_validate (interest, &error); \ + g_assert_no_error (error); \ + g_assert_true (ret); \ + \ + g_assert_true (wp_object_interest_matches_full (interest, \ + WP_TYPE_PROXY, NULL, props, global_props)); \ + \ + g_clear_pointer (&interest, wp_object_interest_free); \ + } G_STMT_END + +#define TEST_EXPECT_NO_MATCH_WP_PROPS(interest, props, global_props) \ + G_STMT_START { \ + g_autoptr (GError) error = NULL; \ + gboolean ret; \ + \ + g_assert_nonnull (interest); \ + \ + ret = wp_object_interest_validate (interest, &error); \ + g_assert_no_error (error); \ + g_assert_true (ret); \ + \ + g_assert_false (wp_object_interest_matches_full (interest, \ + WP_TYPE_PROXY, NULL, props, global_props)); \ + \ + g_clear_pointer (&interest, wp_object_interest_free); \ + } G_STMT_END + +#define TEST_EXPECT_VALIDATION_ERROR(interest) \ + G_STMT_START { \ + g_autoptr (GError) error = NULL; \ + gboolean ret; \ + \ + g_assert_nonnull (interest); \ + \ + ret = wp_object_interest_validate (interest, &error); \ + g_assert_error (error, WP_DOMAIN_LIBRARY, WP_LIBRARY_ERROR_INVARIANT); \ + g_assert_false (ret); \ + \ + g_clear_pointer (&interest, wp_object_interest_free); \ + } G_STMT_END + +static void +test_object_interest_unconstrained (TestFixture * f, gconstpointer data) +{ + g_autoptr (WpObjectInterest) i = NULL; + + i = wp_object_interest_new_type (TEST_TYPE_A); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new_type (WP_TYPE_PROXY); + TEST_EXPECT_NO_MATCH (i); +} + +static void +test_object_interest_constraint_equals (TestFixture * f, gconstpointer data) +{ + g_autoptr (WpObjectInterest) i = NULL; + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "=s", "toast", NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "=s", "fail", NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "=i", -30, NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "=i", 100, NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "=u", 50, NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "=u", 100, NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int64", + "=x", G_GINT64_CONSTANT (-0x1d636b02300a7aa7), NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int64", + "=x", G_GINT64_CONSTANT (100), NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint64", + "=t", G_GUINT64_CONSTANT (0x1d636b02300a7aa7), NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint64", + "=t", G_GUINT64_CONSTANT (100), NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", + "=d", 3.1415926545897932384626433, NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "=d", 3.14, NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-float", "=d", 3.14, NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-float", "=d", 1.0, NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-boolean", "=b", TRUE, NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-boolean", "=b", FALSE, NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "=d", 3.1415926545897932384626433, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "=u", 50, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "=s", "toast", + NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", + "=d", 3.1415926545897932384626433, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "=u", 50, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "=s", "FAIL", + NULL); + TEST_EXPECT_NO_MATCH (i); +} + +static void +test_object_interest_constraint_list (TestFixture * f, gconstpointer data) +{ + g_autoptr (WpObjectInterest) i = NULL; + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", + "c(sss)", "success", "toast", "test", NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", + "c(ss)", "not-a-toast", "fail", NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "c(iii)", -30, 20, -10, NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "c(i)", 100, NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "c(uu)", 100, 50, NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "c(u)", 100, NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int64", "c(xx)", + G_GINT64_CONSTANT (100), G_GINT64_CONSTANT (-0x1d636b02300a7aa7), NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int64", + "c(x)", G_GINT64_CONSTANT (100), NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint64", + "c(t)", G_GUINT64_CONSTANT (0x1d636b02300a7aa7), NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint64", + "c(t)", G_GUINT64_CONSTANT (100), NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", + "c(dd)", 2.0, 3.1415926545897932384626433, NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "c(d)", 3.14, NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-float", "c(dd)", 2.0, 3.14, NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-float", "c(dd)", 1.0, 2.0, NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", + "c(d)", 3.1415926545897932384626433, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "c(u)", 50, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "c(ss)", "random", "toast", + NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", + "c(d)", 3.1415926545897932384626433, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "c(u)", 50, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "c(s)", "FAIL", + NULL); + TEST_EXPECT_NO_MATCH (i); +} + +static void +test_object_interest_constraint_range (TestFixture * f, gconstpointer data) +{ + g_autoptr (WpObjectInterest) i = NULL; + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "~(ii)", -40, 20, NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "~(ii)", 10, 100, NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "~(uu)", 40, 100, NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "~(uu)", 100, 150, NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int64", "~(xx)", + G_GINT64_CONSTANT (-0x1d636b02300a7aaa), + G_GINT64_CONSTANT (-0x1d636b02300a7aa0), + NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int64", "~(xx)", + G_GINT64_CONSTANT (0), + G_GINT64_CONSTANT (100), + NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint64", "~(tt)", + G_GUINT64_CONSTANT (0x1d636b02300a7aa0), + G_GUINT64_CONSTANT (0x1d636b02300a7aaa), + NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint64", "~(tt)", + G_GUINT64_CONSTANT (0), + G_GUINT64_CONSTANT (100), + NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "~(dd)", 2.0, 4.0, NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "~(dd)", -1.0, 3.14, NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-float", "~(dd)", 2.0, 4.0, NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-float", "~(dd)", -1.0, 3.13, NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "~(dd)", 0.0, 10.0, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "~(uu)", 0, 100, + NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-double", "~(dd)", 10.0, 20.0, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-uint", "~(uu)", 0, 100, + NULL); + TEST_EXPECT_NO_MATCH (i); +} + +static void +test_object_interest_constraint_matches (TestFixture * f, gconstpointer data) +{ + g_autoptr (WpObjectInterest) i = NULL; + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "#s", "to*", NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "#s", "t*st", NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "#s", "*a?t", NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "#s", "egg*", NULL); + TEST_EXPECT_NO_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "#s", "t?est", NULL); + TEST_EXPECT_NO_MATCH (i); +} + +static void +test_object_interest_constraint_present_absent (TestFixture * f, + gconstpointer data) +{ + g_autoptr (WpObjectInterest) i = NULL; + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-int", "+", NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "toast", "+", NULL); + TEST_EXPECT_NO_MATCH (i); + + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "toast", "-", NULL); + TEST_EXPECT_MATCH (i); + + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_G_PROPERTY, "test-string", "-", NULL); + TEST_EXPECT_NO_MATCH (i); +} + +static void +test_object_interest_pw_props (TestFixture * f, gconstpointer data) +{ + g_autoptr (WpProperties) props = NULL; + g_autoptr (WpProperties) global_props = NULL; + g_autoptr (WpObjectInterest) i = NULL; + + props = wp_properties_new ( + "object.id", "10", + "port.name", "test", + "port.physical", "true", + "audio.channel", "FR", + "audio.volume", "0.8", + "format.dsp", "32 bit float mono audio", + NULL); + + global_props = wp_properties_new ( + "object.id", "10", + "format.dsp", "32 bit float mono audio", + NULL); + + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "~(ii)", 0, 100, NULL); + TEST_EXPECT_MATCH_WP_PROPS (i, props, global_props); + + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "=i", 11, NULL); + TEST_EXPECT_NO_MATCH_WP_PROPS (i, props, global_props); + + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "format.dsp", "#s", "*audio*", NULL); + TEST_EXPECT_MATCH_WP_PROPS (i, props, global_props); + + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "port.physical", "=b", TRUE, NULL); + TEST_EXPECT_MATCH_WP_PROPS (i, props, global_props); + + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "audio.channel", "c(sss)", + "MONO", "FL", "FR", NULL); + TEST_EXPECT_MATCH_WP_PROPS (i, props, global_props); + + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "audio.volume", "=d", 0.8, NULL); + TEST_EXPECT_MATCH_WP_PROPS (i, props, global_props); + + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "audio.volume", "~(dd)", 0.0, 0.5, + NULL); + TEST_EXPECT_NO_MATCH_WP_PROPS (i, props, global_props); + + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "object.id", "=i", 10, + NULL); + TEST_EXPECT_MATCH_WP_PROPS (i, props, global_props); + + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "object.id", "+", + WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "format.dsp", "+", + WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "port.name", "-", + WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "port.physical", "-", + WP_CONSTRAINT_TYPE_PW_PROPERTY, "port.name", "+", + WP_CONSTRAINT_TYPE_PW_PROPERTY, "port.physical", "+", + NULL); + TEST_EXPECT_MATCH_WP_PROPS (i, props, global_props); +} + +static void +test_object_interest_validate (TestFixture * f, gconstpointer data) +{ + g_autoptr (WpObjectInterest) i = NULL; + + /* invalid type */ + i = wp_object_interest_new (WP_TYPE_PROXY, 32, "object.id", "+", NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + + /* non-WpProxy type with pw property constraint */ + i = wp_object_interest_new (TEST_TYPE_A, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "+", NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + + /* bad verb; the varargs constructor would assert here */ + i = wp_object_interest_new_type (WP_TYPE_PROXY); + wp_object_interest_add_constraint (i, WP_CONSTRAINT_TYPE_PW_PROPERTY, + "object.id", 0, g_variant_new_string ("10")); + TEST_EXPECT_VALIDATION_ERROR (i); + + /* no subject; the varargs version would assert here */ + i = wp_object_interest_new_type (WP_TYPE_PROXY); + wp_object_interest_add_constraint (i, WP_CONSTRAINT_TYPE_PW_PROPERTY, + NULL, WP_CONSTRAINT_VERB_EQUALS, g_variant_new_int32 (10)); + TEST_EXPECT_VALIDATION_ERROR (i); + + /* no value for verb that requires it */ + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "=", NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "~", NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "c", NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "#", NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + + /* value given for verb that doesn't require it */ + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "+s", "10", NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "-s", "10", NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + + /* tuple required */ + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "ci", 10, NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "~i", 10, NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + + /* invalid value type */ + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "=y", (guchar) 10, NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "=n", (gint16) 10, NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "=q", (guint16) 10, NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "c(bb)", TRUE, FALSE, NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "~(ss)", "0", "20", NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "#i", 10, NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + + /* tuple with different types */ + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "c(si)", "9", 10, NULL); + TEST_EXPECT_VALIDATION_ERROR (i); + i = wp_object_interest_new (WP_TYPE_PROXY, + WP_CONSTRAINT_TYPE_PW_PROPERTY, "object.id", "~(iu)", -10, 20, NULL); + TEST_EXPECT_VALIDATION_ERROR (i); +} + +int +main (int argc, char *argv[]) +{ + g_test_init (&argc, &argv, NULL); + g_log_set_writer_func (wp_log_writer_default, NULL, NULL); + + g_test_add ("/wp/object-interest/unconstrained", + TestFixture, NULL, + test_object_interest_setup, + test_object_interest_unconstrained, + test_object_interest_teardown); + + g_test_add ("/wp/object-interest/equals", + TestFixture, NULL, + test_object_interest_setup, + test_object_interest_constraint_equals, + test_object_interest_teardown); + + g_test_add ("/wp/object-interest/list", + TestFixture, NULL, + test_object_interest_setup, + test_object_interest_constraint_list, + test_object_interest_teardown); + + g_test_add ("/wp/object-interest/range", + TestFixture, NULL, + test_object_interest_setup, + test_object_interest_constraint_range, + test_object_interest_teardown); + + g_test_add ("/wp/object-interest/matches", + TestFixture, NULL, + test_object_interest_setup, + test_object_interest_constraint_matches, + test_object_interest_teardown); + + g_test_add ("/wp/object-interest/present-absent", + TestFixture, NULL, + test_object_interest_setup, + test_object_interest_constraint_present_absent, + test_object_interest_teardown); + + g_test_add ("/wp/object-interest/pw-props", + TestFixture, NULL, + test_object_interest_setup, + test_object_interest_pw_props, + test_object_interest_teardown); + + g_test_add ("/wp/object-interest/validate", + TestFixture, NULL, + test_object_interest_setup, + test_object_interest_validate, + test_object_interest_teardown); + + return g_test_run (); +} -- GitLab