From 3c62c315a9537508bfba32101b8a4de64899a368 Mon Sep 17 00:00:00 2001
From: Thibault Saunier <tsaunier@gnome.org>
Date: Fri, 5 Dec 2014 12:16:36 +0100
Subject: [PATCH] validate: Implement RTSP support

---
 .../switch_audio_track_while_paused.scenario  |   2 +-
 ...witch_subtitle_track_while_paused.scenario |   2 +-
 validate/docs/validate-design.txt             |   2 -
 .../gst/validate/media-descriptor-writer.c    |   6 +-
 validate/launcher/apps/gstvalidate.py         | 226 +++++++++++++++---
 validate/launcher/baseclasses.py              |  88 ++++---
 validate/launcher/utils.py                    |  56 ++++-
 7 files changed, 291 insertions(+), 91 deletions(-)

diff --git a/validate/data/scenarios/switch_audio_track_while_paused.scenario b/validate/data/scenarios/switch_audio_track_while_paused.scenario
index 30fc6b672b..fd4c36249f 100644
--- a/validate/data/scenarios/switch_audio_track_while_paused.scenario
+++ b/validate/data/scenarios/switch_audio_track_while_paused.scenario
@@ -1,4 +1,4 @@
-description, summary="Change audio track while pipeline is paused", min-audio-track=2, duration=6.0, need-clock-sync=true
+description, summary="Change audio track while pipeline is paused", min-audio-track=2, duration=6.0, need-clock-sync=true, needs_preroll=true
 pause, playback-time=1.0;
 
 # Wait so that humans can see the pipeline is paused
diff --git a/validate/data/scenarios/switch_subtitle_track_while_paused.scenario b/validate/data/scenarios/switch_subtitle_track_while_paused.scenario
index e90f5795f1..6119142569 100644
--- a/validate/data/scenarios/switch_subtitle_track_while_paused.scenario
+++ b/validate/data/scenarios/switch_subtitle_track_while_paused.scenario
@@ -1,4 +1,4 @@
-description, summary="Change subtitle track while pipeline is PAUSED", min-subtitle-track=2, duration=5.0, handles-states=true, need-clock-sync=true
+description, summary="Change subtitle track while pipeline is PAUSED", min-subtitle-track=2, duration=5.0, handles-states=true, need-clock-sync=true, needs_preroll=true
 pause;
 wait, duration=0.5
 switch-track, type=text, index=(string)+1
diff --git a/validate/docs/validate-design.txt b/validate/docs/validate-design.txt
index 04cb31c43a..cb1a701636 100644
--- a/validate/docs/validate-design.txt
+++ b/validate/docs/validate-design.txt
@@ -1,4 +1,3 @@
-
 == Main components
 
 Gst-validate is composed of 4 parts: the issues, the reports, the runner and
@@ -58,4 +57,3 @@ The file checker is another reporter that is used to make sure a file has a
 few expected properties. It inspects the file and compares the results with
 expected values set by the user. Values such as file duration, file size, if
 it can be played back and also if its encoding and container types.
-
diff --git a/validate/gst/validate/media-descriptor-writer.c b/validate/gst/validate/media-descriptor-writer.c
index d184dc636a..198b171484 100644
--- a/validate/gst/validate/media-descriptor-writer.c
+++ b/validate/gst/validate/media-descriptor-writer.c
@@ -137,9 +137,10 @@ serialize_filenode (GstValidateMediaDescriptorWriter * writer)
     caps_str = g_strdup ("");
 
   res = g_string_new (tmpstr);
-  g_string_append_printf (res, "  <streams caps=\"%s\">\n", caps_str);
-  g_free (caps_str);
   g_free (tmpstr);
+  tmpstr = g_markup_printf_escaped ("  <streams caps=\"%s\">\n", caps_str);
+  g_string_append (res, tmpstr);
+  g_free (caps_str);
   for (tmp = filenode->streams; tmp; tmp = tmp->next) {
     GList *tmp3;
     GstValidateMediaStreamNode
@@ -658,6 +659,7 @@ gst_validate_media_descriptor_writer_new_discover (GstValidateRunner * runner,
   media_descriptor = (GstValidateMediaDescriptor *) writer;
   if (streams == NULL && media_descriptor->filenode->caps)
     writer->priv->raw_caps = gst_caps_copy (media_descriptor->filenode->caps);
+
   gst_discoverer_stream_info_list_free (streams);
 
 
diff --git a/validate/launcher/apps/gstvalidate.py b/validate/launcher/apps/gstvalidate.py
index 70c72115b7..13907d7646 100644
--- a/validate/launcher/apps/gstvalidate.py
+++ b/validate/launcher/apps/gstvalidate.py
@@ -18,9 +18,12 @@
 # Boston, MA 02110-1301, USA.
 import argparse
 import os
+import copy
+import sys
 import time
 import urllib.parse
 import shlex
+import socket
 import subprocess
 import configparser
 from launcher.loggable import Loggable
@@ -31,7 +34,8 @@ from launcher.baseclasses import GstValidateTest, Test, \
     GstValidateBaseTestManager, MediaDescriptor, MediaFormatCombination
 
 from launcher.utils import path2url, url2path, DEFAULT_TIMEOUT, which, \
-    GST_SECOND, Result, Protocols, mkdir, printc, Colors, get_data_file
+    GST_SECOND, Result, Protocols, mkdir, printc, Colors, get_data_file, \
+    kill_subprocess
 
 #
 # Private global variables     #
@@ -40,14 +44,17 @@ from launcher.utils import path2url, url2path, DEFAULT_TIMEOUT, which, \
 # definitions of commands to use
 parser = argparse.ArgumentParser(add_help=False)
 parser.add_argument("--validate-tools-path", dest="validate_tools_path",
-                default="",
-                help="defines the paths to look for GstValidate tools.")
+                    default="",
+                    help="defines the paths to look for GstValidate tools.")
 options, args = parser.parse_known_args()
 
 GST_VALIDATE_COMMAND = which("gst-validate-1.0", options.validate_tools_path)
-GST_VALIDATE_TRANSCODING_COMMAND = which("gst-validate-transcoding-1.0", options.validate_tools_path)
-G_V_DISCOVERER_COMMAND = which("gst-validate-media-check-1.0", options.validate_tools_path)
+GST_VALIDATE_TRANSCODING_COMMAND = which(
+    "gst-validate-transcoding-1.0", options.validate_tools_path)
+G_V_DISCOVERER_COMMAND = which(
+    "gst-validate-media-check-1.0", options.validate_tools_path)
 ScenarioManager.GST_VALIDATE_COMMAND = GST_VALIDATE_COMMAND
+RTSP_SERVER_COMMAND = "gst-rtsp-server-example-uri-1.0"
 
 AUDIO_ONLY_FILE_TRANSCODING_RATIO = 5
 
@@ -62,6 +69,7 @@ GST_VALIDATE_CAPS_TO_PROTOCOL = [("application/x-hls", Protocols.HLS),
                                  ("application/dash+xml", Protocols.DASH)]
 GST_VALIDATE_PROTOCOL_TIMEOUTS = {Protocols.HTTP: 120,
                                   Protocols.HLS: 240,
+                                  Protocols.RTSP: 240,
                                   Protocols.DASH: 240}
 
 
@@ -99,6 +107,10 @@ class GstValidateTranscodingTestsGenerator(GstValidateTestsGenerator):
             if mediainfo.media_descriptor.is_image():
                 continue
 
+            protocol = mediainfo.media_descriptor.get_protocol()
+            if protocol == Protocols.RTSP:
+                continue
+
             for comb in self.test_manager.get_encoding_formats():
                 classname = "validate.%s.transcode.to_%s.%s" % (mediainfo.media_descriptor.get_protocol(),
                                                                 str(comb).replace(
@@ -113,6 +125,7 @@ class GstValidateTranscodingTestsGenerator(GstValidateTestsGenerator):
 
 
 class FakeMediaDescriptor(MediaDescriptor):
+
     def __init__(self, infos, pipeline_desc):
         MediaDescriptor.__init__(self)
         self._infos = infos
@@ -210,7 +223,8 @@ class GstValidatePipelineTestsGenerator(GstValidateTestsGenerator):
 
             for scenario in extra_datas.get('scenarios', scenarios):
                 if isinstance(scenario, str):
-                    scenario = self.test_manager.scenarios_manager.get_scenario(scenario)
+                    scenario = self.test_manager.scenarios_manager.get_scenario(
+                        scenario)
 
                 mediainfo = FakeMediaDescriptor(extra_datas, pipeline)
                 if not mediainfo.is_compatible(scenario):
@@ -229,7 +243,8 @@ class GstValidatePipelineTestsGenerator(GstValidateTestsGenerator):
                 pipeline_desc = pipeline % {'videosink': videosink,
                                             'audiosink': audiosink}
 
-                fname = self.get_fname(scenario, protocol=mediainfo.get_protocol(), name=name)
+                fname = self.get_fname(
+                    scenario, protocol=mediainfo.get_protocol(), name=name)
 
                 expected_failures = extra_datas.get("expected-failures")
                 extra_env_vars = extra_datas.get("extra_env_vars")
@@ -254,11 +269,43 @@ class GstValidatePlaybinTestsGenerator(GstValidatePipelineTestsGenerator):
             GstValidatePipelineTestsGenerator.__init__(
                 self, "playback", test_manager, "playbin3")
 
+    def _set_sinks(self, minfo, pipe_str, scenario):
+        if self.test_manager.options.mute:
+            if scenario.needs_clock_sync() or \
+                    minfo.media_descriptor.need_clock_sync():
+                afakesink = "'fakesink sync=true'"
+                vfakesink = "'fakesink sync=true qos=true max-lateness=20000000'"
+            else:
+                vfakesink = afakesink = "'fakesink'"
+
+            pipe_str += " audio-sink=%s video-sink=%s" % (
+                afakesink, vfakesink)
+
+        return pipe_str
+
+    def _get_name(self, scenario, protocol, minfo):
+        return "%s.%s" % (self.get_fname(scenario,
+                                         protocol),
+                          os.path.basename(minfo.media_descriptor.get_clean_name()))
+
     def populate_tests(self, uri_minfo_special_scenarios, scenarios):
+        test_rtsp = which(RTSP_SERVER_COMMAND)
+        if not test_rtsp:
+            printc("\n\nRTSP server not available, you should make sure"
+                   " that %s is available in your $PATH." % RTSP_SERVER_COMMAND,
+                   Colors.FAIL)
+        elif self.test_manager.options.disable_rtsp:
+            printc("\n\nRTSP tests are disabled")
+            test_rtsp = False
+
         for uri, minfo, special_scenarios in uri_minfo_special_scenarios:
             pipe = self._pipeline_template
             protocol = minfo.media_descriptor.get_protocol()
 
+            if protocol == Protocols.RTSP:
+                self.debug("SKIPPING %s as it is a RTSP stream" % uri)
+                continue
+
             pipe += " uri=%s" % uri
 
             for scenario in special_scenarios + scenarios:
@@ -266,20 +313,9 @@ class GstValidatePlaybinTestsGenerator(GstValidatePipelineTestsGenerator):
                 if not minfo.media_descriptor.is_compatible(scenario):
                     continue
 
-                if self.test_manager.options.mute:
-                    if scenario.needs_clock_sync() or \
-                            minfo.media_descriptor.need_clock_sync():
-                        afakesink = "'fakesink sync=true'"
-                        vfakesink = "'fakesink sync=true qos=true max-lateness=20000000'"
-                    else:
-                        vfakesink = afakesink = "'fakesink'"
+                cpipe = self._set_sinks(minfo, cpipe, scenario)
+                fname = self._get_name(scenario, protocol, minfo)
 
-                    cpipe += " audio-sink=%s video-sink=%s" % (
-                        afakesink, vfakesink)
-
-                fname = "%s.%s" % (self.get_fname(scenario,
-                                   protocol),
-                                   os.path.basename(minfo.media_descriptor.get_clean_name()))
                 self.debug("Adding: %s", fname)
 
                 if scenario.does_reverse_playback() and protocol == Protocols.HTTP:
@@ -294,6 +330,21 @@ class GstValidatePlaybinTestsGenerator(GstValidatePipelineTestsGenerator):
                                                     media_descriptor=minfo.media_descriptor)
                               )
 
+                if test_rtsp and protocol == Protocols.FILE and not minfo.media_descriptor.is_image():
+                    rtspminfo = copy.deepcopy(minfo)
+                    rtspminfo.media_descriptor = GstValidateRTSPMediaDesciptor(minfo.media_descriptor.get_path())
+                    if not rtspminfo.media_descriptor.is_compatible(scenario):
+                        continue
+
+                    cpipe = self._set_sinks(rtspminfo, "%s uri=rtsp://127.0.0.1:<RTSPPORTNUMBER>/test"
+                                            % self._pipeline_template, scenario)
+                    fname = self._get_name(scenario, Protocols.RTSP, rtspminfo)
+
+                    self.add_test(GstValidateRTSPTest(
+                        fname, self.test_manager.options, self.test_manager.reporter,
+                        cpipe, uri, scenario=scenario,
+                        media_descriptor=rtspminfo.media_descriptor))
+
 
 class GstValidateMixerTestsGenerator(GstValidatePipelineTestsGenerator):
 
@@ -443,7 +494,8 @@ class GstValidateMediaCheckTest(GstValidateTest):
 
     def build_arguments(self):
         Test.build_arguments(self)
-        self.add_arguments(self._uri, "--expected-results", self._media_info_path)
+        self.add_arguments(self._uri, "--expected-results",
+                           self._media_info_path)
 
 
 class GstValidateTranscodingTest(GstValidateTest, GstValidateEncodingTestInterface):
@@ -541,6 +593,112 @@ class GstValidateTranscodingTest(GstValidateTest, GstValidateEncodingTestInterfa
         self.set_result(res, msg)
 
 
+class GstValidateBaseRTSPTest:
+    """ Interface for RTSP tests, requires implementing Test"""
+    __used_ports = set()
+
+    def __init__(self, local_uri):
+        self._local_uri = local_uri
+        self.rtsp_server = None
+        self._unsetport_pipeline_desc = None
+        self.optional = True
+
+    @classmethod
+    def __get_open_port(cls):
+        while True:
+            # hackish trick from
+            # http://stackoverflow.com/questions/2838244/get-open-tcp-port-in-python?answertab=votes#tab-top
+            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            s.bind(("", 0))
+            port = s.getsockname()[1]
+            if port not in cls.__used_ports:
+                cls.__used_ports.add(port)
+                s.close()
+                return port
+
+            s.close()
+
+    def launch_server(self):
+        if self.options.redirect_logs == 'stdout':
+            self.rtspserver_logs = sys.stdout
+        elif self.options.redirect_logs == 'stderr':
+            self.rtspserver_logs = sys.stderr
+
+        self.server_port = self.__get_open_port()
+        command = [RTSP_SERVER_COMMAND, self._local_uri, '--port', str(self.server_port)]
+
+        if self.options.validate_gdb_server:
+            command = self.use_gdb(command)
+            self.rtspserver_logs = sys.stdout
+        elif self.options.redirect_logs:
+            self.rtspserver_logs = sys.stdout
+        else:
+            self.rtspserver_logs = open(self.logfile + '_rtspserver.log', 'w+')
+            self.extra_logfiles.append(self.rtspserver_logs.name)
+
+        server_env = os.environ.copy()
+        server_env['GST_TRACERS'] = 'validate'
+
+        self.rtsp_server = subprocess.Popen(command,
+                                            stderr=self.rtspserver_logs,
+                                            stdout=self.rtspserver_logs,
+                                            env=server_env)
+        while True:
+            s = socket.socket()
+            try:
+                s.connect((("127.0.0.1", self.server_port)))
+                break
+            except ConnectionRefusedError:
+                time.sleep(0.5)
+                continue
+            finally:
+                s.close()
+
+        if not self._unsetport_pipeline_desc:
+            self._unsetport_pipeline_desc = self.pipeline_desc
+
+        self.pipeline_desc = self._unsetport_pipeline_desc.replace(
+            "<RTSPPORTNUMBER>", str(self.server_port))
+
+        return 'GST_TRACERS=validate ' + ' '.join(command)
+
+    def close_logfile(self):
+        super().close_logfile()
+        if not self.options.redirect_logs:
+            self.rtspserver_logs.close()
+
+    def process_update(self):
+        res = super().process_update()
+        if res:
+            kill_subprocess(self, self.rtsp_server, DEFAULT_TIMEOUT)
+            self.__used_ports.remove(self.server_port)
+
+        return res
+
+
+class GstValidateRTSPTest(GstValidateBaseRTSPTest, GstValidateLaunchTest):
+
+    def __init__(self, classname, options, reporter, pipeline_desc,
+                 local_uri, timeout=DEFAULT_TIMEOUT, scenario=None,
+                 media_descriptor=None):
+        GstValidateLaunchTest.__init__(self, classname, options, reporter,
+                                       pipeline_desc, timeout, scenario,
+                                       media_descriptor)
+        GstValidateBaseRTSPTest.__init__(self, local_uri)
+
+
+class GstValidateRTSPMediaDesciptor(GstValidateMediaDescriptor):
+
+    def __init__(self, xml_path):
+        GstValidateMediaDescriptor.__init__(self, xml_path)
+
+    def get_uri(self):
+        return "rtsp://127.0.0.1:8554/test"
+
+    def get_protocol(self):
+        return Protocols.RTSP
+
+
 class GstValidateTestManager(GstValidateBaseTestManager):
 
     name = "validate"
@@ -582,6 +740,10 @@ not been tested and explicitely activated if you set use --wanted-tests ALL""")
                            action="append", help="defines the uris to run default tests on")
         group.add_argument("--validate-tools-path", dest="validate_tools_path",
                            action="append", help="defines the paths to look for GstValidate tools.")
+        group.add_argument("--validate-gdb-server", dest="validate_gdb_server",
+                           help="Run the server in GDB.")
+        group.add_argument("--validate-disable-rtsp", dest="disable_rtsp",
+                           help="Disable RTSP tests.")
 
     def print_valgrind_bugs(self):
         # Look for all the 'pending' bugs in our supp file
@@ -629,7 +791,8 @@ not been tested and explicitely activated if you set use --wanted-tests ALL""")
                 self.add_test(test)
 
         if not self.tests and not uris:
-            printc("No valid uris present in the path. Check if media files and info files exist", Colors.FAIL)
+            printc(
+                "No valid uris present in the path. Check if media files and info files exist", Colors.FAIL)
 
         return self.tests
 
@@ -681,10 +844,12 @@ not been tested and explicitely activated if you set use --wanted-tests ALL""")
                 self._add_media(fpath)
                 return True
             elif not self.options.generate_info and not self.options.update_media_info and not self.options.validate_uris:
-                self.info("%s not present. Use --generate-media-info", media_info)
+                self.info(
+                    "%s not present. Use --generate-media-info", media_info)
                 return True
             elif self.options.update_media_info and not os.path.isfile(media_info):
-                self.info("%s not present. Use --generate-media-info", media_info)
+                self.info(
+                    "%s not present. Use --generate-media-info", media_info)
                 return True
 
             include_frames = 0
@@ -750,7 +915,7 @@ not been tested and explicitely activated if you set use --wanted-tests ALL""")
 
                 if protocol in [Protocols.HTTP, Protocols.HLS, Protocols.DASH] and \
                         ("127.0.0.1:%s" % (self.options.http_server_port) in uri or
-                        "127.0.0.1:8079" in uri):
+                         "127.0.0.1:8079" in uri):
                     return True
         return False
 
@@ -862,9 +1027,9 @@ not been tested and explicitely activated if you set use --wanted-tests ALL""")
              "https://bugzilla.gnome.org/show_bug.cgi?id=702595"),
 
             # Fragmented MP4 disabled tests:
-            ('validate.file.playback..*seek.*.fragmented_nonseekable_sink_mp4',
+            ('validate.*.playback..*seek.*.fragmented_nonseekable_sink_mp4',
              "Seeking on fragmented files without indexes isn't implemented"),
-            ('validate.file.playback.reverse_playback.fragmented_nonseekable_sink_mp4',
+            ('validate.*.playback.reverse_playback.fragmented_nonseekable_sink_mp4',
              "Seeking on fragmented files without indexes isn't implemented"),
 
             # HTTP known issues:
@@ -889,7 +1054,12 @@ not been tested and explicitely activated if you set use --wanted-tests ALL""")
 
             # ogg known issues
             ("validate.http.playback.seek.*vorbis_theora_1_ogg",
-             "https://bugzilla.gnome.org/show_bug.cgi?id=769545")
+             "https://bugzilla.gnome.org/show_bug.cgi?id=769545"),
+            # RTSP known issues
+            ('validate.rtsp.playback.reverse.*',
+             'https://bugzilla.gnome.org/show_bug.cgi?id=626811'),
+            ('validate.rtsp.playback.fast_*',
+             'https://bugzilla.gnome.org/show_bug.cgi?id=754575'),
         ])
 
     def register_default_test_generators(self):
