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>
This commit is contained in:
Thibault Saunier 2020-06-15 10:50:14 -04:00 committed by GStreamer Merge Bot
parent e7355ea039
commit 9c08bfcaca
10 changed files with 528 additions and 206 deletions

View file

@ -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;

View file

@ -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"

View file

@ -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;

View file

@ -345,28 +345,37 @@ gst_validate_report_action (GstValidateReporter * reporter,
const gchar * format, ...)
{
va_list var_args;
gint nrepeats;
gchar *f, *repeat = NULL;
GString *f;
if (action && gst_structure_get_int (action->structure, "repeat", &nrepeats))
repeat =
g_strdup_printf (" (repeat: %d/%d)", nrepeats - action->repeat + 1,
nrepeats);
if (!action) {
f = g_string_new (format);
goto done;
}
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);
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

View file

@ -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);
_add_execute_actions_gsource (scenario);
GST_DEBUG_OBJECT (scenario, "Executing only on idle, waiting for"
" next dispatch");
}
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;
}
}
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);
switch (act->priv->state) {
case GST_VALIDATE_EXECUTE_ACTION_ASYNC:
GST_DEBUG_OBJECT (scenario, "Remove source, waiting for action"
" to be done.");
GST_VALIDATE_REPORT_ACTION (scenario, act,
SCENARIO_ACTION_EXECUTION_ERROR, "Could not execute %s", str);
SCENARIO_LOCK (scenario);
priv->execute_actions_source_id = 0;
SCENARIO_UNLOCK (scenario);
g_free (str);
}
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;
}
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
@ -3157,6 +3129,24 @@ _execute_check_action_type_calls (GstValidateScenario * scenario,
"%s called %d times instead of expected %d", type, t->priv->n_calls, n);
done:
return res;
}
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;
}
@ -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);
}
gst_object_unref (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);
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* */
}

View file

@ -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;
};

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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