diff options
author | Thibault Saunier <tsaunier@igalia.com> | 2020-06-15 10:50:14 -0400 |
---|---|---|
committer | GStreamer Merge Bot <gitlab-merge-bot@gstreamer-foundation.org> | 2020-06-22 17:20:32 +0000 |
commit | 9c08bfcaca4ee622dff67c4d890d43ed10dbcc03 (patch) | |
tree | 0fd723969d1f485bc25ab009d20c9bded154dc48 | |
parent | e7355ea039491fbba49edac0be4f7d31722bdf9e (diff) |
validate:scenario: Replace the `sub-action` with a `foreach` action type
Sub-actions were really hard to use and conceptually weird. The
implementation was ugly and made the code complex for nothing.
Instead this commit introduces a `foreach` action type which allows
repeating actions passed in an `actions` array the number of time
specified by any `GstIntRange` value defined in the structure or its
`repeat` field.
This commit also makes sure that all action got through
gst_validate_action_set_done upon finalization.
+ Cleanup surrounding code
+ Add tests
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-devtools/-/merge_requests/207>
10 files changed, 529 insertions, 207 deletions
diff --git a/validate/data/scenarios/change_state_intensive.scenario b/validate/data/scenarios/change_state_intensive.scenario index 06ac60e..042d6fb 100644 --- a/validate/data/scenarios/change_state_intensive.scenario +++ b/validate/data/scenarios/change_state_intensive.scenario @@ -1,3 +1,8 @@ description, duration=0, summary="Set state to NULL->PLAYING->NULL 20 times", need-clock-sync=true, min-media-duration=1.0, live_content_compatible=True, handles-states=true, ignore-eos=true -set-state, state="playing", sub-action="set-state, state=null", repeat=40 + +foreach, i=[0, 40], + actions = { + "set-state, state=playing", + "set-state, state=null", + } stop; diff --git a/validate/gst/validate/gst-validate-internal.h b/validate/gst/validate/gst-validate-internal.h index d454b4d..e5b26e5 100644 --- a/validate/gst/validate/gst-validate-internal.h +++ b/validate/gst/validate/gst-validate-internal.h @@ -47,7 +47,7 @@ void register_action_types (void); * as we used to have to print actions in the action execution function * and this is done by the scenario itself now */ G_GNUC_INTERNAL gboolean _action_check_and_set_printed (GstValidateAction *action); -G_GNUC_INTERNAL gboolean gst_validate_action_is_subaction (GstValidateAction *action); +G_GNUC_INTERNAL gboolean gst_validate_action_get_level (GstValidateAction *action); G_GNUC_INTERNAL gboolean gst_validate_scenario_check_and_set_needs_clock_sync (GList *structures, GstStructure **meta); #define GST_VALIDATE_SCENARIO_SUFFIX ".scenario" diff --git a/validate/gst/validate/gst-validate-report.c b/validate/gst/validate/gst-validate-report.c index 25b15b5..95c55e8 100644 --- a/validate/gst/validate/gst-validate-report.c +++ b/validate/gst/validate/gst-validate-report.c @@ -834,27 +834,36 @@ gst_validate_printf (gpointer source, const gchar * format, ...) va_end (var_args); } +typedef struct +{ + GString *str; + gint indent; + gint printed; +} PrintActionFieldData; + static gboolean -_append_value (GQuark field_id, const GValue * value, GString * string) +_append_value (GQuark field_id, const GValue * value, PrintActionFieldData * d) { gchar *val_str = NULL; + const gchar *fieldname = g_quark_to_string (field_id); - if (g_strcmp0 (g_quark_to_string (field_id), "sub-action") == 0) + if (g_str_has_prefix (fieldname, "__") && g_str_has_suffix (fieldname, "__")) return TRUE; - if (g_strcmp0 (g_quark_to_string (field_id), "repeat") == 0) + if (g_strcmp0 (fieldname, "repeat") == 0) return TRUE; + d->printed++; if (G_VALUE_TYPE (value) == GST_TYPE_CLOCK_TIME) val_str = g_strdup_printf ("%" GST_TIME_FORMAT, GST_TIME_ARGS (g_value_get_uint64 (value))); else val_str = gst_value_serialize (value); - g_string_append (string, "\n - "); - g_string_append (string, g_quark_to_string (field_id)); - g_string_append_len (string, "=", 1); - g_string_append (string, val_str); + g_string_append_printf (d->str, "\n%*c - ", d->indent, ' '); + g_string_append (d->str, fieldname); + g_string_append_len (d->str, "=", 1); + g_string_append (d->str, val_str); g_free (val_str); @@ -874,26 +883,24 @@ gst_validate_print_action (GstValidateAction * action, const gchar * message) GString *string = NULL; if (message == NULL) { - gint nrepeats; - - string = g_string_new (NULL); - - if (gst_validate_action_is_subaction (action)) - g_string_append_printf (string, "(subaction)"); - - if (gst_structure_get_int (action->structure, "repeat", &nrepeats)) - g_string_append_printf (string, "(%d/%d)", nrepeats - action->repeat + 1, - nrepeats); + gint indent = (gst_validate_action_get_level (action) * 2); + PrintActionFieldData d = { NULL, indent, 0 }; + d.str = string = g_string_new (NULL); g_string_append_printf (string, "%s", gst_structure_get_name (action->structure)); - g_string_append_len (string, " ( ", 3); - gst_structure_foreach (action->structure, - (GstStructureForeachFunc) _append_value, string); + if (GST_VALIDATE_ACTION_N_REPEATS (action)) + g_string_append_printf (string, " [%s=%d/%d]", + GST_VALIDATE_ACTION_RANGE_NAME (action) ? + GST_VALIDATE_ACTION_RANGE_NAME (action) : "repeat", action->repeat, + GST_VALIDATE_ACTION_N_REPEATS (action)); - if (gst_structure_n_fields (action->structure)) - g_string_append (string, "\n)\n"); + g_string_append (string, " ( "); + gst_structure_foreach (action->structure, + (GstStructureForeachFunc) _append_value, &d); + if (d.printed) + g_string_append_printf (string, "\n%*c)\n", indent, ' '); else g_string_append (string, ")\n"); message = string->str; @@ -980,12 +987,15 @@ gst_validate_printf_valist (gpointer source, const gchar * format, va_list args) if (source) { if (*(GType *) source == GST_TYPE_VALIDATE_ACTION) { GstValidateAction *action = (GstValidateAction *) source; + gint indent = gst_validate_action_get_level (action) * 2; if (_action_check_and_set_printed (action)) goto out; - g_string_assign (string, "\nExecuting "); - + if (!indent) + g_string_assign (string, "Executing "); + else + g_string_append_printf (string, "%*c↳ Executing ", indent - 2, ' '); } else if (*(GType *) source == GST_TYPE_VALIDATE_ACTION_TYPE) { gint i; gint n_params; diff --git a/validate/gst/validate/gst-validate-reporter.c b/validate/gst/validate/gst-validate-reporter.c index 52c5e1d..f23626a 100644 --- a/validate/gst/validate/gst-validate-reporter.c +++ b/validate/gst/validate/gst-validate-reporter.c @@ -345,28 +345,37 @@ gst_validate_report_action (GstValidateReporter * reporter, const gchar * format, ...) { va_list var_args; - gint nrepeats; - gchar *f, *repeat = NULL; - - if (action && gst_structure_get_int (action->structure, "repeat", &nrepeats)) - repeat = - g_strdup_printf (" (repeat: %d/%d)", nrepeats - action->repeat + 1, - nrepeats); - - f = action ? g_strdup_printf ("\n> %s:%d%s\n> %d | %s\n> %*c|\n", - GST_VALIDATE_ACTION_FILENAME (action), - GST_VALIDATE_ACTION_LINENO (action), repeat ? repeat : "", - GST_VALIDATE_ACTION_LINENO (action), format, - (gint) floor (log10 (abs ((GST_VALIDATE_ACTION_LINENO (action))))) + 1, - ' ') - : g_strdup (format); + GString *f; + if (!action) { + f = g_string_new (format); + goto done; + } + + f = g_string_new (NULL); + g_string_append_printf (f, "\n> %s:%d", GST_VALIDATE_ACTION_FILENAME (action), + GST_VALIDATE_ACTION_LINENO (action)); + + if (GST_VALIDATE_ACTION_N_REPEATS (action)) + g_string_append_printf (f, " (repeat: %d/%d)", + action->repeat, GST_VALIDATE_ACTION_N_REPEATS (action)); + + g_string_append_printf (f, "\n%s", GST_VALIDATE_ACTION_DEBUG (action)); + if (gst_validate_action_get_level (action)) { + gchar *subaction_str = gst_structure_to_string (action->structure); + + g_string_append_printf (f, "\n |-> %s", subaction_str); + g_free (subaction_str); + } + + g_string_append_printf (f, "\n >\n > %s", format); + +done: va_start (var_args, format); - gst_validate_report_valist (reporter, issue_id, f, var_args); + gst_validate_report_valist (reporter, issue_id, f->str, var_args); va_end (var_args); - g_free (f); - g_free (repeat); + g_string_free (f, TRUE); } void diff --git a/validate/gst/validate/gst-validate-scenario.c b/validate/gst/validate/gst-validate-scenario.c index 992e5c6..8218b1b 100644 --- a/validate/gst/validate/gst-validate-scenario.c +++ b/validate/gst/validate/gst-validate-scenario.c @@ -374,9 +374,11 @@ struct _GstValidateActionPrivate GstValidateExecuteActionReturn state; /* Actually ActionState */ gboolean printed; gboolean executing_last_subaction; + gboolean subaction_level; gboolean optional; GstClockTime execution_time; + GstClockTime execution_duration; GstClockTime timeout; GWeakRef scenario; @@ -457,6 +459,31 @@ _action_copy (GstValidateAction * act) return copy; } +const gchar * +gst_validate_action_return_get_name (GstValidateActionReturn r) +{ + switch (r) { + case GST_VALIDATE_EXECUTE_ACTION_ERROR: + return "ERROR"; + case GST_VALIDATE_EXECUTE_ACTION_OK: + return "OK"; + case GST_VALIDATE_EXECUTE_ACTION_ASYNC: + return "ASYNC"; + case GST_VALIDATE_EXECUTE_ACTION_INTERLACED: + return "INTERLACED"; + case GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED: + return "ERROR(reported)"; + case GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS: + return "IN_PROGRESS"; + case GST_VALIDATE_EXECUTE_ACTION_NONE: + return "NONE"; + case GST_VALIDATE_EXECUTE_ACTION_SKIP: + return "SKIP"; + } + g_assert_not_reached (); + return "???"; +} + static void _action_free (GstValidateAction * action) { @@ -508,6 +535,7 @@ gst_validate_action_new (GstValidateScenario * scenario, gst_validate_action_init (action); action->playback_time = GST_CLOCK_TIME_NONE; action->priv->timeout = GST_CLOCK_TIME_NONE; + action->priv->state = GST_VALIDATE_EXECUTE_ACTION_NONE; action->type = action_type->name; action->repeat = -1; @@ -541,11 +569,10 @@ _action_check_and_set_printed (GstValidateAction * action) return TRUE; } -gboolean -gst_validate_action_is_subaction (GstValidateAction * action) +gint +gst_validate_action_get_level (GstValidateAction * action) { - return !gst_structure_is_equal (action->structure, - action->priv->main_structure); + return action->priv->subaction_level; } /* GstValidateActionType implementation */ @@ -1186,7 +1213,7 @@ _set_timed_value (GQuark field_id, const GValue * gvalue, const gchar *unused_fields[] = { "binding-type", "source-type", "interpolation-mode", "timestamp", "__scenario__", "__action__", "__res__", "repeat", - "sub-action", "playback-time", NULL + "playback-time", NULL }; if (g_strv_contains (unused_fields, field)) @@ -1348,7 +1375,7 @@ _set_or_check_properties (GQuark field_id, const GValue * value, GParamSpec *paramspec = NULL; const gchar *field = g_quark_to_string (field_id); const gchar *unused_fields[] = { "__scenario__", "__action__", "__res__", - "sub-action", "playback-time", "repeat", NULL + "playback-time", "repeat", NULL }; if (g_strv_contains (unused_fields, field)) @@ -2381,6 +2408,27 @@ gst_validate_parse_next_action_playback_time (GstValidateScenario * self) return TRUE; } +static gboolean +_foreach_find_iterator (GQuark field_id, GValue * value, + GstValidateAction * action) +{ + if (!g_strcmp0 (g_quark_to_string (field_id), "actions")) + return TRUE; + + if (!GST_VALUE_HOLDS_INT_RANGE (value)) + return TRUE; + + if (GST_VALIDATE_ACTION_RANGE_NAME (action)) { + gst_validate_error_structure (action, "Found several ranges in structure, " + "it is not supported"); + return FALSE; + } + + GST_VALIDATE_ACTION_RANGE_NAME (action) = g_quark_to_string (field_id); + return TRUE; +} + + GstValidateExecuteActionReturn gst_validate_execute_action (GstValidateActionType * action_type, GstValidateAction * action) @@ -2395,6 +2443,11 @@ gst_validate_execute_action (GstValidateActionType * action_type, if (action_type->prepare) { res = action_type->prepare (action); + if (res == GST_VALIDATE_EXECUTE_ACTION_SKIP) { + gst_validate_print_action (action, NULL); + return GST_VALIDATE_EXECUTE_ACTION_OK; + } + if (res != GST_VALIDATE_EXECUTE_ACTION_OK) { GST_ERROR_OBJECT (scenario, "Action %" GST_PTR_FORMAT " could not be prepared", action->structure); @@ -2412,18 +2465,6 @@ gst_validate_execute_action (GstValidateActionType * action_type, res = action_type->execute (scenario, action); gst_object_unref (scenario); - if (!gst_structure_has_field (action->structure, "sub-action")) { - gst_structure_free (action->structure); - action->priv->printed = FALSE; - action->structure = gst_structure_copy (action->priv->main_structure); - - if (!(action->name = gst_structure_get_string (action->structure, "name"))) - action->name = ""; - - if (res == GST_VALIDATE_EXECUTE_ACTION_ASYNC) - action->priv->executing_last_subaction = TRUE; - } - return res; } @@ -2540,75 +2581,24 @@ _fill_action (GstValidateScenario * scenario, GstValidateAction * action, return res; } -static GstValidateExecuteActionReturn -_execute_sub_action_action (GstValidateAction * action) +static gboolean +gst_validate_scenario_execute_next_or_restart_looping (GstValidateScenario * + scenario) { - const gchar *subaction_str; - GstStructure *subaction_struct = NULL; - GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; - GstValidateScenario *scenario = NULL; - - if (action->priv->executing_last_subaction) { - action->priv->executing_last_subaction = FALSE; - - goto done; - } - - scenario = gst_validate_action_get_scenario (action); - g_assert (scenario); - subaction_str = gst_structure_get_string (action->structure, "sub-action"); - if (subaction_str) { - subaction_struct = gst_structure_from_string (subaction_str, NULL); - - if (subaction_struct == NULL) { - GST_VALIDATE_REPORT_ACTION (scenario, action, SCENARIO_FILE_MALFORMED, - "Sub action %s could not be parsed", subaction_str); - - res = GST_VALIDATE_EXECUTE_ACTION_ERROR; - goto done; - } + /* Recurse to the next action if it is possible + * to execute right away */ + if (!scenario->priv->execute_on_idle) { + GST_DEBUG_OBJECT (scenario, "linking next action execution"); + return execute_next_action (scenario); } else { - gst_structure_get (action->structure, "sub-action", GST_TYPE_STRUCTURE, - &subaction_struct, NULL); - } - - if (subaction_struct) { - if (action->structure) { - GST_INFO_OBJECT (scenario, "Clearing old action structure"); - gst_structure_free (action->structure); - } - - res = _fill_action (scenario, action, subaction_struct, FALSE); - if (res == GST_VALIDATE_EXECUTE_ACTION_ERROR) { - GST_VALIDATE_REPORT_ACTION (scenario, action, - SCENARIO_ACTION_EXECUTION_ERROR, - "Sub action %" GST_PTR_FORMAT " could not be filled", - subaction_struct); - - goto done; - } - - if (!GST_CLOCK_TIME_IS_VALID (action->playback_time)) { - GstValidateActionType *action_type = _find_action_type (action->type); - - action->priv->printed = FALSE; - res = gst_validate_execute_action (action_type, action); - - goto done; - } - + _add_execute_actions_gsource (scenario); + GST_DEBUG_OBJECT (scenario, "Executing only on idle, waiting for" + " next dispatch"); } - -done: - if (scenario) - gst_object_unref (scenario); - if (subaction_struct) - gst_structure_free (subaction_struct); - return res; + return G_SOURCE_CONTINUE; } - /* This is the main action execution function * it checks whether it is time to run the next action * and if it is the case executes it. @@ -2620,7 +2610,6 @@ done: static gboolean execute_next_action_full (GstValidateScenario * scenario, GstMessage * message) { - GList *tmp; gdouble rate = 1.0; GstClockTime position = -1; GstValidateAction *act = NULL; @@ -2642,13 +2631,39 @@ execute_next_action_full (GstValidateScenario * scenario, GstMessage * message) if (scenario->priv->actions) act = scenario->priv->actions->data; - if (act) { + if (!act) + return G_SOURCE_CONTINUE; - if (act->priv->state == GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS) { + switch (act->priv->state) { + case GST_VALIDATE_EXECUTE_ACTION_NONE: + break; + case GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS: + GST_INFO_OBJECT (scenario, "Action %s:%d still running", + GST_VALIDATE_ACTION_FILENAME (act), GST_VALIDATE_ACTION_LINENO (act)); return G_SOURCE_CONTINUE; - } else if (act->priv->state == GST_VALIDATE_EXECUTE_ACTION_OK) { - tmp = priv->actions; - priv->actions = g_list_remove_link (priv->actions, tmp); + case GST_VALIDATE_EXECUTE_ACTION_ERROR: + GST_VALIDATE_REPORT_ACTION (scenario, act, + SCENARIO_ACTION_EXECUTION_ERROR, "Action %s failed", act->type); + case GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED: + case GST_VALIDATE_EXECUTE_ACTION_OK: + { + gchar *repeat = NULL; + + if (GST_VALIDATE_ACTION_N_REPEATS (act)) + repeat = + g_strdup_printf ("[%d/%d]", act->repeat, + GST_VALIDATE_ACTION_N_REPEATS (act)); + + gst_validate_printf (NULL, + "%*c⇨ Action %s '%s' %s (duration: %" GST_TIME_FORMAT ")\n\n", + (act->priv->subaction_level * 2) - 1, ' ', + gst_structure_get_name (act->priv->main_structure), + gst_validate_action_return_get_name (act->priv->state), + repeat ? repeat : "", GST_TIME_ARGS (act->priv->execution_duration)); + g_free (repeat); + + priv->actions = g_list_remove (priv->actions, act); + gst_validate_action_unref (act); if (!gst_validate_parse_next_action_playback_time (scenario)) { gst_validate_error_structure (priv->actions ? priv-> @@ -2662,16 +2677,15 @@ execute_next_action_full (GstValidateScenario * scenario, GstMessage * message) GST_INFO_OBJECT (scenario, "Action %" GST_PTR_FORMAT " is DONE now" " executing next", act->structure); - gst_validate_action_unref (act); - g_list_free (tmp); - if (scenario->priv->actions) { act = scenario->priv->actions->data; } else { _check_scenario_is_done (scenario); act = NULL; } - } else if (act->priv->state == GST_VALIDATE_EXECUTE_ACTION_ASYNC) { + break; + } + case GST_VALIDATE_EXECUTE_ACTION_ASYNC: if (GST_CLOCK_TIME_IS_VALID (act->priv->timeout)) { GstClockTime etime = gst_util_get_timestamp () - act->priv->execution_time; @@ -2691,7 +2705,8 @@ execute_next_action_full (GstValidateScenario * scenario, GstMessage * message) act->structure); return G_SOURCE_CONTINUE; - } + default: + g_assert_not_reached (); } if (message) { @@ -2721,68 +2736,25 @@ execute_next_action_full (GstValidateScenario * scenario, GstMessage * message) gst_structure_remove_field (act->structure, "on-message"); act->priv->state = gst_validate_execute_action (type, act); - if (act->priv->state == GST_VALIDATE_EXECUTE_ACTION_ERROR) { - gchar *str = gst_structure_to_string (act->structure); - - GST_VALIDATE_REPORT_ACTION (scenario, act, - SCENARIO_ACTION_EXECUTION_ERROR, "Could not execute %s", str); - - g_free (str); - } + switch (act->priv->state) { + case GST_VALIDATE_EXECUTE_ACTION_ASYNC: + GST_DEBUG_OBJECT (scenario, "Remove source, waiting for action" + " to be done."); - if (act->priv->state == GST_VALIDATE_EXECUTE_ACTION_OK) { - act->priv->state = _execute_sub_action_action (act); - } - - if (act->priv->state != GST_VALIDATE_EXECUTE_ACTION_ASYNC) { - tmp = priv->actions; - priv->actions = g_list_remove_link (priv->actions, tmp); - - if (!gst_validate_parse_next_action_playback_time (scenario)) { - gst_validate_error_structure (priv->actions ? priv->actions->data : NULL, - "Could not determine next action playback time!"); - - return G_SOURCE_REMOVE; - } + SCENARIO_LOCK (scenario); + priv->execute_actions_source_id = 0; + SCENARIO_UNLOCK (scenario); - if (act->priv->state != GST_VALIDATE_EXECUTE_ACTION_INTERLACED) - gst_validate_action_unref (act); - else { + return G_SOURCE_CONTINUE; + case GST_VALIDATE_EXECUTE_ACTION_INTERLACED: SCENARIO_LOCK (scenario); priv->interlaced_actions = g_list_append (priv->interlaced_actions, act); SCENARIO_UNLOCK (scenario); - } - - if (priv->actions == NULL) - _check_scenario_is_done (scenario); - - g_list_free (tmp); - - /* Recurse to the next action if it is possible - * to execute right away */ - if (!scenario->priv->execute_on_idle) { - GST_DEBUG_OBJECT (scenario, "linking next action execution"); - - return execute_next_action (scenario); - } else { - _add_execute_actions_gsource (scenario); - GST_DEBUG_OBJECT (scenario, "Executing only on idle, waiting for" - " next dispatch"); - + return gst_validate_scenario_execute_next_or_restart_looping (scenario); + default: + gst_validate_action_set_done (act); return G_SOURCE_CONTINUE; - } - } else { - GST_DEBUG_OBJECT (scenario, "Remove source, waiting for action" - " to be done."); - - SCENARIO_LOCK (scenario); - priv->execute_actions_source_id = 0; - SCENARIO_UNLOCK (scenario); - - return G_SOURCE_CONTINUE; } - - return G_SOURCE_CONTINUE; } static gboolean @@ -3162,6 +3134,24 @@ done: } static GstValidateExecuteActionReturn +_execute_check_subaction_level (GstValidateScenario * scenario, + GstValidateAction * action) +{ + GstValidateExecuteActionReturn res = GST_VALIDATE_EXECUTE_ACTION_OK; + gint n; + + REPORT_UNLESS (gst_structure_get_int (action->structure, "level", &n), + done, "No `n`!"); + REPORT_UNLESS (gst_validate_action_get_level (action) == n, done, + "Expected subaction level %d, got %d", n, + gst_validate_action_get_level (action)); + + +done: + return res; +} + +static GstValidateExecuteActionReturn _execute_check_position (GstValidateScenario * scenario, GstValidateAction * action) { @@ -3679,7 +3669,9 @@ gst_validate_action_default_prepare_func (GstValidateAction * action) if (GST_VALIDATE_ACTION_N_REPEATS (action)) gst_structure_set (scenario->priv->vars, - "repeat", G_TYPE_INT, action->repeat, NULL); + GST_VALIDATE_ACTION_RANGE_NAME (action) ? + GST_VALIDATE_ACTION_RANGE_NAME (action) : "repeat", G_TYPE_INT, + action->repeat, NULL); gst_validate_structure_resolve_variables (action, action->structure, scenario->priv->vars); for (i = 0; type->parameters[i].name; i++) { @@ -3710,6 +3702,136 @@ gst_validate_set_property_prepare_func (GstValidateAction * action) return gst_validate_action_default_prepare_func (action); } +static GList * +add_gvalue_to_list_as_struct (gpointer source, GList * list, const GValue * v) +{ + if (G_VALUE_HOLDS_STRING (v)) { + GstStructure *structure = + gst_structure_new_from_string (g_value_get_string (v)); + + if (!structure) + gst_validate_error_structure (source, "Invalid structure: %s", + g_value_get_string (v)); + + return g_list_append (list, structure); + } + + if (GST_VALUE_HOLDS_STRUCTURE (v)) + return g_list_append (list, + gst_structure_copy (gst_value_get_structure (v))); + + + gst_validate_error_structure (source, "Expected a string or a structure," + " got %s instead", gst_value_serialize (v)); + return NULL; +} + +static GList * +gst_validate_utils_get_structures (gpointer source, + GstStructure * str, const gchar * fieldname) +{ + guint i, size; + GList *res = NULL; + const GValue *value = gst_structure_get_value (str, fieldname); + + if (!value) + return NULL; + + if (G_VALUE_HOLDS_STRING (value) || GST_VALUE_HOLDS_STRUCTURE (value)) + return add_gvalue_to_list_as_struct (source, res, value); + + if (!GST_VALUE_HOLDS_LIST (value)) { + g_error ("%s must have type list of structure/string (or a string), " + "e.g. %s={ [struct1, a=val1], [struct2, a=val2] }, got: \"%s\" in %s", + fieldname, fieldname, gst_value_serialize (value), + gst_structure_to_string (str)); + return NULL; + } + + size = gst_value_list_get_size (value); + for (i = 0; i < size; i++) + res = + add_gvalue_to_list_as_struct (source, res, + gst_value_list_get_value (value, i)); + + return res; +} + +static GstValidateExecuteActionReturn +gst_validate_foreach_prepare (GstValidateAction * action) +{ + gint it, i; + gint min = 0, max = 1, step = 1; + GstValidateScenario *scenario; + GList *actions, *tmp; + + scenario = gst_validate_action_get_scenario (action); + g_assert (scenario); + _update_well_known_vars (scenario); + gst_validate_action_setup_repeat (scenario, action); + + GST_VALIDATE_ACTION_RANGE_NAME (action) = NULL; + gst_structure_foreach (action->structure, + (GstStructureForeachFunc) _foreach_find_iterator, action); + + /* Allow using the repeat field here too */ + if (!GST_VALIDATE_ACTION_RANGE_NAME (action) + && !GST_VALIDATE_ACTION_N_REPEATS (action)) + gst_validate_error_structure (action, "Missing range specifier field."); + + if (GST_VALIDATE_ACTION_RANGE_NAME (action)) { + const GValue *range = gst_structure_get_value (action->structure, + GST_VALIDATE_ACTION_RANGE_NAME (action)); + min = gst_value_get_int_range_min (range); + max = gst_value_get_int_range_max (range); + step = gst_value_get_int_range_step (range); + + if (min % step != 0) + gst_validate_error_structure (action, + "Range min[%d] must be a multiple of step[%d].", min, step); + + if (max % step != 0) + gst_validate_error_structure (action, + "Range max[%d] must be a multiple of step[%d].", max, step); + } else { + min = action->repeat; + max = action->repeat + 1; + } + + actions = gst_validate_utils_get_structures (action, action->structure, + "actions"); + i = g_list_index (scenario->priv->actions, action); + for (it = min; it < max; it = it + step) { + for (tmp = actions; tmp; tmp = tmp->next) { + GstValidateAction *subaction; + GstStructure *nstruct = gst_structure_copy (tmp->data); + + subaction = gst_validate_action_new (scenario, + _find_action_type (gst_structure_get_name (nstruct)), nstruct, FALSE); + GST_VALIDATE_ACTION_RANGE_NAME (subaction) = + GST_VALIDATE_ACTION_RANGE_NAME (action); + GST_VALIDATE_ACTION_FILENAME (subaction) = + g_strdup (GST_VALIDATE_ACTION_FILENAME (action)); + GST_VALIDATE_ACTION_DEBUG (subaction) = + g_strdup (GST_VALIDATE_ACTION_DEBUG (action)); + GST_VALIDATE_ACTION_LINENO (subaction) = + GST_VALIDATE_ACTION_LINENO (action); + subaction->repeat = it; + subaction->priv->subaction_level = action->priv->subaction_level + 1; + GST_VALIDATE_ACTION_N_REPEATS (subaction) = max; + scenario->priv->actions = + g_list_insert (scenario->priv->actions, subaction, i++); + } + } + g_list_free_full (actions, (GDestroyNotify) gst_structure_free); + + scenario->priv->actions = g_list_remove (scenario->priv->actions, action); + gst_structure_remove_field (action->structure, "actions"); + + gst_object_unref (scenario); + return GST_VALIDATE_EXECUTE_ACTION_SKIP; +} + static void _check_waiting_for_message (GstValidateScenario * scenario, GstMessage * message) @@ -4138,7 +4260,7 @@ gst_validate_scenario_load_structures (GstValidateScenario * scenario, } gst_validate_error_structure (structure, - "We do not handle action types %s", type); + "Unknown action type: '%s'", type); goto failed; } @@ -5635,13 +5757,13 @@ static gboolean _action_set_done (GstValidateAction * action) { JsonBuilder *jbuild; - GstClockTime execution_duration; GstValidateScenario *scenario = gst_validate_action_get_scenario (action); if (scenario == NULL || !action->priv->pending_set_done) return G_SOURCE_REMOVE; - execution_duration = gst_util_get_timestamp () - action->priv->execution_time; + action->priv->execution_duration = + gst_util_get_timestamp () - action->priv->execution_time; jbuild = json_builder_new (); json_builder_begin_object (jbuild); @@ -5651,25 +5773,27 @@ _action_set_done (GstValidateAction * action) json_builder_add_string_value (jbuild, action->type); json_builder_set_member_name (jbuild, "execution-duration"); json_builder_add_double_value (jbuild, - ((gdouble) execution_duration / GST_SECOND)); + ((gdouble) action->priv->execution_duration / GST_SECOND)); json_builder_end_object (jbuild); gst_validate_send (json_builder_get_root (jbuild)); g_object_unref (jbuild); - gst_validate_printf (NULL, " -> Action %s done (duration: %" GST_TIME_FORMAT - ")\n", action->type, GST_TIME_ARGS (execution_duration)); - action->priv->execution_time = GST_CLOCK_TIME_NONE; - action->priv->state = _execute_sub_action_action (action); - - if (action->priv->state != GST_VALIDATE_EXECUTE_ACTION_ASYNC) { - - GST_DEBUG_OBJECT (scenario, "Sub action executed ASYNC"); - execute_next_action (scenario); + action->priv->pending_set_done = FALSE; + switch (action->priv->state) { + case GST_VALIDATE_EXECUTE_ACTION_ASYNC: + case GST_VALIDATE_EXECUTE_ACTION_INTERLACED: + case GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS: + case GST_VALIDATE_EXECUTE_ACTION_NONE: + action->priv->state = GST_VALIDATE_EXECUTE_ACTION_OK; + break; + default: + break; } + gst_structure_free (action->structure); + action->structure = gst_structure_copy (action->priv->main_structure); + gst_validate_scenario_execute_next_or_restart_looping (scenario); gst_object_unref (scenario); - - action->priv->pending_set_done = FALSE; return G_SOURCE_REMOVE; } @@ -6844,9 +6968,25 @@ register_action_types (void) }), "Check current pipeline position.\n", GST_VALIDATE_ACTION_TYPE_NONE); + REGISTER_ACTION_TYPE("foreach", NULL, + ((GstValidateActionParameter[]) { + { .name = "actions", + .description = "The array of actions to repeat", + .mandatory = TRUE, + .types = "strv", + NULL }, + { NULL } }), + "Run actions defined in the `actions` array the number of times specified\n" + " with a GstIntRange `i=[start, end, step]` parameter passed in, one and only\n" + " range is required as parameter.", + GST_VALIDATE_ACTION_TYPE_NONE); + type->prepare = gst_validate_foreach_prepare; + /* Internal actions types to test the validate scenario implementation */ REGISTER_ACTION_TYPE("priv_check-action-type-calls", _execute_check_action_type_calls, NULL, NULL, 0); + REGISTER_ACTION_TYPE("priv_check-subaction-level", + _execute_check_subaction_level, NULL, NULL, 0); /* *INDENT-ON* */ } diff --git a/validate/gst/validate/gst-validate-scenario.h b/validate/gst/validate/gst-validate-scenario.h index 628c0be..aeec38d 100644 --- a/validate/gst/validate/gst-validate-scenario.h +++ b/validate/gst/validate/gst-validate-scenario.h @@ -57,8 +57,11 @@ typedef enum GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED, GST_VALIDATE_EXECUTE_ACTION_IN_PROGRESS, GST_VALIDATE_EXECUTE_ACTION_NONE, + GST_VALIDATE_EXECUTE_ACTION_SKIP, } GstValidateActionReturn; +const gchar *gst_validate_action_return_get_name (GstValidateActionReturn r); + /* TODO 2.0 -- Make it an actual enum type */ #define GstValidateExecuteActionReturn gint @@ -93,6 +96,7 @@ typedef struct _GstValidateActionPrivate GstValidateActionPrivate; #define GST_VALIDATE_ACTION_FILENAME(action) (((GstValidateAction*) action)->ABI.abi.filename) #define GST_VALIDATE_ACTION_DEBUG(action) (((GstValidateAction*) action)->ABI.abi.debug) #define GST_VALIDATE_ACTION_N_REPEATS(action) (((GstValidateAction*) action)->ABI.abi.n_repeats) +#define GST_VALIDATE_ACTION_RANGE_NAME(action) (((GstValidateAction*) action)->ABI.abi.rangename) /** * GstValidateAction: @@ -133,6 +137,7 @@ struct _GstValidateAction gchar *filename; gchar *debug; gint n_repeats; + const gchar *rangename; } abi; } ABI; }; diff --git a/validate/tests/launcher_tests/foreach.validatetest b/validate/tests/launcher_tests/foreach.validatetest new file mode 100644 index 0000000..1ecc9f3 --- /dev/null +++ b/validate/tests/launcher_tests/foreach.validatetest @@ -0,0 +1,47 @@ +meta, + handles-states=true, + args = { + "videotestsrc pattern=ball animation-mode=frames num-buffers=30 ! video/x-raw,framerate=10/1 ! $(videosink) name=sink sync=true", + }, + expected-issues = { + "expected-issue, + level=critical, + issue-id=scenario::execution-error, + details=\"Pipeline position doesn.t match expectations got 0:00:00.100000000 instead of.*\"", + "expected-issue, + level=critical, + issue-id=scenario::execution-error, + details=\"Pipeline position doesn.t match expectations got 0:00:00.200000000 instead of.*\"", + } + +pause; + +foreach, n=[0, 2], + actions = { + "seek, start=\"$(position)+0.1\", flags=\"accurate+flush\"", + "check-position, expected-position=\"expr($(n)*0.01)\"", # expected to fail + } + +priv_check-action-type-calls, type=seek, n=2 +priv_check-action-type-calls, type=check-position, n=2 + +foreach, n=[0, 6], + actions = { + "seek, start=\"$(position)+0.1\", flags=\"accurate+flush\"", + "check-position, expected-position=\"expr((3 + $(n)) * 0.1)\"", + } + +priv_check-action-type-calls, type=seek, n=8 +priv_check-action-type-calls, type=check-position, n=8 +check-position, expected-position=0.8 + +foreach, n=[9, 11], + actions = { + "seek, start=\"$(position)+0.1\", flags=\"accurate+flush\"", + "check-position, expected-position=\"expr($(n)*0.1)\"", + } +priv_check-action-type-calls, type=seek, n=10 +# We called it once manually +priv_check-action-type-calls, type=check-position, n=11 +check-position, expected-position=1.0 +stop diff --git a/validate/tests/launcher_tests/foreach/flow-expectations/log-sink-sink-expected b/validate/tests/launcher_tests/foreach/flow-expectations/log-sink-sink-expected new file mode 100644 index 0000000..f525c3c --- /dev/null +++ b/validate/tests/launcher_tests/foreach/flow-expectations/log-sink-sink-expected @@ -0,0 +1,24 @@ +event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE, group-id=(uint)1; +event stream-start: GstEventStreamStart, flags=(GstStreamFlags)GST_STREAM_FLAG_NONE, group-id=(uint)1; +event caps: video/x-raw, format=(string)AYUV64, width=(int)320, height=(int)240, framerate=(fraction)10/1, multiview-mode=(string)mono, pixel-aspect-ratio=(fraction)1/1, interlace-mode=(string)progressive; +event caps: video/x-raw, format=(string)AYUV64, width=(int)320, height=(int)240, framerate=(fraction)10/1, multiview-mode=(string)mono, pixel-aspect-ratio=(fraction)1/1, interlace-mode=(string)progressive; +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=none, time=0:00:00.000000000, base=0:00:00.000000000, position=0:00:00.000000000 +event segment: format=TIME, start=0:00:00.000000000, offset=0:00:00.000000000, stop=none, time=0:00:00.000000000, base=0:00:00.000000000, position=0:00:00.000000000 +buffer: checksum=5d4a9a9aa2038170a66bb2c675a16672fe70efbe, pts=0:00:00.000000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=5d4a9a9aa2038170a66bb2c675a16672fe70efbe, pts=0:00:00.000000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +event flush-start: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.100000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.100000000, base=0:00:00.000000000, position=0:00:00.100000000 +event segment: format=TIME, start=0:00:00.100000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.100000000, base=0:00:00.000000000, position=0:00:00.100000000 +buffer: checksum=ace920a5c387c5d216c7bf4fdc83df6ac9d2656e, pts=0:00:00.100000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=ace920a5c387c5d216c7bf4fdc83df6ac9d2656e, pts=0:00:00.100000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +event flush-start: (no structure) +event flush-start: (no structure) +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event flush-stop: GstEventFlushStop, reset-time=(boolean)true; +event segment: format=TIME, start=0:00:00.200000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.200000000, base=0:00:00.000000000, position=0:00:00.200000000 +event segment: format=TIME, start=0:00:00.200000000, offset=0:00:00.000000000, stop=none, flags=0x01, time=0:00:00.200000000, base=0:00:00.000000000, position=0:00:00.200000000 +buffer: checksum=b4a5b43f70ad1a1adb1f5e414b39d6bfb5718373, pts=0:00:00.200000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta +buffer: checksum=b4a5b43f70ad1a1adb1f5e414b39d6bfb5718373, pts=0:00:00.200000000, dur=0:00:00.100000000, flags=discont, meta=GstVideoMeta diff --git a/validate/tests/launcher_tests/foreach_deep.validatetest b/validate/tests/launcher_tests/foreach_deep.validatetest new file mode 100644 index 0000000..3fa9f52 --- /dev/null +++ b/validate/tests/launcher_tests/foreach_deep.validatetest @@ -0,0 +1,51 @@ +meta, + handles-states=true, + args = { + "videotestsrc pattern=ball animation-mode=frames num-buffers=30 ! video/x-raw,framerate=10/1 ! $(videosink) name=sink sync=true", + }, + expected-issues = { + "expected-issue, level=critical, issue-id=scenario::execution-error, + details=\"Pipeline position doesn.t match expectations got 0:00:00.100000000 instead of.*\"", + "expected-issue, level=critical, issue-id=scenario::execution-error, + details=\"Pipeline position doesn.t match expectations got 0:00:00.200000000 instead of.*\"", + "expected-issue, level=critical, issue-id=scenario::execution-error, + details=\"Expected subaction level 4, got 3\"", + "expected-issue, level=critical, issue-id=scenario::execution-error, + details=\"Expected subaction level 4, got 3\"", + "expected-issue, level=critical, issue-id=scenario::execution-error, + details=\"Expected subaction level 5, got 4\"", + "expected-issue, level=critical, issue-id=scenario::execution-error, + details=\"Expected subaction level 5, got 4\"", + } + +pause; + + +foreach, n=[0, 2], + actions = { + "seek, start=\"$(position)+0.1\", flags=\"accurate+flush\"", + "check-position, expected-position=\"expr($(n)*0.01)\"", # Expected failling subaction! + } + +priv_check-action-type-calls, type=seek, n=2 +priv_check-action-type-calls, type=check-position, n=2 + +foreach, n=[0, 2], + actions = { + "seek, start=\"$(position)+0.1\", flags=\"accurate+flush\"", + "priv_check-subaction-level, level=1", + "foreach, n=[0, 1], + actions={ + \"priv_check-subaction-level, level=2\", + \"foreach, j=[0, 1], actions={ + \\\"priv_check-subaction-level, level=4\\\", # Failling... twice + \\\"priv_check-subaction-level, level=3\\\", + \\\"foreach, j=[0, 1], actions={ + \\\\\\\"priv_check-subaction-level, level=4\\\\\\\", + \\\\\\\"priv_check-subaction-level, level=5\\\\\\\", # Failling... twice + }\\\", + }\", + }", + } +priv_check-action-type-calls, type=seek, n=4 +stop diff --git a/validate/tests/launcher_tests/foreach_repeat.validatetest b/validate/tests/launcher_tests/foreach_repeat.validatetest new file mode 100644 index 0000000..94ccc92 --- /dev/null +++ b/validate/tests/launcher_tests/foreach_repeat.validatetest @@ -0,0 +1,31 @@ +meta, + handles-states=true, + args = { + "videotestsrc name=src pattern=ball animation-mode=frames num-buffers=30 ! video/x-raw,framerate=10/1 ! $(videosink) name=sink sync=true", + } + +pause; + +foreach, repeat="max(1, 2)", + actions = { + "seek, start=\"$(position)+0.1\", flags=\"accurate+flush\"", + "check-position, expected-position=\"expr((1+$(repeat))*0.1)\"", + } + +priv_check-action-type-calls, type=seek, n=2 +priv_check-action-type-calls, type=check-position, n=2 + +foreach, + repeat=2, + pattern=[0, 10, 5], + actions = { + "set-properties, src::horizontal-speed=\"$(pattern)\"", + "check-properties, src::horizontal-speed=\"$(pattern)\"", + } + +check-properties, src::horizontal-speed=5 +priv_check-action-type-calls, type=set-properties, n=4 +priv_check-action-type-calls, type=check-properties, n=5 +priv_check-action-type-calls, type=seek, n=2 +priv_check-action-type-calls, type=check-position, n=2 +stop |