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.
31 #include "../validate.h"
32 #include "../gst-validate-utils.h"
33 #include "../gst-validate-report.h"
34 #include "../gst-validate-internal.h"
35 #include "formatting.h"
36 #include <sys/types.h>
41 #include "gstvalidateflow.h"
43 #define VALIDATE_FLOW_MISMATCH g_quark_from_static_string ("validateflow::mismatch")
44 #define VALIDATE_FLOW_NOT_ATTACHED g_quark_from_static_string ("validateflow::not-attached")
46 typedef enum _ValidateFlowMode
48 VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS,
49 VALIDATE_FLOW_MODE_WRITING_ACTUAL_RESULTS
52 #define GST_TYPE_VALIDATE_FLOW_CHECKSUM_TYPE (validate_flow_checksum_type_get_type ())
54 validate_flow_checksum_type_get_type (void)
56 static GType gtype = 0;
59 static const GEnumValue values[] = {
60 {CHECKSUM_TYPE_NONE, "NONE", "none"},
61 {CHECKSUM_TYPE_AS_ID, "AS-ID", "as-id"},
62 {CHECKSUM_TYPE_CONTENT_HEX, "raw-hex", "raw-hex"},
63 {G_CHECKSUM_MD5, "MD5", "md5"},
64 {G_CHECKSUM_SHA1, "SHA-1", "sha1"},
65 {G_CHECKSUM_SHA256, "SHA-256", "sha256"},
66 {G_CHECKSUM_SHA512, "SHA-512", "sha512"},
70 gtype = g_enum_register_static ("ValidateFlowChecksumType", values);
75 struct _ValidateFlowOverride
77 GstValidateOverride parent;
79 const gchar *pad_name;
80 gboolean record_buffers;
82 gchar *expectations_dir;
83 gchar *actual_results_dir;
84 gboolean error_writing_file;
85 gchar **caps_properties;
86 GstStructure *ignored_fields;
87 GstStructure *logged_fields;
89 gchar **logged_event_types;
90 gchar **ignored_event_types;
92 gchar *expectations_file_path;
93 gchar *actual_results_file_path;
94 ValidateFlowMode mode;
95 gboolean was_attached;
98 /* output_file will refer to the expectations file if it did not exist,
99 * or to the actual results file otherwise. */
100 gchar *output_file_path;
102 GMutex output_file_mutex;
106 GList *all_overrides = NULL;
108 static void validate_flow_override_finalize (GObject * object);
109 static void validate_flow_override_attached (GstValidateOverride * override);
110 static void _runner_set (GObject * object, GParamSpec * pspec,
112 static void runner_stopping (GstValidateRunner * runner,
113 ValidateFlowOverride * flow);
115 #define VALIDATE_TYPE_FLOW_OVERRIDE validate_flow_override_get_type ()
116 G_DEFINE_TYPE (ValidateFlowOverride, validate_flow_override,
117 GST_TYPE_VALIDATE_OVERRIDE);
120 validate_flow_override_init (ValidateFlowOverride * self)
125 validate_flow_override_class_init (ValidateFlowOverrideClass * klass)
127 GObjectClass *object_class = G_OBJECT_CLASS (klass);
128 GstValidateOverrideClass *override_class =
129 GST_VALIDATE_OVERRIDE_CLASS (klass);
131 object_class->finalize = validate_flow_override_finalize;
132 override_class->attached = validate_flow_override_attached;
134 g_assert (gst_validate_is_initialized ());
136 gst_validate_issue_register (gst_validate_issue_new
137 (VALIDATE_FLOW_MISMATCH,
138 "The recorded log does not match the expectation file.",
139 "The recorded log does not match the expectation file.",
140 GST_VALIDATE_REPORT_LEVEL_CRITICAL));
142 gst_validate_issue_register (gst_validate_issue_new
143 (VALIDATE_FLOW_NOT_ATTACHED,
144 "The pad to monitor was never attached.",
145 "The pad to monitor was never attached.",
146 GST_VALIDATE_REPORT_LEVEL_CRITICAL));
154 validate_flow_override_vprintf (ValidateFlowOverride * flow, const char *format,
157 g_mutex_lock (&flow->output_file_mutex);
158 if (!flow->error_writing_file && vfprintf (flow->output_file, format, ap) < 0) {
159 GST_ERROR_OBJECT (flow, "Writing to file %s failed",
160 flow->output_file_path);
161 flow->error_writing_file = TRUE;
163 g_mutex_unlock (&flow->output_file_mutex);
171 validate_flow_override_printf (ValidateFlowOverride * flow, const char *format,
175 va_start (ap, format);
176 validate_flow_override_vprintf (flow, format, ap);
181 validate_flow_override_event_handler (GstValidateOverride * override,
182 GstValidateMonitor * pad_monitor, GstEvent * event)
184 ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (override);
187 if (flow->error_writing_file)
190 event_string = validate_flow_format_event (event,
191 (const gchar * const *) flow->caps_properties,
193 flow->ignored_fields,
194 (const gchar * const *) flow->ignored_event_types,
195 (const gchar * const *) flow->logged_event_types);
198 validate_flow_override_printf (flow, "event %s\n", event_string);
199 g_free (event_string);
204 validate_flow_override_buffer_handler (GstValidateOverride * override,
205 GstValidateMonitor * pad_monitor, GstBuffer * buffer)
207 ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (override);
210 if (flow->error_writing_file || !flow->record_buffers)
213 buffer_str = validate_flow_format_buffer (buffer, flow->checksum_type,
214 flow->logged_fields, flow->ignored_fields);
215 validate_flow_override_printf (flow, "buffer: %s\n", buffer_str);
220 make_safe_file_name (const gchar * name)
222 gchar *ret = g_strdup (name);
224 for (c = ret; *c; c++) {
242 static ValidateFlowOverride *
243 validate_flow_override_new (GstStructure * config)
245 ValidateFlowOverride *flow;
246 GstValidateOverride *override;
247 gboolean use_checksum = FALSE;
248 gchar *ignored_fields = NULL, *logged_fields;
249 const GValue *tmpval;
251 flow = g_object_new (VALIDATE_TYPE_FLOW_OVERRIDE, NULL);
252 flow->config = config;
254 GST_OBJECT_FLAG_SET (flow, GST_OBJECT_FLAG_MAY_BE_LEAKED);
255 override = GST_VALIDATE_OVERRIDE (flow);
257 /* pad: Name of the pad where flowing buffers and events will be monitorized. */
258 flow->pad_name = gst_structure_get_string (config, "pad");
259 if (!flow->pad_name) {
260 gst_validate_error_structure (config,
261 "pad property is mandatory, not found in %" GST_PTR_FORMAT, config);
264 /* record-buffers: Whether buffers will be written to the expectation log. */
265 flow->record_buffers = FALSE;
266 gst_structure_get_boolean (config, "record-buffers", &flow->record_buffers);
268 flow->checksum_type = CHECKSUM_TYPE_NONE;
269 gst_structure_get_boolean (config, "buffers-checksum", &use_checksum);
272 flow->checksum_type = G_CHECKSUM_SHA1;
274 const gchar *checksum_type =
275 gst_structure_get_string (config, "buffers-checksum");
278 if (!gst_validate_utils_enum_from_str
279 (GST_TYPE_VALIDATE_FLOW_CHECKSUM_TYPE, checksum_type,
280 (guint *) & flow->checksum_type))
281 gst_validate_error_structure (config,
282 "Invalid value for buffers-checksum: %s", checksum_type);
286 if (flow->checksum_type != CHECKSUM_TYPE_NONE)
287 flow->record_buffers = TRUE;
289 /* caps-properties: Caps events can include many dfferent properties, but
290 * many of these may be irrelevant for some tests. If this option is set,
291 * only the listed properties will be written to the expectation log. */
292 flow->caps_properties =
293 gst_validate_utils_get_strv (config, "caps-properties");
295 flow->logged_event_types =
296 gst_validate_utils_get_strv (config, "logged-event-types");
297 flow->ignored_event_types =
298 gst_validate_utils_get_strv (config, "ignored-event-types");
300 tmpval = gst_structure_get_value (config, "ignored-fields");
302 if (!G_VALUE_HOLDS_STRING (tmpval)) {
303 gst_validate_error_structure (config,
304 "Invalid value type for `ignored-fields`: '%s' instead of 'string'",
305 G_VALUE_TYPE_NAME (tmpval));
307 ignored_fields = (gchar *) g_value_get_string (tmpval);
310 if (ignored_fields) {
311 ignored_fields = g_strdup_printf ("ignored,%s", ignored_fields);
312 flow->ignored_fields = gst_structure_new_from_string (ignored_fields);
313 if (!flow->ignored_fields)
314 gst_validate_error_structure (config,
315 "Could not parse 'ignored-event-fields' structure: `%s`",
317 g_free (ignored_fields);
319 flow->ignored_fields =
320 gst_structure_new_from_string ("ignored,stream-start={stream-id}");
323 if (!gst_structure_has_field (flow->ignored_fields, "stream-start"))
324 gst_structure_set (flow->ignored_fields, "stream-start",
325 G_TYPE_STRING, "stream-id", NULL);
327 logged_fields = (gchar *) gst_structure_get_string (config, "logged-fields");
329 logged_fields = g_strdup_printf ("logged,%s", logged_fields);
330 flow->logged_fields = gst_structure_new_from_string (logged_fields);
331 if (!flow->logged_fields)
332 gst_validate_error_structure (config,
333 "Could not parse 'logged-fields' %s", logged_fields);
334 g_free (logged_fields);
336 flow->logged_fields = NULL;
339 /* expectations-dir: Path to the directory where the expectations will be
340 * written if they don't exist, relative to the current working directory.
341 * By default the current working directory is used. */
342 flow->expectations_dir =
343 g_strdup (gst_structure_get_string (config, "expectations-dir"));
344 if (!flow->expectations_dir)
345 flow->expectations_dir = g_strdup (".");
347 /* actual-results-dir: Path to the directory where the events will be
348 * recorded. The expectation file will be compared to this. */
349 flow->actual_results_dir =
350 g_strdup (gst_structure_get_string (config, "actual-results-dir"));
351 if (!flow->actual_results_dir)
352 flow->actual_results_dir = g_strdup (".");
355 gchar *pad_name_safe = make_safe_file_name (flow->pad_name);
356 gchar *expectations_file_name =
357 g_strdup_printf ("log-%s-expected", pad_name_safe);
358 gchar *actual_results_file_name =
359 g_strdup_printf ("log-%s-actual", pad_name_safe);
360 flow->expectations_file_path =
361 g_build_path (G_DIR_SEPARATOR_S, flow->expectations_dir,
362 expectations_file_name, NULL);
363 flow->actual_results_file_path =
364 g_build_path (G_DIR_SEPARATOR_S, flow->actual_results_dir,
365 actual_results_file_name, NULL);
366 g_free (expectations_file_name);
367 g_free (actual_results_file_name);
368 g_free (pad_name_safe);
371 flow->was_attached = FALSE;
373 gst_validate_override_register_by_name (flow->pad_name, override);
375 override->buffer_handler = validate_flow_override_buffer_handler;
376 override->buffer_probe_handler = validate_flow_override_buffer_handler;
377 override->event_handler = validate_flow_override_event_handler;
379 g_signal_connect (flow, "notify::validate-runner",
380 G_CALLBACK (_runner_set), NULL);
386 validate_flow_setup_files (ValidateFlowOverride * flow, gint default_generate)
388 gint local_generate_expectations = -1;
389 gboolean generate_if_doesn_exit = default_generate == -1;
391 g_file_test (flow->expectations_file_path, G_FILE_TEST_EXISTS);
393 if (generate_if_doesn_exit) {
394 gst_structure_get_boolean (flow->config, "generate-expectations",
395 &local_generate_expectations);
396 generate_if_doesn_exit = local_generate_expectations == -1;
399 if ((!default_generate || !local_generate_expectations) && !exists) {
400 gst_validate_error_structure (flow->config, "Not writing expectations and"
401 " configured expectation file %s doesn't exist in config:\n > %"
402 GST_PTR_FORMAT, flow->expectations_file_path, flow->config);
405 if (exists && local_generate_expectations != 1 && default_generate != 1) {
406 flow->mode = VALIDATE_FLOW_MODE_WRITING_ACTUAL_RESULTS;
407 flow->output_file_path = g_strdup (flow->actual_results_file_path);
408 gst_validate_printf (NULL, "**-> Checking expectations file: '%s'**\n",
409 flow->expectations_file_path);
411 flow->mode = VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS;
412 flow->output_file_path = g_strdup (flow->expectations_file_path);
413 gst_validate_printf (NULL, "**-> Writing expectations file: '%s'**\n",
414 flow->expectations_file_path);
418 gchar *directory_path = g_path_get_dirname (flow->output_file_path);
419 if (g_mkdir_with_parents (directory_path, 0755) < 0) {
420 gst_validate_abort ("Could not create directory tree: %s Reason: %s",
421 directory_path, g_strerror (errno));
423 g_free (directory_path);
426 flow->output_file = fopen (flow->output_file_path, "w");
427 if (!flow->output_file)
428 gst_validate_abort ("Could not open for writing: %s",
429 flow->output_file_path);
434 _runner_set (GObject * object, GParamSpec * pspec, gpointer user_data)
436 ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (object);
437 GstValidateRunner *runner =
438 gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (flow));
440 g_signal_connect (runner, "stopping", G_CALLBACK (runner_stopping), flow);
441 gst_object_unref (runner);
445 validate_flow_override_attached (GstValidateOverride * override)
447 ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (override);
448 flow->was_attached = TRUE;
452 run_diff (const gchar * expected_file, const gchar * actual_file)
454 GError *error = NULL;
455 GSubprocess *process =
456 g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE, &error, "diff", "-u",
457 "--", expected_file, actual_file, NULL);
458 gchar *stdout_text = NULL;
460 g_subprocess_communicate_utf8 (process, NULL, NULL, &stdout_text, NULL,
463 gboolean colored = gst_validate_has_colored_output ();
464 GSubprocess *process2;
466 gint f = g_file_open_tmp ("XXXXXX.diff", &fname, NULL);
470 g_file_set_contents (fname, stdout_text, -1, NULL);
474 g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE, &error, "bat", "-l",
475 "diff", "--paging", "never", "--color", colored ? "always" : "never",
478 g_subprocess_communicate_utf8 (process2, NULL, NULL, &tmpstdout, NULL,
481 g_free (stdout_text);
482 stdout_text = tmpstdout;
485 GST_DEBUG ("Could not use bat: %s", error->message);
486 g_clear_error (&error);
488 g_clear_object (&process2);
492 fprintf (stderr, "%s%s%s\n",
493 !colored ? "``` diff\n" : "", stdout_text, !colored ? "\n```" : "");
495 fprintf (stderr, "Cannot show more details, failed to run diff: %s",
497 g_error_free (error);
500 g_object_unref (process);
501 g_free (stdout_text);
505 _line_to_show (gchar ** lines, gsize i)
507 if (lines[i] == NULL) {
509 } else if (*lines[i] == '\0') {
510 if (lines[i + 1] != NULL)
511 /* skip blank lines for reporting purposes (e.g. before CHECKPOINT) */
514 /* last blank line in the file */
522 show_mismatch_error (ValidateFlowOverride * flow, gchar ** lines_expected,
523 gchar ** lines_actual, gsize line_index)
525 const gchar *line_expected = _line_to_show (lines_expected, line_index);
526 const gchar *line_actual = _line_to_show (lines_actual, line_index);
528 GST_VALIDATE_REPORT (flow, VALIDATE_FLOW_MISMATCH,
529 "Mismatch error in pad %s, line %" G_GSIZE_FORMAT
530 ". Expected:\n%s\nActual:\n%s\n", flow->pad_name, line_index + 1,
531 line_expected, line_actual);
533 run_diff (flow->expectations_file_path, flow->actual_results_file_path);
537 runner_stopping (GstValidateRunner * runner, ValidateFlowOverride * flow)
539 gchar **lines_expected, **lines_actual;
542 fclose (flow->output_file);
543 flow->output_file = NULL;
545 if (!flow->was_attached) {
546 GST_VALIDATE_REPORT (flow, VALIDATE_FLOW_NOT_ATTACHED,
547 "The test ended without the pad ever being attached: %s",
552 if (flow->mode == VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS) {
553 gst_validate_skip_test ("wrote expectation files for %s.\n",
561 GError *error = NULL;
562 g_file_get_contents (flow->expectations_file_path, &contents, NULL, &error);
564 gst_validate_abort ("Failed to open expectations file: %s Reason: %s",
565 flow->expectations_file_path, error->message);
567 lines_expected = g_strsplit (contents, "\n", 0);
573 GError *error = NULL;
574 g_file_get_contents (flow->actual_results_file_path, &contents, NULL,
577 gst_validate_abort ("Failed to open actual results file: %s Reason: %s",
578 flow->actual_results_file_path, error->message);
580 lines_actual = g_strsplit (contents, "\n", 0);
584 gst_validate_printf (flow, "Checking that flow %s matches expected flow %s\n",
585 flow->expectations_file_path, flow->actual_results_file_path);
587 for (i = 0; lines_expected[i] && lines_actual[i]; i++) {
588 if (g_strcmp0 (lines_expected[i], lines_actual[i])) {
589 show_mismatch_error (flow, lines_expected, lines_actual, i);
593 gst_validate_printf (flow, "OK\n");
594 if (!lines_expected[i] && lines_actual[i]) {
595 show_mismatch_error (flow, lines_expected, lines_actual, i);
597 } else if (lines_expected[i] && !lines_actual[i]) {
598 show_mismatch_error (flow, lines_expected, lines_actual, i);
603 g_strfreev (lines_expected);
604 g_strfreev (lines_actual);
608 validate_flow_override_finalize (GObject * object)
610 ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (object);
612 all_overrides = g_list_remove (all_overrides, flow);
613 g_free (flow->actual_results_dir);
614 g_free (flow->actual_results_file_path);
615 g_free (flow->expectations_dir);
616 g_free (flow->expectations_file_path);
617 g_free (flow->output_file_path);
618 if (flow->output_file)
619 fclose (flow->output_file);
620 g_strfreev (flow->caps_properties);
621 g_strfreev (flow->logged_event_types);
622 g_strfreev (flow->ignored_event_types);
623 if (flow->ignored_fields)
624 gst_structure_free (flow->ignored_fields);
626 G_OBJECT_CLASS (validate_flow_override_parent_class)->finalize (object);
630 _execute_checkpoint (GstValidateScenario * scenario, GstValidateAction * action)
633 gchar *checkpoint_name =
634 g_strdup (gst_structure_get_string (action->structure, "text"));
636 for (i = all_overrides; i; i = i->next) {
637 ValidateFlowOverride *flow = (ValidateFlowOverride *) i->data;
640 validate_flow_override_printf (flow, "\nCHECKPOINT: %s\n\n",
643 validate_flow_override_printf (flow, "\nCHECKPOINT\n\n");
646 g_free (checkpoint_name);
651 gst_validate_flow_init ()
654 gint default_generate = -1;
655 GList *config_list = gst_validate_get_config ("validateflow");
660 for (tmp = config_list; tmp; tmp = tmp->next) {
661 GstStructure *config = tmp->data;
662 ValidateFlowOverride *flow;
664 if (gst_structure_has_field (config, "generate-expectations") &&
665 !gst_structure_has_field (config, "pad")) {
666 if (!gst_structure_get_boolean (config, "generate-expectations",
667 &default_generate)) {
668 gst_validate_error_structure (config,
669 "Field 'generate-expectations' should be a boolean");
675 flow = validate_flow_override_new (config);
676 all_overrides = g_list_append (all_overrides, flow);
678 g_list_free (config_list);
680 for (tmp = all_overrides; tmp; tmp = tmp->next)
681 validate_flow_setup_files (tmp->data, default_generate);
684 gst_validate_register_action_type ("checkpoint", "validateflow",
685 _execute_checkpoint, ((GstValidateActionParameter [])
689 .description = "Text that will be logged in validateflow",
695 "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.",
696 GST_VALIDATE_ACTION_TYPE_NONE);