/* GStreamer * * Copyright (C) 2013 Collabora Ltd. * Author: Thiago Sousa Santos * * validate.c - Validate generic functions * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ /** * SECTION:validate * @title: Initialization * @short_description: Initialize GstValidate */ #include "gst/gstidstr.h" #ifdef HAVE_CONFIG_H # include "config.h" #endif /* HAVE_CONFIG_H */ #include /* for LC_NUMERIC */ #include /* For g_stat () */ #include #include #include #include #include "validate.h" #include "gst-validate-utils.h" #include "gst-validate-internal.h" #ifdef G_OS_WIN32 #define WIN32_LEAN_AND_MEAN /* prevents from including too many things */ #include /* GetStdHandle, windows console */ HMODULE _priv_gstvalidate_dll_handle = NULL; #endif /* G_OS_WIN32 */ GST_DEBUG_CATEGORY (gstvalidate_debug); static GMutex _gst_validate_registry_mutex; static GstRegistry *_gst_validate_registry_default = NULL; static GRecMutex init_lock = { 0, }; G_LOCK_DEFINE_STATIC (all_configs_lock); static GList *all_configs = NULL; static gboolean got_configs = FALSE; static GList *core_config = NULL; static gboolean testfile_used = FALSE; static GList *testfile_structs = NULL; static gchar *global_testfile = NULL; static gboolean validate_initialized = FALSE; static gboolean loaded_globals = FALSE; GstClockTime _priv_start_time; GQuark _Q_VALIDATE_MONITOR; #ifdef G_OS_WIN32 BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved); BOOL WINAPI DllMain (HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { if (fdwReason == DLL_PROCESS_ATTACH) _priv_gstvalidate_dll_handle = (HMODULE) hinstDLL; return TRUE; } #endif /* G_OS_WIN32 */ static GstRegistry * gst_validate_registry_get (void) { GstRegistry *registry; g_mutex_lock (&_gst_validate_registry_mutex); if (G_UNLIKELY (!_gst_validate_registry_default)) { _gst_validate_registry_default = g_object_new (GST_TYPE_REGISTRY, NULL); gst_object_ref_sink (GST_OBJECT_CAST (_gst_validate_registry_default)); } registry = _gst_validate_registry_default; g_mutex_unlock (&_gst_validate_registry_mutex); return registry; } #define GST_VALIDATE_PLUGIN_CONFIG "gst-validate-plugin-config" static void _free_plugin_config (gpointer data) { g_list_free (data); } /* Copied from gststructure.c to avoid assertion */ static gboolean gst_structure_validate_name (const gchar * name) { const gchar *s; g_return_val_if_fail (name != NULL, FALSE); if (G_UNLIKELY (!g_ascii_isalpha (*name))) { GST_INFO ("Invalid character '%c' at offset 0 in structure name: %s", *name, name); return FALSE; } /* FIXME: test name string more */ s = &name[1]; while (*s && (g_ascii_isalnum (*s) || strchr ("/-_.:+", *s) != NULL)) s++; if (*s == ',') return TRUE; if (G_UNLIKELY (*s != '\0')) { GST_INFO ("Invalid character '%c' at offset %" G_GUINTPTR_FORMAT " in" " structure name: %s", *s, ((guintptr) s - (guintptr) name), name); return FALSE; } return TRUE; } static gboolean _set_vars_func (const GstIdStr * fieldname, const GValue * value, GstStructure * vars) { gst_structure_id_str_set_value (vars, fieldname, value); return TRUE; } static GstStructure * get_test_file_meta (void) { GList *tmp; for (tmp = testfile_structs; tmp; tmp = tmp->next) { if (gst_structure_has_name (tmp->data, "meta")) return tmp->data; } return NULL; } static void create_config (const gchar * config) { GstStructure *local_vars; GList *structures = NULL, *tmp; gchar *config_file = NULL; GFile *f; local_vars = gst_structure_new_empty ("vars"); f = g_file_new_for_path (config); if (g_file_query_exists (f, NULL)) { structures = gst_validate_utils_structs_parse_from_filename (config, NULL, &config_file); } else { GstCaps *confs = NULL; if (gst_structure_validate_name (config)) confs = gst_caps_from_string (config); if (confs) { gint i; for (i = 0; i < gst_caps_get_size (confs); i++) { GstStructure *structure = gst_caps_get_structure (confs, i); structures = g_list_append (structures, gst_structure_copy (structure)); } gst_caps_unref (confs); } } g_object_unref (f); gst_validate_structure_set_variables_from_struct_file (local_vars, config_file); g_free (config_file); for (tmp = structures; tmp; tmp = tmp->next) { GstStructure *structure = tmp->data; if (gst_structure_has_field (structure, "set-vars")) { gst_structure_remove_field (structure, "set-vars"); gst_structure_foreach_id_str (structure, (GstStructureForeachIdStrFunc) _set_vars_func, local_vars); gst_structure_free (structure); } else if (!loaded_globals && gst_structure_has_name (structure, "set-globals")) { gst_validate_structure_resolve_variables (NULL, structure, local_vars, 0); gst_validate_set_globals (structure); gst_structure_free (structure); } else { gst_validate_structure_resolve_variables (NULL, structure, local_vars, 0); all_configs = g_list_append (all_configs, structure); } } loaded_globals = TRUE; gst_structure_free (local_vars); g_list_free (structures); } static GList * get_structures_from_array (GstStructure * structure, const gchar * fieldname) { const GValue *value; GList *res = NULL; guint i, size; value = gst_structure_get_value (structure, fieldname); if (!value) return NULL; if (GST_VALUE_HOLDS_STRUCTURE (value)) { return g_list_append (res, gst_structure_copy (gst_value_get_structure (value))); } if (!GST_VALUE_HOLDS_LIST (value)) { return NULL; } size = gst_value_list_get_size (value); for (i = 0; i < size; i++) { const GValue *v1 = gst_value_list_get_value (value, i); if (!GST_VALUE_HOLDS_STRUCTURE (v1)) break; res = g_list_append (res, gst_structure_copy (gst_value_get_structure (v1))); } return res; } static GList * get_structures_from_array_in_meta (const gchar * fieldname) { GList *res = NULL; gchar **strs = NULL, *filename = NULL, *debug = NULL; gint current_lineno = -1; GstStructure *meta = get_test_file_meta (); if (!meta) return NULL; gst_structure_get (meta, "__lineno__", G_TYPE_INT, ¤t_lineno, "__debug__", G_TYPE_STRING, &debug, "__filename__", G_TYPE_STRING, &filename, NULL); res = get_structures_from_array (meta, fieldname); if (res) { for (GList * tmp = res; tmp; tmp = tmp->next) { gst_structure_set (tmp->data, "__lineno__", G_TYPE_INT, current_lineno, "__filename__", G_TYPE_STRING, filename, "__debug__", G_TYPE_STRING, debug, NULL); } goto done; } strs = gst_validate_utils_get_strv (meta, fieldname); if (strs) { gint i; for (i = 0; strs[i]; i++) { GstStructure *tmpstruct = gst_structure_from_string (strs[i], NULL); if (tmpstruct == NULL) { gst_validate_abort ("%s:%d: Invalid structure\n %4d | %s\n%s", filename, current_lineno, current_lineno, strs[i], debug); } gst_structure_set (tmpstruct, "__lineno__", G_TYPE_INT, current_lineno, "__filename__", G_TYPE_STRING, filename, "__debug__", G_TYPE_STRING, debug, NULL); res = g_list_append (res, tmpstruct); } } done: g_free (filename); g_free (debug); g_strfreev (strs); return res; } /** * gst_validate_plugin_get_config: * @plugin: a #GstPlugin, or #NULL * * Return the configuration specific to @plugin, or the "core" one if @plugin * is #NULL * * Returns: (transfer none) (element-type GstStructure): a list of #GstStructure */ GList * gst_validate_plugin_get_config (GstPlugin * plugin) { const gchar *suffix; GList *plugin_conf = NULL; if (plugin) { if ((plugin_conf = g_object_get_data (G_OBJECT (plugin), GST_VALIDATE_PLUGIN_CONFIG))) return plugin_conf; suffix = gst_plugin_get_name (plugin); } else { if (core_config) return core_config; suffix = "core"; } plugin_conf = gst_validate_get_config (suffix); if (plugin) g_object_set_data_full (G_OBJECT (plugin), GST_VALIDATE_PLUGIN_CONFIG, plugin_conf, _free_plugin_config); else core_config = plugin_conf; return plugin_conf; } static void gst_validate_ensure_all_configs (void) { GStrv tmp; gint i; const gchar *config; if (got_configs) return; got_configs = TRUE; all_configs = get_structures_from_array_in_meta ("configs"); config = g_getenv ("GST_VALIDATE_CONFIG"); if (!config) return; tmp = g_strsplit (config, G_SEARCHPATH_SEPARATOR_S, -1); for (i = 0; tmp[i] != NULL; i++) { if (tmp[i][0] == '\0') continue; create_config (tmp[i]); } g_strfreev (tmp); } GList * gst_validate_get_config (const gchar * structname) { GList *tmp, *res = NULL; G_LOCK (all_configs_lock); gst_validate_ensure_all_configs (); for (tmp = all_configs; tmp; tmp = tmp->next) { gint n_usages = 0; if (structname && !gst_structure_has_name (tmp->data, structname)) { continue; } else if (structname) { gst_structure_get (tmp->data, "__n_usages__", G_TYPE_INT, &n_usages, NULL); n_usages++; gst_structure_set (tmp->data, "__n_usages__", G_TYPE_INT, n_usages, NULL); } res = g_list_append (res, tmp->data); } G_UNLOCK (all_configs_lock); return res; } static void gst_validate_init_plugins (void) { GstRegistry *registry; const gchar *plugin_path; gst_registry_fork_set_enabled (FALSE); registry = gst_validate_registry_get (); plugin_path = g_getenv ("GST_VALIDATE_PLUGIN_PATH"); if (plugin_path) { char **list; int i; GST_DEBUG ("GST_VALIDATE_PLUGIN_PATH set to %s", plugin_path); list = g_strsplit (plugin_path, G_SEARCHPATH_SEPARATOR_S, 0); for (i = 0; list[i]; i++) { gst_registry_scan_path (registry, list[i]); } g_strfreev (list); } else { GST_DEBUG ("GST_VALIDATE_PLUGIN_PATH not set"); } if (plugin_path == NULL) { char *home_plugins; /* plugins in the user's home directory take precedence over * system-installed ones */ home_plugins = g_build_filename (g_get_user_data_dir (), "gstreamer-" GST_API_VERSION, "plugins", NULL); GST_DEBUG ("scanning home plugins %s", home_plugins); gst_registry_scan_path (registry, home_plugins); g_free (home_plugins); /* add the main (installed) library path */ #ifdef G_OS_WIN32 { char *base_dir; char *dir; base_dir = g_win32_get_package_installation_directory_of_module (_priv_gstvalidate_dll_handle); dir = g_build_filename (base_dir, "lib", "gstreamer-" GST_API_VERSION, "validate", NULL); GST_DEBUG ("scanning DLL dir %s", dir); gst_registry_scan_path (registry, dir); g_free (dir); g_free (base_dir); } #else gst_registry_scan_path (registry, VALIDATEPLUGINDIR); #endif } gst_registry_fork_set_enabled (TRUE); } void gst_validate_init_debug (void) { GST_DEBUG_CATEGORY_INIT (gstvalidate_debug, "validate", 0, "Validation library"); } /** * gst_validate_init: * * Initializes GstValidate. Call this before any usage of GstValidate. * You should take care of initializing GStreamer before calling this * function. */ void gst_validate_init (void) { g_rec_mutex_lock (&init_lock); if (validate_initialized) { g_rec_mutex_unlock (&init_lock); return; } gst_validate_init_debug (); _priv_start_time = gst_util_get_timestamp (); _Q_VALIDATE_MONITOR = g_quark_from_static_string ("validate-monitor"); setlocale (LC_NUMERIC, "C"); /* init the report system (can be called multiple times) */ gst_validate_report_init (); /* Init the scenario system */ init_scenarios (); /* Ensure we load overrides before any use of a monitor */ gst_validate_override_registry_preload (); validate_initialized = TRUE; gst_validate_extra_checks_init (); gst_validate_flow_init (); gst_validate_init_plugins (); gst_validate_init_runner (); g_rec_mutex_unlock (&init_lock); } void gst_validate_deinit (void) { g_mutex_lock (&_gst_validate_registry_mutex); g_list_free (core_config); core_config = NULL; g_list_free_full (all_configs, (GDestroyNotify) gst_structure_free); gst_validate_deinit_runner (); gst_validate_scenario_deinit (); g_clear_object (&_gst_validate_registry_default); g_list_free_full (testfile_structs, (GDestroyNotify) gst_structure_free); testfile_structs = NULL; g_clear_pointer (&global_testfile, g_free); _priv_validate_override_registry_deinit (); validate_initialized = FALSE; gst_validate_report_deinit (); g_mutex_unlock (&_gst_validate_registry_mutex); } gboolean gst_validate_is_initialized (void) { return validate_initialized; } GList * gst_validate_get_test_file_expected_issues (void) { GList *res = get_structures_from_array_in_meta ("expected-issues"), *tmp; for (tmp = res; tmp; tmp = tmp->next) { GstStructure *known_issue = tmp->data; const gchar *summary = gst_structure_get_string (known_issue, "summary"); const gchar *id = gst_structure_get_string (known_issue, "issue-id"); if (!id && !summary) gst_validate_error_structure (known_issue, "Missing 'summary' or 'issue-id' fields."); } return res; } gboolean gst_validate_get_test_file_scenario (GList ** structs, const gchar ** scenario_name, gchar ** original_name) { GList *res = NULL, *tmp; GstStructure *meta = get_test_file_meta (); if (!testfile_structs || testfile_used) return FALSE; if (meta && gst_structure_has_field (meta, "scenario")) { *scenario_name = gst_structure_get_string (meta, "scenario"); return TRUE; } for (tmp = testfile_structs; tmp; tmp = tmp->next) { GstStructure *structure = NULL; if (gst_structure_has_name (tmp->data, "set-globals")) continue; structure = gst_structure_copy (tmp->data); if (gst_structure_has_name (structure, "meta")) gst_structure_remove_fields (structure, "configs", "gst-validate-args", NULL); res = g_list_append (res, structure); } *structs = res; *original_name = global_testfile; testfile_used = TRUE; return TRUE; } static gchar ** validate_test_include_paths (const gchar * includer_file) { gchar **env_configdir; gchar *configs_path = g_strdup (g_getenv ("GST_VALIDATE_TEST_CONFIG_PATH")); if (includer_file) { gchar *relative_dir = g_path_get_dirname (includer_file); gchar *tmp_configs_path = configs_path ? g_strdup_printf ("%s" G_SEARCHPATH_SEPARATOR_S "%s", configs_path, relative_dir) : g_strdup (relative_dir); g_free (relative_dir); g_free (configs_path); configs_path = tmp_configs_path; } env_configdir = configs_path ? g_strsplit (configs_path, G_SEARCHPATH_SEPARATOR_S, 0) : NULL; g_free (configs_path); return env_configdir; } static gboolean _set_feature_rank (const GstIdStr * fieldname, GValue * value, GstStructure * structure) { GstRegistry *registry = gst_registry_get (); guint rank = 0; if (gst_validate_structure_file_field_is_metadata (fieldname)) return TRUE; if (G_VALUE_TYPE (value) == G_TYPE_UINT) { rank = (guint) g_value_get_uint (value); } else if (G_VALUE_TYPE (value) == G_TYPE_INT) { rank = g_value_get_int (value); } else { gst_validate_error_structure (structure, "Invalid value %s for field '%s' (expecting int) in the 'features-rank' structure", G_VALUE_TYPE_NAME (value), gst_value_serialize (value)); return FALSE; } GstPluginFeature *feature = gst_registry_lookup_feature (registry, gst_id_str_as_str (fieldname)); if (!feature) { if (gst_structure_has_name (structure, "mandatory")) { gst_validate_error_structure (structure, "Feature `%s` not found while its ranks has been requested to be set to %d", gst_id_str_as_str (fieldname), rank); return FALSE; } return TRUE; } gst_plugin_feature_set_rank (feature, rank); return TRUE; } /* Only the first monitor pipeline will be used */ GstStructure * gst_validate_setup_test_file (const gchar * testfile, gboolean use_fakesinks) { const gchar *tool; GstStructure *res = NULL; g_assert (!got_configs); if (global_testfile) gst_validate_abort ("A testfile was already loaded: %s", global_testfile); global_testfile = g_canonicalize_filename (testfile, NULL); gst_validate_set_globals (NULL); gst_validate_structure_set_variables_from_struct_file (NULL, global_testfile); testfile_structs = gst_validate_utils_structs_parse_from_filename (global_testfile, validate_test_include_paths, NULL); if (!testfile_structs) gst_validate_abort ("Could not load test file: %s", global_testfile); res = testfile_structs->data; if (gst_structure_has_name (testfile_structs->data, "set-globals")) { GstStructure *globals = testfile_structs->data; gst_validate_set_globals (globals); if (!testfile_structs->next) gst_validate_abort ("Only one `set-globals` structure in %s, nothing to test here.", global_testfile); res = testfile_structs->next->data; } if (!gst_structure_has_name (res, "meta")) gst_validate_abort ("First structure of a .validatetest file should be a `meta` or " "`set-gobals` then `meta`, got: %s", gst_structure_to_string (res)); register_action_types (); gst_validate_scenario_check_and_set_needs_clock_sync (testfile_structs, &res); GList *feature_ranks_def = get_structures_from_array_in_meta ("features-rank"); for (GList * tmp = feature_ranks_def; tmp; tmp = tmp->next) { GstStructure *feature_ranks = tmp->data; if (!gst_structure_has_name (feature_ranks, "mandatory") && !gst_structure_has_name (feature_ranks, "optional")) { gst_validate_error_structure (res, "Feature rank structures should have either `mandatory` or `optional` as a name, got: %s", gst_structure_to_string (feature_ranks)); return NULL; } gst_structure_filter_and_map_in_place_id_str (feature_ranks, (GstStructureFilterMapIdStrFunc) _set_feature_rank, feature_ranks); } gst_validate_set_test_file_globals (res, global_testfile, use_fakesinks); gst_validate_structure_resolve_variables (NULL, res, NULL, 0); tool = gst_structure_get_string (res, "tool"); if (!tool) tool = "gst-validate-" GST_API_VERSION; if (g_strcmp0 (tool, g_get_prgname ())) gst_validate_abort ("Validate test file: '%s' was made to be run with '%s' not '%s'", global_testfile, tool, g_get_prgname ()); return res; }