mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-06-05 06:58:56 +00:00
validate:tools: Add support for testing http streams locally
This commit is contained in:
parent
c2e5c10767
commit
c0673d7eea
6 changed files with 418 additions and 7 deletions
|
@ -9,6 +9,8 @@ launcher_PYTHON = \
|
|||
loggable.py \
|
||||
reporters.py \
|
||||
main.py \
|
||||
httpserver.py \
|
||||
RangeHTTPServer.py \
|
||||
utils.py
|
||||
|
||||
clean-local:
|
||||
|
|
274
validate/tools/launcher/RangeHTTPServer.py
Normal file
274
validate/tools/launcher/RangeHTTPServer.py
Normal file
|
@ -0,0 +1,274 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
#Portions Copyright (C) 2009,2010 Xyne
|
||||
#Portions Copyright (C) 2011 Sean Goller
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# (version 2) as published by the Free Software Foundation.
|
||||
#
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
|
||||
|
||||
"""Range HTTP Server.
|
||||
|
||||
This module builds on BaseHTTPServer by implementing the standard GET
|
||||
and HEAD requests in a fairly straightforward manner, and includes support
|
||||
for the Range header.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
__all__ = ["RangeHTTPRequestHandler"]
|
||||
|
||||
import os
|
||||
import posixpath
|
||||
import BaseHTTPServer
|
||||
import urllib
|
||||
import cgi
|
||||
import shutil
|
||||
import mimetypes
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
|
||||
|
||||
class RangeHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
|
||||
"""Simple HTTP request handler with GET and HEAD commands.
|
||||
|
||||
This serves files from the current directory and any of its
|
||||
subdirectories. The MIME type for files is determined by
|
||||
calling the .guess_type() method.
|
||||
|
||||
The GET and HEAD requests are identical except that the HEAD
|
||||
request omits the actual contents of the file.
|
||||
|
||||
"""
|
||||
|
||||
server_version = "RangeHTTP/" + __version__
|
||||
|
||||
def do_GET(self):
|
||||
"""Serve a GET request."""
|
||||
f, start_range, end_range = self.send_head()
|
||||
print "Got values of ", start_range, " and ", end_range, "...\n"
|
||||
if f:
|
||||
f.seek(start_range, 0)
|
||||
chunk = 0x1000
|
||||
total = 0
|
||||
while chunk > 0:
|
||||
if start_range + chunk > end_range:
|
||||
chunk = end_range - start_range
|
||||
try:
|
||||
self.wfile.write(f.read(chunk))
|
||||
except:
|
||||
break
|
||||
total += chunk
|
||||
start_range += chunk
|
||||
f.close()
|
||||
|
||||
def do_HEAD(self):
|
||||
"""Serve a HEAD request."""
|
||||
f, start_range, end_range = self.send_head()
|
||||
if f:
|
||||
f.close()
|
||||
|
||||
def send_head(self):
|
||||
"""Common code for GET and HEAD commands.
|
||||
|
||||
This sends the response code and MIME headers.
|
||||
|
||||
Return value is either a file object (which has to be copied
|
||||
to the outputfile by the caller unless the command was HEAD,
|
||||
and must be closed by the caller under all circumstances), or
|
||||
None, in which case the caller has nothing further to do.
|
||||
|
||||
"""
|
||||
path = self.translate_path(self.path)
|
||||
f = None
|
||||
if os.path.isdir(path):
|
||||
if not self.path.endswith('/'):
|
||||
# redirect browser - doing basically what apache does
|
||||
self.send_response(301)
|
||||
self.send_header("Location", self.path + "/")
|
||||
self.end_headers()
|
||||
return (None, 0, 0)
|
||||
for index in "index.html", "index.htm":
|
||||
index = os.path.join(path, index)
|
||||
if os.path.exists(index):
|
||||
path = index
|
||||
break
|
||||
else:
|
||||
return self.list_directory(path)
|
||||
ctype = self.guess_type(path)
|
||||
try:
|
||||
# Always read in binary mode. Opening files in text mode may cause
|
||||
# newline translations, making the actual size of the content
|
||||
# transmitted *less* than the content-length!
|
||||
f = open(path, 'rb')
|
||||
except IOError:
|
||||
self.send_error(404, "File not found")
|
||||
return (None, 0, 0)
|
||||
if "Range" in self.headers:
|
||||
self.send_response(206)
|
||||
else:
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", ctype)
|
||||
fs = os.fstat(f.fileno())
|
||||
size = int(fs[6])
|
||||
start_range = 0
|
||||
end_range = size
|
||||
self.send_header("Accept-Ranges", "bytes")
|
||||
if "Range" in self.headers:
|
||||
s, e = self.headers['range'][6:].split('-', 1)
|
||||
sl = len(s)
|
||||
el = len(e)
|
||||
if sl > 0:
|
||||
start_range = int(s)
|
||||
if el > 0:
|
||||
end_range = int(e) + 1
|
||||
elif el > 0:
|
||||
ei = int(e)
|
||||
if ei < size:
|
||||
start_range = size - ei
|
||||
self.send_header("Content-Range", 'bytes ' + str(start_range) + '-' + str(end_range - 1) + '/' + str(size))
|
||||
self.send_header("Content-Length", end_range - start_range)
|
||||
self.send_header("Last-Modified", self.date_time_string(fs.st_mtime))
|
||||
self.end_headers()
|
||||
print "Sending Bytes ",start_range, " to ", end_range, "...\n"
|
||||
return (f, start_range, end_range)
|
||||
|
||||
def list_directory(self, path):
|
||||
"""Helper to produce a directory listing (absent index.html).
|
||||
|
||||
Return value is either a file object, or None (indicating an
|
||||
error). In either case, the headers are sent, making the
|
||||
interface the same as for send_head().
|
||||
|
||||
"""
|
||||
try:
|
||||
list = os.listdir(path)
|
||||
except os.error:
|
||||
self.send_error(404, "No permission to list directory")
|
||||
return None
|
||||
list.sort(key=lambda a: a.lower())
|
||||
f = StringIO()
|
||||
displaypath = cgi.escape(urllib.unquote(self.path))
|
||||
f.write('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
|
||||
f.write("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
|
||||
f.write("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
|
||||
f.write("<hr>\n<ul>\n")
|
||||
for name in list:
|
||||
fullname = os.path.join(path, name)
|
||||
displayname = linkname = name
|
||||
# Append / for directories or @ for symbolic links
|
||||
if os.path.isdir(fullname):
|
||||
displayname = name + "/"
|
||||
linkname = name + "/"
|
||||
if os.path.islink(fullname):
|
||||
displayname = name + "@"
|
||||
# Note: a link to a directory displays with @ and links with /
|
||||
f.write('<li><a href="%s">%s</a>\n'
|
||||
% (urllib.quote(linkname), cgi.escape(displayname)))
|
||||
f.write("</ul>\n<hr>\n</body>\n</html>\n")
|
||||
length = f.tell()
|
||||
f.seek(0)
|
||||
self.send_response(200)
|
||||
self.send_header("Content-type", "text/html")
|
||||
self.send_header("Content-Length", str(length))
|
||||
self.end_headers()
|
||||
return (f, 0, length)
|
||||
|
||||
def translate_path(self, path):
|
||||
"""Translate a /-separated PATH to the local filename syntax.
|
||||
|
||||
Components that mean special things to the local file system
|
||||
(e.g. drive or directory names) are ignored. (XXX They should
|
||||
probably be diagnosed.)
|
||||
|
||||
"""
|
||||
# abandon query parameters
|
||||
path = path.split('?',1)[0]
|
||||
path = path.split('#',1)[0]
|
||||
path = posixpath.normpath(urllib.unquote(path))
|
||||
words = path.split('/')
|
||||
words = filter(None, words)
|
||||
path = os.getcwd()
|
||||
for word in words:
|
||||
drive, word = os.path.splitdrive(word)
|
||||
head, word = os.path.split(word)
|
||||
if word in (os.curdir, os.pardir): continue
|
||||
path = os.path.join(path, word)
|
||||
return path
|
||||
|
||||
def copyfile(self, source, outputfile):
|
||||
"""Copy all data between two file objects.
|
||||
|
||||
The SOURCE argument is a file object open for reading
|
||||
(or anything with a read() method) and the DESTINATION
|
||||
argument is a file object open for writing (or
|
||||
anything with a write() method).
|
||||
|
||||
The only reason for overriding this would be to change
|
||||
the block size or perhaps to replace newlines by CRLF
|
||||
-- note however that this the default server uses this
|
||||
to copy binary data as well.
|
||||
|
||||
"""
|
||||
shutil.copyfileobj(source, outputfile)
|
||||
|
||||
def guess_type(self, path):
|
||||
"""Guess the type of a file.
|
||||
|
||||
Argument is a PATH (a filename).
|
||||
|
||||
Return value is a string of the form type/subtype,
|
||||
usable for a MIME Content-type header.
|
||||
|
||||
The default implementation looks the file's extension
|
||||
up in the table self.extensions_map, using application/octet-stream
|
||||
as a default; however it would be permissible (if
|
||||
slow) to look inside the data to make a better guess.
|
||||
|
||||
"""
|
||||
|
||||
base, ext = posixpath.splitext(path)
|
||||
if ext in self.extensions_map:
|
||||
return self.extensions_map[ext]
|
||||
ext = ext.lower()
|
||||
if ext in self.extensions_map:
|
||||
return self.extensions_map[ext]
|
||||
else:
|
||||
return self.extensions_map['']
|
||||
|
||||
if not mimetypes.inited:
|
||||
mimetypes.init() # try to read system mime.types
|
||||
extensions_map = mimetypes.types_map.copy()
|
||||
extensions_map.update({
|
||||
'': 'application/octet-stream', # Default
|
||||
'.py': 'text/plain',
|
||||
'.c': 'text/plain',
|
||||
'.h': 'text/plain',
|
||||
'.mp4': 'video/mp4',
|
||||
'.ogg': 'video/ogg',
|
||||
})
|
||||
|
||||
|
||||
def test(HandlerClass = RangeHTTPRequestHandler,
|
||||
ServerClass = BaseHTTPServer.HTTPServer):
|
||||
BaseHTTPServer.test(HandlerClass, ServerClass)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
|
@ -286,3 +286,9 @@ class GstValidateManager(TestsManager, Loggable):
|
|||
self.reporter,
|
||||
pipe,
|
||||
scenario=scenario))
|
||||
|
||||
def needs_http_server(self):
|
||||
for uri, mediainfo in self._list_uris():
|
||||
if urlparse.urlparse(uri).scheme == "http" and \
|
||||
"127.0.0.1:%s" % (self.options.http_server_port) in uri:
|
||||
return True
|
||||
|
|
|
@ -178,6 +178,8 @@ class Test(Loggable):
|
|||
"=================\n\n"
|
||||
% (self.classname, self.command))
|
||||
|
||||
return self.result
|
||||
|
||||
|
||||
class GstValidateTest(Test):
|
||||
|
||||
|
@ -299,6 +301,9 @@ class TestsManager(Loggable):
|
|||
test.run()
|
||||
self.reporter.after_test()
|
||||
|
||||
def needs_http_server(self):
|
||||
return False
|
||||
|
||||
|
||||
class _TestsLauncher(Loggable):
|
||||
def __init__(self):
|
||||
|
@ -375,3 +380,8 @@ class _TestsLauncher(Loggable):
|
|||
|
||||
def final_report(self):
|
||||
self.reporter.final_report()
|
||||
|
||||
def needs_http_server(self):
|
||||
for tester in self.testers:
|
||||
if tester.needs_http_server():
|
||||
return True
|
||||
|
|
101
validate/tools/launcher/httpserver.py
Normal file
101
validate/tools/launcher/httpserver.py
Normal file
|
@ -0,0 +1,101 @@
|
|||
#!/usr//bin/python
|
||||
#
|
||||
# Copyright (c) 2013,Thibault Saunier <thibault.saunier@collabora.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this program; if not, write to the
|
||||
# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
||||
# Boston, MA 02110-1301, USA.
|
||||
|
||||
import os
|
||||
import time
|
||||
import loggable
|
||||
import subprocess
|
||||
|
||||
logcat = "httpserver"
|
||||
|
||||
|
||||
class HTTPServer(loggable.Loggable):
|
||||
""" Class to run a SimpleHttpServer in a process."""
|
||||
def __init__(self, options):
|
||||
loggable.Loggable.__init__(self)
|
||||
self.options = options
|
||||
self._process = None
|
||||
self._logsfile = None
|
||||
|
||||
def _check_is_up(self, timeout=60):
|
||||
""" Check if the server is up, running a simple test based on wget. """
|
||||
start = time.time()
|
||||
while True:
|
||||
try:
|
||||
subprocess.check_output(["wget", "127.0.0.1:%s" %
|
||||
(self.options.http_server_port),
|
||||
"-O", os.devnull],
|
||||
stderr=self._logsfile)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
|
||||
if time.time() - start > timeout:
|
||||
return False
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
def start(self):
|
||||
""" Start the server in a subprocess """
|
||||
self._logsfile = open(os.path.join(self.options.logsdir,
|
||||
"httpserver.logs"),
|
||||
'w+')
|
||||
if self.options.http_server_dir is not None:
|
||||
if self._check_is_up(timeout=2):
|
||||
return True
|
||||
|
||||
print "Starting Server"
|
||||
try:
|
||||
self.debug("Lunching twistd server")
|
||||
cmd = "python %s %d" % (os.path.join(os.path.dirname(__file__),
|
||||
"RangeHTTPServer.py"),
|
||||
self.options.http_server_port)
|
||||
curdir = os.path.abspath(os.curdir)
|
||||
os.chdir(self.options.http_server_dir)
|
||||
#cmd = "twistd -no web --path=%s -p %d" % (
|
||||
# self.options.http_server_dir, self.options.http_server_port)
|
||||
self.debug("Lunching server: %s", cmd)
|
||||
self._process = subprocess.Popen(cmd.split(" "),
|
||||
stderr=self._logsfile,
|
||||
stdout=self._logsfile)
|
||||
os.chdir(curdir)
|
||||
self.debug("Lunched twistd server")
|
||||
# Dirty way to avoid eating to much CPU...
|
||||
# good enough for us anyway.
|
||||
time.sleep(1)
|
||||
|
||||
if self._check_is_up():
|
||||
print "Started"
|
||||
return True
|
||||
else:
|
||||
print "Failed starting server"
|
||||
self._process.terminate()
|
||||
self._process = None
|
||||
except OSError as ex:
|
||||
print "Failed starting server"
|
||||
self.warning(logcat, "Could not launch server %s" % ex)
|
||||
|
||||
return False
|
||||
|
||||
def stop(self):
|
||||
""" Stop the server subprocess if running. """
|
||||
if self._process:
|
||||
self._process.terminate()
|
||||
self._process = None
|
||||
self.debug("Server stoped")
|
|
@ -22,6 +22,7 @@ import urlparse
|
|||
import loggable
|
||||
from optparse import OptionParser
|
||||
|
||||
from httpserver import HTTPServer
|
||||
from baseclasses import _TestsLauncher
|
||||
from utils import printc, path2url, DEFAULT_GST_QA_ASSETS, launch_command
|
||||
|
||||
|
@ -74,6 +75,12 @@ def main():
|
|||
parser.add_option("-g", "--generate-media-info", dest="generate_info",
|
||||
action="store_true", default=False,
|
||||
help="Set it in order to generate the missing .media_infos files")
|
||||
parser.add_option("-s", "--folder-for-http-server", dest="http_server_dir",
|
||||
default=os.path.join(DEFAULT_GST_QA_ASSETS, "medias"),
|
||||
help="Folder in which to create an http server on localhost")
|
||||
parser.add_option("", "--http-server-port", dest="http_server_port",
|
||||
default=8079,
|
||||
help="Port on which to run the http server on localhost")
|
||||
|
||||
loggable.init("GST_VALIDATE_LAUNCHER_DEBUG", True, False)
|
||||
|
||||
|
@ -91,7 +98,6 @@ def main():
|
|||
if options.no_color:
|
||||
utils.desactivate_colors()
|
||||
|
||||
|
||||
tests_launcher.set_settings(options, args)
|
||||
|
||||
if options.paths == [os.path.join(DEFAULT_GST_QA_ASSETS, "medias")]:
|
||||
|
@ -102,12 +108,24 @@ def main():
|
|||
|
||||
tests_launcher.list_tests()
|
||||
|
||||
if options.list_tests:
|
||||
for test in tests_launcher.tests:
|
||||
printc(test)
|
||||
return 0
|
||||
httpsrv = HTTPServer(options)
|
||||
if tests_launcher.needs_http_server():
|
||||
httpsrv.start()
|
||||
|
||||
tests_launcher.run_tests()
|
||||
tests_launcher.final_report()
|
||||
e = None
|
||||
try:
|
||||
if options.list_tests:
|
||||
for test in tests_launcher.tests:
|
||||
printc(test)
|
||||
return 0
|
||||
|
||||
tests_launcher.run_tests()
|
||||
tests_launcher.final_report()
|
||||
except Exception as e:
|
||||
pass
|
||||
finally:
|
||||
httpsrv.stop()
|
||||
if e is not None:
|
||||
raise
|
||||
|
||||
return 0
|
||||
|
|
Loading…
Reference in a new issue