From ca584da58db585788dc2c16af3bde997fcbaf7bf Mon Sep 17 00:00:00 2001 From: Thibault Saunier Date: Thu, 20 Jul 2023 09:30:30 -0400 Subject: [PATCH] validate: scenario: Add a way to wait for a property to reach a specified value Part-of: --- .../gst/validate/gst-validate-scenario.c | 187 ++++++++++++++---- 1 file changed, 144 insertions(+), 43 deletions(-) diff --git a/subprojects/gst-devtools/validate/gst/validate/gst-validate-scenario.c b/subprojects/gst-devtools/validate/gst/validate/gst-validate-scenario.c index aafe400365..81587e765c 100644 --- a/subprojects/gst-devtools/validate/gst/validate/gst-validate-scenario.c +++ b/subprojects/gst-devtools/validate/gst/validate/gst-validate-scenario.c @@ -1380,7 +1380,8 @@ _set_timed_value_property (GstValidateScenario * scenario, static GstValidateExecuteActionReturn _check_property (GstValidateScenario * scenario, GstValidateAction * action, - gpointer object, const gchar * propname, const GValue * expected_value) + gpointer object, const gchar * propname, const GValue * expected_value, + gboolean report_error) { GValue cvalue = G_VALUE_INIT; @@ -1388,6 +1389,9 @@ _check_property (GstValidateScenario * scenario, GstValidateAction * action, g_object_get_property (object, propname, &cvalue); if (gst_value_compare (&cvalue, expected_value) != GST_VALUE_EQUAL) { + if (!report_error) + return GST_VALIDATE_EXECUTE_ACTION_ERROR; + gchar *expected = gst_value_serialize (expected_value), *observed = gst_value_serialize (&cvalue); @@ -1450,7 +1454,7 @@ _set_or_check_properties (GQuark field_id, const GValue * value, gst_validate_object_set_property_full (GST_VALIDATE_REPORTER (scenario), G_OBJECT (obj), paramspec->name, value, flags); else - res = _check_property (scenario, action, obj, paramspec->name, value); + res = _check_property (scenario, action, obj, paramspec->name, value, TRUE); done: gst_clear_object (&obj); @@ -2831,30 +2835,84 @@ stop_waiting (GstValidateAction * action) return G_SOURCE_REMOVE; } -static void -stop_waiting_signal (GstStructure * data) +typedef struct { - guint sigid = 0; GstElement *target; - GstStructure *check = NULL; GstValidateAction *action; - GstValidateScenario *scenario; + guint sigid; + gboolean check_done; + gboolean check_property; + GMutex lock; +} WaitingSignalData; - gst_structure_get (data, "target", G_TYPE_POINTER, &target, - "action", GST_TYPE_VALIDATE_ACTION, &action, "sigid", G_TYPE_UINT, &sigid, - NULL); - gst_structure_free (data); +static WaitingSignalData * +waiting_signal_data_new (GstElement * target, GstValidateAction * action) +{ + WaitingSignalData *data = g_new0 (WaitingSignalData, 1); - scenario = gst_validate_action_get_scenario (action); + data->target = gst_object_ref (target); + data->action = gst_validate_action_ref (action); + + return data; +} + +static void +waiting_signal_data_free (WaitingSignalData * data) +{ + GstValidateScenario *scenario = + gst_validate_action_get_scenario (data->action); g_assert (scenario); + + gst_object_unref (data->target); + gst_validate_action_unref (data->action); + g_free (data); + + gst_object_unref (scenario); +} + +static void +waiting_signal_data_disconnect (WaitingSignalData * data, + GstValidateScenario * scenario) +{ + g_assert (scenario); SCENARIO_LOCK (scenario); - g_signal_handler_disconnect (target, - sigid ? sigid : scenario->priv->signal_handler_id); - if (!sigid) + g_signal_handler_disconnect (data->target, + data->sigid ? data->sigid : scenario->priv->signal_handler_id); + if (!data->sigid) scenario->priv->signal_handler_id = 0; + data->sigid = 0; SCENARIO_UNLOCK (scenario); +} + +static void +stop_waiting_signal_cb (WaitingSignalData * data) +{ + GstStructure *check = NULL; + GstValidateAction *action = gst_validate_action_ref (data->action); + GstValidateScenario *scenario = NULL; + + g_mutex_lock (&data->lock); + if (data->check_done) { + GST_INFO_OBJECT (data->action, "Check already done, ignoring signal"); + g_mutex_unlock (&data->lock); + + goto cleanup; + } + scenario = gst_validate_action_get_scenario (data->action); + if (data->check_property && + _check_property (scenario, action, data->target, + gst_structure_get_string (action->structure, "property-name"), + gst_structure_get_value (action->structure, "property-value"), + FALSE) != GST_VALIDATE_EXECUTE_ACTION_OK) { + + GST_INFO_OBJECT (scenario, "Property check failed, keep waiting"); + + goto cleanup; + } + + waiting_signal_data_disconnect (data, scenario); if (gst_structure_get (action->structure, "check", GST_TYPE_STRUCTURE, &check, NULL)) { GstValidateAction *subact = @@ -2871,10 +2929,11 @@ stop_waiting_signal (GstStructure * data) } gst_validate_action_set_done (action); - gst_validate_action_unref (action); _add_execute_actions_gsource (scenario); - gst_object_unref (scenario); - gst_object_unref (target); + +cleanup: + gst_validate_action_unref (action); + gst_clear_object (&scenario); } static GstValidateExecuteActionReturn @@ -2933,54 +2992,77 @@ _execute_wait_for_signal (GstValidateScenario * scenario, { gboolean non_blocking; GstValidateScenarioPrivate *priv = scenario->priv; - const gchar *signal_name = gst_structure_get_string - (action->structure, "signal-name"); + gchar *signal_name = g_strdup (gst_structure_get_string + (action->structure, "signal-name")); + const gchar *property_name = gst_structure_get_string + (action->structure, "property-name"); GList *targets = NULL; GstElement *target; - GstStructure *data; + WaitingSignalData *data; GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; + const GValue *property_value = + gst_structure_get_value (action->structure, "property-value"); DECLARE_AND_GET_PIPELINE (scenario, action); + gboolean checking_property = signal_name == NULL; - REPORT_UNLESS (signal_name, err, "No signal-name given for wait action"); + REPORT_UNLESS (signal_name || property_name, done, + "No signal-name or property-name given for wait action"); + REPORT_UNLESS (!property_name || (property_name && property_value), done, + "`property-name` specified without a `property-value`"); targets = _find_elements_defined_in_action (scenario, action); - REPORT_UNLESS ((g_list_length (targets) == 1), err, + REPORT_UNLESS ((g_list_length (targets) == 1), done, "Could not find target element."); - gst_validate_printf (action, "Waiting for '%s' signal\n", signal_name); + gst_validate_printf (action, "Waiting for '%s'\n", + signal_name ? signal_name : property_name); + target = targets->data; + data = waiting_signal_data_new (target, action); + + if (checking_property) { + signal_name = g_strdup_printf ("notify::%s", property_name); + g_mutex_lock (&data->lock); + } + + SCENARIO_LOCK (scenario); if (priv->execute_actions_source_id) { g_source_remove (priv->execute_actions_source_id); priv->execute_actions_source_id = 0; } - - target = targets->data; - data = - gst_structure_new ("a", "action", GST_TYPE_VALIDATE_ACTION, action, - "target", G_TYPE_POINTER, target, NULL); - SCENARIO_LOCK (scenario); - priv->signal_handler_id = g_signal_connect_swapped (target, signal_name, - (GCallback) stop_waiting_signal, data); + priv->signal_handler_id = g_signal_connect_data (target, signal_name, + (GCallback) stop_waiting_signal_cb, data, + (GClosureNotify) waiting_signal_data_free, G_CONNECT_SWAPPED); non_blocking = gst_structure_get_boolean (action->structure, "non-blocking", &non_blocking); if (non_blocking) { - gst_structure_set (data, "sigid", G_TYPE_UINT, priv->signal_handler_id, - NULL); + data->sigid = priv->signal_handler_id; priv->signal_handler_id = 0; } SCENARIO_UNLOCK (scenario); - gst_object_unref (pipeline); - g_list_free (targets); - - - return non_blocking ? GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING : + res = + non_blocking ? GST_VALIDATE_EXECUTE_ACTION_NON_BLOCKING : GST_VALIDATE_EXECUTE_ACTION_ASYNC; + if (checking_property) { + GST_ERROR ("Checking property value"); + if (_check_property (scenario, action, target, property_name, + property_value, FALSE) == GST_VALIDATE_EXECUTE_ACTION_OK) { + data->check_done = TRUE; + waiting_signal_data_disconnect (data, scenario); -err: + GST_ERROR ("Property check done, returning OK"); + res = GST_VALIDATE_EXECUTE_ACTION_OK; + } + g_mutex_unlock (&data->lock); + } + +done: + g_free (signal_name); g_list_free_full (targets, gst_object_unref); gst_object_unref (pipeline); + return res; } @@ -3015,6 +3097,8 @@ _execute_wait (GstValidateScenario * scenario, GstValidateAction * action) gst_structure_get_boolean (action->structure, "on-clock", &on_clock); if (gst_structure_has_field (action->structure, "signal-name")) { return _execute_wait_for_signal (scenario, action); + } else if (gst_structure_has_field (action->structure, "property-name")) { + return _execute_wait_for_signal (scenario, action); } else if (gst_structure_has_field (action->structure, "message-type")) { return _execute_wait_for_message (scenario, action); } else if (on_clock) { @@ -3437,7 +3521,8 @@ _execute_set_or_check_property (GstValidateScenario * scenario, ret = tmpres; } else { ret = - _check_property (scenario, action, l->data, property, property_value); + _check_property (scenario, action, l->data, property, property_value, + TRUE); } } @@ -6334,8 +6419,8 @@ _action_set_done (GstValidateAction * action) _check_scenario_is_done (scenario); if (!gst_validate_parse_next_action_playback_time (scenario)) { - gst_validate_error_structure (scenario->priv->actions ? scenario-> - priv->actions->data : NULL, + gst_validate_error_structure (scenario->priv->actions ? scenario->priv-> + actions->data : NULL, "Could not determine next action playback time!"); } @@ -7029,6 +7114,22 @@ register_action_types (void) .types = "string", NULL }, + { + .name = "property-name", + .description = "The name of the property to wait for value to be set to what is specified by @property-value.", + .mandatory = FALSE, + .types = "string", + NULL + }, + { + .name = "property-value", + .description = "The value of the property to be waiting." + "\n Example: " + "\n `wait, property-name=current-uri, property-value=file:///some/value.mp4, target-element-name=uridecodebin`", + .mandatory = FALSE, + .types = "string", + NULL + }, { .name = "non-blocking", .description = "**Only for signals**."