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://cgit.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.
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__)]
134 "\n * ".join([getattr(Protocols, att) for att in
135 dir(Protocols) if isinstance(getattr(Protocols, att), str) and not
136 att.startswith("_")]))
138 if "--help" not in sys.argv:
139 HELP = "Use --help for the full help"
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"
146 def download_assets(options):
148 launch_command("%s %s %s" % (options.get_assets_command,
149 options.remote_assets_url,
152 except subprocess.CalledProcessError as e:
153 if "git" in options.get_assets_command:
154 m = "\n\nMAKE SURE YOU HAVE git INSTALLED!"
158 printc("Could not download assets\n\nError: %s%s" % (e, m),
166 class PrintUsage(argparse.Action):
168 def __init__(self, option_strings, dest=argparse.SUPPRESS, default=argparse.SUPPRESS, help=None):
170 PrintUsage, self).__init__(option_strings=option_strings, dest=dest,
171 default=default, nargs=0, help=help)
173 def __call__(self, parser, namespace, values, option_string=None):
178 class LauncherConfig(Loggable):
185 self.fatal_error = False
186 self.wanted_tests = []
187 self.blacklisted_tests = []
188 self.list_tests = False
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
197 self.valgrind = False
199 self.no_display = False
200 self.xunit_file = None
201 self.main_dir = utils.DEFAULT_MAIN_DIR
202 self.output_dir = None
204 self.privatedir = None
205 self.redirect_logs = False
206 self.num_jobs = int(multiprocessing.cpu_count() / 2)
208 self._using_default_paths = False
209 # paths passed with --media-path, and not defined by a testsuite
212 self.testsuites_dirs = utils.DEFAULT_TESTSUITES_DIRS
214 self.clone_dir = None
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
223 self.force_sync = False
224 self.sync_all = False
225 self.check_bugs_status = False
226 self.retry_on_failures = False
231 Cleanup the options looking after user options have been parsed
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
238 # default for output_dir is MAINDIR
239 if not self.output_dir:
240 self.output_dir = self.main_dir
242 self.output_dir = os.path.abspath(self.output_dir)
244 self.mute = not self.unmute
245 if self.gdb_non_stop:
249 self.logsdir = "stdout"
253 subprocess.check_output("gdb --help", shell=True)
254 except subprocess.CalledProcessError:
255 printc("Want to use gdb, but not available on the system",
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
265 self.redirect_logs = 'stdout'
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")
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)
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)
285 if urllib.parse.urlparse(self.dest).scheme == "":
286 self.dest = path2url(self.dest)
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)
294 self.clone_dir = self.main_dir
296 if not isinstance(self.paths, list):
297 self.paths = [self.paths]
299 if not isinstance(self.user_paths, list):
300 self.user_paths = [self.user_paths]
302 self.paths = list(set(self.paths).union(set(self.user_paths)))
304 if self.generate_info_full is True:
305 self.generate_info = True
307 if not utils.USING_SUBPROJECT:
308 if self.sync_all is True or self.force_sync is True:
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)
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)
324 subprocess.check_output("valgrind --help", shell=True)
325 except subprocess.CalledProcessError:
326 printc("Want to use valgrind, but not available on the system",
334 printc("You want to output html logs but commonmark not found. Install it"
335 " with `pip install commonmark` and try again.", Colors.FAIL)
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)
345 self.http_server_dir = path
347 def add_paths(self, paths, force=False):
351 if not isinstance(paths, list):
354 if self._using_default_paths:
356 self._using_default_paths = False
359 if path not in self.paths:
360 self.paths.append(path)
364 parser = argparse.ArgumentParser(
365 formatter_class=argparse.RawTextHelpFormatter,
366 prog='gst-validate-launcher', description=HELP)
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.
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)
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:
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
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
396 Note that all testsuite should be inside python modules, so the directory should contain a __init__.py file
398 default=["validate"])
399 parser.add_argument("-d", "--debug", dest="debug",
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",
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",
414 help="Stop on first fail")
415 parser.add_argument("--fail-on-testlist-change",
416 dest="fail_on_testlist_change",
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",
423 help="Define the tests to execute, it can be a regex."
424 " If it contains defaults_only, only default scenarios"
426 parser.add_argument("-b", "--blacklisted-tests", dest="blacklisted_tests",
428 help="Define the tests not to execute, it can be a regex.")
429 parser.add_argument("--check-bugs", dest="check_bugs_status",
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",
436 help="List tests and exit")
437 parser.add_argument("--unmute", dest="unmute",
439 help="Unmute playback output, which means that we use "
441 parser.add_argument("-m", "--mute", dest="mute",
443 help="Mute playback output, which means that we use "
445 parser.add_argument("-n", "--no-color", dest="no_color",
447 help="Set it to output no colored text in the terminal")
448 parser.add_argument("-g", "--generate-media-info", dest="generate_info",
450 help="Set it in order to generate the missing .media_infos files")
451 parser.add_argument("--update-media-info", dest="update_media_info",
453 help="Set it in order to update existing .media_infos files")
455 "-G", "--generate-media-info-with-frame-detection", dest="generate_info_full",
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",
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",
475 help="Run the tests inside Valgrind")
476 parser.add_argument("--gdb", dest="gdb",
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",
482 help="Run the test automatically in gdb (implies --gdb)")
483 parser.add_argument("-nd", "--no-display", dest="no_display",
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"
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",
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)",
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)",
537 dir_group.add_argument("--part-index", dest="part_index",
538 help="The index of the part to be run (starts at 1).",
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",
554 help="Start the http server and quit")
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")
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)
581 if "--help" in sys.argv and which(LESS):
582 tmpf = tempfile.NamedTemporaryFile(mode='r+')
584 parser.print_help(file=tmpf)
585 os.system("%s %s" % (LESS, tmpf.name))
586 return False, None, None
588 options = LauncherConfig()
589 parser.parse_args(args=args, namespace=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
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
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
607 return True, options, tests_launcher
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", "")
618 res, options, tests_launcher = setup_launcher_from_args(sys.argv[1:])
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)
628 tests = tests_launcher.tests
632 printc("\nNumber of tests: %d" % len(tests), Colors.OKGREEN)
635 if options.httponly is True:
636 print("Running HTTP server only")
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"
648 tests_launcher.run_tests()
649 except Exception as e:
653 res = tests_launcher.final_report()
654 if options.ignore_numfailures:
656 if exception is not None: