mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-21 05:26:23 +00:00
New validate plugin: validateflow
validateflow can be used to check the buffers and events flowing through a custom pipeline match an expectation file. This can be used to test non-regular-playback use cases like demuxers handling adaptive streaming fragment pushing. This patch includes also new actions used for these cases: `appsrc-push`, `appsrc-eos` and `flush` (plus `checkpoint`, which is only available with validateflow).
This commit is contained in:
parent
0c83ff56dd
commit
e96f2ca714
9 changed files with 1206 additions and 31 deletions
|
@ -90,6 +90,9 @@ GST_VALIDATE_API
|
|||
GstValidateOverride * gst_validate_override_new (void);
|
||||
|
||||
void gst_validate_override_free (GstValidateOverride * override);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GstValidateOverride, gst_validate_override_free)
|
||||
|
||||
GST_VALIDATE_API
|
||||
void gst_validate_override_change_severity (GstValidateOverride * override, GstValidateIssueId issue_id, GstValidateReportLevel new_level);
|
||||
GST_VALIDATE_API
|
||||
|
|
|
@ -2507,6 +2507,248 @@ _execute_emit_signal (GstValidateScenario * scenario,
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
typedef GstFlowReturn (*ChainWrapperFunction) (GstPad * pad, GstObject * parent,
|
||||
GstBuffer * buffer, gpointer * user_data, gboolean * remove_wrapper);
|
||||
|
||||
typedef struct _ChainWrapperFunctionData
|
||||
{
|
||||
GstPadChainFunction wrapped_chain_func;
|
||||
gpointer wrapped_chain_data;
|
||||
GDestroyNotify wrapped_chain_notify;
|
||||
ChainWrapperFunction wrapper_function;
|
||||
gpointer wrapper_function_user_data;
|
||||
} ChainWrapperFunctionData;
|
||||
|
||||
static GstFlowReturn
|
||||
_pad_chain_wrapper (GstPad * pad, GstObject * parent, GstBuffer * buffer)
|
||||
{
|
||||
ChainWrapperFunctionData *data = pad->chaindata;
|
||||
GstFlowReturn ret;
|
||||
gboolean remove_wrapper = FALSE;
|
||||
|
||||
pad->chainfunc = data->wrapped_chain_func;
|
||||
pad->chaindata = data->wrapped_chain_data;
|
||||
pad->chainnotify = data->wrapped_chain_notify;
|
||||
|
||||
ret = data->wrapper_function (pad, parent, buffer,
|
||||
data->wrapper_function_user_data, &remove_wrapper);
|
||||
|
||||
if (!remove_wrapper) {
|
||||
/* The chain function may have changed during the calling (e.g. if it was
|
||||
* a nested wrapper that decided to remove itself) so we need to update the
|
||||
* wrapped function just in case. */
|
||||
data->wrapped_chain_func = pad->chainfunc;
|
||||
data->wrapped_chain_data = pad->chaindata;
|
||||
data->wrapped_chain_notify = pad->chainnotify;
|
||||
|
||||
/* Restore the wrapper as chain function */
|
||||
pad->chainfunc = _pad_chain_wrapper;
|
||||
pad->chaindata = data;
|
||||
pad->chainnotify = g_free;
|
||||
} else
|
||||
g_free (data);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
wrap_pad_chain_function (GstPad * pad, ChainWrapperFunction new_function,
|
||||
gpointer user_data)
|
||||
{
|
||||
ChainWrapperFunctionData *data = g_new (ChainWrapperFunctionData, 1);
|
||||
data->wrapped_chain_func = pad->chainfunc;
|
||||
data->wrapped_chain_data = pad->chaindata;
|
||||
data->wrapped_chain_notify = pad->chainnotify;
|
||||
data->wrapper_function = new_function;
|
||||
data->wrapper_function_user_data = user_data;
|
||||
|
||||
pad->chainfunc = _pad_chain_wrapper;
|
||||
pad->chaindata = data;
|
||||
pad->chainnotify = g_free;
|
||||
}
|
||||
|
||||
static GstFlowReturn
|
||||
appsrc_push_chain_wrapper (GstPad * pad, GstObject * parent, GstBuffer * buffer,
|
||||
gpointer * user_data, gboolean * remove_wrapper)
|
||||
{
|
||||
GstValidateAction *action = (GstValidateAction *) user_data;
|
||||
GstFlowReturn ret = pad->chainfunc (pad, parent, buffer);
|
||||
gst_validate_action_set_done (action);
|
||||
*remove_wrapper = TRUE;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
structure_get_uint64_permissive (const GstStructure * structure,
|
||||
const gchar * fieldname, guint64 * dest)
|
||||
{
|
||||
const GValue *original;
|
||||
GValue transformed = G_VALUE_INIT;
|
||||
|
||||
original = gst_structure_get_value (structure, fieldname);
|
||||
if (!original)
|
||||
return FALSE;
|
||||
|
||||
g_value_init (&transformed, G_TYPE_UINT64);
|
||||
if (!g_value_transform (original, &transformed))
|
||||
return FALSE;
|
||||
|
||||
*dest = g_value_get_uint64 (&transformed);
|
||||
g_value_unset (&transformed);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gint
|
||||
_execute_appsrc_push (GstValidateScenario * scenario,
|
||||
GstValidateAction * action)
|
||||
{
|
||||
GstElement *target;
|
||||
gchar *file_name;
|
||||
gchar *file_contents;
|
||||
gsize file_length;
|
||||
GError *error = NULL;
|
||||
GstBuffer *buffer;
|
||||
guint64 offset = 0;
|
||||
guint64 size = -1;
|
||||
gint push_buffer_ret;
|
||||
|
||||
target = _get_target_element (scenario, action);
|
||||
if (target == NULL) {
|
||||
gchar *structure_string = gst_structure_to_string (action->structure);
|
||||
GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,
|
||||
"No element found for action: %s", structure_string);
|
||||
g_free (structure_string);
|
||||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||||
}
|
||||
|
||||
file_name =
|
||||
g_strdup (gst_structure_get_string (action->structure, "file-name"));
|
||||
if (file_name == NULL) {
|
||||
gchar *structure_string = gst_structure_to_string (action->structure);
|
||||
GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,
|
||||
"Missing file-name property: %s", structure_string);
|
||||
g_free (structure_string);
|
||||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||||
}
|
||||
|
||||
structure_get_uint64_permissive (action->structure, "offset", &offset);
|
||||
structure_get_uint64_permissive (action->structure, "size", &size);
|
||||
|
||||
g_file_get_contents (file_name, &file_contents, &file_length, &error);
|
||||
if (error != NULL) {
|
||||
gchar *structure_string = gst_structure_to_string (action->structure);
|
||||
GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,
|
||||
"Could not open file for action: %s. Error: %s", structure_string,
|
||||
error->message);
|
||||
g_free (structure_string);
|
||||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||||
}
|
||||
buffer = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY, file_contents,
|
||||
file_length, offset, (size == -1 ? file_length : size), NULL, g_free);
|
||||
|
||||
{
|
||||
const GValue *caps_value;
|
||||
caps_value = gst_structure_get_value (action->structure, "caps");
|
||||
if (caps_value)
|
||||
g_object_set (target, "caps", gst_value_get_caps (caps_value), NULL);
|
||||
}
|
||||
|
||||
/* We temporarily override the peer pad chain function to finish the action
|
||||
* once the buffer chain actually ends. */
|
||||
{
|
||||
GstPad *appsrc_pad = gst_element_get_static_pad (target, "src");
|
||||
GstPad *peer_pad = gst_pad_get_peer (appsrc_pad);
|
||||
if (!peer_pad) {
|
||||
gchar *structure_string = gst_structure_to_string (action->structure);
|
||||
GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,
|
||||
"Action failed, pad not linked: %s", structure_string);
|
||||
g_free (structure_string);
|
||||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||||
}
|
||||
|
||||
wrap_pad_chain_function (peer_pad, appsrc_push_chain_wrapper, action);
|
||||
|
||||
gst_object_unref (appsrc_pad);
|
||||
gst_object_unref (peer_pad);
|
||||
}
|
||||
|
||||
g_signal_emit_by_name (target, "push-buffer", buffer, &push_buffer_ret);
|
||||
if (push_buffer_ret != GST_FLOW_OK) {
|
||||
gchar *structure_string = gst_structure_to_string (action->structure);
|
||||
GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,
|
||||
"push-buffer signal failed in action: %s", structure_string);
|
||||
g_free (structure_string);
|
||||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||||
}
|
||||
|
||||
g_free (file_name);
|
||||
gst_object_unref (target);
|
||||
return GST_VALIDATE_EXECUTE_ACTION_ASYNC;
|
||||
}
|
||||
|
||||
static gint
|
||||
_execute_appsrc_eos (GstValidateScenario * scenario, GstValidateAction * action)
|
||||
{
|
||||
GstElement *target;
|
||||
gint eos_ret;
|
||||
|
||||
target = _get_target_element (scenario, action);
|
||||
if (target == NULL) {
|
||||
gchar *structure_string = gst_structure_to_string (action->structure);
|
||||
GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,
|
||||
"No element found for action: %s", structure_string);
|
||||
g_free (structure_string);
|
||||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||||
}
|
||||
|
||||
g_signal_emit_by_name (target, "end-of-stream", &eos_ret);
|
||||
if (eos_ret != GST_FLOW_OK) {
|
||||
gchar *structure_string = gst_structure_to_string (action->structure);
|
||||
GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,
|
||||
"Failed to emit end-of-stream signal for action: %s", structure_string);
|
||||
g_free (structure_string);
|
||||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||||
}
|
||||
|
||||
gst_object_unref (target);
|
||||
return GST_VALIDATE_EXECUTE_ACTION_OK;
|
||||
}
|
||||
|
||||
static gint
|
||||
_execute_flush (GstValidateScenario * scenario, GstValidateAction * action)
|
||||
{
|
||||
GstElement *target;
|
||||
GstEvent *event;
|
||||
gboolean reset_time = TRUE;
|
||||
|
||||
target = _get_target_element (scenario, action);
|
||||
if (target == NULL) {
|
||||
gchar *structure_string = gst_structure_to_string (action->structure);
|
||||
GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,
|
||||
"No element found for action: %s", structure_string);
|
||||
g_free (structure_string);
|
||||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||||
}
|
||||
|
||||
gst_structure_get_boolean (action->structure, "reset-time", &reset_time);
|
||||
|
||||
event = gst_event_new_flush_start ();
|
||||
if (!gst_element_send_event (target, event)) {
|
||||
GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,
|
||||
"FLUSH_START event was not handled");
|
||||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||||
}
|
||||
|
||||
event = gst_event_new_flush_stop (reset_time);
|
||||
if (!gst_element_send_event (target, event)) {
|
||||
GST_VALIDATE_REPORT (scenario, SCENARIO_ACTION_EXECUTION_ERROR,
|
||||
"FLUSH_STOP event was not handled");
|
||||
return GST_VALIDATE_EXECUTE_ACTION_ERROR_REPORTED;
|
||||
}
|
||||
|
||||
return GST_VALIDATE_EXECUTE_ACTION_OK;
|
||||
}
|
||||
|
||||
static GstValidateExecuteActionReturn
|
||||
_execute_disable_plugin (GstValidateScenario * scenario,
|
||||
GstValidateAction * action)
|
||||
|
@ -4679,6 +4921,79 @@ init_scenarios (void)
|
|||
"Emits a signal to an element in the pipeline",
|
||||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||||
|
||||
REGISTER_ACTION_TYPE ("appsrc-push", _execute_appsrc_push,
|
||||
((GstValidateActionParameter [])
|
||||
{
|
||||
{
|
||||
.name = "target-element-name",
|
||||
.description = "The name of the appsrc to push data on",
|
||||
.mandatory = TRUE,
|
||||
.types = "string"
|
||||
},
|
||||
{
|
||||
.name = "file-name",
|
||||
.description = "Relative path to a file whose contents will be pushed as a buffer",
|
||||
.mandatory = TRUE,
|
||||
.types = "string"
|
||||
},
|
||||
{
|
||||
.name = "offset",
|
||||
.description = "Offset within the file where the buffer will start",
|
||||
.mandatory = FALSE,
|
||||
.types = "uint64"
|
||||
},
|
||||
{
|
||||
.name = "size",
|
||||
.description = "Number of bytes from the file that will be pushed as a buffer",
|
||||
.mandatory = FALSE,
|
||||
.types = "uint64"
|
||||
},
|
||||
{
|
||||
.name = "caps",
|
||||
.description = "Caps for the buffer to be pushed",
|
||||
.mandatory = FALSE,
|
||||
.types = "caps"
|
||||
},
|
||||
{NULL}
|
||||
}),
|
||||
"Queues a buffer from an appsrc and waits for it to be handled by downstream elements in the same streaming thread.",
|
||||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||||
|
||||
REGISTER_ACTION_TYPE ("appsrc-eos", _execute_appsrc_eos,
|
||||
((GstValidateActionParameter [])
|
||||
{
|
||||
{
|
||||
.name = "target-element-name",
|
||||
.description = "The name of the appsrc to emit EOS on",
|
||||
.mandatory = TRUE,
|
||||
.types = "string"
|
||||
},
|
||||
{NULL}
|
||||
}),
|
||||
"Queues a EOS event in an appsrc.",
|
||||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||||
|
||||
REGISTER_ACTION_TYPE ("flush", _execute_flush,
|
||||
((GstValidateActionParameter [])
|
||||
{
|
||||
{
|
||||
.name = "target-element-name",
|
||||
.description = "The name of the appsrc to flush on",
|
||||
.mandatory = TRUE,
|
||||
.types = "string"
|
||||
},
|
||||
{
|
||||
.name = "reset-time",
|
||||
.description = "Whether the flush should reset running time",
|
||||
.mandatory = FALSE,
|
||||
.types = "boolean",
|
||||
.def = "TRUE"
|
||||
},
|
||||
{NULL}
|
||||
}),
|
||||
"Sends FLUSH_START and FLUSH_STOP events.",
|
||||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||||
|
||||
REGISTER_ACTION_TYPE ("disable-plugin", _execute_disable_plugin,
|
||||
((GstValidateActionParameter [])
|
||||
{
|
||||
|
|
|
@ -156,7 +156,7 @@ class FakeMediaDescriptor(MediaDescriptor):
|
|||
class GstValidatePipelineTestsGenerator(GstValidateTestsGenerator):
|
||||
|
||||
def __init__(self, name, test_manager, pipeline_template=None,
|
||||
pipelines_descriptions=None, valid_scenarios=[]):
|
||||
pipelines_descriptions=None, valid_scenarios=None):
|
||||
"""
|
||||
@name: The name of the generator
|
||||
@pipeline_template: A template pipeline to be used to generate actual pipelines
|
||||
|
@ -170,6 +170,7 @@ class GstValidatePipelineTestsGenerator(GstValidateTestsGenerator):
|
|||
|
||||
@valid_scenarios: A list of scenario name that can be used with that generator
|
||||
"""
|
||||
valid_scenarios = valid_scenarios or []
|
||||
GstValidateTestsGenerator.__init__(self, name, test_manager)
|
||||
self._pipeline_template = pipeline_template
|
||||
self._pipelines_descriptions = []
|
||||
|
@ -185,7 +186,14 @@ class GstValidatePipelineTestsGenerator(GstValidateTestsGenerator):
|
|||
self._valid_scenarios = valid_scenarios
|
||||
|
||||
@classmethod
|
||||
def from_json(self, test_manager, json_file):
|
||||
def from_json(cls, test_manager, json_file, extra_data=None):
|
||||
"""
|
||||
:param json_file: Path to a JSON file containing pipeline tests.
|
||||
:param extra_data: Variables available for interpolation in validate
|
||||
configs and scenario actions.
|
||||
"""
|
||||
if extra_data is None:
|
||||
extra_data = {}
|
||||
with open(json_file, 'r') as f:
|
||||
descriptions = json.load(f)
|
||||
|
||||
|
@ -193,28 +201,56 @@ class GstValidatePipelineTestsGenerator(GstValidateTestsGenerator):
|
|||
pipelines_descriptions = []
|
||||
for test_name, defs in descriptions.items():
|
||||
tests_definition = {'name': test_name, 'pipeline': defs['pipeline']}
|
||||
test_private_dir = os.path.join(test_manager.options.privatedir,
|
||||
name, test_name)
|
||||
|
||||
config_file = None
|
||||
if 'config' in defs:
|
||||
os.makedirs(test_private_dir, exist_ok=True)
|
||||
config_file = os.path.join(test_private_dir,
|
||||
test_name + '.config')
|
||||
with open(config_file, 'w') as f:
|
||||
f.write(cls._format_config_template(extra_data,
|
||||
'\n'.join(defs['config']) + '\n', test_name))
|
||||
|
||||
scenarios = []
|
||||
for scenario in defs['scenarios']:
|
||||
for scenario in defs.get('scenarios', []):
|
||||
if isinstance(scenario, str):
|
||||
# Path to a scenario file
|
||||
scenarios.append(scenario)
|
||||
else:
|
||||
# Dictionary defining a new scenario in-line
|
||||
scenario_name = scenario_file = scenario['name']
|
||||
actions = scenario.get('actions')
|
||||
if actions:
|
||||
scenario_dir = os.path.join(
|
||||
test_manager.options.privatedir, name, test_name)
|
||||
os.makedirs(test_private_dir, exist_ok=True)
|
||||
scenario_file = os.path.join(
|
||||
scenario_dir, scenario_name + '.scenario')
|
||||
os.makedirs(scenario_dir, exist_ok=True)
|
||||
test_private_dir, scenario_name + '.scenario')
|
||||
with open(scenario_file, 'w') as f:
|
||||
f.write('\n'.join(actions) + '\n')
|
||||
f.write('\n'.join(action % extra_data for action in actions) + '\n')
|
||||
scenarios.append(scenario_file)
|
||||
tests_definition['extra_data'] = {'scenarios': scenarios}
|
||||
tests_definition['extra_data'] = {'scenarios': scenarios, 'config_file': config_file}
|
||||
tests_definition['pipeline_data'] = {"config_path": os.path.dirname(json_file)}
|
||||
pipelines_descriptions.append(tests_definition)
|
||||
|
||||
return GstValidatePipelineTestsGenerator(name, test_manager, pipelines_descriptions=pipelines_descriptions)
|
||||
|
||||
@classmethod
|
||||
def _format_config_template(cls, extra_data, config_text, test_name):
|
||||
# Variables available for interpolation inside config blocks.
|
||||
|
||||
extra_vars = extra_data.copy()
|
||||
|
||||
if 'validate-flow-expectations-dir' in extra_vars and \
|
||||
'validate-flow-actual-results-dir' in extra_vars:
|
||||
expectations_dir = os.path.join(extra_vars['validate-flow-expectations-dir'],
|
||||
test_name.replace('.', os.sep))
|
||||
actual_results_dir = os.path.join(extra_vars['validate-flow-actual-results-dir'],
|
||||
test_name.replace('.', os.sep))
|
||||
extra_vars['validateflow'] = "validateflow, expectations-dir=\"%s\", actual-results-dir=\"%s\"" % (expectations_dir, actual_results_dir)
|
||||
|
||||
return config_text % extra_vars
|
||||
|
||||
def get_fname(self, scenario, protocol=None, name=None):
|
||||
if name is None:
|
||||
name = self.name
|
||||
|
@ -245,7 +281,16 @@ class GstValidatePipelineTestsGenerator(GstValidateTestsGenerator):
|
|||
extra_data = description.get('extra_data', {})
|
||||
pipeline_data = description.get('pipeline_data', {})
|
||||
|
||||
for scenario in extra_data.get('scenarios', scenarios):
|
||||
if 'scenarios' in extra_data:
|
||||
# A pipeline description can override the default scenario set.
|
||||
# The pipeline description may specify an empty list of
|
||||
# scenarios, in which case one test will be generated with no
|
||||
# scenario.
|
||||
scenarios_to_iterate = extra_data['scenarios'] or [None]
|
||||
else:
|
||||
scenarios_to_iterate = scenarios
|
||||
|
||||
for scenario in scenarios_to_iterate:
|
||||
if isinstance(scenario, str):
|
||||
scenario = self.test_manager.scenarios_manager.get_scenario(
|
||||
scenario)
|
||||
|
@ -255,7 +300,8 @@ class GstValidatePipelineTestsGenerator(GstValidateTestsGenerator):
|
|||
continue
|
||||
|
||||
if self.test_manager.options.mute:
|
||||
needs_clock = scenario.needs_clock_sync()
|
||||
needs_clock = scenario.needs_clock_sync() \
|
||||
if scenario else False
|
||||
audiosink = self.get_fakesink_for_media_type(
|
||||
"audio", needs_clock)
|
||||
videosink = self.get_fakesink_for_media_type(
|
||||
|
@ -272,15 +318,17 @@ class GstValidatePipelineTestsGenerator(GstValidateTestsGenerator):
|
|||
|
||||
expected_failures = extra_data.get("expected-failures")
|
||||
extra_env_vars = extra_data.get("extra_env_vars")
|
||||
self.add_test(GstValidateLaunchTest(fname,
|
||||
self.test_manager.options,
|
||||
self.test_manager.reporter,
|
||||
pipeline_desc,
|
||||
scenario=scenario,
|
||||
media_descriptor=mediainfo,
|
||||
expected_failures=expected_failures,
|
||||
extra_env_variables=extra_env_vars)
|
||||
)
|
||||
test = GstValidateLaunchTest(fname,
|
||||
self.test_manager.options,
|
||||
self.test_manager.reporter,
|
||||
pipeline_desc,
|
||||
scenario=scenario,
|
||||
media_descriptor=mediainfo,
|
||||
expected_failures=expected_failures,
|
||||
extra_env_variables=extra_env_vars)
|
||||
if extra_data.get('config_file'):
|
||||
test.add_validate_config(extra_data['config_file'])
|
||||
self.add_test(test)
|
||||
|
||||
|
||||
class GstValidatePlaybinTestsGenerator(GstValidatePipelineTestsGenerator):
|
||||
|
@ -377,7 +425,10 @@ class GstValidatePlaybinTestsGenerator(GstValidatePipelineTestsGenerator):
|
|||
class GstValidateMixerTestsGenerator(GstValidatePipelineTestsGenerator):
|
||||
|
||||
def __init__(self, name, test_manager, mixer, media_type, converter="",
|
||||
num_sources=3, mixed_srcs={}, valid_scenarios=[]):
|
||||
num_sources=3, mixed_srcs=None, valid_scenarios=None):
|
||||
mixed_srcs = mixed_srcs or {}
|
||||
valid_scenarios = valid_scenarios or []
|
||||
|
||||
pipe_template = "%(mixer)s name=_mixer ! " + \
|
||||
converter + " ! %(sink)s "
|
||||
self.converter = converter
|
||||
|
|
|
@ -1520,10 +1520,11 @@ class _TestsLauncher(Loggable):
|
|||
for testsuite in self.options.testsuites:
|
||||
loaded = False
|
||||
wanted_test_manager = None
|
||||
if hasattr(testsuite, "TEST_MANAGER"):
|
||||
wanted_test_manager = testsuite.TEST_MANAGER
|
||||
if not isinstance(wanted_test_manager, list):
|
||||
wanted_test_manager = [wanted_test_manager]
|
||||
# TEST_MANAGER has been set in _load_testsuites()
|
||||
assert hasattr(testsuite, "TEST_MANAGER")
|
||||
wanted_test_manager = testsuite.TEST_MANAGER
|
||||
if not isinstance(wanted_test_manager, list):
|
||||
wanted_test_manager = [wanted_test_manager]
|
||||
|
||||
for tester in self.testers:
|
||||
if wanted_test_manager is not None and \
|
||||
|
@ -1763,11 +1764,8 @@ class _TestsLauncher(Loggable):
|
|||
return True
|
||||
|
||||
def _run_tests(self):
|
||||
cur_test_num = 0
|
||||
|
||||
if not self.all_tests:
|
||||
all_tests = self.list_tests()
|
||||
self.all_tests = all_tests
|
||||
self.all_tests = self.list_tests()
|
||||
self.total_num_tests = len(self.all_tests)
|
||||
if not sys.stdout.isatty():
|
||||
printc("\nRunning %d tests..." % self.total_num_tests, color=Colors.HEADER)
|
||||
|
@ -1806,8 +1804,8 @@ class _TestsLauncher(Loggable):
|
|||
current_test_num += 1
|
||||
res = test.test_end()
|
||||
self.reporter.after_test(test)
|
||||
if res != Result.PASSED and (self.options.forever or
|
||||
self.options.fatal_error):
|
||||
if res != Result.PASSED and (self.options.forever
|
||||
or self.options.fatal_error):
|
||||
return False
|
||||
if self.start_new_job(tests_left):
|
||||
jobs_running += 1
|
||||
|
|
275
validate/plugins/flow/formatting.c
Normal file
275
validate/plugins/flow/formatting.c
Normal file
|
@ -0,0 +1,275 @@
|
|||
/* GStreamer
|
||||
*
|
||||
* Copyright (C) 2018-2019 Igalia S.L.
|
||||
* Copyright (C) 2018 Metrological Group B.V.
|
||||
* Author: Alicia Boya García <aboya@igalia.com>
|
||||
*
|
||||
* formatting.c: Functions used by validateflow to get string
|
||||
* representations of buffers.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include "formatting.h"
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
typedef void (*Uint64Formatter) (gchar * dest, guint64 time);
|
||||
|
||||
void
|
||||
format_time (gchar * dest_str, guint64 time)
|
||||
{
|
||||
if (GST_CLOCK_TIME_IS_VALID (time)) {
|
||||
sprintf (dest_str, "%" GST_TIME_FORMAT, GST_TIME_ARGS (time));
|
||||
} else {
|
||||
strcpy (dest_str, "none");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
format_number (gchar * dest_str, guint64 number)
|
||||
{
|
||||
sprintf (dest_str, "%" G_GUINT64_FORMAT, number);
|
||||
}
|
||||
|
||||
gchar *
|
||||
validate_flow_format_segment (const GstSegment * segment)
|
||||
{
|
||||
Uint64Formatter uint64_format;
|
||||
gchar *segment_str;
|
||||
gchar *parts[7];
|
||||
GString *format;
|
||||
gchar start_str[32], offset_str[32], stop_str[32], time_str[32], base_str[32],
|
||||
position_str[32], duration_str[32];
|
||||
int parts_index = 0;
|
||||
|
||||
uint64_format =
|
||||
segment->format == GST_FORMAT_TIME ? format_time : format_number;
|
||||
uint64_format (start_str, segment->start);
|
||||
uint64_format (offset_str, segment->offset);
|
||||
uint64_format (stop_str, segment->stop);
|
||||
uint64_format (time_str, segment->time);
|
||||
uint64_format (base_str, segment->base);
|
||||
uint64_format (position_str, segment->position);
|
||||
uint64_format (duration_str, segment->duration);
|
||||
|
||||
format = g_string_new (gst_format_get_name (segment->format));
|
||||
format = g_string_ascii_up (format);
|
||||
parts[parts_index++] =
|
||||
g_strdup_printf ("format=%s, start=%s, offset=%s, stop=%s", format->str,
|
||||
start_str, offset_str, stop_str);
|
||||
if (segment->rate != 1.0)
|
||||
parts[parts_index++] = g_strdup_printf ("rate=%f", segment->rate);
|
||||
if (segment->applied_rate != 1.0)
|
||||
parts[parts_index++] =
|
||||
g_strdup_printf ("applied_rate=%f", segment->applied_rate);
|
||||
if (segment->flags)
|
||||
parts[parts_index++] = g_strdup_printf ("flags=0x%02x", segment->flags);
|
||||
parts[parts_index++] =
|
||||
g_strdup_printf ("time=%s, base=%s, position=%s", time_str, base_str,
|
||||
position_str);
|
||||
if (GST_CLOCK_TIME_IS_VALID (segment->duration))
|
||||
parts[parts_index++] = g_strdup_printf ("duration=%s", duration_str);
|
||||
parts[parts_index] = NULL;
|
||||
|
||||
segment_str = g_strjoinv (", ", parts);
|
||||
|
||||
while (parts_index > 0)
|
||||
g_free (parts[--parts_index]);
|
||||
g_string_free (format, TRUE);
|
||||
|
||||
return segment_str;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
structure_only_given_keys (GQuark field_id, GValue * value,
|
||||
gpointer _keys_to_print)
|
||||
{
|
||||
const gchar *const *keys_to_print = (const gchar * const *) _keys_to_print;
|
||||
return (!keys_to_print
|
||||
|| g_strv_contains (keys_to_print, g_quark_to_string (field_id)));
|
||||
}
|
||||
|
||||
static void
|
||||
gpointer_free (gpointer pointer_location)
|
||||
{
|
||||
g_free (*(void **) pointer_location);
|
||||
}
|
||||
|
||||
gchar *
|
||||
validate_flow_format_caps (const GstCaps * caps,
|
||||
const gchar * const *keys_to_print)
|
||||
{
|
||||
guint i;
|
||||
GArray *structures_strv = g_array_new (TRUE, FALSE, sizeof (gchar *));
|
||||
gchar *caps_str;
|
||||
|
||||
g_array_set_clear_func (structures_strv, gpointer_free);
|
||||
|
||||
/* A single GstCaps can contain several caps structures (although only one is
|
||||
* used in most cases). We will print them separated with spaces. */
|
||||
for (i = 0; i < gst_caps_get_size (caps); i++) {
|
||||
GstStructure *structure =
|
||||
gst_structure_copy (gst_caps_get_structure (caps, i));
|
||||
gchar *structure_str;
|
||||
gst_structure_filter_and_map_in_place (structure, structure_only_given_keys,
|
||||
(gpointer) keys_to_print);
|
||||
structure_str = gst_structure_to_string (structure);
|
||||
g_array_append_val (structures_strv, structure_str);
|
||||
}
|
||||
|
||||
caps_str = g_strjoinv (" ", (gchar **) structures_strv->data);
|
||||
g_array_free (structures_strv, TRUE);
|
||||
return caps_str;
|
||||
}
|
||||
|
||||
|
||||
static gchar *
|
||||
buffer_get_flags_string (GstBuffer * buffer)
|
||||
{
|
||||
GFlagsClass *flags_class =
|
||||
G_FLAGS_CLASS (g_type_class_ref (gst_buffer_flags_get_type ()));
|
||||
GstBufferFlags flags = GST_BUFFER_FLAGS (buffer);
|
||||
GString *string = NULL;
|
||||
|
||||
while (1) {
|
||||
GFlagsValue *value = g_flags_get_first_value (flags_class, flags);
|
||||
if (!value)
|
||||
break;
|
||||
|
||||
if (string == NULL)
|
||||
string = g_string_new (NULL);
|
||||
else
|
||||
g_string_append (string, " ");
|
||||
|
||||
g_string_append (string, value->value_nick);
|
||||
flags &= ~value->value;
|
||||
}
|
||||
|
||||
return (string != NULL) ? g_string_free (string, FALSE) : NULL;
|
||||
}
|
||||
|
||||
/* Returns a newly-allocated string describing the metas on this buffer, or NULL */
|
||||
static gchar *
|
||||
buffer_get_meta_string (GstBuffer * buffer)
|
||||
{
|
||||
gpointer state = NULL;
|
||||
GstMeta *meta;
|
||||
GString *s = NULL;
|
||||
|
||||
while ((meta = gst_buffer_iterate_meta (buffer, &state))) {
|
||||
const gchar *desc = g_type_name (meta->info->type);
|
||||
|
||||
if (s == NULL)
|
||||
s = g_string_new (NULL);
|
||||
else
|
||||
g_string_append (s, ", ");
|
||||
|
||||
g_string_append (s, desc);
|
||||
}
|
||||
|
||||
return (s != NULL) ? g_string_free (s, FALSE) : NULL;
|
||||
}
|
||||
|
||||
gchar *
|
||||
validate_flow_format_buffer (GstBuffer * buffer)
|
||||
{
|
||||
gchar *flags_str, *meta_str, *buffer_str;
|
||||
gchar *buffer_parts[6];
|
||||
int buffer_parts_index = 0;
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID (buffer->dts)) {
|
||||
gchar time_str[32];
|
||||
format_time (time_str, buffer->dts);
|
||||
buffer_parts[buffer_parts_index++] = g_strdup_printf ("dts=%s", time_str);
|
||||
}
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID (buffer->pts)) {
|
||||
gchar time_str[32];
|
||||
format_time (time_str, buffer->pts);
|
||||
buffer_parts[buffer_parts_index++] = g_strdup_printf ("pts=%s", time_str);
|
||||
}
|
||||
|
||||
if (GST_CLOCK_TIME_IS_VALID (buffer->duration)) {
|
||||
gchar time_str[32];
|
||||
format_time (time_str, buffer->duration);
|
||||
buffer_parts[buffer_parts_index++] = g_strdup_printf ("dur=%s", time_str);
|
||||
}
|
||||
|
||||
flags_str = buffer_get_flags_string (buffer);
|
||||
if (flags_str) {
|
||||
buffer_parts[buffer_parts_index++] =
|
||||
g_strdup_printf ("flags=%s", flags_str);
|
||||
}
|
||||
|
||||
meta_str = buffer_get_meta_string (buffer);
|
||||
if (meta_str)
|
||||
buffer_parts[buffer_parts_index++] = g_strdup_printf ("meta=%s", meta_str);
|
||||
|
||||
buffer_parts[buffer_parts_index] = NULL;
|
||||
buffer_str =
|
||||
buffer_parts_index > 0 ? g_strjoinv (", ",
|
||||
buffer_parts) : g_strdup ("(empty)");
|
||||
|
||||
g_free (meta_str);
|
||||
g_free (flags_str);
|
||||
while (buffer_parts_index > 0)
|
||||
g_free (buffer_parts[--buffer_parts_index]);
|
||||
|
||||
return buffer_str;
|
||||
}
|
||||
|
||||
gchar *
|
||||
validate_flow_format_event (GstEvent * event, gboolean allow_stream_id,
|
||||
const gchar * const *caps_properties)
|
||||
{
|
||||
const gchar *event_type;
|
||||
gchar *structure_string;
|
||||
gchar *event_string;
|
||||
|
||||
event_type = gst_event_type_get_name (GST_EVENT_TYPE (event));
|
||||
|
||||
if (GST_EVENT_TYPE (event) == GST_EVENT_SEGMENT) {
|
||||
const GstSegment *segment;
|
||||
gst_event_parse_segment (event, &segment);
|
||||
structure_string = validate_flow_format_segment (segment);
|
||||
} else if (GST_EVENT_TYPE (event) == GST_EVENT_CAPS) {
|
||||
GstCaps *caps;
|
||||
gst_event_parse_caps (event, &caps);
|
||||
structure_string = validate_flow_format_caps (caps, caps_properties);
|
||||
} else if (!gst_event_get_structure (event)) {
|
||||
structure_string = g_strdup ("(no structure)");
|
||||
} else {
|
||||
GstStructure *printable =
|
||||
gst_structure_copy (gst_event_get_structure (event));
|
||||
|
||||
if (GST_EVENT_TYPE (event) == GST_EVENT_STREAM_START && !allow_stream_id)
|
||||
gst_structure_remove_fields (printable, "stream-id", NULL);
|
||||
|
||||
structure_string = gst_structure_to_string (printable);
|
||||
gst_structure_free (printable);
|
||||
}
|
||||
|
||||
event_string = g_strdup_printf ("%s: %s", event_type, structure_string);
|
||||
g_free (structure_string);
|
||||
return event_string;
|
||||
}
|
38
validate/plugins/flow/formatting.h
Normal file
38
validate/plugins/flow/formatting.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
/* GStreamer
|
||||
*
|
||||
* Copyright (C) 2018-2019 Igalia S.L.
|
||||
* Copyright (C) 2018 Metrological Group B.V.
|
||||
* Author: Alicia Boya García <aboya@igalia.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#ifndef __GST_VALIDATE_FLOW_FORMATTING_H__
|
||||
#define __GST_VALIDATE_FLOW_FORMATTING_H__
|
||||
|
||||
#include <gst/gst.h>
|
||||
|
||||
void format_time(gchar* dest_str, guint64 time);
|
||||
|
||||
gchar* validate_flow_format_segment (const GstSegment *segment);
|
||||
|
||||
gchar* validate_flow_format_caps (const GstCaps* caps, const gchar * const *keys_to_print);
|
||||
|
||||
gchar* validate_flow_format_buffer (GstBuffer *buffer);
|
||||
|
||||
gchar* validate_flow_format_event (GstEvent *event, gboolean allow_stream_id, const gchar * const *caps_properties);
|
||||
|
||||
#endif // __GST_VALIDATE_FLOW_FORMATTING_H__
|
485
validate/plugins/flow/gstvalidateflow.c
Normal file
485
validate/plugins/flow/gstvalidateflow.c
Normal file
|
@ -0,0 +1,485 @@
|
|||
/* GStreamer
|
||||
*
|
||||
* Copyright (C) 2018-2019 Igalia S.L.
|
||||
* Copyright (C) 2018 Metrological Group B.V.
|
||||
* Author: Alicia Boya García <aboya@igalia.com>
|
||||
*
|
||||
* gstvalidateflow.c: A plugin to record streams and match them to
|
||||
* expectation files.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the
|
||||
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
||||
* Boston, MA 02111-1307, USA.
|
||||
*/
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include "../../gst/validate/validate.h"
|
||||
#include "../../gst/validate/gst-validate-utils.h"
|
||||
#include "../../gst/validate/gst-validate-report.h"
|
||||
#include "formatting.h"
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#define VALIDATE_FLOW_MISMATCH g_quark_from_static_string ("validateflow::mismatch")
|
||||
|
||||
typedef enum _ValidateFlowMode
|
||||
{
|
||||
VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS,
|
||||
VALIDATE_FLOW_MODE_WRITING_ACTUAL_RESULTS
|
||||
} ValidateFlowMode;
|
||||
|
||||
typedef struct _ValidateFlowOverride
|
||||
{
|
||||
GstValidateOverride parent;
|
||||
|
||||
const gchar *pad_name;
|
||||
gboolean record_buffers;
|
||||
gchar *expectations_dir;
|
||||
gchar *actual_results_dir;
|
||||
gboolean error_writing_file;
|
||||
gchar **caps_properties;
|
||||
gboolean record_stream_id;
|
||||
|
||||
gchar *expectations_file_path;
|
||||
gchar *actual_results_file_path;
|
||||
ValidateFlowMode mode;
|
||||
|
||||
/* output_file will refer to the expectations file if it did not exist,
|
||||
* or to the actual results file otherwise. */
|
||||
gchar *output_file_path;
|
||||
FILE *output_file;
|
||||
GMutex output_file_mutex;
|
||||
|
||||
} ValidateFlowOverride;
|
||||
|
||||
GList *all_overrides = NULL;
|
||||
|
||||
static void validate_flow_override_finalize (GObject * object);
|
||||
static void _runner_set (GObject * object, GParamSpec * pspec,
|
||||
gpointer user_data);
|
||||
static void runner_stopping (GstValidateRunner * runner,
|
||||
ValidateFlowOverride * flow);
|
||||
|
||||
#define VALIDATE_TYPE_FLOW_OVERRIDE validate_flow_override_get_type ()
|
||||
G_DECLARE_FINAL_TYPE (ValidateFlowOverride, validate_flow_override,
|
||||
VALIDATE, FLOW_OVERRIDE, GstValidateOverride);
|
||||
G_DEFINE_TYPE (ValidateFlowOverride, validate_flow_override,
|
||||
GST_TYPE_VALIDATE_OVERRIDE);
|
||||
|
||||
void
|
||||
validate_flow_override_init (ValidateFlowOverride * self)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
validate_flow_override_class_init (ValidateFlowOverrideClass * klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
object_class->finalize = validate_flow_override_finalize;
|
||||
|
||||
g_assert (gst_validate_is_initialized ());
|
||||
|
||||
gst_validate_issue_register (gst_validate_issue_new
|
||||
(VALIDATE_FLOW_MISMATCH,
|
||||
"The recorded log does not match the expectation file.",
|
||||
"The recorded log does not match the expectation file.",
|
||||
GST_VALIDATE_REPORT_LEVEL_CRITICAL));
|
||||
}
|
||||
|
||||
static void
|
||||
validate_flow_override_vprintf (ValidateFlowOverride * flow, const char *format,
|
||||
va_list ap)
|
||||
{
|
||||
g_mutex_lock (&flow->output_file_mutex);
|
||||
if (!flow->error_writing_file && vfprintf (flow->output_file, format, ap) < 0) {
|
||||
GST_ERROR_OBJECT (flow, "Writing to file %s failed",
|
||||
flow->output_file_path);
|
||||
flow->error_writing_file = TRUE;
|
||||
}
|
||||
g_mutex_unlock (&flow->output_file_mutex);
|
||||
}
|
||||
|
||||
static void
|
||||
validate_flow_override_printf (ValidateFlowOverride * flow, const char *format,
|
||||
...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start (ap, format);
|
||||
validate_flow_override_vprintf (flow, format, ap);
|
||||
va_end (ap);
|
||||
}
|
||||
|
||||
static void
|
||||
validate_flow_override_event_handler (GstValidateOverride * override,
|
||||
GstValidateMonitor * pad_monitor, GstEvent * event)
|
||||
{
|
||||
ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (override);
|
||||
gchar *event_string;
|
||||
|
||||
if (flow->error_writing_file)
|
||||
return;
|
||||
|
||||
event_string = validate_flow_format_event (event, flow->record_stream_id,
|
||||
(const gchar * const *) flow->caps_properties);
|
||||
validate_flow_override_printf (flow, "event %s\n", event_string);
|
||||
g_free (event_string);
|
||||
}
|
||||
|
||||
static void
|
||||
validate_flow_override_buffer_handler (GstValidateOverride * override,
|
||||
GstValidateMonitor * pad_monitor, GstBuffer * buffer)
|
||||
{
|
||||
ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (override);
|
||||
gchar *buffer_str;
|
||||
|
||||
if (flow->error_writing_file || !flow->record_buffers)
|
||||
return;
|
||||
|
||||
buffer_str = validate_flow_format_buffer (buffer);
|
||||
validate_flow_override_printf (flow, "buffer: %s\n", buffer_str);
|
||||
g_free (buffer_str);
|
||||
}
|
||||
|
||||
static gchar **
|
||||
parse_caps_properties_setting (const ValidateFlowOverride * flow,
|
||||
GstStructure * config)
|
||||
{
|
||||
const GValue *list;
|
||||
gchar **parsed_list;
|
||||
guint i, size;
|
||||
|
||||
list = gst_structure_get_value (config, "caps-properties");
|
||||
if (!list)
|
||||
return NULL;
|
||||
|
||||
if (!GST_VALUE_HOLDS_LIST (list)) {
|
||||
GST_ERROR_OBJECT (flow,
|
||||
"caps-properties must have type list of string, e.g. caps-properties={ width, height };");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size = gst_value_list_get_size (list);
|
||||
parsed_list = g_malloc_n (size + 1, sizeof (gchar *));
|
||||
for (i = 0; i < size; i++)
|
||||
parsed_list[i] = g_value_dup_string (gst_value_list_get_value (list, i));
|
||||
parsed_list[i] = NULL;
|
||||
return parsed_list;
|
||||
}
|
||||
|
||||
static ValidateFlowOverride *
|
||||
validate_flow_override_new (GstStructure * config)
|
||||
{
|
||||
ValidateFlowOverride *flow;
|
||||
GstValidateOverride *override;
|
||||
|
||||
flow = g_object_new (VALIDATE_TYPE_FLOW_OVERRIDE, NULL);
|
||||
override = GST_VALIDATE_OVERRIDE (flow);
|
||||
|
||||
/* pad: Name of the pad where flowing buffers and events will be monitorized. */
|
||||
flow->pad_name = gst_structure_get_string (config, "pad");
|
||||
if (!flow->pad_name) {
|
||||
g_error ("pad property is mandatory, not found in %s",
|
||||
gst_structure_to_string (config));
|
||||
}
|
||||
|
||||
/* record-buffers: Whether buffers will be written to the expectation log. */
|
||||
flow->record_buffers = FALSE;
|
||||
gst_structure_get_boolean (config, "record-buffers", &flow->record_buffers);
|
||||
|
||||
/* caps-properties: Caps events can include many dfferent properties, but
|
||||
* many of these may be irrelevant for some tests. If this option is set,
|
||||
* only the listed properties will be written to the expectation log. */
|
||||
flow->caps_properties = parse_caps_properties_setting (flow, config);
|
||||
|
||||
/* record-stream-id: stream-id's are often non reproducible (this is the case
|
||||
* for basesrc, for instance). For this reason, they are omitted by default
|
||||
* when recording a stream-start event. This setting allows to override that
|
||||
* behavior. */
|
||||
flow->record_stream_id = FALSE;
|
||||
gst_structure_get_boolean (config, "record-stream-id",
|
||||
&flow->record_stream_id);
|
||||
|
||||
/* expectations-dir: Path to the directory where the expectations will be
|
||||
* written if they don't exist, relative to the current working directory.
|
||||
* By default the current working directory is used. */
|
||||
flow->expectations_dir =
|
||||
g_strdup (gst_structure_get_string (config, "expectations-dir"));
|
||||
if (!flow->expectations_dir)
|
||||
flow->expectations_dir = g_strdup (".");
|
||||
|
||||
/* actual-results-dir: Path to the directory where the events will be
|
||||
* recorded. The expectation file will be compared to this. */
|
||||
flow->actual_results_dir =
|
||||
g_strdup (gst_structure_get_string (config, "actual-results-dir"));
|
||||
if (!flow->actual_results_dir)
|
||||
flow->actual_results_dir = g_strdup (".");
|
||||
|
||||
{
|
||||
gchar *expectations_file_name =
|
||||
g_strdup_printf ("log-%s-expected", flow->pad_name);
|
||||
gchar *actual_results_file_name =
|
||||
g_strdup_printf ("log-%s-actual", flow->pad_name);
|
||||
flow->expectations_file_path =
|
||||
g_build_path (G_DIR_SEPARATOR_S, flow->expectations_dir,
|
||||
expectations_file_name, NULL);
|
||||
flow->actual_results_file_path =
|
||||
g_build_path (G_DIR_SEPARATOR_S, flow->actual_results_dir,
|
||||
actual_results_file_name, NULL);
|
||||
g_free (expectations_file_name);
|
||||
g_free (actual_results_file_name);
|
||||
}
|
||||
|
||||
if (g_file_test (flow->expectations_file_path, G_FILE_TEST_EXISTS)) {
|
||||
flow->mode = VALIDATE_FLOW_MODE_WRITING_ACTUAL_RESULTS;
|
||||
flow->output_file_path = g_strdup (flow->actual_results_file_path);
|
||||
} else {
|
||||
flow->mode = VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS;
|
||||
flow->output_file_path = g_strdup (flow->expectations_file_path);
|
||||
gst_validate_printf (NULL, "Writing expectations file: %s\n",
|
||||
flow->expectations_file_path);
|
||||
}
|
||||
|
||||
{
|
||||
gchar *directory_path = g_path_get_dirname (flow->output_file_path);
|
||||
if (g_mkdir_with_parents (directory_path, 0755) < 0) {
|
||||
g_error ("Could not create directory tree: %s Reason: %s",
|
||||
directory_path, g_strerror (errno));
|
||||
}
|
||||
g_free (directory_path);
|
||||
}
|
||||
|
||||
flow->output_file = fopen (flow->output_file_path, "w");
|
||||
if (!flow->output_file)
|
||||
g_error ("Could not open for writing: %s", flow->output_file_path);
|
||||
|
||||
gst_validate_override_register_by_name (flow->pad_name, override);
|
||||
|
||||
override->buffer_handler = validate_flow_override_buffer_handler;
|
||||
override->event_handler = validate_flow_override_event_handler;
|
||||
|
||||
g_signal_connect (flow, "notify::validate-runner",
|
||||
G_CALLBACK (_runner_set), NULL);
|
||||
|
||||
return flow;
|
||||
}
|
||||
|
||||
static void
|
||||
_runner_set (GObject * object, GParamSpec * pspec, gpointer user_data)
|
||||
{
|
||||
ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (object);
|
||||
GstValidateRunner *runner =
|
||||
gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (flow));
|
||||
|
||||
g_signal_connect (runner, "stopping", G_CALLBACK (runner_stopping), flow);
|
||||
gst_object_unref (runner);
|
||||
}
|
||||
|
||||
static void
|
||||
run_diff (const gchar * expected_file, const gchar * actual_file)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GSubprocess *process =
|
||||
g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE, &error, "diff", "-u",
|
||||
"--", expected_file, actual_file, NULL);
|
||||
gchar *stdout_text = NULL;
|
||||
|
||||
g_subprocess_communicate_utf8 (process, NULL, NULL, &stdout_text, NULL,
|
||||
&error);
|
||||
if (!error) {
|
||||
fprintf (stderr, "%s\n", stdout_text);
|
||||
} else {
|
||||
fprintf (stderr, "Cannot show more details, failed to run diff: %s",
|
||||
error->message);
|
||||
g_error_free (error);
|
||||
}
|
||||
|
||||
g_object_unref (process);
|
||||
g_free (stdout);
|
||||
}
|
||||
|
||||
static const gchar *
|
||||
_line_to_show (gchar ** lines, gsize i)
|
||||
{
|
||||
if (lines[i] == NULL) {
|
||||
return "<nothing>";
|
||||
} else if (*lines[i] == '\0') {
|
||||
if (lines[i + 1] != NULL)
|
||||
/* skip blank lines for reporting purposes (e.g. before CHECKPOINT) */
|
||||
return lines[i + 1];
|
||||
else
|
||||
/* last blank line in the file */
|
||||
return "<nothing>";
|
||||
} else {
|
||||
return lines[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
show_mismatch_error (ValidateFlowOverride * flow, gchar ** lines_expected,
|
||||
gchar ** lines_actual, gsize line_index)
|
||||
{
|
||||
const gchar *line_expected = _line_to_show (lines_expected, line_index);
|
||||
const gchar *line_actual = _line_to_show (lines_actual, line_index);
|
||||
|
||||
GST_VALIDATE_REPORT (flow, VALIDATE_FLOW_MISMATCH,
|
||||
"Mismatch error in pad %s, line %" G_GSIZE_FORMAT
|
||||
". Expected:\n%s\nActual:\n%s\n", flow->pad_name, line_index + 1,
|
||||
line_expected, line_actual);
|
||||
|
||||
run_diff (flow->expectations_file_path, flow->actual_results_file_path);
|
||||
}
|
||||
|
||||
static void
|
||||
runner_stopping (GstValidateRunner * runner, ValidateFlowOverride * flow)
|
||||
{
|
||||
gchar **lines_expected, **lines_actual;
|
||||
gsize i = 0;
|
||||
|
||||
fclose (flow->output_file);
|
||||
flow->output_file = NULL;
|
||||
if (flow->mode == VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS)
|
||||
return;
|
||||
|
||||
{
|
||||
gchar *contents;
|
||||
GError *error = NULL;
|
||||
g_file_get_contents (flow->expectations_file_path, &contents, NULL, &error);
|
||||
if (error) {
|
||||
g_error ("Failed to open expectations file: %s Reason: %s",
|
||||
flow->expectations_file_path, error->message);
|
||||
}
|
||||
lines_expected = g_strsplit (contents, "\n", 0);
|
||||
}
|
||||
|
||||
{
|
||||
gchar *contents;
|
||||
GError *error = NULL;
|
||||
g_file_get_contents (flow->actual_results_file_path, &contents, NULL,
|
||||
&error);
|
||||
if (error) {
|
||||
g_error ("Failed to open actual results file: %s Reason: %s",
|
||||
flow->actual_results_file_path, error->message);
|
||||
}
|
||||
lines_actual = g_strsplit (contents, "\n", 0);
|
||||
}
|
||||
|
||||
for (i = 0; lines_expected[i] && lines_actual[i]; i++) {
|
||||
if (strcmp (lines_expected[i], lines_actual[i])) {
|
||||
show_mismatch_error (flow, lines_expected, lines_actual, i);
|
||||
goto stop;
|
||||
}
|
||||
}
|
||||
|
||||
if (!lines_expected[i] && lines_actual[i]) {
|
||||
show_mismatch_error (flow, lines_expected, lines_actual, i);
|
||||
} else if (lines_expected[i] && !lines_actual[i]) {
|
||||
show_mismatch_error (flow, lines_expected, lines_actual, i);
|
||||
}
|
||||
|
||||
stop:
|
||||
g_strfreev (lines_expected);
|
||||
g_strfreev (lines_actual);
|
||||
}
|
||||
|
||||
static void
|
||||
validate_flow_override_finalize (GObject * object)
|
||||
{
|
||||
ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (object);
|
||||
|
||||
all_overrides = g_list_remove (all_overrides, flow);
|
||||
g_free (flow->actual_results_dir);
|
||||
g_free (flow->actual_results_file_path);
|
||||
g_free (flow->expectations_dir);
|
||||
g_free (flow->expectations_file_path);
|
||||
g_free (flow->output_file_path);
|
||||
if (flow->output_file)
|
||||
fclose (flow->output_file);
|
||||
if (flow->caps_properties) {
|
||||
gchar **str_pointer;
|
||||
for (str_pointer = flow->caps_properties; *str_pointer != NULL;
|
||||
str_pointer++)
|
||||
g_free (*str_pointer);
|
||||
g_free (flow->caps_properties);
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (validate_flow_override_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
_execute_checkpoint (GstValidateScenario * scenario, GstValidateAction * action)
|
||||
{
|
||||
GList *i;
|
||||
gchar *checkpoint_name =
|
||||
g_strdup (gst_structure_get_string (action->structure, "text"));
|
||||
|
||||
for (i = all_overrides; i; i = i->next) {
|
||||
ValidateFlowOverride *flow = (ValidateFlowOverride *) i->data;
|
||||
|
||||
if (checkpoint_name)
|
||||
validate_flow_override_printf (flow, "\nCHECKPOINT: %s\n\n",
|
||||
checkpoint_name);
|
||||
else
|
||||
validate_flow_override_printf (flow, "\nCHECKPOINT\n\n");
|
||||
}
|
||||
|
||||
g_free (checkpoint_name);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_validate_flow_init (GstPlugin * plugin)
|
||||
{
|
||||
GList *tmp;
|
||||
GList *config_list = gst_validate_plugin_get_config (plugin);
|
||||
|
||||
if (!config_list)
|
||||
return TRUE;
|
||||
|
||||
for (tmp = config_list; tmp; tmp = tmp->next) {
|
||||
GstStructure *config = tmp->data;
|
||||
ValidateFlowOverride *flow = validate_flow_override_new (config);
|
||||
all_overrides = g_list_append (all_overrides, flow);
|
||||
}
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
gst_validate_register_action_type_dynamic (plugin, "checkpoint",
|
||||
GST_RANK_PRIMARY, _execute_checkpoint, ((GstValidateActionParameter [])
|
||||
{
|
||||
{
|
||||
.name = "text",
|
||||
.description = "Text that will be logged in validateflow",
|
||||
.mandatory = FALSE,
|
||||
.types = "string"
|
||||
},
|
||||
{NULL}
|
||||
}),
|
||||
"Prints a line of text in validateflow logs so that it's easy to distinguish buffers and events ocurring before or after a given action.",
|
||||
GST_VALIDATE_ACTION_TYPE_NONE);
|
||||
/* *INDENT-ON* */
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
||||
GST_VERSION_MINOR,
|
||||
validateflow,
|
||||
"GstValidate plugin that records buffers and events on specified pads and matches the log with expectation files.",
|
||||
gst_validate_flow_init, VERSION, "LGPL", GST_PACKAGE_NAME,
|
||||
GST_PACKAGE_ORIGIN)
|
9
validate/plugins/flow/meson.build
Normal file
9
validate/plugins/flow/meson.build
Normal file
|
@ -0,0 +1,9 @@
|
|||
shared_library('gstvalidateflow',
|
||||
'gstvalidateflow.c', 'formatting.c',
|
||||
include_directories : inc_dirs,
|
||||
c_args: ['-DHAVE_CONFIG_H'],
|
||||
install: true,
|
||||
install_dir: validate_plugins_install_dir,
|
||||
dependencies : [gst_dep, gst_pbutils_dep, gio_dep],
|
||||
link_with : [gstvalidate]
|
||||
)
|
|
@ -2,6 +2,7 @@ subdir('fault_injection')
|
|||
subdir('gapplication')
|
||||
subdir('ssim')
|
||||
subdir('extra_checks')
|
||||
subdir('flow')
|
||||
|
||||
if gtk_dep.found()
|
||||
subdir('gtk')
|
||||
|
|
Loading…
Reference in a new issue