a39bd2298e89e6978038007e5980d79e342b92c5
[platform/upstream/gstreamer.git] / validate / launcher / main.py
1 #!/usr/bin/env python3
2 #
3 # Copyright (c) 2014,Thibault Saunier <thibault.saunier@collabora.com>
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU Lesser General Public
7 # License as published by the Free Software Foundation; either
8 # version 2.1 of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # Lesser General Public License for more details.
14 #
15 # You should have received a copy of the GNU Lesser General Public
16 # License along with this program; if not, write to the
17 # Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18 # Boston, MA 02110-1301, USA.
19 import os
20 import sys
21 from . import utils
22 import urllib.parse
23 from . import loggable
24 import multiprocessing
25 import argparse
26 import tempfile
27 from . import reporters
28 import subprocess
29
30
31 from .loggable import Loggable
32 from .baseclasses import _TestsLauncher, ScenarioManager
33 from .utils import printc, path2url, DEFAULT_MAIN_DIR, launch_command, Colors, Protocols, which
34
35
36 LESS = "less"
37 HELP = '''
38
39 ===============================================================================
40                        gst-validate-launcher
41 ===============================================================================
42
43 1. Introduction
44 ----------------
45
46 gst-validate-launcher is a test launcher tool. It has been designed to
47 launch the various tools included in GstValidate, running tests on real
48 media files. This means that with gst-validate-launcher, you can launch
49 many tests automatically in one simple command. It then permits to
50 aggregate results and print them in a human readable way on stdout
51 and serializing them in the following implemented formats:
52
53  * %s
54
55 We support all the tools provided in GstValidate in the launcher, but
56 we also support ges-launch when the GStreamer Editing Services have
57 been compiled against GstValidate.
58
59 2. Default test suite
60 ---------------------
61
62 A default suite of tests is provided and is available at: http://cgit.freedesktop.org/gstreamer/gst-integration-testsuites/
63 You can run it pretty simply doing:
64
65 .    $gst-validate-launcher --sync
66
67 That will download Gstreamer upstream default assets into the
68 default folder (%s) and run all currently
69 activated tests. Note that we use git-annex https://git-annex.branchable.com/ so
70 you will need that tool to get started.
71
72 3. Implement your own tests
73 ---------------------------
74
75 To implement new tests, you will just need to set the media path using the
76 --medias-paths argument. If you want to run all available scenarios on all the
77 file present in that folder, you should run the first time:
78
79 .    $gst-validate-launcher --medias-paths /path/to/media/files --generate-media-info
80
81 That will generate the .media_info files that contains information about the media
82 files present in that folder. Those media_info files are simple XML file describing
83 the topology of the media files. You need not reuse --generate-media-info from
84 next time. The generated media files will be used as a reference for following
85 runs. You might want to check that they contain the right information yourself
86 the first time.
87
88 Once .media-info is generated, you can update it using --update-media-info.
89
90 Those .media_info are the files that are used by gst-validate-launcher to know
91 what media files can be used for the different scenarios. For example if a
92 file is not seekable, seeking scenarios will not be run on it etc...
93
94 3.1 Scenarios specific to a media file/stream:
95 ----------------------------------------------
96
97 It is possible that some scenarios are very specific to one media file. In that case,
98 the .scenario file should be present in the same folder as the .media_info file and
99 be called similarly. For example for a file called /some/media/file.mp4, the media_info
100 file will be called /some/media/file.media_info and a scenario that will seek to a position that
101 is known to fail would be called: /some/media/file.mp4.seek_to_failing_pos.scenario and
102 gst-validate-launcher will run that scenario only on that media file.
103
104 3.2 Test media accessible through other protocols:
105 --------------------------------------------------
106
107 Currently gst-validate-launcher supports the following protocols:
108
109   * %s
110
111 It does not mean you can not test other protocols but it means that it has not been
112 properly tested.
113
114 To test medias that use those protocols, you should simply make sure that there
115 is a media descriptor file with .stream_info as an extension in your --media-paths.
116 You can generate such a file doing:
117
118 .   $gst-validate-media-check-1.0 http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8 --output-file /somewhere/in/you/media/path/bipbop.stream_info
119
120 Once this is done, gst-validate-launcher will run the scenarios on those media files the
121 same way as if they were local files.
122
123
124 4. Debug gst-validate-launcher execution
125 ----------------------------------------
126
127 You can activate debug logs setting the environment variable GST_VALIDATE_LAUNCHER_DEBUG.
128 It uses the same syntax as PITIVI_DEBUG (more information at:
129 http://wiki.pitivi.org/wiki/Bug_reporting#Debug_logs).
130 ''' % ("\n  * ".join([reporter.name for reporter in
131                       utils.get_subclasses(reporters.Reporter, reporters.__dict__)]
132                      ),
133        DEFAULT_MAIN_DIR,
134        "\n  * ".join([getattr(Protocols, att) for att in
135                       dir(Protocols) if isinstance(getattr(Protocols, att), str) and not
136                       att.startswith("_")]))
137
138 if "--help" not in sys.argv:
139     HELP = "Use --help for the full help"
140
141 QA_ASSETS = "gst-integration-testsuites"
142 MEDIAS_FOLDER = "medias"
143 DEFAULT_GST_QA_ASSETS_REPO = "https://gitlab.freedesktop.org/gstreamer/gst-integration-testsuites.git"
144
145
146 def download_assets(options):
147     try:
148         launch_command("%s %s %s" % (options.get_assets_command,
149                                      options.remote_assets_url,
150                                      options.clone_dir),
151                        fails=True)
152     except subprocess.CalledProcessError as e:
153         if "git" in options.get_assets_command:
154             m = "\n\nMAKE SURE YOU HAVE git INSTALLED!"
155         else:
156             m = ""
157
158         printc("Could not download assets\n\nError: %s%s" % (e, m),
159                Colors.FAIL, True)
160
161         return False
162
163     return True
164
165
166 class PrintUsage(argparse.Action):
167
168     def __init__(self, option_strings, dest=argparse.SUPPRESS, default=argparse.SUPPRESS, help=None):
169         super(
170             PrintUsage, self).__init__(option_strings=option_strings, dest=dest,
171                                        default=default, nargs=0, help=help)
172
173     def __call__(self, parser, namespace, values, option_string=None):
174         print(HELP)
175         parser.exit()
176
177
178 class LauncherConfig(Loggable):
179
180     def __init__(self):
181         self.testsuites = []
182         self.debug = False
183         self.forever = False
184         self.n_runs = None
185         self.fatal_error = False
186         self.wanted_tests = []
187         self.blacklisted_tests = []
188         self.list_tests = False
189         self.mute = True
190         self.unmute = not self.mute
191         self.no_color = False
192         self.generate_info = False
193         self.update_media_info = False
194         self.generate_info_full = False
195         self.long_limit = utils.LONG_TEST
196         self.config = None
197         self.valgrind = False
198         self.gdb = False
199         self.no_display = False
200         self.xunit_file = None
201         self.main_dir = utils.DEFAULT_MAIN_DIR
202         self.output_dir = None
203         self.logsdir = None
204         self.privatedir = None
205         self.redirect_logs = False
206         self.num_jobs = int(multiprocessing.cpu_count() / 2)
207         self.dest = None
208         self._using_default_paths = False
209         # paths passed with --media-path, and not defined by a testsuite
210         self.user_paths = []
211         self.paths = []
212         self.testsuites_dirs = utils.DEFAULT_TESTSUITES_DIRS
213
214         self.clone_dir = None
215
216         self.http_server_port = 8079
217         self.http_bandwith = 1024 * 1024
218         self.http_server_dir = None
219         self.httponly = False
220         self.get_assets_command = "git clone"
221         self.remote_assets_url = DEFAULT_GST_QA_ASSETS_REPO
222         self.sync = False
223         self.force_sync = False
224         self.sync_all = False
225         self.check_bugs_status = False
226         self.retry_on_failures = False
227         self.html = False
228
229     def cleanup(self):
230         """
231         Cleanup the options looking after user options have been parsed
232         """
233
234         # Get absolute path for main_dir and base everything on that
235         self.main_dir = os.path.abspath(self.main_dir)
236         os.environ['GST_VALIDATE_LAUNCHER_MAIN_DIR'] = self.main_dir
237
238         # default for output_dir is MAINDIR
239         if not self.output_dir:
240             self.output_dir = self.main_dir
241         else:
242             self.output_dir = os.path.abspath(self.output_dir)
243
244         self.mute = not self.unmute
245         if self.gdb_non_stop:
246             self.gdb = True
247
248         if self.gdb:
249             self.logsdir = "stdout"
250             self.debug = True
251             self.num_jobs = 1
252             try:
253                 subprocess.check_output("gdb --help", shell=True)
254             except subprocess.CalledProcessError:
255                 printc("Want to use gdb, but not available on the system",
256                        Colors.FAIL)
257                 return False
258
259         # other output directories
260         if self.logsdir in ['stdout', 'stderr']:
261             # Allow -l stdout/stderr to work like -rl stdout/stderr
262             self.redirect_logs = self.logsdir
263             self.logsdir = None
264         if self.verbose:
265             self.redirect_logs = 'stdout'
266             self.logsdir = None
267         if self.logsdir is None:
268             self.logsdir = os.path.join(self.output_dir, "logs")
269         if self.dest is None:
270             self.dest = os.path.join(self.output_dir, "rendered")
271         self.privatedir = os.path.join(self.output_dir, "launcher-private")
272
273         if not os.path.exists(self.dest):
274             os.makedirs(self.dest)
275         if not os.path.exists(self.logsdir):
276             os.makedirs(self.logsdir)
277         if not os.path.exists(self.privatedir):
278             os.makedirs(self.privatedir)
279
280         if self.redirect_logs not in ['stdout', 'stderr', False]:
281             printc("Log redirection (%s) must be either 'stdout' or 'stderr'."
282                    % self.redirect_logs, Colors.FAIL, True)
283             return False
284
285         if urllib.parse.urlparse(self.dest).scheme == "":
286             self.dest = path2url(self.dest)
287
288         if self.no_color:
289             utils.desactivate_colors()
290         if self.clone_dir is None:
291             if not utils.USING_SUBPROJECT:
292                 self.clone_dir = os.path.join(self.main_dir, QA_ASSETS)
293             else:
294                 self.clone_dir = self.main_dir
295
296         if not isinstance(self.paths, list):
297             self.paths = [self.paths]
298
299         if not isinstance(self.user_paths, list):
300             self.user_paths = [self.user_paths]
301
302         self.paths = list(set(self.paths).union(set(self.user_paths)))
303
304         if self.generate_info_full is True:
305             self.generate_info = True
306
307         if not utils.USING_SUBPROJECT:
308             if self.sync_all is True or self.force_sync is True:
309                 self.sync = True
310
311             if not self.sync and not os.path.exists(self.clone_dir) and \
312                     self.clone_dir == os.path.join(self.clone_dir, MEDIAS_FOLDER):
313                 printc("Media path (%s) does not exists. Forgot to run --sync ?"
314                     % self.clone_dir, Colors.FAIL, True)
315                 return False
316
317         if (self.main_dir != DEFAULT_MAIN_DIR or self.clone_dir != QA_ASSETS):
318             local_clone_dir = os.path.join(
319                 self.main_dir, self.clone_dir, "testsuites")
320             if local_clone_dir not in self.testsuites_dirs:
321                 self.testsuites_dirs.insert(0, local_clone_dir)
322         if self.valgrind:
323             try:
324                 subprocess.check_output("valgrind --help", shell=True)
325             except subprocess.CalledProcessError:
326                 printc("Want to use valgrind, but not available on the system",
327                        Colors.FAIL)
328                 return False
329
330         if self.html:
331             try:
332                 import commonmark
333             except ImportError:
334                 printc("You want to output html logs but commonmark not found. Install it"
335                        " with `pip install commonmark` and try again.", Colors.FAIL)
336                 return False
337
338         return True
339
340     def set_http_server_dir(self, path):
341         if self.http_server_dir is not None:
342             printc("Server directory already set to %s" % self.http_server_dir)
343             return
344
345         self.http_server_dir = path
346
347     def add_paths(self, paths, force=False):
348         if force is False:
349             if self.paths:
350                 return
351         if not isinstance(paths, list):
352             paths = [paths]
353
354         if self._using_default_paths:
355             self.paths = paths
356             self._using_default_paths = False
357         else:
358             for path in paths:
359                 if path not in self.paths:
360                     self.paths.append(path)
361
362     @staticmethod
363     def create_parser():
364         parser = argparse.ArgumentParser(
365             formatter_class=argparse.RawTextHelpFormatter,
366             prog='gst-validate-launcher', description=HELP)
367
368         parser.add_argument('testsuites', metavar='N', nargs='*',
369                             help="""Lets you specify a test to run, a testsuite name or a file where the testsuite to execute is defined.
370
371     In the module if you want to work with a specific test manager(s) (for example,
372     'ges' or 'validate'), you should define the TEST_MANAGER variable in the
373     testsuite file (it can be a list of test manager names)
374
375     In this file you should implement a setup_tests function. That function takes
376     a TestManager and the GstValidateLauncher option as parameters and return True
377     if it succeeded loading the tests, False otherwise.
378     You will be able to configure the TestManager with its various methods. This
379     function will be called with each TestManager usable, for example you will be
380     passed the 'validate' TestManager in case the GstValidateManager launcher is
381     available. You should configure it using:
382
383     * test_manager.add_scenarios: which allows you to register a list of scenario names to be run
384     * test_manager.set_default_blacklist: Lets you set a list of tuple of the form:
385             (@regex_defining_blacklister_test_names, @reason_for_the_blacklisting)
386     * test_manager.add_generators: which allows you to register a list of #GstValidateTestsGenerator
387         to be used to generate tests
388     * test_manager.add_encoding_formats:: which allows you to register a list #MediaFormatCombination to be used for transcoding tests
389
390     You can also set default values with:
391         * test_manager.register_defaults: Sets default values for all parametters
392         * test_manager.register_default_test_generators: Sets default values for the TestsGenerators to be used
393         * test_manager.register_default_scenarios: Sets default values for the scenarios to be executed
394         * test_manager.register_default_encoding_formats: Sets default values for the encoding formats to be tested
395
396     Note that all testsuite should be inside python modules, so the directory should contain a __init__.py file
397     """,
398                             default=["validate"])
399         parser.add_argument("-d", "--debug", dest="debug",
400                             action="store_true",
401                             help="Let user debug the process on timeout")
402         parser.add_argument("--timeout-factor", dest="timeout_factor",
403                             default=1.0, type=float,
404                             help="Factor to be applied on all timeout values.")
405         parser.add_argument("-f", "--forever", dest="forever",
406                             action="store_true",
407                             help="Keep running tests until one fails")
408         parser.add_argument("--n-runs", dest="n_runs", action='store',
409                             help="Number of runs, if the testsuites."
410                             " Meaning no failure will stop the testuite"
411                             " run meanwhile.", type=int),
412         parser.add_argument("-F", "--fatal-error", dest="fatal_error",
413                             action="store_true",
414                             help="Stop on first fail")
415         parser.add_argument("--fail-on-testlist-change",
416                             dest="fail_on_testlist_change",
417                             action="store_true",
418                             help="Fail the testsuite if a test has been added"
419                             " or removed without being explicitely added/removed "
420                             "from the testlist file.")
421         parser.add_argument("-t", "--wanted-tests", dest="wanted_tests",
422                             action="append",
423                             help="Define the tests to execute, it can be a regex."
424                             " If it contains defaults_only, only default scenarios"
425                             " will be executed")
426         parser.add_argument("-b", "--blacklisted-tests", dest="blacklisted_tests",
427                             action="append",
428                             help="Define the tests not to execute, it can be a regex.")
429         parser.add_argument("--check-bugs", dest="check_bugs_status",
430                             action="store_true",
431                             help="Check if the bug linked to blacklisted tests has"
432                             " been marked as resolved. (works with gitlab and bugzilla)")
433         parser.add_argument("-L", "--list-tests",
434                             dest="list_tests",
435                             action="store_true",
436                             help="List tests and exit")
437         parser.add_argument("--unmute", dest="unmute",
438                             action="store_true",
439                             help="Unmute playback output, which means that we use "
440                             "'real' sinks")
441         parser.add_argument("-m", "--mute", dest="mute",
442                             action="store_true",
443                             help="Mute playback output, which means that we use "
444                             "a fakesink")
445         parser.add_argument("-n", "--no-color", dest="no_color",
446                             action="store_true",
447                             help="Set it to output no colored text in the terminal")
448         parser.add_argument("-g", "--generate-media-info", dest="generate_info",
449                             action="store_true",
450                             help="Set it in order to generate the missing .media_infos files")
451         parser.add_argument("--update-media-info", dest="update_media_info",
452                             action="store_true",
453                             help="Set it in order to update existing .media_infos files")
454         parser.add_argument(
455             "-G", "--generate-media-info-with-frame-detection", dest="generate_info_full",
456             action="store_true",
457             help="Set it in order to generate the missing .media_infos files. "
458             "It implies --generate-media-info but enabling frame detection")
459         parser.add_argument("-lt", "--long-test-limit", dest="long_limit",
460                             action='store',
461                             help="Defines the limit for which a test is considered as long (in seconds)."
462                             " Note that 0 will enable all tests", type=int),
463         parser.add_argument("--dump-on-failure", dest="dump_on_failure",
464                             action="store_true", default=False,
465                             help="Dump logs to stdout when a test fails."
466                             " Note that bat is used to enhance output if available"
467                             " (See https://github.com/sharkdp/bat)")
468         parser.add_argument("--max-dump-size", dest="max_dump_size", type=float,
469                             default=0.5, help="Maximum size of logs to dump on stdout in MB.")
470         parser.add_argument("-c", "--config", dest="config",
471                             help="This is DEPRECATED, prefer using the testsuite format"
472                             " to configure testsuites")
473         parser.add_argument("-vg", "--valgrind", dest="valgrind",
474                             action="store_true",
475                             help="Run the tests inside Valgrind")
476         parser.add_argument("--gdb", dest="gdb",
477                             action="store_true",
478                             help="Run the tests inside gdb (implies"
479                             " --output-dir=stdout and --jobs=1)")
480         parser.add_argument("--gdb-non-stop", dest="gdb_non_stop",
481                             action="store_true",
482                             help="Run the test automatically in gdb (implies --gdb)")
483         parser.add_argument("-nd", "--no-display", dest="no_display",
484                             action="store_true",
485                             help="Run the tests without outputting graphics"
486                             " on any display. It tries to run all graphical operation"
487                             " in a virtual framebuffer."
488                             " Note that it is currently implemented only"
489                             " for the X  server thanks to Xvfb (which is requeried in that case)")
490         parser.add_argument('--xunit-file', dest='xunit_file',
491                             action='store', metavar="FILE",
492                             help=("Path to xml file to store the xunit report in."))
493         parser.add_argument('--shuffle', dest="shuffle", action="store_true",
494                             help="Runs the test in a random order. Can help speed up the overall"
495                             " test time by running synchronized and unsynchronized tests"
496                             " at the same time")
497         parser.add_argument('--retry-on-failures', dest="retry_on_failures", action="store_true",
498                             help="Re-try tests that produce unexpected results")
499         parser.add_argument('--html', dest="html", action="store_true",
500                             help="Write logs as html")
501         dir_group = parser.add_argument_group(
502             "Directories and files to be used by the launcher")
503         dir_group.add_argument("-M", "--main-dir", dest="main_dir",
504                                help="Main directory where to put files."
505                                " Respects the GST_VALIDATE_LAUNCHER_MAIN_DIR environment variable."
506                                " Default is %s" % DEFAULT_MAIN_DIR)
507         dir_group.add_argument("--testsuites-dir", dest="testsuites_dirs", action='append',
508                                help="Directory where to look for testsuites. Default is %s"
509                                % utils.DEFAULT_TESTSUITES_DIRS)
510         dir_group.add_argument("-o", "--output-dir", dest="output_dir",
511                                help="Directory where to store logs and rendered files. Default is MAIN_DIR")
512         dir_group.add_argument("-l", "--logs-dir", dest="logsdir",
513                                help="Directory where to store logs, default is OUTPUT_DIR/logs.")
514         dir_group.add_argument("-R", "--render-path", dest="dest",
515                                help="Set the path to which projects should be rendered, default is OUTPUT_DIR/rendered")
516         dir_group.add_argument("-p", "--medias-paths", dest="user_paths", action="append",
517                                help="Paths in which to look for media files")
518         dir_group.add_argument("-a", "--clone-dir", dest="clone_dir",
519                                help="Paths where to clone the testuite to run."
520                                " default is MAIN_DIR/gst-integration-testsuites")
521         dir_group.add_argument("-rl", "--redirect-logs", dest="redirect_logs",
522                                help="Redirect logs to 'stdout' or 'sdterr'.")
523         dir_group.add_argument("-v", "--verbose", dest="verbose",
524                                action='count',
525                                help="Redirect logs to stdout.")
526         dir_group.add_argument("-j", "--jobs", dest="num_jobs",
527                                help="Number of tests to execute simultaneously"
528                                " (Defaults to number of cores of the processor)",
529                                type=int)
530         dir_group.add_argument("--ignore-numfailures", dest="ignore_numfailures",
531                                help="Ignore the number of failed test in exit code",
532                                default=False, action='store_true')
533         dir_group.add_argument("--parts", dest="num_parts",
534                                help="Splits the tests in equally distributed parts and only run one part"
535                                " (Defaults to 1 part)",
536                                type=int, default=1)
537         dir_group.add_argument("--part-index", dest="part_index",
538                                help="The index of the part to be run (starts at 1).",
539                                type=int, default=1)
540
541         http_server_group = parser.add_argument_group(
542             "Handle the HTTP server to be created")
543         http_server_group.add_argument(
544             "--http-server-port", dest="http_server_port",
545             help="Port on which to run the http server on localhost", type=int)
546         http_server_group.add_argument(
547             "--http-bandwith-limitation", dest="http_bandwith",
548             help="The artificial bandwith limitation to introduce to the local server (in Bytes/sec) (default: 1 MBps)")
549         http_server_group.add_argument(
550             "-s", "--folder-for-http-server", dest="http_server_dir",
551             help="Folder in which to create an http server on localhost. Default is PATHS")
552         http_server_group.add_argument("--http-only", dest="httponly",
553                                        action='store_true',
554                                        help="Start the http server and quit")
555
556         assets_group = parser.add_argument_group("Handle remote assets")
557         assets_group.add_argument(
558             "--get-assets-command", dest="get_assets_command",
559             help="Command to get assets")
560         assets_group.add_argument("--remote-assets-url", dest="remote_assets_url",
561                                   help="Url to the remote assets (default:%s)" % DEFAULT_GST_QA_ASSETS_REPO)
562         assets_group.add_argument("-S", "--sync", dest="sync", action="store_true",
563                                   help="Synchronize asset repository")
564         assets_group.add_argument("-fs", "--force-sync", dest="force_sync", action="store_true",
565                                   help="Synchronize asset repository reseting any change that might have"
566                                   " happened in the testsuite")
567         assets_group.add_argument("--sync-all", dest="sync_all", action="store_true",
568                                   help="Synchronize asset repository,"
569                                   " including big media files")
570         assets_group.add_argument("--usage", action=PrintUsage,
571                                   help="Print usage documentation")
572         return parser
573
574
575 def setup_launcher_from_args(args, main_options=None):
576     loggable.init("GST_VALIDATE_LAUNCHER_DEBUG", True, False)
577     parser = LauncherConfig.create_parser()
578     tests_launcher = _TestsLauncher()
579     tests_launcher.add_options(parser)
580
581     if "--help" in sys.argv and which(LESS):
582         tmpf = tempfile.NamedTemporaryFile(mode='r+')
583
584         parser.print_help(file=tmpf)
585         os.system("%s %s" % (LESS, tmpf.name))
586         return False, None, None
587
588     options = LauncherConfig()
589     parser.parse_args(args=args, namespace=options)
590     if main_options:
591         # Override output directories and logging properties of the sub launcher.
592         for option in ["main_dir", "output_dir", "logsdir", "dest", "clone_dir",
593                        "redirect_logs", "verbose", "timeout_factor"]:
594             setattr(options, option, getattr(main_options, option))
595     if not options.cleanup():
596         return False, None, None
597
598     if options.remote_assets_url and options.sync and not os.path.exists(options.clone_dir):
599         if not download_assets(options):
600             return False, None, None
601
602     # Ensure that the scenario manager singleton is ready to be used
603     ScenarioManager().config = options
604     if not tests_launcher.set_settings(options, []):
605         return False, None, None
606
607     return True, options, tests_launcher
608
609
610 def main(libsdir):
611     global LIBSDIR
612     LIBSDIR = libsdir
613
614     utils.DEFAULT_TESTSUITES_DIRS.append(os.path.join(LIBSDIR, "testsuites"))
615     os.environ["GST_VALIDATE_APPS_DIR"] = os.path.join(
616         LIBSDIR, "apps") + os.pathsep + os.environ.get("GST_VALIDATE_APPS_DIR", "")
617
618     res, options, tests_launcher = setup_launcher_from_args(sys.argv[1:])
619     if res is False:
620         return 1
621
622     if options.list_tests:
623         if tests_launcher.list_tests() == -1:
624             printc("\nFailling as tests have been removed/added "
625                    " (--fail-on-testlist-change)", Colors.FAIL)
626             return 1
627
628         tests = tests_launcher.tests
629         for test in tests:
630             printc(test)
631
632         printc("\nNumber of tests: %d" % len(tests), Colors.OKGREEN)
633         return 0
634
635     if options.httponly is True:
636         print("Running HTTP server only")
637         return 0
638
639     # There seems to be some issue with forking, dconf and some gtype
640     # initialization that deadlocks occasionally, setting the
641     # GSettings backend make it go away.
642     # Also happened here:
643     # https://cgit.freedesktop.org/gstreamer/gst-plugins-good/commit/tests/check/Makefile.am?id=8e2c1d1de56bddbff22170f8b17473882e0e63f9
644     os.environ['GSETTINGS_BACKEND'] = "memory"
645
646     exception = None
647     try:
648         tests_launcher.run_tests()
649     except Exception as e:
650         exception = e
651         pass
652     finally:
653         res = tests_launcher.final_report()
654         if options.ignore_numfailures:
655             res = 0
656         if exception is not None:
657             raise exception
658
659     return res