mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-04-26 06:54:49 +00:00
validate: Implement RTSP support
This commit is contained in:
parent
a18cef9c3f
commit
3c62c315a9
7 changed files with 291 additions and 91 deletions
|
@ -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;
|
pause, playback-time=1.0;
|
||||||
|
|
||||||
# Wait so that humans can see the pipeline is paused
|
# Wait so that humans can see the pipeline is paused
|
||||||
|
|
|
@ -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;
|
pause;
|
||||||
wait, duration=0.5
|
wait, duration=0.5
|
||||||
switch-track, type=text, index=(string)+1
|
switch-track, type=text, index=(string)+1
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
== Main components
|
== Main components
|
||||||
|
|
||||||
Gst-validate is composed of 4 parts: the issues, the reports, the runner and
|
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
|
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
|
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.
|
it can be played back and also if its encoding and container types.
|
||||||
|
|
||||||
|
|
|
@ -137,9 +137,10 @@ serialize_filenode (GstValidateMediaDescriptorWriter * writer)
|
||||||
caps_str = g_strdup ("");
|
caps_str = g_strdup ("");
|
||||||
|
|
||||||
res = g_string_new (tmpstr);
|
res = g_string_new (tmpstr);
|
||||||
g_string_append_printf (res, " <streams caps=\"%s\">\n", caps_str);
|
|
||||||
g_free (caps_str);
|
|
||||||
g_free (tmpstr);
|
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) {
|
for (tmp = filenode->streams; tmp; tmp = tmp->next) {
|
||||||
GList *tmp3;
|
GList *tmp3;
|
||||||
GstValidateMediaStreamNode
|
GstValidateMediaStreamNode
|
||||||
|
@ -658,6 +659,7 @@ gst_validate_media_descriptor_writer_new_discover (GstValidateRunner * runner,
|
||||||
media_descriptor = (GstValidateMediaDescriptor *) writer;
|
media_descriptor = (GstValidateMediaDescriptor *) writer;
|
||||||
if (streams == NULL && media_descriptor->filenode->caps)
|
if (streams == NULL && media_descriptor->filenode->caps)
|
||||||
writer->priv->raw_caps = gst_caps_copy (media_descriptor->filenode->caps);
|
writer->priv->raw_caps = gst_caps_copy (media_descriptor->filenode->caps);
|
||||||
|
|
||||||
gst_discoverer_stream_info_list_free (streams);
|
gst_discoverer_stream_info_list_free (streams);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,12 @@
|
||||||
# Boston, MA 02110-1301, USA.
|
# Boston, MA 02110-1301, USA.
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
import copy
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import shlex
|
import shlex
|
||||||
|
import socket
|
||||||
import subprocess
|
import subprocess
|
||||||
import configparser
|
import configparser
|
||||||
from launcher.loggable import Loggable
|
from launcher.loggable import Loggable
|
||||||
|
@ -31,7 +34,8 @@ from launcher.baseclasses import GstValidateTest, Test, \
|
||||||
GstValidateBaseTestManager, MediaDescriptor, MediaFormatCombination
|
GstValidateBaseTestManager, MediaDescriptor, MediaFormatCombination
|
||||||
|
|
||||||
from launcher.utils import path2url, url2path, DEFAULT_TIMEOUT, which, \
|
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 #
|
# Private global variables #
|
||||||
|
@ -40,14 +44,17 @@ from launcher.utils import path2url, url2path, DEFAULT_TIMEOUT, which, \
|
||||||
# definitions of commands to use
|
# definitions of commands to use
|
||||||
parser = argparse.ArgumentParser(add_help=False)
|
parser = argparse.ArgumentParser(add_help=False)
|
||||||
parser.add_argument("--validate-tools-path", dest="validate_tools_path",
|
parser.add_argument("--validate-tools-path", dest="validate_tools_path",
|
||||||
default="",
|
default="",
|
||||||
help="defines the paths to look for GstValidate tools.")
|
help="defines the paths to look for GstValidate tools.")
|
||||||
options, args = parser.parse_known_args()
|
options, args = parser.parse_known_args()
|
||||||
|
|
||||||
GST_VALIDATE_COMMAND = which("gst-validate-1.0", options.validate_tools_path)
|
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)
|
GST_VALIDATE_TRANSCODING_COMMAND = which(
|
||||||
G_V_DISCOVERER_COMMAND = which("gst-validate-media-check-1.0", options.validate_tools_path)
|
"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
|
ScenarioManager.GST_VALIDATE_COMMAND = GST_VALIDATE_COMMAND
|
||||||
|
RTSP_SERVER_COMMAND = "gst-rtsp-server-example-uri-1.0"
|
||||||
|
|
||||||
AUDIO_ONLY_FILE_TRANSCODING_RATIO = 5
|
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)]
|
("application/dash+xml", Protocols.DASH)]
|
||||||
GST_VALIDATE_PROTOCOL_TIMEOUTS = {Protocols.HTTP: 120,
|
GST_VALIDATE_PROTOCOL_TIMEOUTS = {Protocols.HTTP: 120,
|
||||||
Protocols.HLS: 240,
|
Protocols.HLS: 240,
|
||||||
|
Protocols.RTSP: 240,
|
||||||
Protocols.DASH: 240}
|
Protocols.DASH: 240}
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,6 +107,10 @@ class GstValidateTranscodingTestsGenerator(GstValidateTestsGenerator):
|
||||||
if mediainfo.media_descriptor.is_image():
|
if mediainfo.media_descriptor.is_image():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
protocol = mediainfo.media_descriptor.get_protocol()
|
||||||
|
if protocol == Protocols.RTSP:
|
||||||
|
continue
|
||||||
|
|
||||||
for comb in self.test_manager.get_encoding_formats():
|
for comb in self.test_manager.get_encoding_formats():
|
||||||
classname = "validate.%s.transcode.to_%s.%s" % (mediainfo.media_descriptor.get_protocol(),
|
classname = "validate.%s.transcode.to_%s.%s" % (mediainfo.media_descriptor.get_protocol(),
|
||||||
str(comb).replace(
|
str(comb).replace(
|
||||||
|
@ -113,6 +125,7 @@ class GstValidateTranscodingTestsGenerator(GstValidateTestsGenerator):
|
||||||
|
|
||||||
|
|
||||||
class FakeMediaDescriptor(MediaDescriptor):
|
class FakeMediaDescriptor(MediaDescriptor):
|
||||||
|
|
||||||
def __init__(self, infos, pipeline_desc):
|
def __init__(self, infos, pipeline_desc):
|
||||||
MediaDescriptor.__init__(self)
|
MediaDescriptor.__init__(self)
|
||||||
self._infos = infos
|
self._infos = infos
|
||||||
|
@ -210,7 +223,8 @@ class GstValidatePipelineTestsGenerator(GstValidateTestsGenerator):
|
||||||
|
|
||||||
for scenario in extra_datas.get('scenarios', scenarios):
|
for scenario in extra_datas.get('scenarios', scenarios):
|
||||||
if isinstance(scenario, str):
|
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)
|
mediainfo = FakeMediaDescriptor(extra_datas, pipeline)
|
||||||
if not mediainfo.is_compatible(scenario):
|
if not mediainfo.is_compatible(scenario):
|
||||||
|
@ -229,7 +243,8 @@ class GstValidatePipelineTestsGenerator(GstValidateTestsGenerator):
|
||||||
pipeline_desc = pipeline % {'videosink': videosink,
|
pipeline_desc = pipeline % {'videosink': videosink,
|
||||||
'audiosink': audiosink}
|
'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")
|
expected_failures = extra_datas.get("expected-failures")
|
||||||
extra_env_vars = extra_datas.get("extra_env_vars")
|
extra_env_vars = extra_datas.get("extra_env_vars")
|
||||||
|
@ -254,11 +269,43 @@ class GstValidatePlaybinTestsGenerator(GstValidatePipelineTestsGenerator):
|
||||||
GstValidatePipelineTestsGenerator.__init__(
|
GstValidatePipelineTestsGenerator.__init__(
|
||||||
self, "playback", test_manager, "playbin3")
|
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):
|
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:
|
for uri, minfo, special_scenarios in uri_minfo_special_scenarios:
|
||||||
pipe = self._pipeline_template
|
pipe = self._pipeline_template
|
||||||
protocol = minfo.media_descriptor.get_protocol()
|
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
|
pipe += " uri=%s" % uri
|
||||||
|
|
||||||
for scenario in special_scenarios + scenarios:
|
for scenario in special_scenarios + scenarios:
|
||||||
|
@ -266,20 +313,9 @@ class GstValidatePlaybinTestsGenerator(GstValidatePipelineTestsGenerator):
|
||||||
if not minfo.media_descriptor.is_compatible(scenario):
|
if not minfo.media_descriptor.is_compatible(scenario):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if self.test_manager.options.mute:
|
cpipe = self._set_sinks(minfo, cpipe, scenario)
|
||||||
if scenario.needs_clock_sync() or \
|
fname = self._get_name(scenario, protocol, minfo)
|
||||||
minfo.media_descriptor.need_clock_sync():
|
|
||||||
afakesink = "'fakesink sync=true'"
|
|
||||||
vfakesink = "'fakesink sync=true qos=true max-lateness=20000000'"
|
|
||||||
else:
|
|
||||||
vfakesink = afakesink = "'fakesink'"
|
|
||||||
|
|
||||||
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)
|
self.debug("Adding: %s", fname)
|
||||||
|
|
||||||
if scenario.does_reverse_playback() and protocol == Protocols.HTTP:
|
if scenario.does_reverse_playback() and protocol == Protocols.HTTP:
|
||||||
|
@ -294,6 +330,21 @@ class GstValidatePlaybinTestsGenerator(GstValidatePipelineTestsGenerator):
|
||||||
media_descriptor=minfo.media_descriptor)
|
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):
|
class GstValidateMixerTestsGenerator(GstValidatePipelineTestsGenerator):
|
||||||
|
|
||||||
|
@ -443,7 +494,8 @@ class GstValidateMediaCheckTest(GstValidateTest):
|
||||||
|
|
||||||
def build_arguments(self):
|
def build_arguments(self):
|
||||||
Test.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):
|
class GstValidateTranscodingTest(GstValidateTest, GstValidateEncodingTestInterface):
|
||||||
|
@ -541,6 +593,112 @@ class GstValidateTranscodingTest(GstValidateTest, GstValidateEncodingTestInterfa
|
||||||
self.set_result(res, msg)
|
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):
|
class GstValidateTestManager(GstValidateBaseTestManager):
|
||||||
|
|
||||||
name = "validate"
|
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")
|
action="append", help="defines the uris to run default tests on")
|
||||||
group.add_argument("--validate-tools-path", dest="validate_tools_path",
|
group.add_argument("--validate-tools-path", dest="validate_tools_path",
|
||||||
action="append", help="defines the paths to look for GstValidate tools.")
|
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):
|
def print_valgrind_bugs(self):
|
||||||
# Look for all the 'pending' bugs in our supp file
|
# 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)
|
self.add_test(test)
|
||||||
|
|
||||||
if not self.tests and not uris:
|
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
|
return self.tests
|
||||||
|
|
||||||
|
@ -681,10 +844,12 @@ not been tested and explicitely activated if you set use --wanted-tests ALL""")
|
||||||
self._add_media(fpath)
|
self._add_media(fpath)
|
||||||
return True
|
return True
|
||||||
elif not self.options.generate_info and not self.options.update_media_info and not self.options.validate_uris:
|
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
|
return True
|
||||||
elif self.options.update_media_info and not os.path.isfile(media_info):
|
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
|
return True
|
||||||
|
|
||||||
include_frames = 0
|
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 \
|
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:%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 True
|
||||||
return False
|
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"),
|
"https://bugzilla.gnome.org/show_bug.cgi?id=702595"),
|
||||||
|
|
||||||
# Fragmented MP4 disabled tests:
|
# 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"),
|
"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"),
|
"Seeking on fragmented files without indexes isn't implemented"),
|
||||||
|
|
||||||
# HTTP known issues:
|
# HTTP known issues:
|
||||||
|
@ -889,7 +1054,12 @@ not been tested and explicitely activated if you set use --wanted-tests ALL""")
|
||||||
|
|
||||||
# ogg known issues
|
# ogg known issues
|
||||||
("validate.http.playback.seek.*vorbis_theora_1_ogg",
|
("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):
|
def register_default_test_generators(self):
|
||||||
|
|
|
@ -82,6 +82,7 @@ class Test(Loggable):
|
||||||
self.options = options
|
self.options = options
|
||||||
self.application = application_name
|
self.application = application_name
|
||||||
self.command = []
|
self.command = []
|
||||||
|
self.server_command = None
|
||||||
self.reporter = reporter
|
self.reporter = reporter
|
||||||
self.process = None
|
self.process = None
|
||||||
self.proc_env = None
|
self.proc_env = None
|
||||||
|
@ -98,6 +99,7 @@ class Test(Loggable):
|
||||||
|
|
||||||
extra_env_variables = extra_env_variables or {}
|
extra_env_variables = extra_env_variables or {}
|
||||||
self.extra_env_variables = extra_env_variables
|
self.extra_env_variables = extra_env_variables
|
||||||
|
self.optional = False
|
||||||
|
|
||||||
self.clean()
|
self.clean()
|
||||||
|
|
||||||
|
@ -333,26 +335,7 @@ class Test(Loggable):
|
||||||
return os.environ.copy()
|
return os.environ.copy()
|
||||||
|
|
||||||
def kill_subprocess(self):
|
def kill_subprocess(self):
|
||||||
if self.process is None:
|
utils.kill_subprocess(self, self.process, DEFAULT_TIMEOUT)
|
||||||
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()
|
|
||||||
|
|
||||||
def thread_wrapper(self):
|
def thread_wrapper(self):
|
||||||
self.process = subprocess.Popen(self.command,
|
self.process = subprocess.Popen(self.command,
|
||||||
|
@ -373,14 +356,15 @@ class Test(Loggable):
|
||||||
def get_valgrind_suppressions(self):
|
def get_valgrind_suppressions(self):
|
||||||
return [self.get_valgrind_suppression_file('data', 'gstvalidate.supp')]
|
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:
|
if self.hard_timeout is not None:
|
||||||
self.hard_timeout *= GDB_TIMEOUT_FACTOR
|
self.hard_timeout *= GDB_TIMEOUT_FACTOR
|
||||||
self.timeout *= GDB_TIMEOUT_FACTOR
|
self.timeout *= GDB_TIMEOUT_FACTOR
|
||||||
self.command = ["gdb", "-ex", "run", "-ex", "quit",
|
return ["gdb", "-ex", "run", "-ex", "backtrace", "-ex", "quit", "--args"] + command
|
||||||
"--args"] + self.command
|
|
||||||
|
|
||||||
def use_valgrind(self):
|
def use_valgrind(self, command, subenv):
|
||||||
|
vglogsfile = self.logfile + '.valgrind'
|
||||||
|
self.extra_logfiles.append(vglogsfile)
|
||||||
|
|
||||||
vg_args = []
|
vg_args = []
|
||||||
|
|
||||||
|
@ -404,14 +388,11 @@ class Test(Loggable):
|
||||||
for supp in self.get_valgrind_suppressions():
|
for supp in self.get_valgrind_suppressions():
|
||||||
vg_args.append("--suppressions=%s" % supp)
|
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
|
# Tune GLib's memory allocator to be more valgrind friendly
|
||||||
self.proc_env['G_DEBUG'] = 'gc-friendly'
|
subenv['G_DEBUG'] = 'gc-friendly'
|
||||||
self.add_env_variable('G_DEBUG', 'gc-friendly')
|
subenv['G_SLICE'] = 'always-malloc'
|
||||||
|
|
||||||
self.proc_env['G_SLICE'] = 'always-malloc'
|
|
||||||
self.add_env_variable('G_SLICE', 'always-malloc')
|
|
||||||
|
|
||||||
if self.hard_timeout is not None:
|
if self.hard_timeout is not None:
|
||||||
self.hard_timeout *= VALGRIND_TIMEOUT_FACTOR
|
self.hard_timeout *= VALGRIND_TIMEOUT_FACTOR
|
||||||
|
@ -421,15 +402,24 @@ class Test(Loggable):
|
||||||
vg_config = get_data_file('data', 'valgrind.config')
|
vg_config = get_data_file('data', 'valgrind.config')
|
||||||
|
|
||||||
if self.proc_env.get('GST_VALIDATE_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:
|
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):
|
def test_start(self, queue):
|
||||||
self.open_logfile()
|
self.open_logfile()
|
||||||
|
|
||||||
|
server_command = self.launch_server()
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.command = [self.application]
|
self.command = [self.application]
|
||||||
self._starting_time = time.time()
|
self._starting_time = time.time()
|
||||||
|
@ -442,14 +432,17 @@ class Test(Loggable):
|
||||||
self.add_env_variable(var, self.proc_env[var])
|
self.add_env_variable(var, self.proc_env[var])
|
||||||
|
|
||||||
if self.options.gdb:
|
if self.options.gdb:
|
||||||
self.use_gdb()
|
self.command = self.use_gdb(self.command)
|
||||||
|
|
||||||
if self.options.valgrind:
|
if self.options.valgrind:
|
||||||
self.use_valgrind()
|
self.command = self.use_valgrind(self.command, self.proc_env)
|
||||||
|
|
||||||
message = "Launching: %s%s\n" \
|
message = "Launching: %s%s\n" \
|
||||||
" Command: '%s %s'\n" % (Colors.ENDC, self.classname,
|
" Command: '%s & %s %s'\n" % (
|
||||||
self._env_variable, ' '.join(self.command))
|
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:
|
if not self.options.redirect_logs:
|
||||||
message += " Logs:\n" \
|
message += " Logs:\n" \
|
||||||
" - %s" % (self.logfile)
|
" - %s" % (self.logfile)
|
||||||
|
@ -1541,16 +1534,18 @@ class _TestsLauncher(Loggable):
|
||||||
testlist_file = open(os.path.splitext(testsuite.__file__)[0] + ".testslist",
|
testlist_file = open(os.path.splitext(testsuite.__file__)[0] + ".testslist",
|
||||||
'w')
|
'w')
|
||||||
except IOError:
|
except IOError:
|
||||||
return
|
continue
|
||||||
|
|
||||||
for test in know_tests:
|
for test in know_tests:
|
||||||
if test and test not in tests_names:
|
if test and test.strip('~') not in tests_names:
|
||||||
testlist_changed = True
|
if not test.startswith('~'):
|
||||||
printc("Test %s Not in testsuite %s anymore"
|
testlist_changed = True
|
||||||
% (test, testsuite.__file__), Colors.FAIL)
|
printc("Test %s Not in testsuite %s anymore"
|
||||||
|
% (test, testsuite.__file__), Colors.FAIL)
|
||||||
|
|
||||||
for test in tests_names:
|
for test in tests:
|
||||||
testlist_file.write("%s\n" % test)
|
testlist_file.write("%s%s\n" % ('~' if test.optional else '',
|
||||||
|
test.classname))
|
||||||
if test and test not in know_tests:
|
if test and test not in know_tests:
|
||||||
printc("Test %s is NEW in testsuite %s"
|
printc("Test %s is NEW in testsuite %s"
|
||||||
% (test, testsuite.__file__), Colors.OKGREEN)
|
% (test, testsuite.__file__), Colors.OKGREEN)
|
||||||
|
@ -1719,6 +1714,7 @@ class Scenario(object):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Scenario %s>" % self.name
|
return "<Scenario %s>" % self.name
|
||||||
|
|
||||||
|
|
||||||
class ScenarioManager(Loggable):
|
class ScenarioManager(Loggable):
|
||||||
_instance = None
|
_instance = None
|
||||||
all_scenarios = []
|
all_scenarios = []
|
||||||
|
@ -1844,7 +1840,7 @@ class GstValidateBaseTestManager(TestsManager):
|
||||||
"""
|
"""
|
||||||
self._scenarios = []
|
self._scenarios = []
|
||||||
self.add_scenarios(scenarios)
|
self.add_scenarios(scenarios)
|
||||||
|
|
||||||
def get_scenarios(self):
|
def get_scenarios(self):
|
||||||
return self._scenarios
|
return self._scenarios
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ import os
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -43,7 +44,8 @@ from xml.etree import ElementTree
|
||||||
GST_SECOND = int(1000000000)
|
GST_SECOND = int(1000000000)
|
||||||
DEFAULT_TIMEOUT = 30
|
DEFAULT_TIMEOUT = 30
|
||||||
DEFAULT_MAIN_DIR = os.path.join(os.path.expanduser("~"), "gst-validate")
|
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"
|
DISCOVERER_COMMAND = "gst-discoverer-1.0"
|
||||||
# Use to set the duration from which a test is considered as being 'long'
|
# Use to set the duration from which a test is considered as being 'long'
|
||||||
LONG_TEST = 40
|
LONG_TEST = 40
|
||||||
|
@ -63,6 +65,7 @@ class Protocols(object):
|
||||||
FILE = "file"
|
FILE = "file"
|
||||||
HLS = "hls"
|
HLS = "hls"
|
||||||
DASH = "dash"
|
DASH = "dash"
|
||||||
|
RTSP = "rtsp"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def needs_clock_sync(protocol):
|
def needs_clock_sync(protocol):
|
||||||
|
@ -208,7 +211,8 @@ def TIME_ARGS(time):
|
||||||
|
|
||||||
|
|
||||||
def look_for_file_in_source_dir(subdir, name):
|
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)
|
p = os.path.join(root_dir, subdir, name)
|
||||||
if os.path.exists(p):
|
if os.path.exists(p):
|
||||||
return p
|
return p
|
||||||
|
@ -253,7 +257,8 @@ def get_duration(media_file):
|
||||||
duration = 0
|
duration = 0
|
||||||
res = ''
|
res = ''
|
||||||
try:
|
try:
|
||||||
res = subprocess.check_output([DISCOVERER_COMMAND, media_file]).decode()
|
res = subprocess.check_output(
|
||||||
|
[DISCOVERER_COMMAND, media_file]).decode()
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
# gst-media-check returns !0 if seeking is not possible, we do not care
|
# gst-media-check returns !0 if seeking is not possible, we do not care
|
||||||
# in that case.
|
# in that case.
|
||||||
|
@ -308,7 +313,7 @@ class BackTraceGenerator(Loggable):
|
||||||
"installed."
|
"installed."
|
||||||
|
|
||||||
gdb = ['gdb', '-ex', 't a a bt', '-batch',
|
gdb = ['gdb', '-ex', 't a a bt', '-batch',
|
||||||
'-p', str(test.process.pid)]
|
'-p', str(test.process.pid)]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return subprocess.check_output(
|
return subprocess.check_output(
|
||||||
|
@ -320,7 +325,8 @@ class BackTraceGenerator(Loggable):
|
||||||
def get_trace_from_systemd(self, test):
|
def get_trace_from_systemd(self, test):
|
||||||
for ntry in range(10):
|
for ntry in range(10):
|
||||||
if ntry != 0:
|
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)
|
time.sleep(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -334,7 +340,8 @@ class BackTraceGenerator(Loggable):
|
||||||
|
|
||||||
info = info.decode()
|
info = info.decode()
|
||||||
try:
|
try:
|
||||||
executable = BackTraceGenerator._executable_regex.findall(info)[0]
|
executable = BackTraceGenerator._executable_regex.findall(info)[
|
||||||
|
0]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
self.debug("Backtrace could not be found yet, trying harder.")
|
self.debug("Backtrace could not be found yet, trying harder.")
|
||||||
# The trace might not be ready yet
|
# The trace might not be ready yet
|
||||||
|
@ -357,11 +364,11 @@ class BackTraceGenerator(Loggable):
|
||||||
try:
|
try:
|
||||||
tf = tempfile.NamedTemporaryFile()
|
tf = tempfile.NamedTemporaryFile()
|
||||||
subprocess.check_output(['coredumpctl', 'dump',
|
subprocess.check_output(['coredumpctl', 'dump',
|
||||||
str(test.process.pid), '--output=' +
|
str(test.process.pid), '--output=' +
|
||||||
tf.name], stderr=subprocess.STDOUT)
|
tf.name], stderr=subprocess.STDOUT)
|
||||||
|
|
||||||
gdb = ['gdb', '-ex', 't a a bt', '-ex', 'quit',
|
gdb = ['gdb', '-ex', 't a a bt', '-ex', 'quit',
|
||||||
test.application, tf.name]
|
test.application, tf.name]
|
||||||
bt_all = subprocess.check_output(
|
bt_all = subprocess.check_output(
|
||||||
gdb, stderr=subprocess.STDOUT).decode()
|
gdb, stderr=subprocess.STDOUT).decode()
|
||||||
|
|
||||||
|
@ -387,13 +394,14 @@ def check_bugs_resolution(bugs_definitions):
|
||||||
|
|
||||||
if "bugzilla" not in url.netloc:
|
if "bugzilla" not in url.netloc:
|
||||||
printc(" + %s \n --> bug: %s\n --> Status: Not a bugzilla report\n" % (regex, bug),
|
printc(" + %s \n --> bug: %s\n --> Status: Not a bugzilla report\n" % (regex, bug),
|
||||||
Colors.WARNING)
|
Colors.WARNING)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
query = urllib.parse.parse_qs(url.query)
|
query = urllib.parse.parse_qs(url.query)
|
||||||
_id = query.get('id')
|
_id = query.get('id')
|
||||||
if not _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
|
continue
|
||||||
|
|
||||||
if isinstance(_id, list):
|
if isinstance(_id, list):
|
||||||
|
@ -447,3 +455,29 @@ def check_bugs_resolution(bugs_definitions):
|
||||||
regex, bugid, desc, status), Colors.OKGREEN)
|
regex, bugid, desc, status), Colors.OKGREEN)
|
||||||
|
|
||||||
return res
|
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
|
||||||
|
|
Loading…
Reference in a new issue