diff --git a/validate/launcher/baseclasses.py b/validate/launcher/baseclasses.py
index 353591a2b7..6e8e9f33e7 100644
--- a/validate/launcher/baseclasses.py
+++ b/validate/launcher/baseclasses.py
@@ -82,6 +82,7 @@ class Test(Loggable):
         self.options = options
         self.application = application_name
         self.command = []
+        self.server_command = None
         self.reporter = reporter
         self.process = None
         self.proc_env = None
@@ -98,6 +99,7 @@ class Test(Loggable):
 
         extra_env_variables = extra_env_variables or {}
         self.extra_env_variables = extra_env_variables
+        self.optional = False
 
         self.clean()
 
@@ -333,26 +335,7 @@ class Test(Loggable):
         return os.environ.copy()
 
     def kill_subprocess(self):
-        if self.process is None:
-            return
-
-        stime = time.time()
-        res = self.process.poll()
-        while res is None:
-            try:
-                self.debug("Subprocess is still alive, sending KILL signal")
-                if utils.is_windows():
-                    subprocess.call(['taskkill', '/F', '/T', '/PID', str(self.process.pid)])
-                else:
-                    self.process.send_signal(signal.SIGKILL)
-                time.sleep(1)
-            except OSError:
-                pass
-            if time.time() - stime > DEFAULT_TIMEOUT:
-                raise RuntimeError("Could not kill subprocess after %s second"
-                                   " Something is really wrong, => EXITING"
-                                   % DEFAULT_TIMEOUT)
-            res = self.process.poll()
+        utils.kill_subprocess(self, self.process, DEFAULT_TIMEOUT)
 
     def thread_wrapper(self):
         self.process = subprocess.Popen(self.command,
@@ -373,14 +356,15 @@ class Test(Loggable):
     def get_valgrind_suppressions(self):
         return [self.get_valgrind_suppression_file('data', 'gstvalidate.supp')]
 
-    def use_gdb(self):
+    def use_gdb(self, command):
         if self.hard_timeout is not None:
             self.hard_timeout *= GDB_TIMEOUT_FACTOR
         self.timeout *= GDB_TIMEOUT_FACTOR
-        self.command = ["gdb", "-ex", "run", "-ex", "quit",
-                        "--args"] + self.command
+        return ["gdb", "-ex", "run", "-ex", "backtrace", "-ex", "quit", "--args"] + command
 
-    def use_valgrind(self):
+    def use_valgrind(self, command, subenv):
+        vglogsfile = self.logfile + '.valgrind'
+        self.extra_logfiles.append(vglogsfile)
 
         vg_args = []
 
@@ -404,14 +388,11 @@ class Test(Loggable):
         for supp in self.get_valgrind_suppressions():
             vg_args.append("--suppressions=%s" % supp)
 
-        self.command = ["valgrind"] + vg_args + self.command
+        command = ["valgrind"] + vg_args + command
 
         # Tune GLib's memory allocator to be more valgrind friendly
-        self.proc_env['G_DEBUG'] = 'gc-friendly'
-        self.add_env_variable('G_DEBUG', 'gc-friendly')
-
-        self.proc_env['G_SLICE'] = 'always-malloc'
-        self.add_env_variable('G_SLICE', 'always-malloc')
+        subenv['G_DEBUG'] = 'gc-friendly'
+        subenv['G_SLICE'] = 'always-malloc'
 
         if self.hard_timeout is not None:
             self.hard_timeout *= VALGRIND_TIMEOUT_FACTOR
@@ -421,15 +402,24 @@ class Test(Loggable):
         vg_config = get_data_file('data', 'valgrind.config')
 
         if self.proc_env.get('GST_VALIDATE_CONFIG'):
-            self.proc_env['GST_VALIDATE_CONFIG'] = '%s%s%s' % (self.proc_env['GST_VALIDATE_CONFIG'], os.pathsep, vg_config)
+            subenv['GST_VALIDATE_CONFIG'] = '%s%s%s' % (self.proc_env['GST_VALIDATE_CONFIG'], os.pathsep, vg_config)
         else:
-            self.proc_env['GST_VALIDATE_CONFIG'] = vg_config
+            subenv['GST_VALIDATE_CONFIG'] = vg_config
 
-        self.add_env_variable('GST_VALIDATE_CONFIG', self.proc_env['GST_VALIDATE_CONFIG'])
+        if subenv == self.proc_env:
+            self.add_env_variable('G_DEBUG', 'gc-friendly')
+            self.add_env_variable('G_SLICE', 'always-malloc')
+            self.add_env_variable('GST_VALIDATE_CONFIG', self.proc_env['GST_VALIDATE_CONFIG'])
+
+        return command
+
+    def launch_server(self):
+        return None
 
     def test_start(self, queue):
         self.open_logfile()
 
+        server_command = self.launch_server()
         self.queue = queue
         self.command = [self.application]
         self._starting_time = time.time()
@@ -442,14 +432,17 @@ class Test(Loggable):
             self.add_env_variable(var, self.proc_env[var])
 
         if self.options.gdb:
-            self.use_gdb()
-
+            self.command = self.use_gdb(self.command)
         if self.options.valgrind:
-            self.use_valgrind()
+            self.command = self.use_valgrind(self.command, self.proc_env)
 
         message = "Launching: %s%s\n" \
-                  "    Command: '%s %s'\n" % (Colors.ENDC, self.classname,
-                                              self._env_variable, ' '.join(self.command))
+                  "    Command: '%s & %s %s'\n" % (
+                      Colors.ENDC, self.classname, server_command,
+                      self._env_variable, ' '.join(self.command))
+        if server_command:
+            message += "    Server command: %s\n" % server_command
+
         if not self.options.redirect_logs:
             message += "    Logs:\n" \
                        "         - %s" % (self.logfile)
@@ -1541,16 +1534,18 @@ class _TestsLauncher(Loggable):
                     testlist_file = open(os.path.splitext(testsuite.__file__)[0] + ".testslist",
                                          'w')
                 except IOError:
-                    return
+                    continue
 
                 for test in know_tests:
-                    if test and test not in tests_names:
-                        testlist_changed = True
-                        printc("Test %s Not in testsuite %s anymore"
-                               % (test, testsuite.__file__), Colors.FAIL)
+                    if test and test.strip('~') not in tests_names:
+                        if not test.startswith('~'):
+                            testlist_changed = True
+                            printc("Test %s Not in testsuite %s anymore"
+                                % (test, testsuite.__file__), Colors.FAIL)
 
-                for test in tests_names:
-                    testlist_file.write("%s\n" % test)
+                for test in tests:
+                    testlist_file.write("%s%s\n" % ('~' if test.optional else '',
+                                                    test.classname))
                     if test and test not in know_tests:
                         printc("Test %s is NEW in testsuite %s"
                                % (test, testsuite.__file__), Colors.OKGREEN)
@@ -1719,6 +1714,7 @@ class Scenario(object):
     def __repr__(self):
         return "<Scenario %s>" % self.name
 
+
 class ScenarioManager(Loggable):
     _instance = None
     all_scenarios = []
@@ -1844,7 +1840,7 @@ class GstValidateBaseTestManager(TestsManager):
         """
         self._scenarios = []
         self.add_scenarios(scenarios)
-        
+
     def get_scenarios(self):
         return self._scenarios
 
diff --git a/validate/launcher/utils.py b/validate/launcher/utils.py
index 606c88293d..ea7c4b4365 100644
--- a/validate/launcher/utils.py
+++ b/validate/launcher/utils.py
@@ -27,6 +27,7 @@ import os
 import platform
 import re
 import shutil
+import signal
 import subprocess
 import sys
 import tempfile
@@ -43,7 +44,8 @@ from xml.etree import ElementTree
 GST_SECOND = int(1000000000)
 DEFAULT_TIMEOUT = 30
 DEFAULT_MAIN_DIR = os.path.join(os.path.expanduser("~"), "gst-validate")
-DEFAULT_GST_QA_ASSETS = os.path.join(DEFAULT_MAIN_DIR, "gst-integration-testsuites")
+DEFAULT_GST_QA_ASSETS = os.path.join(
+    DEFAULT_MAIN_DIR, "gst-integration-testsuites")
 DISCOVERER_COMMAND = "gst-discoverer-1.0"
 # Use to set the duration from which a test is considered as being 'long'
 LONG_TEST = 40
@@ -63,6 +65,7 @@ class Protocols(object):
     FILE = "file"
     HLS = "hls"
     DASH = "dash"
+    RTSP = "rtsp"
 
     @staticmethod
     def needs_clock_sync(protocol):
@@ -208,7 +211,8 @@ def TIME_ARGS(time):
 
 
 def look_for_file_in_source_dir(subdir, name):
-    root_dir = os.path.abspath(os.path.dirname(os.path.join(os.path.dirname(os.path.abspath(__file__)))))
+    root_dir = os.path.abspath(os.path.dirname(
+        os.path.join(os.path.dirname(os.path.abspath(__file__)))))
     p = os.path.join(root_dir, subdir, name)
     if os.path.exists(p):
         return p
@@ -253,7 +257,8 @@ def get_duration(media_file):
     duration = 0
     res = ''
     try:
-        res = subprocess.check_output([DISCOVERER_COMMAND, media_file]).decode()
+        res = subprocess.check_output(
+            [DISCOVERER_COMMAND, media_file]).decode()
     except subprocess.CalledProcessError:
         # gst-media-check returns !0 if seeking is not possible, we do not care
         # in that case.
@@ -308,7 +313,7 @@ class BackTraceGenerator(Loggable):
                 "installed."
 
         gdb = ['gdb', '-ex', 't a a bt', '-batch',
-            '-p', str(test.process.pid)]
+               '-p', str(test.process.pid)]
 
         try:
             return subprocess.check_output(
@@ -320,7 +325,8 @@ class BackTraceGenerator(Loggable):
     def get_trace_from_systemd(self, test):
         for ntry in range(10):
             if ntry != 0:
-                # Loopping, it means we conceder the logs might not be ready yet.
+                # Loopping, it means we conceder the logs might not be ready
+                # yet.
                 time.sleep(1)
 
             try:
@@ -334,7 +340,8 @@ class BackTraceGenerator(Loggable):
 
             info = info.decode()
             try:
-                executable = BackTraceGenerator._executable_regex.findall(info)[0]
+                executable = BackTraceGenerator._executable_regex.findall(info)[
+                    0]
             except IndexError:
                 self.debug("Backtrace could not be found yet, trying harder.")
                 # The trace might not be ready yet
@@ -357,11 +364,11 @@ class BackTraceGenerator(Loggable):
                 try:
                     tf = tempfile.NamedTemporaryFile()
                     subprocess.check_output(['coredumpctl', 'dump',
-                                            str(test.process.pid), '--output=' +
-                                            tf.name], stderr=subprocess.STDOUT)
+                                             str(test.process.pid), '--output=' +
+                                             tf.name], stderr=subprocess.STDOUT)
 
                     gdb = ['gdb', '-ex', 't a a bt', '-ex', 'quit',
-                        test.application, tf.name]
+                           test.application, tf.name]
                     bt_all = subprocess.check_output(
                         gdb, stderr=subprocess.STDOUT).decode()
 
@@ -387,13 +394,14 @@ def check_bugs_resolution(bugs_definitions):
 
             if "bugzilla" not in url.netloc:
                 printc("  + %s \n   --> bug: %s\n   --> Status: Not a bugzilla report\n" % (regex, bug),
-                    Colors.WARNING)
+                       Colors.WARNING)
                 continue
 
             query = urllib.parse.parse_qs(url.query)
             _id = query.get('id')
             if not _id:
-                printc("  + '%s' -- Can't check bug '%s'\n" % (regex, bug), Colors.WARNING)
+                printc("  + '%s' -- Can't check bug '%s'\n" %
+                       (regex, bug), Colors.WARNING)
                 continue
 
             if isinstance(_id, list):
@@ -447,3 +455,29 @@ def check_bugs_resolution(bugs_definitions):
                    regex, bugid, desc, status), Colors.OKGREEN)
 
     return res
+
+
+def kill_subprocess(owner, process, timeout):
+    if process is None:
+        return
+
+    stime = time.time()
+    res = process.poll()
+    while res is None:
+        try:
+            owner.debug("Subprocess is still alive, sending KILL signal")
+            if is_windows():
+                subprocess.call(
+                    ['taskkill', '/F', '/T', '/PID', str(process.pid)])
+            else:
+                process.send_signal(signal.SIGKILL)
+            time.sleep(1)
+        except OSError:
+            pass
+        if time.time() - stime > DEFAULT_TIMEOUT:
+            raise RuntimeError("Could not kill subprocess after %s second"
+                               " Something is really wrong, => EXITING"
+                               % DEFAULT_TIMEOUT)
+        res = process.poll()
+
+    return res