3 # Copyright (c) 2014,Thibault Saunier <thibault.saunier@collabora.com>
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.
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.
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.
23 from . import loggable
24 import multiprocessing
27 from . import reporters
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
39 ===============================================================================
41 ===============================================================================
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:
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.
62 A default suite of tests is provided and is available at: http://gitlab.freedesktop.org/gstreamer/gst-integration-testsuites/
63 You can run it pretty simply doing:
65 . $gst-validate-launcher --sync
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.
72 3. Implement your own tests
73 ---------------------------
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:
79 . $gst-validate-launcher --medias-paths /path/to/media/files --generate-media-info
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
88 Once .media-info is generated, you can update it using --update-media-info.
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...
94 3.1 Scenarios specific to a media file/stream:
95 ----------------------------------------------
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.
104 3.2 Test media accessible through other protocols:
105 --------------------------------------------------
107 Currently gst-validate-launcher supports the following protocols:
111 It does not mean you can not test other protocols but it means that it has not been
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:
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
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.
124 4. Debug gst-validate-launcher execution
125 ----------------------------------------
127 You can activate debug logs setting the environment variable GST_VALIDATE_LAUNCHER_DEBUG.
129 . $GST_VALIDATE_LAUNCHER_DEBUG=6 gst-validate-launcher
131 It uses the same syntax as PITIVI_DEBUG (more information at:
132 https://developer.pitivi.org/Bug_reporting.html#debug-logs).
133 ''' % ("\n * ".join([reporter.name for reporter in
134 utils.get_subclasses(reporters.Reporter, reporters.__dict__)]
137 "\n * ".join([getattr(Protocols, att) for att in
138 dir(Protocols) if isinstance(getattr(Protocols, att), str) and not
139 att.startswith("_")]))
141 if "--help" not in sys.argv:
142 HELP = "Use --help for the full help"
144 QA_ASSETS = "gst-integration-testsuites"
145 MEDIAS_FOLDER = "medias"
146 DEFAULT_GST_QA_ASSETS_REPO = "https://gitlab.freedesktop.org/gstreamer/gst-integration-testsuites.git"
149 def download_assets(options):
151 printc("About to download assets from %s to %s" % options.remote_assets_url,
153 launch_command("%s %s %s" % (options.get_assets_command,
154 options.remote_assets_url,
157 except subprocess.CalledProcessError as e:
158 if "git" in options.get_assets_command:
159 m = "\n\nMAKE SURE YOU HAVE git INSTALLED!"
163 printc("Could not download assets\n\nError: %s%s" % (e, m),
171 class PrintUsage(argparse.Action):
173 def __init__(self, option_strings, dest=argparse.SUPPRESS, default=argparse.SUPPRESS, help=None):
175 PrintUsage, self).__init__(option_strings=option_strings, dest=dest,
176 default=default, nargs=0, help=help)
178 def __call__(self, parser, namespace, values, option_string=None):
183 class LauncherConfig(Loggable):
190 self.fatal_error = False
191 self.wanted_tests = []
192 self.blacklisted_tests = []
193 self.list_tests = False
195 self.unmute = not self.mute
196 self.no_color = False
197 self.generate_info = False
198 self.update_media_info = False
199 self.generate_info_full = False
200 self.long_limit = utils.LONG_TEST
202 self.valgrind = False
204 self.no_display = False
205 self.xunit_file = None
206 self.main_dir = utils.DEFAULT_MAIN_DIR
207 self.output_dir = None
209 self.privatedir = None
210 self.redirect_logs = False
211 self.num_jobs = int(multiprocessing.cpu_count() / 2)
213 self._using_default_paths = False
214 # paths passed with --media-path, and not defined by a testsuite
217 self.testsuites_dirs = utils.DEFAULT_TESTSUITES_DIRS
219 self.clone_dir = None
221 self.http_server_port = 8079
222 self.http_bandwith = 1024 * 1024
223 self.http_server_dir = None
224 self.httponly = False
225 self.get_assets_command = "git clone"
226 self.remote_assets_url = DEFAULT_GST_QA_ASSETS_REPO
228 self.force_sync = False
229 self.sync_all = False
230 self.check_bugs_status = False
231 self.retry_on_failures = False
236 Cleanup the options looking after user options have been parsed
239 # Get absolute path for main_dir and base everything on that
240 self.main_dir = os.path.abspath(self.main_dir)
241 os.environ['GST_VALIDATE_LAUNCHER_MAIN_DIR'] = self.main_dir
243 # default for output_dir is MAINDIR
244 if not self.output_dir:
245 self.output_dir = self.main_dir
247 self.output_dir = os.path.abspath(self.output_dir)
249 self.mute = not self.unmute
250 if self.gdb_non_stop:
254 self.logsdir = "stdout"
258 subprocess.check_output("gdb --help", shell=True)
259 except subprocess.CalledProcessError:
260 printc("Want to use gdb, but not available on the system",
264 # other output directories
265 if self.logsdir in ['stdout', 'stderr']:
266 # Allow -l stdout/stderr to work like -rl stdout/stderr
267 self.redirect_logs = self.logsdir
270 self.redirect_logs = 'stdout'
272 if self.logsdir is None:
273 self.logsdir = os.path.join(self.output_dir, "logs")
274 if self.dest is None:
275 self.dest = os.path.join(self.output_dir, "rendered")
276 self.privatedir = os.path.join(self.output_dir, "launcher-private")
278 if not os.path.exists(self.dest):
279 os.makedirs(self.dest)
280 if not os.path.exists(self.logsdir):
281 os.makedirs(self.logsdir)
282 if not os.path.exists(self.privatedir):
283 os.makedirs(self.privatedir)
285 if self.redirect_logs not in ['stdout', 'stderr', False]:
286 printc("Log redirection (%s) must be either 'stdout' or 'stderr'."
287 % self.redirect_logs, Colors.FAIL, True)
290 if urllib.parse.urlparse(self.dest).scheme == "":
291 self.dest = path2url(self.dest)
294 utils.desactivate_colors()
295 if self.clone_dir is None:
296 if not utils.USING_SUBPROJECT:
297 self.clone_dir = os.path.join(self.main_dir, QA_ASSETS)
299 self.clone_dir = self.main_dir
301 if not isinstance(self.paths, list):
302 self.paths = [self.paths]
304 if not isinstance(self.user_paths, list):
305 self.user_paths = [self.user_paths]
307 self.paths = list(set(self.paths).union(set(self.user_paths)))
309 if self.generate_info_full is True:
310 self.generate_info = True
312 if not utils.USING_SUBPROJECT:
313 if self.sync_all is True or self.force_sync is True:
316 if not self.sync and not os.path.exists(self.clone_dir) and \
317 self.clone_dir == os.path.join(self.clone_dir, MEDIAS_FOLDER):
318 printc("Media path (%s) does not exists. Forgot to run --sync ?"
319 % self.clone_dir, Colors.FAIL, True)
322 if (self.main_dir != DEFAULT_MAIN_DIR or self.clone_dir != QA_ASSETS):
323 local_clone_dir = os.path.join(
324 self.main_dir, self.clone_dir, "testsuites")
325 if local_clone_dir not in self.testsuites_dirs:
326 self.testsuites_dirs.insert(0, local_clone_dir)
329 subprocess.check_output("valgrind --help", shell=True)
330 except subprocess.CalledProcessError:
331 printc("Want to use valgrind, but not available on the system",
339 printc("You want to output html logs but commonmark not found. Install it"
340 " with `pip install commonmark` and try again.", Colors.FAIL)
345 def set_http_server_dir(self, path):
346 if self.http_server_dir is not None:
347 printc("Server directory already set to %s" % self.http_server_dir)
350 self.http_server_dir = path
352 def add_paths(self, paths, force=False):
356 if not isinstance(paths, list):
359 if self._using_default_paths:
361 self._using_default_paths = False
364 if path not in self.paths:
365 self.paths.append(path)
369 parser = argparse.ArgumentParser(
370 formatter_class=argparse.RawTextHelpFormatter,
371 prog='gst-validate-launcher', description=HELP)
373 parser.add_argument('testsuites', metavar='N', nargs='*',
374 help="""Lets you specify a test to run, a testsuite name or a file where the testsuite to execute is defined.
376 In the module if you want to work with a specific test manager(s) (for example,
377 'ges' or 'validate'), you should define the TEST_MANAGER variable in the
378 testsuite file (it can be a list of test manager names)
380 In this file you should implement a setup_tests function. That function takes
381 a TestManager and the GstValidateLauncher option as parameters and return True
382 if it succeeded loading the tests, False otherwise.
383 You will be able to configure the TestManager with its various methods. This
384 function will be called with each TestManager usable, for example you will be
385 passed the 'validate' TestManager in case the GstValidateManager launcher is
386 available. You should configure it using:
388 * test_manager.add_scenarios: which allows you to register a list of scenario names to be run
389 * test_manager.set_default_blacklist: Lets you set a list of tuple of the form:
390 (@regex_defining_blacklister_test_names, @reason_for_the_blacklisting)
391 * test_manager.add_generators: which allows you to register a list of #GstValidateTestsGenerator
392 to be used to generate tests
393 * test_manager.add_encoding_formats:: which allows you to register a list #MediaFormatCombination to be used for transcoding tests
395 You can also set default values with:
396 * test_manager.register_defaults: Sets default values for all parametters
397 * test_manager.register_default_test_generators: Sets default values for the TestsGenerators to be used
398 * test_manager.register_default_scenarios: Sets default values for the scenarios to be executed
399 * test_manager.register_default_encoding_formats: Sets default values for the encoding formats to be tested
401 Note that all testsuite should be inside python modules, so the directory should contain a __init__.py file
403 default=["validate"])
404 parser.add_argument("-d", "--debug", dest="debug",
406 help="Let user debug the process on timeout")
407 parser.add_argument("--timeout-factor", dest="timeout_factor",
408 default=1.0, type=float,
409 help="Factor to be applied on all timeout values.")
410 parser.add_argument("-f", "--forever", dest="forever",
412 help="Keep running tests until one fails")
413 parser.add_argument("--n-runs", dest="n_runs", action='store',
414 help="Number of runs, if the testsuites."
415 " Meaning no failure will stop the testuite"
416 " run meanwhile.", type=int),
417 parser.add_argument("-F", "--fatal-error", dest="fatal_error",
419 help="Stop on first fail")
420 parser.add_argument("--fail-on-testlist-change",
421 dest="fail_on_testlist_change",
423 help="Fail the testsuite if a test has been added"
424 " or removed without being explicitely added/removed "
425 "from the testlist file.")
426 parser.add_argument("-t", "--wanted-tests", dest="wanted_tests",
428 help="Define the tests to execute, it can be a regex."
429 " If it contains defaults_only, only default scenarios"
431 parser.add_argument("-b", "--blacklisted-tests", dest="blacklisted_tests",
433 help="Define the tests not to execute, it can be a regex.")
434 parser.add_argument("--check-bugs", dest="check_bugs_status",
436 help="Check if the bug linked to blacklisted tests has"
437 " been marked as resolved. (works with gitlab and bugzilla)")
438 parser.add_argument("-L", "--list-tests",
441 help="List tests and exit")
442 parser.add_argument("--unmute", dest="unmute",
444 help="Unmute playback output, which means that we use "
446 parser.add_argument("-m", "--mute", dest="mute",
448 help="Mute playback output, which means that we use "
450 parser.add_argument("-n", "--no-color", dest="no_color",
452 help="Set it to output no colored text in the terminal")
453 parser.add_argument("-g", "--generate-media-info", dest="generate_info",
455 help="Set it in order to generate the missing .media_infos files")
456 parser.add_argument("--update-media-info", dest="update_media_info",
458 help="Set it in order to update existing .media_infos files")
460 "-G", "--generate-media-info-with-frame-detection", dest="generate_info_full",
462 help="Set it in order to generate the missing .media_infos files. "
463 "It implies --generate-media-info but enabling frame detection")
464 parser.add_argument("-lt", "--long-test-limit", dest="long_limit",
466 help="Defines the limit for which a test is considered as long (in seconds)."
467 " Note that 0 will enable all tests", type=int),
468 parser.add_argument("--dump-on-failure", dest="dump_on_failure",
469 action="store_true", default=False,
470 help="Dump logs to stdout when a test fails."
471 " Note that bat is used to enhance output if available"
472 " (See https://github.com/sharkdp/bat)")
473 parser.add_argument("--max-dump-size", dest="max_dump_size", type=float,
474 default=0.5, help="Maximum size of logs to dump on stdout in MB.")
475 parser.add_argument("-c", "--config", dest="config",
476 help="This is DEPRECATED, prefer using the testsuite format"
477 " to configure testsuites")
478 parser.add_argument("-vg", "--valgrind", dest="valgrind",
480 help="Run the tests inside Valgrind")
481 parser.add_argument("--gdb", dest="gdb",
483 help="Run the tests inside gdb (implies"
484 " --output-dir=stdout and --jobs=1)")
485 parser.add_argument("--gdb-non-stop", dest="gdb_non_stop",
487 help="Run the test automatically in gdb (implies --gdb)")
488 parser.add_argument("-nd", "--no-display", dest="no_display",
490 help="Run the tests without outputting graphics"
491 " on any display. It tries to run all graphical operation"
492 " in a virtual framebuffer."
493 " Note that it is currently implemented only"
494 " for the X server thanks to Xvfb (which is requeried in that case)")
495 parser.add_argument('--xunit-file', dest='xunit_file',
496 action='store', metavar="FILE",
497 help=("Path to xml file to store the xunit report in."))
498 parser.add_argument('--shuffle', dest="shuffle", action="store_true",
499 help="Runs the test in a random order. Can help speed up the overall"
500 " test time by running synchronized and unsynchronized tests"
502 parser.add_argument('--retry-on-failures', dest="retry_on_failures", action="store_true",
503 help="Re-try tests that produce unexpected results")
504 parser.add_argument('--html', dest="html", action="store_true",
505 help="Write logs as html")
506 dir_group = parser.add_argument_group(
507 "Directories and files to be used by the launcher")
508 dir_group.add_argument("-M", "--main-dir", dest="main_dir",
509 help="Main directory where to put files."
510 " Respects the GST_VALIDATE_LAUNCHER_MAIN_DIR environment variable."
511 " Default is %s" % DEFAULT_MAIN_DIR)
512 dir_group.add_argument("--testsuites-dir", dest="testsuites_dirs", action='append',
513 help="Directory where to look for testsuites. Default is %s"
514 % utils.DEFAULT_TESTSUITES_DIRS)
515 dir_group.add_argument("-o", "--output-dir", dest="output_dir",
516 help="Directory where to store logs and rendered files. Default is MAIN_DIR")
517 dir_group.add_argument("-l", "--logs-dir", dest="logsdir",
518 help="Directory where to store logs, default is OUTPUT_DIR/logs.")
519 dir_group.add_argument("-R", "--render-path", dest="dest",
520 help="Set the path to which projects should be rendered, default is OUTPUT_DIR/rendered")
521 dir_group.add_argument("-p", "--medias-paths", dest="user_paths", action="append",
522 help="Paths in which to look for media files")
523 dir_group.add_argument("-a", "--clone-dir", dest="clone_dir",
524 help="Paths where to clone the testuite to run."
525 " default is MAIN_DIR/gst-integration-testsuites")
526 dir_group.add_argument("-rl", "--redirect-logs", dest="redirect_logs",
527 help="Redirect logs to 'stdout' or 'sdterr'.")
528 dir_group.add_argument("-v", "--verbose", dest="verbose",
530 help="Redirect logs to stdout.")
531 dir_group.add_argument("-j", "--jobs", dest="num_jobs",
532 help="Number of tests to execute simultaneously"
533 " (Defaults to number of cores of the processor)",
535 dir_group.add_argument("--ignore-numfailures", dest="ignore_numfailures",
536 help="Ignore the number of failed test in exit code",
537 default=False, action='store_true')
538 dir_group.add_argument("--parts", dest="num_parts",
539 help="Splits the tests in equally distributed parts and only run one part"
540 " (Defaults to 1 part)",
542 dir_group.add_argument("--part-index", dest="part_index",
543 help="The index of the part to be run (starts at 1).",
546 http_server_group = parser.add_argument_group(
547 "Handle the HTTP server to be created")
548 http_server_group.add_argument(
549 "--http-server-port", dest="http_server_port",
550 help="Port on which to run the http server on localhost", type=int)
551 http_server_group.add_argument(
552 "--http-bandwith-limitation", dest="http_bandwith",
553 help="The artificial bandwith limitation to introduce to the local server (in Bytes/sec) (default: 1 MBps)")
554 http_server_group.add_argument(
555 "-s", "--folder-for-http-server", dest="http_server_dir",
556 help="Folder in which to create an http server on localhost. Default is PATHS")
557 http_server_group.add_argument("--http-only", dest="httponly",
559 help="Start the http server and quit")
561 assets_group = parser.add_argument_group("Handle remote assets")
562 assets_group.add_argument(
563 "--get-assets-command", dest="get_assets_command",
564 help="Command to get assets")
565 assets_group.add_argument("--remote-assets-url", dest="remote_assets_url",
566 help="Url to the remote assets (default:%s)" % DEFAULT_GST_QA_ASSETS_REPO)
567 assets_group.add_argument("-S", "--sync", dest="sync", action="store_true",
568 help="Synchronize asset repository")
569 assets_group.add_argument("-fs", "--force-sync", dest="force_sync", action="store_true",
570 help="Synchronize asset repository reseting any change that might have"
571 " happened in the testsuite")
572 assets_group.add_argument("--sync-all", dest="sync_all", action="store_true",
573 help="Synchronize asset repository,"
574 " including big media files")
575 assets_group.add_argument("--usage", action=PrintUsage,
576 help="Print usage documentation")
580 def setup_launcher_from_args(args, main_options=None):
581 loggable.init("GST_VALIDATE_LAUNCHER_DEBUG", True, False)
582 parser = LauncherConfig.create_parser()
583 tests_launcher = _TestsLauncher()
584 tests_launcher.add_options(parser)
586 if "--help" in sys.argv and which(LESS):
587 tmpf = tempfile.NamedTemporaryFile(mode='r+')
589 parser.print_help(file=tmpf)
590 os.system("%s %s" % (LESS, tmpf.name))
591 return False, None, None
593 options = LauncherConfig()
594 parser.parse_args(args=args, namespace=options)
596 # Override output directories and logging properties of the sub launcher.
597 for option in ["main_dir", "output_dir", "logsdir", "dest", "clone_dir",
598 "redirect_logs", "verbose", "timeout_factor"]:
599 setattr(options, option, getattr(main_options, option))
600 if not options.cleanup():
601 return False, None, None
603 if options.remote_assets_url and options.sync and not os.path.exists(options.clone_dir):
604 if not download_assets(options):
605 return False, None, None
607 # Ensure that the scenario manager singleton is ready to be used
608 ScenarioManager().config = options
609 if not tests_launcher.set_settings(options, []):
610 return False, None, None
612 return True, options, tests_launcher
619 utils.DEFAULT_TESTSUITES_DIRS.append(os.path.join(LIBSDIR, "testsuites"))
620 os.environ["GST_VALIDATE_APPS_DIR"] = os.path.join(
621 LIBSDIR, "apps") + os.pathsep + os.environ.get("GST_VALIDATE_APPS_DIR", "")
623 res, options, tests_launcher = setup_launcher_from_args(sys.argv[1:])
627 if options.list_tests:
628 if tests_launcher.list_tests() == -1:
629 printc("\nFailling as tests have been removed/added "
630 " (--fail-on-testlist-change)", Colors.FAIL)
633 tests = tests_launcher.tests
637 printc("\nNumber of tests: %d" % len(tests), Colors.OKGREEN)
640 if options.httponly is True:
641 print("Running HTTP server only")
644 # There seems to be some issue with forking, dconf and some gtype
645 # initialization that deadlocks occasionally, setting the
646 # GSettings backend make it go away.
647 # Also happened here:
648 # https://cgit.freedesktop.org/gstreamer/gst-plugins-good/commit/tests/check/Makefile.am?id=8e2c1d1de56bddbff22170f8b17473882e0e63f9
649 os.environ['GSETTINGS_BACKEND'] = "memory"
653 tests_launcher.run_tests()
654 except Exception as e:
658 res = tests_launcher.final_report()
659 if options.ignore_numfailures:
661 if exception is not None: