New validate plugin: validateflow
[platform/upstream/gstreamer.git] / validate / plugins / 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 #include <gst/gst.h>
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>
32 #include <sys/stat.h>
33 #include <fcntl.h>
34 #include <stdio.h>
35
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
39
40 #define VALIDATE_FLOW_MISMATCH g_quark_from_static_string ("validateflow::mismatch")
41
42 typedef enum _ValidateFlowMode
43 {
44   VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS,
45   VALIDATE_FLOW_MODE_WRITING_ACTUAL_RESULTS
46 } ValidateFlowMode;
47
48 typedef struct _ValidateFlowOverride
49 {
50   GstValidateOverride parent;
51
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;
59
60   gchar *expectations_file_path;
61   gchar *actual_results_file_path;
62   ValidateFlowMode mode;
63
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;
67   FILE *output_file;
68   GMutex output_file_mutex;
69
70 } ValidateFlowOverride;
71
72 GList *all_overrides = NULL;
73
74 static void validate_flow_override_finalize (GObject * object);
75 static void _runner_set (GObject * object, GParamSpec * pspec,
76     gpointer user_data);
77 static void runner_stopping (GstValidateRunner * runner,
78     ValidateFlowOverride * flow);
79
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);
85
86 void
87 validate_flow_override_init (ValidateFlowOverride * self)
88 {
89 }
90
91 void
92 validate_flow_override_class_init (ValidateFlowOverrideClass * klass)
93 {
94   GObjectClass *object_class = G_OBJECT_CLASS (klass);
95   object_class->finalize = validate_flow_override_finalize;
96
97   g_assert (gst_validate_is_initialized ());
98
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));
104 }
105
106 static void
107 validate_flow_override_vprintf (ValidateFlowOverride * flow, const char *format,
108     va_list ap)
109 {
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;
115   }
116   g_mutex_unlock (&flow->output_file_mutex);
117 }
118
119 static void
120 validate_flow_override_printf (ValidateFlowOverride * flow, const char *format,
121     ...)
122 {
123   va_list ap;
124   va_start (ap, format);
125   validate_flow_override_vprintf (flow, format, ap);
126   va_end (ap);
127 }
128
129 static void
130 validate_flow_override_event_handler (GstValidateOverride * override,
131     GstValidateMonitor * pad_monitor, GstEvent * event)
132 {
133   ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (override);
134   gchar *event_string;
135
136   if (flow->error_writing_file)
137     return;
138
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);
143 }
144
145 static void
146 validate_flow_override_buffer_handler (GstValidateOverride * override,
147     GstValidateMonitor * pad_monitor, GstBuffer * buffer)
148 {
149   ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (override);
150   gchar *buffer_str;
151
152   if (flow->error_writing_file || !flow->record_buffers)
153     return;
154
155   buffer_str = validate_flow_format_buffer (buffer);
156   validate_flow_override_printf (flow, "buffer: %s\n", buffer_str);
157   g_free (buffer_str);
158 }
159
160 static gchar **
161 parse_caps_properties_setting (const ValidateFlowOverride * flow,
162     GstStructure * config)
163 {
164   const GValue *list;
165   gchar **parsed_list;
166   guint i, size;
167
168   list = gst_structure_get_value (config, "caps-properties");
169   if (!list)
170     return NULL;
171
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 };");
175     return NULL;
176   }
177
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;
183   return parsed_list;
184 }
185
186 static ValidateFlowOverride *
187 validate_flow_override_new (GstStructure * config)
188 {
189   ValidateFlowOverride *flow;
190   GstValidateOverride *override;
191
192   flow = g_object_new (VALIDATE_TYPE_FLOW_OVERRIDE, NULL);
193   override = GST_VALIDATE_OVERRIDE (flow);
194
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));
200   }
201
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);
205
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);
210
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
214    * behavior. */
215   flow->record_stream_id = FALSE;
216   gst_structure_get_boolean (config, "record-stream-id",
217       &flow->record_stream_id);
218
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 (".");
226
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 (".");
233
234   {
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);
247   }
248
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);
252   } else {
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);
257   }
258
259   {
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));
264     }
265     g_free (directory_path);
266   }
267
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);
271
272   gst_validate_override_register_by_name (flow->pad_name, override);
273
274   override->buffer_handler = validate_flow_override_buffer_handler;
275   override->event_handler = validate_flow_override_event_handler;
276
277   g_signal_connect (flow, "notify::validate-runner",
278       G_CALLBACK (_runner_set), NULL);
279
280   return flow;
281 }
282
283 static void
284 _runner_set (GObject * object, GParamSpec * pspec, gpointer user_data)
285 {
286   ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (object);
287   GstValidateRunner *runner =
288       gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (flow));
289
290   g_signal_connect (runner, "stopping", G_CALLBACK (runner_stopping), flow);
291   gst_object_unref (runner);
292 }
293
294 static void
295 run_diff (const gchar * expected_file, const gchar * actual_file)
296 {
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;
302
303   g_subprocess_communicate_utf8 (process, NULL, NULL, &stdout_text, NULL,
304       &error);
305   if (!error) {
306     fprintf (stderr, "%s\n", stdout_text);
307   } else {
308     fprintf (stderr, "Cannot show more details, failed to run diff: %s",
309         error->message);
310     g_error_free (error);
311   }
312
313   g_object_unref (process);
314   g_free (stdout);
315 }
316
317 static const gchar *
318 _line_to_show (gchar ** lines, gsize i)
319 {
320   if (lines[i] == NULL) {
321     return "<nothing>";
322   } else if (*lines[i] == '\0') {
323     if (lines[i + 1] != NULL)
324       /* skip blank lines for reporting purposes (e.g. before CHECKPOINT) */
325       return lines[i + 1];
326     else
327       /* last blank line in the file */
328       return "<nothing>";
329   } else {
330     return lines[i];
331   }
332 }
333
334 static void
335 show_mismatch_error (ValidateFlowOverride * flow, gchar ** lines_expected,
336     gchar ** lines_actual, gsize line_index)
337 {
338   const gchar *line_expected = _line_to_show (lines_expected, line_index);
339   const gchar *line_actual = _line_to_show (lines_actual, line_index);
340
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);
345
346   run_diff (flow->expectations_file_path, flow->actual_results_file_path);
347 }
348
349 static void
350 runner_stopping (GstValidateRunner * runner, ValidateFlowOverride * flow)
351 {
352   gchar **lines_expected, **lines_actual;
353   gsize i = 0;
354
355   fclose (flow->output_file);
356   flow->output_file = NULL;
357   if (flow->mode == VALIDATE_FLOW_MODE_WRITING_EXPECTATIONS)
358     return;
359
360   {
361     gchar *contents;
362     GError *error = NULL;
363     g_file_get_contents (flow->expectations_file_path, &contents, NULL, &error);
364     if (error) {
365       g_error ("Failed to open expectations file: %s Reason: %s",
366           flow->expectations_file_path, error->message);
367     }
368     lines_expected = g_strsplit (contents, "\n", 0);
369   }
370
371   {
372     gchar *contents;
373     GError *error = NULL;
374     g_file_get_contents (flow->actual_results_file_path, &contents, NULL,
375         &error);
376     if (error) {
377       g_error ("Failed to open actual results file: %s Reason: %s",
378           flow->actual_results_file_path, error->message);
379     }
380     lines_actual = g_strsplit (contents, "\n", 0);
381   }
382
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);
386       goto stop;
387     }
388   }
389
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);
394   }
395
396 stop:
397   g_strfreev (lines_expected);
398   g_strfreev (lines_actual);
399 }
400
401 static void
402 validate_flow_override_finalize (GObject * object)
403 {
404   ValidateFlowOverride *flow = VALIDATE_FLOW_OVERRIDE (object);
405
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) {
415     gchar **str_pointer;
416     for (str_pointer = flow->caps_properties; *str_pointer != NULL;
417         str_pointer++)
418       g_free (*str_pointer);
419     g_free (flow->caps_properties);
420   }
421
422   G_OBJECT_CLASS (validate_flow_override_parent_class)->finalize (object);
423 }
424
425 static gboolean
426 _execute_checkpoint (GstValidateScenario * scenario, GstValidateAction * action)
427 {
428   GList *i;
429   gchar *checkpoint_name =
430       g_strdup (gst_structure_get_string (action->structure, "text"));
431
432   for (i = all_overrides; i; i = i->next) {
433     ValidateFlowOverride *flow = (ValidateFlowOverride *) i->data;
434
435     if (checkpoint_name)
436       validate_flow_override_printf (flow, "\nCHECKPOINT: %s\n\n",
437           checkpoint_name);
438     else
439       validate_flow_override_printf (flow, "\nCHECKPOINT\n\n");
440   }
441
442   g_free (checkpoint_name);
443   return TRUE;
444 }
445
446 static gboolean
447 gst_validate_flow_init (GstPlugin * plugin)
448 {
449   GList *tmp;
450   GList *config_list = gst_validate_plugin_get_config (plugin);
451
452   if (!config_list)
453     return TRUE;
454
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);
459   }
460
461 /*  *INDENT-OFF* */
462   gst_validate_register_action_type_dynamic (plugin, "checkpoint",
463       GST_RANK_PRIMARY, _execute_checkpoint, ((GstValidateActionParameter [])
464       {
465         {
466           .name = "text",
467           .description = "Text that will be logged in validateflow",
468           .mandatory = FALSE,
469           .types = "string"
470         },
471         {NULL}
472       }),
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);
475 /*  *INDENT-ON* */
476
477   return TRUE;
478 }
479
480 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
481     GST_VERSION_MINOR,
482     validateflow,
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,
485     GST_PACKAGE_ORIGIN)