Move files from gst-devtools into the "subprojects/gst-devtools/" subdir
[platform/upstream/gstreamer.git] / subprojects / gst-devtools / validate / gst / validate / flow / gstvalidateflow.c
1 /* GStreamer
2  *
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>
6  *
7  * gstvalidateflow.c: A plugin to record streams and match them to
8  * expectation files.
9  *
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.
14  *
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.
19  *
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.
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
29
30 #include <gst/gst.h>
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>
37 #include <sys/stat.h>
38 #include <fcntl.h>
39 #include <stdio.h>
40
41 #include "gstvalidateflow.h"
42
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")
45
46 typedef enum _ValidateFlowMode
47 {
48   VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS,
49   VALIDATE_FLOW_MODE_WRITING_ACTUAL_RESULTS
50 } ValidateFlowMode;
51
52 #define GST_TYPE_VALIDATE_FLOW_CHECKSUM_TYPE (validate_flow_checksum_type_get_type ())
53 static GType
54 validate_flow_checksum_type_get_type (void)
55 {
56   static GType gtype = 0;
57
58   if (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"},
67       {0, NULL, NULL},
68     };
69
70     gtype = g_enum_register_static ("ValidateFlowChecksumType", values);
71   }
72   return gtype;
73 }
74
75 struct _ValidateFlowOverride
76 {
77   GstValidateOverride parent;
78
79   const gchar *pad_name;
80   gboolean record_buffers;
81   gint checksum_type;
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;
88
89   gchar **logged_event_types;
90   gchar **ignored_event_types;
91
92   gchar *expectations_file_path;
93   gchar *actual_results_file_path;
94   ValidateFlowMode mode;
95   gboolean was_attached;
96   GstStructure *config;
97
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;
101   FILE *output_file;
102   GMutex output_file_mutex;
103
104 };
105
106 GList *all_overrides = NULL;
107
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,
111     gpointer user_data);
112 static void runner_stopping (GstValidateRunner * runner,
113     ValidateFlowOverride * flow);
114
115 #define VALIDATE_TYPE_FLOW_OVERRIDE validate_flow_override_get_type ()
116 G_DEFINE_TYPE (ValidateFlowOverride, validate_flow_override,
117     GST_TYPE_VALIDATE_OVERRIDE);
118
119 void
120 validate_flow_override_init (ValidateFlowOverride * self)
121 {
122 }
123
124 void
125 validate_flow_override_class_init (ValidateFlowOverrideClass * klass)
126 {
127   GObjectClass *object_class = G_OBJECT_CLASS (klass);
128   GstValidateOverrideClass *override_class =
129       GST_VALIDATE_OVERRIDE_CLASS (klass);
130
131   object_class->finalize = validate_flow_override_finalize;
132   override_class->attached = validate_flow_override_attached;
133
134   g_assert (gst_validate_is_initialized ());
135
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));
141
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));
147 }
148
149 /* *INDENT-OFF* */
150 G_GNUC_PRINTF (2, 0)
151 /* *INDENT-ON* */
152
153 static void
154 validate_flow_override_vprintf (ValidateFlowOverride * flow, const char *format,
155     va_list ap)
156 {
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;
162   }
163   g_mutex_unlock (&flow->output_file_mutex);
164 }
165
166 /* *INDENT-OFF* */
167 G_GNUC_PRINTF (2, 3)
168 /* *INDENT-ON* */
169
170 static void
171 validate_flow_override_printf (ValidateFlowOverride * flow, const char *format,
172     ...)
173 {
174   va_list ap;
175   va_start (ap, format);
176   validate_flow_override_vprintf (flow, format, ap);
177   va_end (ap);
178 }
179
180 static void
181 validate_flow_override_event_handler (GstValidateOverride * override,
182     GstValidateMonitor * pad_monitor, GstEvent * event)
183 {
184   ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (override);
185   gchar *event_string;
186
187   if (flow->error_writing_file)
188     return;
189
190   event_string = validate_flow_format_event (event,
191       (const gchar * const *) flow->caps_properties,
192       flow->logged_fields,
193       flow->ignored_fields,
194       (const gchar * const *) flow->ignored_event_types,
195       (const gchar * const *) flow->logged_event_types);
196
197   if (event_string) {
198     validate_flow_override_printf (flow, "event %s\n", event_string);
199     g_free (event_string);
200   }
201 }
202
203 static void
204 validate_flow_override_buffer_handler (GstValidateOverride * override,
205     GstValidateMonitor * pad_monitor, GstBuffer * buffer)
206 {
207   ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (override);
208   gchar *buffer_str;
209
210   if (flow->error_writing_file || !flow->record_buffers)
211     return;
212
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);
216   g_free (buffer_str);
217 }
218
219 static gchar *
220 make_safe_file_name (const gchar * name)
221 {
222   gchar *ret = g_strdup (name);
223   gchar *c;
224   for (c = ret; *c; c++) {
225     switch (*c) {
226       case '<':
227       case '>':
228       case ':':
229       case '"':
230       case '/':
231       case '\\':
232       case '|':
233       case '?':
234       case '*':
235         *c = '-';
236         break;
237     }
238   }
239   return ret;
240 }
241
242 static ValidateFlowOverride *
243 validate_flow_override_new (GstStructure * config)
244 {
245   ValidateFlowOverride *flow;
246   GstValidateOverride *override;
247   gboolean use_checksum = FALSE;
248   gchar *ignored_fields = NULL, *logged_fields;
249   const GValue *tmpval;
250
251   flow = g_object_new (VALIDATE_TYPE_FLOW_OVERRIDE, NULL);
252   flow->config = config;
253
254   GST_OBJECT_FLAG_SET (flow, GST_OBJECT_FLAG_MAY_BE_LEAKED);
255   override = GST_VALIDATE_OVERRIDE (flow);
256
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);
262   }
263
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);
267
268   flow->checksum_type = CHECKSUM_TYPE_NONE;
269   gst_structure_get_boolean (config, "buffers-checksum", &use_checksum);
270
271   if (use_checksum) {
272     flow->checksum_type = G_CHECKSUM_SHA1;
273   } else {
274     const gchar *checksum_type =
275         gst_structure_get_string (config, "buffers-checksum");
276
277     if (checksum_type) {
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);
283     }
284   }
285
286   if (flow->checksum_type != CHECKSUM_TYPE_NONE)
287     flow->record_buffers = TRUE;
288
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");
294
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");
299
300   tmpval = gst_structure_get_value (config, "ignored-fields");
301   if (tmpval) {
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));
306     }
307     ignored_fields = (gchar *) g_value_get_string (tmpval);
308   }
309
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`",
316           ignored_fields);
317     g_free (ignored_fields);
318   } else {
319     flow->ignored_fields =
320         gst_structure_new_from_string ("ignored,stream-start={stream-id}");
321   }
322
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);
326
327   logged_fields = (gchar *) gst_structure_get_string (config, "logged-fields");
328   if (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);
335   } else {
336     flow->logged_fields = NULL;
337   }
338
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 (".");
346
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 (".");
353
354   {
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);
369   }
370
371   flow->was_attached = FALSE;
372
373   gst_validate_override_register_by_name (flow->pad_name, override);
374
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;
378
379   g_signal_connect (flow, "notify::validate-runner",
380       G_CALLBACK (_runner_set), NULL);
381
382   return flow;
383 }
384
385 static void
386 validate_flow_setup_files (ValidateFlowOverride * flow, gint default_generate)
387 {
388   gint local_generate_expectations = -1;
389   gboolean generate_if_doesn_exit = default_generate == -1;
390   gboolean exists =
391       g_file_test (flow->expectations_file_path, G_FILE_TEST_EXISTS);
392
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;
397   }
398
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);
403   }
404
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);
410   } else {
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);
415   }
416
417   {
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));
422     }
423     g_free (directory_path);
424   }
425
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);
430
431 }
432
433 static void
434 _runner_set (GObject * object, GParamSpec * pspec, gpointer user_data)
435 {
436   ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (object);
437   GstValidateRunner *runner =
438       gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (flow));
439
440   g_signal_connect (runner, "stopping", G_CALLBACK (runner_stopping), flow);
441   gst_object_unref (runner);
442 }
443
444 static void
445 validate_flow_override_attached (GstValidateOverride * override)
446 {
447   ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (override);
448   flow->was_attached = TRUE;
449 }
450
451 static void
452 run_diff (const gchar * expected_file, const gchar * actual_file)
453 {
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;
459
460   g_subprocess_communicate_utf8 (process, NULL, NULL, &stdout_text, NULL,
461       &error);
462   if (!error) {
463     gboolean colored = gst_validate_has_colored_output ();
464     GSubprocess *process2;
465     gchar *fname = NULL;
466     gint f = g_file_open_tmp ("XXXXXX.diff", &fname, NULL);
467
468     if (f > 0) {
469       gchar *tmpstdout;
470       g_file_set_contents (fname, stdout_text, -1, NULL);
471       close (f);
472
473       process2 =
474           g_subprocess_new (G_SUBPROCESS_FLAGS_STDOUT_PIPE, &error, "bat", "-l",
475           "diff", "--paging", "never", "--color", colored ? "always" : "never",
476           fname, NULL);
477
478       g_subprocess_communicate_utf8 (process2, NULL, NULL, &tmpstdout, NULL,
479           &error);
480       if (!error) {
481         g_free (stdout_text);
482         stdout_text = tmpstdout;
483       } else {
484         colored = FALSE;
485         GST_DEBUG ("Could not use bat: %s", error->message);
486         g_clear_error (&error);
487       }
488       g_clear_object (&process2);
489       g_free (fname);
490     }
491
492     fprintf (stderr, "%s%s%s\n",
493         !colored ? "``` diff\n" : "", stdout_text, !colored ? "\n```" : "");
494   } else {
495     fprintf (stderr, "Cannot show more details, failed to run diff: %s",
496         error->message);
497     g_error_free (error);
498   }
499
500   g_object_unref (process);
501   g_free (stdout_text);
502 }
503
504 static const gchar *
505 _line_to_show (gchar ** lines, gsize i)
506 {
507   if (lines[i] == NULL) {
508     return "<nothing>";
509   } else if (*lines[i] == '\0') {
510     if (lines[i + 1] != NULL)
511       /* skip blank lines for reporting purposes (e.g. before CHECKPOINT) */
512       return lines[i + 1];
513     else
514       /* last blank line in the file */
515       return "<nothing>";
516   } else {
517     return lines[i];
518   }
519 }
520
521 static void
522 show_mismatch_error (ValidateFlowOverride * flow, gchar ** lines_expected,
523     gchar ** lines_actual, gsize line_index)
524 {
525   const gchar *line_expected = _line_to_show (lines_expected, line_index);
526   const gchar *line_actual = _line_to_show (lines_actual, line_index);
527
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);
532
533   run_diff (flow->expectations_file_path, flow->actual_results_file_path);
534 }
535
536 static void
537 runner_stopping (GstValidateRunner * runner, ValidateFlowOverride * flow)
538 {
539   gchar **lines_expected, **lines_actual;
540   gsize i = 0;
541
542   fclose (flow->output_file);
543   flow->output_file = NULL;
544
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",
548         flow->pad_name);
549     return;
550   }
551
552   if (flow->mode == VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS) {
553     gst_validate_skip_test ("wrote expectation files for %s.\n",
554         flow->pad_name);
555
556     return;
557   }
558
559   {
560     gchar *contents;
561     GError *error = NULL;
562     g_file_get_contents (flow->expectations_file_path, &contents, NULL, &error);
563     if (error) {
564       gst_validate_abort ("Failed to open expectations file: %s Reason: %s",
565           flow->expectations_file_path, error->message);
566     }
567     lines_expected = g_strsplit (contents, "\n", 0);
568     g_free (contents);
569   }
570
571   {
572     gchar *contents;
573     GError *error = NULL;
574     g_file_get_contents (flow->actual_results_file_path, &contents, NULL,
575         &error);
576     if (error) {
577       gst_validate_abort ("Failed to open actual results file: %s Reason: %s",
578           flow->actual_results_file_path, error->message);
579     }
580     lines_actual = g_strsplit (contents, "\n", 0);
581     g_free (contents);
582   }
583
584   gst_validate_printf (flow, "Checking that flow %s matches expected flow %s\n",
585       flow->expectations_file_path, flow->actual_results_file_path);
586
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);
590       goto stop;
591     }
592   }
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);
596     goto stop;
597   } else if (lines_expected[i] && !lines_actual[i]) {
598     show_mismatch_error (flow, lines_expected, lines_actual, i);
599     goto stop;
600   }
601
602 stop:
603   g_strfreev (lines_expected);
604   g_strfreev (lines_actual);
605 }
606
607 static void
608 validate_flow_override_finalize (GObject * object)
609 {
610   ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (object);
611
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);
625
626   G_OBJECT_CLASS (validate_flow_override_parent_class)->finalize (object);
627 }
628
629 static gboolean
630 _execute_checkpoint (GstValidateScenario * scenario, GstValidateAction * action)
631 {
632   GList *i;
633   gchar *checkpoint_name =
634       g_strdup (gst_structure_get_string (action->structure, "text"));
635
636   for (i = all_overrides; i; i = i->next) {
637     ValidateFlowOverride *flow = (ValidateFlowOverride *) i->data;
638
639     if (checkpoint_name)
640       validate_flow_override_printf (flow, "\nCHECKPOINT: %s\n\n",
641           checkpoint_name);
642     else
643       validate_flow_override_printf (flow, "\nCHECKPOINT\n\n");
644   }
645
646   g_free (checkpoint_name);
647   return TRUE;
648 }
649
650 gboolean
651 gst_validate_flow_init ()
652 {
653   GList *tmp;
654   gint default_generate = -1;
655   GList *config_list = gst_validate_get_config ("validateflow");
656
657   if (!config_list)
658     return TRUE;
659
660   for (tmp = config_list; tmp; tmp = tmp->next) {
661     GstStructure *config = tmp->data;
662     ValidateFlowOverride *flow;
663
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");
670       }
671
672       continue;
673     }
674
675     flow = validate_flow_override_new (config);
676     all_overrides = g_list_append (all_overrides, flow);
677   }
678   g_list_free (config_list);
679
680   for (tmp = all_overrides; tmp; tmp = tmp->next)
681     validate_flow_setup_files (tmp->data, default_generate);
682
683 /*  *INDENT-OFF* */
684   gst_validate_register_action_type ("checkpoint", "validateflow",
685       _execute_checkpoint, ((GstValidateActionParameter [])
686       {
687         {
688           .name = "text",
689           .description = "Text that will be logged in validateflow",
690           .mandatory = FALSE,
691           .types = "string"
692         },
693         {NULL}
694       }),
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);
697 /*  *INDENT-ON* */
698
699   return TRUE;
700 }