3 * Copyright (C) 2018-2019 Igalia S.L.
4 * Copyright (C) 2018 Metrological Group B.V.
5 * Author: Alicia Boya GarcĂa <aboya@igalia.com>
7 * gstvalidateflow.c: A plugin to record streams and match them to
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the
22 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 * Boston, MA 02111-1307, USA.
27 #include "../../gst/validate/validate.h"
28 #include "../../gst/validate/gst-validate-utils.h"
29 #include "../../gst/validate/gst-validate-report.h"
30 #include "formatting.h"
31 #include <sys/types.h>
40 #define VALIDATE_FLOW_MISMATCH g_quark_from_static_string ("validateflow::mismatch")
42 typedef enum _ValidateFlowMode
44 VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS,
45 VALIDATE_FLOW_MODE_WRITING_ACTUAL_RESULTS
48 typedef struct _ValidateFlowOverride
50 GstValidateOverride parent;
52 const gchar *pad_name;
53 gboolean record_buffers;
54 gchar *expectations_dir;
55 gchar *actual_results_dir;
56 gboolean error_writing_file;
57 gchar **caps_properties;
58 gboolean record_stream_id;
60 gchar *expectations_file_path;
61 gchar *actual_results_file_path;
62 ValidateFlowMode mode;
64 /* output_file will refer to the expectations file if it did not exist,
65 * or to the actual results file otherwise. */
66 gchar *output_file_path;
68 GMutex output_file_mutex;
70 } ValidateFlowOverride;
72 GList *all_overrides = NULL;
74 static void validate_flow_override_finalize (GObject * object);
75 static void _runner_set (GObject * object, GParamSpec * pspec,
77 static void runner_stopping (GstValidateRunner * runner,
78 ValidateFlowOverride * flow);
80 #define VALIDATE_TYPE_FLOW_OVERRIDE validate_flow_override_get_type ()
81 G_DECLARE_FINAL_TYPE (ValidateFlowOverride, validate_flow_override,
82 VALIDATE, FLOW_OVERRIDE, GstValidateOverride);
83 G_DEFINE_TYPE (ValidateFlowOverride, validate_flow_override,
84 GST_TYPE_VALIDATE_OVERRIDE);
87 validate_flow_override_init (ValidateFlowOverride * self)
92 validate_flow_override_class_init (ValidateFlowOverrideClass * klass)
94 GObjectClass *object_class = G_OBJECT_CLASS (klass);
95 object_class->finalize = validate_flow_override_finalize;
97 g_assert (gst_validate_is_initialized ());
99 gst_validate_issue_register (gst_validate_issue_new
100 (VALIDATE_FLOW_MISMATCH,
101 "The recorded log does not match the expectation file.",
102 "The recorded log does not match the expectation file.",
103 GST_VALIDATE_REPORT_LEVEL_CRITICAL));
107 validate_flow_override_vprintf (ValidateFlowOverride * flow, const char *format,
110 g_mutex_lock (&flow->output_file_mutex);
111 if (!flow->error_writing_file && vfprintf (flow->output_file, format, ap) < 0) {
112 GST_ERROR_OBJECT (flow, "Writing to file %s failed",
113 flow->output_file_path);
114 flow->error_writing_file = TRUE;
116 g_mutex_unlock (&flow->output_file_mutex);
120 validate_flow_override_printf (ValidateFlowOverride * flow, const char *format,
124 va_start (ap, format);
125 validate_flow_override_vprintf (flow, format, ap);
130 validate_flow_override_event_handler (GstValidateOverride * override,
131 GstValidateMonitor * pad_monitor, GstEvent * event)
133 ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (override);
136 if (flow->error_writing_file)
139 event_string = validate_flow_format_event (event, flow->record_stream_id,
140 (const gchar * const *) flow->caps_properties);
141 validate_flow_override_printf (flow, "event %s\n", event_string);
142 g_free (event_string);
146 validate_flow_override_buffer_handler (GstValidateOverride * override,
147 GstValidateMonitor * pad_monitor, GstBuffer * buffer)
149 ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (override);
152 if (flow->error_writing_file || !flow->record_buffers)
155 buffer_str = validate_flow_format_buffer (buffer);
156 validate_flow_override_printf (flow, "buffer: %s\n", buffer_str);
161 parse_caps_properties_setting (const ValidateFlowOverride * flow,
162 GstStructure * config)
168 list = gst_structure_get_value (config, "caps-properties");
172 if (!GST_VALUE_HOLDS_LIST (list)) {
173 GST_ERROR_OBJECT (flow,
174 "caps-properties must have type list of string, e.g. caps-properties={ width, height };");
178 size = gst_value_list_get_size (list);
179 parsed_list = g_malloc_n (size + 1, sizeof (gchar *));
180 for (i = 0; i < size; i++)
181 parsed_list[i] = g_value_dup_string (gst_value_list_get_value (list, i));
182 parsed_list[i] = NULL;
186 static ValidateFlowOverride *
187 validate_flow_override_new (GstStructure * config)
189 ValidateFlowOverride *flow;
190 GstValidateOverride *override;
192 flow = g_object_new (VALIDATE_TYPE_FLOW_OVERRIDE, NULL);
193 override = GST_VALIDATE_OVERRIDE (flow);
195 /* pad: Name of the pad where flowing buffers and events will be monitorized. */
196 flow->pad_name = gst_structure_get_string (config, "pad");
197 if (!flow->pad_name) {
198 g_error ("pad property is mandatory, not found in %s",
199 gst_structure_to_string (config));
202 /* record-buffers: Whether buffers will be written to the expectation log. */
203 flow->record_buffers = FALSE;
204 gst_structure_get_boolean (config, "record-buffers", &flow->record_buffers);
206 /* caps-properties: Caps events can include many dfferent properties, but
207 * many of these may be irrelevant for some tests. If this option is set,
208 * only the listed properties will be written to the expectation log. */
209 flow->caps_properties = parse_caps_properties_setting (flow, config);
211 /* record-stream-id: stream-id's are often non reproducible (this is the case
212 * for basesrc, for instance). For this reason, they are omitted by default
213 * when recording a stream-start event. This setting allows to override that
215 flow->record_stream_id = FALSE;
216 gst_structure_get_boolean (config, "record-stream-id",
217 &flow->record_stream_id);
219 /* expectations-dir: Path to the directory where the expectations will be
220 * written if they don't exist, relative to the current working directory.
221 * By default the current working directory is used. */
222 flow->expectations_dir =
223 g_strdup (gst_structure_get_string (config, "expectations-dir"));
224 if (!flow->expectations_dir)
225 flow->expectations_dir = g_strdup (".");
227 /* actual-results-dir: Path to the directory where the events will be
228 * recorded. The expectation file will be compared to this. */
229 flow->actual_results_dir =
230 g_strdup (gst_structure_get_string (config, "actual-results-dir"));
231 if (!flow->actual_results_dir)
232 flow->actual_results_dir = g_strdup (".");
235 gchar *expectations_file_name =
236 g_strdup_printf ("log-%s-expected", flow->pad_name);
237 gchar *actual_results_file_name =
238 g_strdup_printf ("log-%s-actual", flow->pad_name);
239 flow->expectations_file_path =
240 g_build_path (G_DIR_SEPARATOR_S, flow->expectations_dir,
241 expectations_file_name, NULL);
242 flow->actual_results_file_path =
243 g_build_path (G_DIR_SEPARATOR_S, flow->actual_results_dir,
244 actual_results_file_name, NULL);
245 g_free (expectations_file_name);
246 g_free (actual_results_file_name);
249 if (g_file_test (flow->expectations_file_path, G_FILE_TEST_EXISTS)) {
250 flow->mode = VALIDATE_FLOW_MODE_WRITING_ACTUAL_RESULTS;
251 flow->output_file_path = g_strdup (flow->actual_results_file_path);
253 flow->mode = VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS;
254 flow->output_file_path = g_strdup (flow->expectations_file_path);
255 gst_validate_printf (NULL, "Writing expectations file: %s\n",
256 flow->expectations_file_path);
260 gchar *directory_path = g_path_get_dirname (flow->output_file_path);
261 if (g_mkdir_with_parents (directory_path, 0755) < 0) {
262 g_error ("Could not create directory tree: %s Reason: %s",
263 directory_path, g_strerror (errno));
265 g_free (directory_path);
268 flow->output_file = fopen (flow->output_file_path, "w");
269 if (!flow->output_file)
270 g_error ("Could not open for writing: %s", flow->output_file_path);
272 gst_validate_override_register_by_name (flow->pad_name, override);
274 override->buffer_handler = validate_flow_override_buffer_handler;
275 override->event_handler = validate_flow_override_event_handler;
277 g_signal_connect (flow, "notify::validate-runner",
278 G_CALLBACK (_runner_set), NULL);
284 _runner_set (GObject * object, GParamSpec * pspec, gpointer user_data)
286 ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (object);
287 GstValidateRunner *runner =
288 gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (flow));
290 g_signal_connect (runner, "stopping", G_CALLBACK (runner_stopping), flow);
291 gst_object_unref (runner);
295 run_diff (const gchar * expected_file, const gchar * actual_file)
297 GError *error = NULL;
298 GSubprocess *process =
299 g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE, &error, "diff", "-u",
300 "--", expected_file, actual_file, NULL);
301 gchar *stdout_text = NULL;
303 g_subprocess_communicate_utf8 (process, NULL, NULL, &stdout_text, NULL,
306 fprintf (stderr, "%s\n", stdout_text);
308 fprintf (stderr, "Cannot show more details, failed to run diff: %s",
310 g_error_free (error);
313 g_object_unref (process);
318 _line_to_show (gchar ** lines, gsize i)
320 if (lines[i] == NULL) {
322 } else if (*lines[i] == '\0') {
323 if (lines[i + 1] != NULL)
324 /* skip blank lines for reporting purposes (e.g. before CHECKPOINT) */
327 /* last blank line in the file */
335 show_mismatch_error (ValidateFlowOverride * flow, gchar ** lines_expected,
336 gchar ** lines_actual, gsize line_index)
338 const gchar *line_expected = _line_to_show (lines_expected, line_index);
339 const gchar *line_actual = _line_to_show (lines_actual, line_index);
341 GST_VALIDATE_REPORT (flow, VALIDATE_FLOW_MISMATCH,
342 "Mismatch error in pad %s, line %" G_GSIZE_FORMAT
343 ". Expected:\n%s\nActual:\n%s\n", flow->pad_name, line_index + 1,
344 line_expected, line_actual);
346 run_diff (flow->expectations_file_path, flow->actual_results_file_path);
350 runner_stopping (GstValidateRunner * runner, ValidateFlowOverride * flow)
352 gchar **lines_expected, **lines_actual;
355 fclose (flow->output_file);
356 flow->output_file = NULL;
357 if (flow->mode == VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS)
362 GError *error = NULL;
363 g_file_get_contents (flow->expectations_file_path, &contents, NULL, &error);
365 g_error ("Failed to open expectations file: %s Reason: %s",
366 flow->expectations_file_path, error->message);
368 lines_expected = g_strsplit (contents, "\n", 0);
373 GError *error = NULL;
374 g_file_get_contents (flow->actual_results_file_path, &contents, NULL,
377 g_error ("Failed to open actual results file: %s Reason: %s",
378 flow->actual_results_file_path, error->message);
380 lines_actual = g_strsplit (contents, "\n", 0);
383 for (i = 0; lines_expected[i] && lines_actual[i]; i++) {
384 if (strcmp (lines_expected[i], lines_actual[i])) {
385 show_mismatch_error (flow, lines_expected, lines_actual, i);
390 if (!lines_expected[i] && lines_actual[i]) {
391 show_mismatch_error (flow, lines_expected, lines_actual, i);
392 } else if (lines_expected[i] && !lines_actual[i]) {
393 show_mismatch_error (flow, lines_expected, lines_actual, i);
397 g_strfreev (lines_expected);
398 g_strfreev (lines_actual);
402 validate_flow_override_finalize (GObject * object)
404 ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (object);
406 all_overrides = g_list_remove (all_overrides, flow);
407 g_free (flow->actual_results_dir);
408 g_free (flow->actual_results_file_path);
409 g_free (flow->expectations_dir);
410 g_free (flow->expectations_file_path);
411 g_free (flow->output_file_path);
412 if (flow->output_file)
413 fclose (flow->output_file);
414 if (flow->caps_properties) {
416 for (str_pointer = flow->caps_properties; *str_pointer != NULL;
418 g_free (*str_pointer);
419 g_free (flow->caps_properties);
422 G_OBJECT_CLASS (validate_flow_override_parent_class)->finalize (object);
426 _execute_checkpoint (GstValidateScenario * scenario, GstValidateAction * action)
429 gchar *checkpoint_name =
430 g_strdup (gst_structure_get_string (action->structure, "text"));
432 for (i = all_overrides; i; i = i->next) {
433 ValidateFlowOverride *flow = (ValidateFlowOverride *) i->data;
436 validate_flow_override_printf (flow, "\nCHECKPOINT: %s\n\n",
439 validate_flow_override_printf (flow, "\nCHECKPOINT\n\n");
442 g_free (checkpoint_name);
447 gst_validate_flow_init (GstPlugin * plugin)
450 GList *config_list = gst_validate_plugin_get_config (plugin);
455 for (tmp = config_list; tmp; tmp = tmp->next) {
456 GstStructure *config = tmp->data;
457 ValidateFlowOverride *flow = validate_flow_override_new (config);
458 all_overrides = g_list_append (all_overrides, flow);
462 gst_validate_register_action_type_dynamic (plugin, "checkpoint",
463 GST_RANK_PRIMARY, _execute_checkpoint, ((GstValidateActionParameter [])
467 .description = "Text that will be logged in validateflow",
473 "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.",
474 GST_VALIDATE_ACTION_TYPE_NONE);
480 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
483 "GstValidate plugin that records buffers and events on specified pads and matches the log with expectation files.",
484 gst_validate_flow_init, VERSION, "LGPL", GST_PACKAGE_NAME,