Bump GLib requirement to >= 2.62
[platform/upstream/gstreamer.git] / subprojects / gst-devtools / validate / gst / validate / validate.c
1 /* GStreamer
2  *
3  * Copyright (C) 2013 Collabora Ltd.
4  *  Author: Thiago Sousa Santos <thiago.sousa.santos@collabora.com>
5  *
6  * validate.c - Validate generic functions
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23 /**
24  * SECTION:validate
25  * @title: Initialization
26  * @short_description: Initialize GstValidate
27  */
28
29 #ifdef HAVE_CONFIG_H
30 #  include "config.h"
31 #endif /* HAVE_CONFIG_H */
32
33 #include <locale.h>             /* for LC_NUMERIC */
34
35 #include <string.h>
36 /* For g_stat () */
37 #include <glib/gstdio.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40
41 #include <math.h>
42
43 #include "validate.h"
44 #include "gst-validate-utils.h"
45 #include "gst-validate-internal.h"
46
47 #ifdef G_OS_WIN32
48 #define WIN32_LEAN_AND_MEAN     /* prevents from including too many things */
49 #include <windows.h>            /* GetStdHandle, windows console */
50 HMODULE _priv_gstvalidate_dll_handle = NULL;
51 #endif /* G_OS_WIN32 */
52
53 GST_DEBUG_CATEGORY (gstvalidate_debug);
54
55 static GMutex _gst_validate_registry_mutex;
56 static GstRegistry *_gst_validate_registry_default = NULL;
57
58 G_LOCK_DEFINE_STATIC (all_configs_lock);
59 static GList *all_configs = NULL;
60 static gboolean got_configs = FALSE;
61
62 static GList *core_config = NULL;
63 static gboolean testfile_used = FALSE;
64 static GList *testfile_structs = NULL;
65 static gchar *global_testfile = NULL;
66 static gboolean validate_initialized = FALSE;
67 static gboolean loaded_globals = FALSE;
68 GstClockTime _priv_start_time;
69
70 GQuark _Q_VALIDATE_MONITOR;
71
72 #ifdef G_OS_WIN32
73 BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);
74 BOOL WINAPI
75 DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
76 {
77   if (fdwReason == DLL_PROCESS_ATTACH)
78     _priv_gstvalidate_dll_handle = (HMODULE) hinstDLL;
79
80   return TRUE;
81 }
82 #endif /* G_OS_WIN32 */
83
84 static GstRegistry *
85 gst_validate_registry_get (void)
86 {
87   GstRegistry *registry;
88
89   g_mutex_lock (&_gst_validate_registry_mutex);
90   if (G_UNLIKELY (!_gst_validate_registry_default)) {
91     _gst_validate_registry_default = g_object_new (GST_TYPE_REGISTRY, NULL);
92     gst_object_ref_sink (GST_OBJECT_CAST (_gst_validate_registry_default));
93   }
94   registry = _gst_validate_registry_default;
95   g_mutex_unlock (&_gst_validate_registry_mutex);
96
97   return registry;
98 }
99
100 #define GST_VALIDATE_PLUGIN_CONFIG "gst-validate-plugin-config"
101
102 static void
103 _free_plugin_config (gpointer data)
104 {
105   g_list_free (data);
106 }
107
108 /* Copied from gststructure.c to avoid assertion */
109 static gboolean
110 gst_structure_validate_name (const gchar * name)
111 {
112   const gchar *s;
113
114   g_return_val_if_fail (name != NULL, FALSE);
115
116   if (G_UNLIKELY (!g_ascii_isalpha (*name))) {
117     GST_INFO ("Invalid character '%c' at offset 0 in structure name: %s",
118         *name, name);
119     return FALSE;
120   }
121
122   /* FIXME: test name string more */
123   s = &name[1];
124   while (*s && (g_ascii_isalnum (*s) || strchr ("/-_.:+", *s) != NULL))
125     s++;
126   if (*s == ',')
127     return TRUE;
128
129   if (G_UNLIKELY (*s != '\0')) {
130     GST_INFO ("Invalid character '%c' at offset %" G_GUINTPTR_FORMAT " in"
131         " structure name: %s", *s, ((guintptr) s - (guintptr) name), name);
132     return FALSE;
133   }
134
135   return TRUE;
136 }
137
138 static gboolean
139 _set_vars_func (GQuark field_id, const GValue * value, GstStructure * vars)
140 {
141   gst_structure_id_set_value (vars, field_id, value);
142
143   return TRUE;
144 }
145
146 static GstStructure *
147 get_test_file_meta (void)
148 {
149   GList *tmp;
150
151   for (tmp = testfile_structs; tmp; tmp = tmp->next) {
152     if (gst_structure_has_name (tmp->data, "meta"))
153       return tmp->data;
154   }
155
156   return NULL;
157 }
158
159 static void
160 create_config (const gchar * config)
161 {
162   GstStructure *local_vars;
163   GList *structures = NULL, *tmp;
164   gchar *config_file = NULL;
165   GFile *f;
166
167   local_vars = gst_structure_new_empty ("vars");
168   f = g_file_new_for_path (config);
169   if (g_file_query_exists (f, NULL)) {
170     structures = gst_validate_utils_structs_parse_from_filename (config, NULL,
171         &config_file);
172   } else {
173     GstCaps *confs = NULL;
174
175     if (gst_structure_validate_name (config))
176       confs = gst_caps_from_string (config);
177
178     if (confs) {
179       gint i;
180
181       for (i = 0; i < gst_caps_get_size (confs); i++) {
182         GstStructure *structure = gst_caps_get_structure (confs, i);
183
184         structures = g_list_append (structures, gst_structure_copy (structure));
185       }
186
187       gst_caps_unref (confs);
188     }
189   }
190   g_object_unref (f);
191   gst_validate_structure_set_variables_from_struct_file (local_vars,
192       config_file);
193   g_free (config_file);
194
195   for (tmp = structures; tmp; tmp = tmp->next) {
196     GstStructure *structure = tmp->data;
197
198     if (gst_structure_has_field (structure, "set-vars")) {
199       gst_structure_remove_field (structure, "set-vars");
200       gst_structure_foreach (structure,
201           (GstStructureForeachFunc) _set_vars_func, local_vars);
202       gst_structure_free (structure);
203     } else if (!loaded_globals
204         && gst_structure_has_name (structure, "set-globals")) {
205       gst_validate_structure_resolve_variables (NULL, structure, local_vars, 0);
206       gst_validate_set_globals (structure);
207       gst_structure_free (structure);
208     } else {
209       gst_validate_structure_resolve_variables (NULL, structure, local_vars, 0);
210       all_configs = g_list_append (all_configs, structure);
211     }
212   }
213
214   loaded_globals = TRUE;
215   gst_structure_free (local_vars);
216   g_list_free (structures);
217 }
218
219 static GList *
220 get_structures_from_array (GstStructure * structure, const gchar * fieldname)
221 {
222   const GValue *value;
223   GList *res = NULL;
224   guint i, size;
225
226   value = gst_structure_get_value (structure, fieldname);
227   if (!value)
228     return NULL;
229
230   if (GST_VALUE_HOLDS_STRUCTURE (value)) {
231     return g_list_append (res,
232         gst_structure_copy (gst_value_get_structure (value)));
233   }
234
235   if (!GST_VALUE_HOLDS_LIST (value)) {
236     return NULL;
237   }
238
239   size = gst_value_list_get_size (value);
240   for (i = 0; i < size; i++) {
241     const GValue *v1 = gst_value_list_get_value (value, i);
242
243     if (!GST_VALUE_HOLDS_STRUCTURE (v1))
244       break;
245
246     res =
247         g_list_append (res, gst_structure_copy (gst_value_get_structure (v1)));
248   }
249
250
251   return res;
252 }
253
254 static GList *
255 get_structures_from_array_in_meta (const gchar * fieldname)
256 {
257   GList *res = NULL;
258   gchar **strs = NULL, *filename = NULL, *debug = NULL;
259   gint current_lineno = -1;
260   GstStructure *meta = get_test_file_meta ();
261
262   if (!meta)
263     return NULL;
264
265   res = get_structures_from_array (meta, fieldname);
266   if (res)
267     return res;
268
269   gst_structure_get (meta,
270       "__lineno__", G_TYPE_INT, &current_lineno,
271       "__debug__", G_TYPE_STRING, &debug,
272       "__filename__", G_TYPE_STRING, &filename, NULL);
273   strs = gst_validate_utils_get_strv (meta, fieldname);
274
275   if (strs) {
276     gint i;
277
278     for (i = 0; strs[i]; i++) {
279       GstStructure *tmpstruct = gst_structure_from_string (strs[i], NULL);
280
281       if (tmpstruct == NULL) {
282         gst_validate_abort ("%s:%d: Invalid structure\n  %4d | %s\n%s",
283             filename, current_lineno, current_lineno, strs[i], debug);
284       }
285
286       gst_structure_set (tmpstruct,
287           "__lineno__", G_TYPE_INT, current_lineno,
288           "__filename__", G_TYPE_STRING, filename,
289           "__debug__", G_TYPE_STRING, debug, NULL);
290       res = g_list_append (res, tmpstruct);
291     }
292   }
293
294   g_free (filename);
295   g_free (debug);
296   g_strfreev (strs);
297
298   return res;
299 }
300
301 /**
302  * gst_validate_plugin_get_config:
303  * @plugin: a #GstPlugin, or #NULL
304  *
305  * Return the configuration specific to @plugin, or the "core" one if @plugin
306  * is #NULL
307  *
308  * Returns: (transfer none) (element-type GstStructure): a list of #GstStructure
309  */
310 GList *
311 gst_validate_plugin_get_config (GstPlugin * plugin)
312 {
313   const gchar *suffix;
314   GList *plugin_conf = NULL;
315
316   if (plugin) {
317     if ((plugin_conf =
318             g_object_get_data (G_OBJECT (plugin), GST_VALIDATE_PLUGIN_CONFIG)))
319       return plugin_conf;
320
321     suffix = gst_plugin_get_name (plugin);
322   } else {
323     if (core_config)
324       return core_config;
325
326     suffix = "core";
327   }
328
329   plugin_conf = gst_validate_get_config (suffix);
330   if (plugin)
331     g_object_set_data_full (G_OBJECT (plugin), GST_VALIDATE_PLUGIN_CONFIG,
332         plugin_conf, _free_plugin_config);
333   else
334     core_config = plugin_conf;
335
336   return plugin_conf;
337 }
338
339 static void
340 gst_validate_ensure_all_configs (void)
341 {
342   GStrv tmp;
343   gint i;
344   const gchar *config;
345
346   if (got_configs)
347     return;
348
349   got_configs = TRUE;
350   all_configs = get_structures_from_array_in_meta ("configs");
351   config = g_getenv ("GST_VALIDATE_CONFIG");
352   if (!config)
353     return;
354
355   tmp = g_strsplit (config, G_SEARCHPATH_SEPARATOR_S, -1);
356   for (i = 0; tmp[i] != NULL; i++) {
357     if (tmp[i][0] == '\0')
358       continue;
359
360     create_config (tmp[i]);
361   }
362   g_strfreev (tmp);
363 }
364
365 GList *
366 gst_validate_get_config (const gchar * structname)
367 {
368   GList *tmp, *res = NULL;
369
370   G_LOCK (all_configs_lock);
371   gst_validate_ensure_all_configs ();
372
373   for (tmp = all_configs; tmp; tmp = tmp->next) {
374     gint n_usages = 0;
375
376     if (structname && !gst_structure_has_name (tmp->data, structname)) {
377       continue;
378     } else if (structname) {
379       gst_structure_get (tmp->data, "__n_usages__", G_TYPE_INT, &n_usages,
380           NULL);
381       n_usages++;
382       gst_structure_set (tmp->data, "__n_usages__", G_TYPE_INT, n_usages, NULL);
383     }
384     res = g_list_append (res, tmp->data);
385   }
386   G_UNLOCK (all_configs_lock);
387
388   return res;
389 }
390
391 static void
392 gst_validate_init_plugins (void)
393 {
394   GstRegistry *registry;
395   const gchar *plugin_path;
396
397   gst_registry_fork_set_enabled (FALSE);
398   registry = gst_validate_registry_get ();
399
400   plugin_path = g_getenv ("GST_VALIDATE_PLUGIN_PATH");
401   if (plugin_path) {
402     char **list;
403     int i;
404
405     GST_DEBUG ("GST_VALIDATE_PLUGIN_PATH set to %s", plugin_path);
406     list = g_strsplit (plugin_path, G_SEARCHPATH_SEPARATOR_S, 0);
407     for (i = 0; list[i]; i++) {
408       gst_registry_scan_path (registry, list[i]);
409     }
410     g_strfreev (list);
411   } else {
412     GST_DEBUG ("GST_VALIDATE_PLUGIN_PATH not set");
413   }
414
415   if (plugin_path == NULL) {
416     char *home_plugins;
417
418     /* plugins in the user's home directory take precedence over
419      * system-installed ones */
420     home_plugins = g_build_filename (g_get_user_data_dir (),
421         "gstreamer-" GST_API_VERSION, "plugins", NULL);
422
423     GST_DEBUG ("scanning home plugins %s", home_plugins);
424     gst_registry_scan_path (registry, home_plugins);
425     g_free (home_plugins);
426
427     /* add the main (installed) library path */
428
429 #ifdef G_OS_WIN32
430     {
431       char *base_dir;
432       char *dir;
433
434       base_dir =
435           g_win32_get_package_installation_directory_of_module
436           (_priv_gstvalidate_dll_handle);
437
438       dir = g_build_filename (base_dir,
439           "lib", "gstreamer-" GST_API_VERSION, "validate", NULL);
440
441       GST_DEBUG ("scanning DLL dir %s", dir);
442
443       gst_registry_scan_path (registry, dir);
444
445       g_free (dir);
446       g_free (base_dir);
447     }
448 #else
449     gst_registry_scan_path (registry, VALIDATEPLUGINDIR);
450 #endif
451   }
452   gst_registry_fork_set_enabled (TRUE);
453 }
454
455 void
456 gst_validate_init_debug (void)
457 {
458   GST_DEBUG_CATEGORY_INIT (gstvalidate_debug, "validate", 0,
459       "Validation library");
460 }
461
462 /**
463  * gst_validate_init:
464  *
465  * Initializes GstValidate. Call this before any usage of GstValidate.
466  * You should take care of initializing GStreamer before calling this
467  * function.
468  */
469 void
470 gst_validate_init (void)
471 {
472   if (validate_initialized) {
473     return;
474   }
475   gst_validate_init_debug ();
476   _priv_start_time = gst_util_get_timestamp ();
477   _Q_VALIDATE_MONITOR = g_quark_from_static_string ("validate-monitor");
478
479   setlocale (LC_NUMERIC, "C");
480
481   /* init the report system (can be called multiple times) */
482   gst_validate_report_init ();
483
484   /* Init the scenario system */
485   init_scenarios ();
486
487   /* Ensure we load overrides before any use of a monitor */
488   gst_validate_override_registry_preload ();
489
490   validate_initialized = TRUE;
491
492   gst_validate_extra_checks_init ();
493   gst_validate_flow_init ();
494   gst_validate_init_plugins ();
495   gst_validate_init_runner ();
496 }
497
498 void
499 gst_validate_deinit (void)
500 {
501   g_mutex_lock (&_gst_validate_registry_mutex);
502
503   g_list_free (core_config);
504   core_config = NULL;
505
506   g_list_free_full (all_configs, (GDestroyNotify) gst_structure_free);
507   gst_validate_deinit_runner ();
508
509   gst_validate_scenario_deinit ();
510
511   g_clear_object (&_gst_validate_registry_default);
512
513   g_list_free_full (testfile_structs, (GDestroyNotify) gst_structure_free);
514   testfile_structs = NULL;
515   g_clear_pointer (&global_testfile, g_free);
516
517   _priv_validate_override_registry_deinit ();
518   validate_initialized = FALSE;
519   gst_validate_report_deinit ();
520
521   g_mutex_unlock (&_gst_validate_registry_mutex);
522   g_mutex_clear (&_gst_validate_registry_mutex);
523 }
524
525 gboolean
526 gst_validate_is_initialized (void)
527 {
528   return validate_initialized;
529 }
530
531 GList *
532 gst_validate_get_test_file_expected_issues (void)
533 {
534   GList *res = get_structures_from_array_in_meta ("expected-issues"), *tmp;
535
536   for (tmp = res; tmp; tmp = tmp->next) {
537     GstStructure *known_issue = tmp->data;
538     const gchar *summary = gst_structure_get_string (known_issue, "summary");
539     const gchar *id = gst_structure_get_string (known_issue, "issue-id");
540
541     if (!id && !summary)
542       gst_validate_error_structure (known_issue,
543           "Missing 'summary' or 'issue-id' fields.");
544   }
545
546   return res;
547 }
548
549 gboolean
550 gst_validate_get_test_file_scenario (GList ** structs,
551     const gchar ** scenario_name, gchar ** original_name)
552 {
553   GList *res = NULL, *tmp;
554   GstStructure *meta = get_test_file_meta ();
555
556   if (!testfile_structs || testfile_used)
557     return FALSE;
558
559   if (meta && gst_structure_has_field (meta, "scenario")) {
560     *scenario_name = gst_structure_get_string (meta, "scenario");
561
562     return TRUE;
563   }
564
565   for (tmp = testfile_structs; tmp; tmp = tmp->next) {
566     GstStructure *structure = NULL;
567
568     if (gst_structure_has_name (tmp->data, "set-globals"))
569       continue;
570
571     structure = gst_structure_copy (tmp->data);
572     if (gst_structure_has_name (structure, "meta"))
573       gst_structure_remove_fields (structure, "configs", "gst-validate-args",
574           NULL);
575     res = g_list_append (res, structure);
576   }
577
578   *structs = res;
579   *original_name = global_testfile;
580   testfile_used = TRUE;
581
582   return TRUE;
583 }
584
585 /* Only the first monitor pipeline will be used */
586 GstStructure *
587 gst_validate_setup_test_file (const gchar * testfile, gboolean use_fakesinks)
588 {
589   const gchar *tool;
590   GstStructure *res = NULL;
591
592   g_assert (!got_configs);
593   if (global_testfile)
594     gst_validate_abort ("A testfile was already loaded: %s", global_testfile);
595
596   global_testfile = g_canonicalize_filename (testfile, NULL);
597
598   gst_validate_set_globals (NULL);
599   gst_validate_structure_set_variables_from_struct_file (NULL, global_testfile);
600   testfile_structs =
601       gst_validate_utils_structs_parse_from_filename (global_testfile, NULL,
602       NULL);
603
604   if (!testfile_structs)
605     gst_validate_abort ("Could not load test file: %s", global_testfile);
606
607   res = testfile_structs->data;
608   if (gst_structure_has_name (testfile_structs->data, "set-globals")) {
609     GstStructure *globals = testfile_structs->data;
610     gst_validate_set_globals (globals);
611     if (!testfile_structs->next)
612       gst_validate_abort
613           ("Only one `set-globals` structure in %s, nothing to test here.",
614           global_testfile);
615     res = testfile_structs->next->data;
616   }
617
618   if (!gst_structure_has_name (res, "meta"))
619     gst_validate_abort
620         ("First structure of a .validatetest file should be a `meta` or "
621         "`set-gobals` then `meta`, got: %s", gst_structure_to_string (res));
622
623   register_action_types ();
624   gst_validate_scenario_check_and_set_needs_clock_sync (testfile_structs, &res);
625
626   gst_validate_set_test_file_globals (res, global_testfile, use_fakesinks);
627   gst_validate_structure_resolve_variables (NULL, res, NULL, 0);
628
629   tool = gst_structure_get_string (res, "tool");
630   if (!tool)
631     tool = "gst-validate-" GST_API_VERSION;
632
633   if (g_strcmp0 (tool, g_get_prgname ()))
634     gst_validate_abort
635         ("Validate test file: '%s' was made to be run with '%s' not '%s'",
636         global_testfile, tool, g_get_prgname ());
637
638   return res;
639 }