validate:launcher: Port to Python3
authorThibault Saunier <thibault.saunier@osg.samsung.com>
Fri, 4 Nov 2016 21:04:37 +0000 (18:04 -0300)
committerThibault Saunier <thibault.saunier@osg.samsung.com>
Wed, 9 Nov 2016 13:13:42 +0000 (10:13 -0300)
And sync logging.py with Pitivi version

14 files changed:
hooks/pre-commit-python.hook
validate/launcher/RangeHTTPServer.py
validate/launcher/__init__.py
validate/launcher/apps/gstvalidate.py
validate/launcher/baseclasses.py
validate/launcher/config.py.in
validate/launcher/httpserver.py
validate/launcher/loggable.py
validate/launcher/main.py
validate/launcher/reporters.py
validate/launcher/utils.py
validate/launcher/vfb_server.py
validate/tools/gst-validate-analyze
validate/tools/gst-validate-launcher.in

index 1c0efb6..5129f00 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 import os
 import subprocess
 import sys
@@ -68,13 +68,13 @@ def main():
             break
 
     if output_message:
-        print output_message
+        print(output_message)
         if non_compliant_files:
-            print NOT_PEP8_COMPLIANT_MESSAGE_POST
+            print(NOT_PEP8_COMPLIANT_MESSAGE_POST)
             for non_compliant_file in non_compliant_files:
-                print "autopep8 -i ", non_compliant_file, "; git add ", \
-                    non_compliant_file
-            print "git commit"
+                print("autopep8 -i ", non_compliant_file, "; git add ",
+                    non_compliant_file)
+            print("git commit")
         sys.exit(1)
 
 
index 9f97c47..c29ac96 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
 # Portions Copyright (C) 2009,2010  Xyne
 # Portions Copyright (C) 2011 Sean Goller
@@ -34,24 +34,21 @@ __all__ = ["RangeHTTPRequestHandler"]
 
 import os
 import sys
+
 import posixpath
-import BaseHTTPServer
-from SocketServer import ThreadingMixIn
-import urllib
-import cgi
+import http.server
+import urllib.parse
+import html
 import shutil
 import mimetypes
+import io
 import time
-try:
-    from cStringIO import StringIO
-except ImportError:
-    from StringIO import StringIO
 
 
 _bandwidth = 0
 
 
-class RangeHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+class RangeHTTPRequestHandler(http.server.BaseHTTPRequestHandler):
 
     """Simple HTTP request handler with GET and HEAD commands.
 
@@ -69,7 +66,7 @@ class RangeHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
     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"
+        print ("Got values of {} and {}".format(start_range, end_range))
         if f:
             f.seek(start_range, 0)
             chunk = 0x1000
@@ -110,13 +107,13 @@ class RangeHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
         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
+            if not self.path.endswith("/"):
+                # redirect browser
                 self.send_response(301)
                 self.send_header("Location", self.path + "/")
                 self.end_headers()
                 return (None, 0, 0)
-            for index in "index.html", "index.htm":
+            for index in "index.html", "index.html":
                 index = os.path.join(path, index)
                 if os.path.exists(index):
                     path = index
@@ -124,83 +121,97 @@ class RangeHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
             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')
+            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(206) #partial content response
+        else :
             self.send_response(200)
+
         self.send_header("Content-type", ctype)
-        fs = os.fstat(f.fileno())
-        size = int(fs[6])
+        file_size = os.path.getsize(path)
+
         start_range = 0
-        end_range = size
+        end_range = file_size
+
         self.send_header("Accept-Ranges", "bytes")
         if "Range" in self.headers:
-            s, e = self.headers['range'][6:].split('-', 1)
+            s, e = self.headers['range'][6:].split('-', 1) #bytes:%d-%d
             sl = len(s)
             el = len(e)
-            if sl > 0:
+
+            if sl:
                 start_range = int(s)
-                if el > 0:
+                if el:
                     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))
+            elif el:
+                start_range = file_size - min(file_size, int(e))
+
+        self.send_header("Content-Range", "bytes {}-{}/{}".format(start_range, end_range, file_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 {} to {}...".format(start_range, end_range))
         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().
+                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")
+            lst = os.listdir(path)
+        except OSError:
+            self.send_error(404, "Access Forbidden")
             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:
+
+        lst.sort(key=lambda file_name : file_name.lower())
+        html_text = []
+
+        displaypath = html.escape(urllib.parse.unquote(self.path))
+        html_text.append('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
+        html_text.append("<html>\n<title>Directory listing for {}</title>\n".format(displaypath))
+        html_text.append("<body>\n<h2>Directory listing for {}</h2>\n".format(displaypath))
+        html_text.append("<hr>\n<ul>\n")
+
+        for name in lst:
             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()
+
+            html_text.append('<li><a href = "{}">{}</a>\n'.format(urllib.parse.quote(linkname), html.escape(displayname)))
+
+        html_text.append('</ul>\n</hr>\n</body>\n</html>\n')
+
+        byte_encoded_string = "\n".join(html_text).encode("utf-8", "surrogateescape")
+        f = io.BytesIO()
+        f.write(byte_encoded_string)
+        length = len(byte_encoded_string)
+
         f.seek(0)
+
         self.send_response(200)
         self.send_header("Content-type", "text/html")
-        self.send_header("Content-Length", str(length))
+        self.send_header("Content-length", str(length))
         self.end_headers()
+
         return (f, 0, length)
 
     def translate_path(self, path):
@@ -211,36 +222,21 @@ class RangeHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
         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('/')
+        #abandon query parameters
+        path = path.split("?", 1)[0]
+        path = path.split("#", 1)[0]
+        path = posixpath.normpath(urllib.parse.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
+            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.
@@ -258,37 +254,31 @@ class RangeHTTPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
         """
 
         base, ext = posixpath.splitext(path)
-        if ext in self.extensions_map:
-            return self.extensions_map[ext]
+        if ext in self.extension_map:
+            return self.extension_map[ext]
         ext = ext.lower()
-        if ext in self.extensions_map:
-            return self.extensions_map[ext]
+        if ext in self.extension_map:
+            return self.extension_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',
-    })
-
-
-class ThreadedHTTPServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
-    """Handle requests in a separate thread."""
-
-
-def test(HandlerClass=RangeHTTPRequestHandler,
-         ServerClass=ThreadedHTTPServer):
-    BaseHTTPServer.test(HandlerClass, ServerClass)
-
-
-if __name__ == '__main__':
-    if len(sys.argv) > 2:
-        _bandwidth = int(sys.argv[2])
-    test()
+            return self.extension_map['']
+
+    if not  mimetypes.inited:
+        mimetypes.init()
+    extension_map = mimetypes.types_map.copy()
+    extension_map.update({
+            '': 'application/octet-stream', # Default
+            '.py': 'text/plain',
+            '.c': 'text/plain',
+            '.h': 'text/plain',
+            '.mp4': 'video/mp4',
+            '.ogg': 'video/ogg',
+            '.java' : 'text/plain',
+        })
+
+
+def test(handler_class = RangeHTTPRequestHandler,server_class = http.server.HTTPServer):
+    http.server.test(handler_class, server_class)
+
+if __name__ == "__main__":
+    httpd = http.server.HTTPServer(("0.0.0.0", int(sys.argv[1])), RangeHTTPRequestHandler)
+    httpd.serve_forever()
index 4d1ecfc..13694d6 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 #
 # Copyright (c) 2014,Thibault Saunier <thibault.saunier@collabora.com>
 #
index a70e7bc..711ee78 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 #
 # Copyright (c) 2013,Thibault Saunier <thibault.saunier@collabora.com>
 #
@@ -19,9 +19,9 @@
 import argparse
 import os
 import time
-import urlparse
+import urllib.parse
 import subprocess
-import ConfigParser
+import configparser
 from launcher.loggable import Loggable
 
 from launcher.baseclasses import GstValidateTest, Test, \
@@ -338,7 +338,7 @@ class GstValidateMixerTestsGenerator(GstValidatePipelineTestsGenerator):
 
                 self.mixed_srcs[name] = tuple(srcs)
 
-        for name, srcs in self.mixed_srcs.iteritems():
+        for name, srcs in self.mixed_srcs.items():
             if isinstance(srcs, dict):
                 pipe_arguments = {
                     "mixer": self.mixer + " %s" % srcs["mixer_props"]}
@@ -454,7 +454,7 @@ class GstValidateTranscodingTest(GstValidateTest, GstValidateEncodingTestInterfa
 
         extra_env_variables = extra_env_variables or {}
 
-        file_dur = long(media_descriptor.get_duration()) / GST_SECOND
+        file_dur = int(media_descriptor.get_duration()) / GST_SECOND
         if not media_descriptor.get_num_tracks("video"):
             self.debug("%s audio only file applying transcoding ratio."
                        "File 'duration' : %s" % (classname, file_dur))
@@ -490,8 +490,8 @@ class GstValidateTranscodingTest(GstValidateTest, GstValidateEncodingTestInterfa
         self.dest_file = os.path.join(self.options.dest,
                                       self.classname.replace(".transcode.", os.sep).
                                       replace(".", os.sep))
-        mkdir(os.path.dirname(urlparse.urlsplit(self.dest_file).path))
-        if urlparse.urlparse(self.dest_file).scheme == "":
+        mkdir(os.path.dirname(urllib.parse.urlsplit(self.dest_file).path))
+        if urllib.parse.urlparse(self.dest_file).scheme == "":
             self.dest_file = path2url(self.dest_file)
 
         profile = self.get_profile()
@@ -647,7 +647,7 @@ not been tested and explicitely activated if you set use --wanted-tests ALL""")
                uri.startswith("http://127.0.0.1:8079/"):
                 uri = uri.replace("http://127.0.0.1:8079/",
                                   "http://127.0.0.1:%r/" % self.options.http_server_port, 1)
-            media_descriptor.set_protocol(urlparse.urlparse(uri).scheme)
+            media_descriptor.set_protocol(urllib.parse.urlparse(uri).scheme)
             for caps2, prot in GST_VALIDATE_CAPS_TO_PROTOCOL:
                 if caps2 == caps:
                     media_descriptor.set_protocol(prot)
@@ -660,7 +660,7 @@ not been tested and explicitely activated if you set use --wanted-tests ALL""")
                                NamedDic({"path": media_info,
                                          "media_descriptor": media_descriptor}),
                                special_scenarios))
-        except ConfigParser.NoOptionError as e:
+        except configparser.NoOptionError as e:
             self.debug("Exception: %s for %s", e, media_info)
 
     def _discover_file(self, uri, fpath):
index 73f0328..a8c0a3c 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 #
 # Copyright (c) 2013,Thibault Saunier <thibault.saunier@collabora.com>
 #
@@ -24,22 +24,22 @@ import os
 import sys
 import re
 import copy
-import SocketServer
+import socketserver
 import struct
 import time
-import utils
+from . import utils
 import signal
-import urlparse
+import urllib.parse
 import subprocess
 import threading
-import Queue
-import reporters
-import ConfigParser
-import loggable
-from loggable import Loggable
+import queue
+from . import reporters
+import configparser
+from . import loggable
+from .loggable import Loggable
 import xml.etree.cElementTree as ET
 
-from utils import mkdir, Result, Colors, printc, DEFAULT_TIMEOUT, GST_SECOND, \
+from .utils import mkdir, Result, Colors, printc, DEFAULT_TIMEOUT, GST_SECOND, \
     Protocols, look_for_file_in_source_dir, get_data_file
 
 # The factor by which we increase the hard timeout when running inside
@@ -208,8 +208,8 @@ class Test(Loggable):
                 self.process.communicate()
             else:
                 pname = subprocess.check_output(("readlink -e /proc/%s/exe"
-                                                 % self.process.pid).split(' ')).replace('\n', '')
-                raw_input("%sTimeout happened you can attach gdb doing: $gdb %s %d%s\n"
+                                                 % self.process.pid).decode().split(' ')).replace('\n', '')
+                input("%sTimeout happened you can attach gdb doing: $gdb %s %d%s\n"
                           "Press enter to continue" % (Colors.FAIL, pname, self.process.pid,
                                                        Colors.ENDC))
 
@@ -354,7 +354,7 @@ class Test(Loggable):
         for supp in self.get_valgrind_suppressions():
             vg_args.append(('suppressions', supp))
 
-        self.command = "valgrind %s %s" % (' '.join(map(lambda x: '--%s=%s' % (x[0], x[1]), vg_args)),
+        self.command = "valgrind %s %s" % (' '.join(['--%s=%s' % (x[0], x[1]) for x in vg_args]),
                                            self.command)
 
         # Tune GLib's memory allocator to be more valgrind friendly
@@ -387,7 +387,7 @@ class Test(Loggable):
         self.build_arguments()
         self.proc_env = self.get_subproc_env()
 
-        for var, value in self.extra_env_variables.items():
+        for var, value in list(self.extra_env_variables.items()):
             value = self.proc_env.get(var, '') + os.pathsep + value
             self.proc_env[var] = value.strip(os.pathsep)
             self.add_env_variable(var, self.proc_env[var])
@@ -428,7 +428,7 @@ class Test(Loggable):
         printc(message, Colors.FAIL)
 
         with open(logfile, 'r') as fin:
-            print fin.read()
+            print(fin.read())
 
     def _dump_log_files(self):
         printc("Dumping log files on failure\n", Colors.FAIL)
@@ -454,15 +454,15 @@ class Test(Loggable):
         return self.result
 
 
-class GstValidateListener(SocketServer.BaseRequestHandler):
+class GstValidateListener(socketserver.BaseRequestHandler):
     def handle(self):
         """Implements BaseRequestHandler handle method"""
         while True:
             raw_len = self.request.recv(4)
-            if raw_len == '':
+            if raw_len == b'':
                 return
             msglen = struct.unpack('>I', raw_len)[0]
-            msg = self.request.recv(msglen)
+            msg = self.request.recv(msglen).decode()
             if msg == '':
                 return
 
@@ -575,7 +575,7 @@ class GstValidateTest(Test):
         self.actions_infos.append(action_infos)
 
     def server_wrapper(self, ready):
-        self.server = SocketServer.TCPServer(('localhost', 0), GstValidateListener)
+        self.server = socketserver.TCPServer(('localhost', 0), GstValidateListener)
         self.server.socket.settimeout(0.0)
         self.server.test = self
         self.serverport = self.server.socket.getsockname()[1]
@@ -709,7 +709,7 @@ class GstValidateTest(Test):
         for key in ['bug', 'sometimes']:
             if key in expected_failure:
                 del expected_failure[key]
-        for key, value in report.items():
+        for key, value in list(report.items()):
             if key in expected_failure:
                 if not re.findall(expected_failure[key], value):
                     return False
@@ -826,7 +826,7 @@ class GstValidateEncodingTestInterface(object):
 
     def get_current_size(self):
         try:
-            size = os.stat(urlparse.urlparse(self.dest_file).path).st_size
+            size = os.stat(urllib.parse.urlparse(self.dest_file).path).st_size
         except OSError:
             return None
 
@@ -963,7 +963,7 @@ class TestsManager(Loggable):
         self.wanted_tests_patterns = []
         self.blacklisted_tests_patterns = []
         self._generators = []
-        self.queue = Queue.Queue()
+        self.queue = queue.Queue()
         self.jobs = []
         self.total_num_tests = 0
         self.starting_test_num = 0
@@ -979,7 +979,7 @@ class TestsManager(Loggable):
 
     def add_expected_issues(self, expected_failures):
         expected_failures_re = {}
-        for test_name_regex, failures in expected_failures.items():
+        for test_name_regex, failures in list(expected_failures.items()):
             regex = re.compile(test_name_regex)
             expected_failures_re[regex] = failures
             for test in self.tests:
@@ -989,7 +989,7 @@ class TestsManager(Loggable):
         self.expected_failures.update(expected_failures_re)
 
     def add_test(self, test):
-        for regex, failures in self.expected_failures.items():
+        for regex, failures in list(self.expected_failures.items()):
             if regex.findall(test.classname):
                 test.expected_failures.extend(failures)
 
@@ -1094,7 +1094,7 @@ class TestsManager(Loggable):
             # Check process every second for timeout
             try:
                 self.queue.get(timeout=1)
-            except Queue.Empty:
+            except queue.Empty:
                 pass
 
             for test in self.jobs:
@@ -1232,7 +1232,7 @@ class _TestsLauncher(Loggable):
             files = []
         for f in files:
             if f.endswith(".py"):
-                execfile(os.path.join(app_dir, f), env)
+                exec(compile(open(os.path.join(app_dir, f)).read(), os.path.join(app_dir, f), 'exec'), env)
 
     def _exec_apps(self, env):
         app_dirs = self._list_app_dirs()
@@ -1315,7 +1315,7 @@ class _TestsLauncher(Loggable):
         globals()["options"] = options
         c__file__ = __file__
         globals()["__file__"] = self.options.config
-        execfile(self.options.config, globals())
+        exec(compile(open(self.options.config).read(), self.options.config, 'exec'), globals())
         globals()["__file__"] = c__file__
 
     def set_settings(self, options, args):
@@ -1374,7 +1374,7 @@ class _TestsLauncher(Loggable):
                     and tester.check_testslist:
                 try:
                     testlist_file = open(os.path.splitext(testsuite.__file__)[0] + ".testslist",
-                                         'rw')
+                                         'r+')
 
                     know_tests = testlist_file.read().split("\n")
                     testlist_file.close()
@@ -1410,7 +1410,7 @@ class _TestsLauncher(Loggable):
                 return -1
 
             self.tests.extend(tests)
-        return sorted(list(self.tests))
+        return sorted(list(self.tests), key=lambda t: t.classname)
 
     def _run_tests(self):
         cur_test_num = 0
@@ -1458,7 +1458,7 @@ class NamedDic(object):
 
     def __init__(self, props):
         if props:
-            for name, value in props.iteritems():
+            for name, value in props.items():
                 setattr(self, name, value)
 
 
@@ -1562,7 +1562,7 @@ class ScenarioManager(Loggable):
         except subprocess.CalledProcessError:
             pass
 
-        config = ConfigParser.ConfigParser()
+        config = configparser.RawConfigParser()
         f = open(scenario_defs)
         config.readfp(f)
 
@@ -1582,7 +1582,8 @@ class ScenarioManager(Loggable):
                 name = section
                 path = None
 
-            scenarios.append(Scenario(name, config.items(section), path))
+            props = config.items(section)
+            scenarios.append(Scenario(name, props, path))
 
         if not scenario_paths:
             self.discovered = True
@@ -1744,7 +1745,7 @@ class GstValidateMediaDescriptor(MediaDescriptor):
         self.media_xml.attrib["duration"]
         self.media_xml.attrib["seekable"]
 
-        self.set_protocol(urlparse.urlparse(urlparse.urlparse(self.get_uri()).scheme).scheme)
+        self.set_protocol(urllib.parse.urlparse(urllib.parse.urlparse(self.get_uri()).scheme).scheme)
 
     @staticmethod
     def new_from_uri(uri, verbose=False, full=False):
@@ -1808,7 +1809,7 @@ class GstValidateMediaDescriptor(MediaDescriptor):
         return self.media_xml.attrib["uri"]
 
     def get_duration(self):
-        return long(self.media_xml.attrib["duration"])
+        return int(self.media_xml.attrib["duration"])
 
     def set_protocol(self, protocol):
         self.media_xml.attrib["protocol"] = protocol
index 5739c6e..3c6e0cd 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 #
 # Copyright (c) 2015,Thibault Saunier <thibault.saunier@collabora.com>
 #
index 42dc003..f813bce 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 #
 # Copyright (c) 2013,Thibault Saunier <thibault.saunier@collabora.com>
 #
 
 import os
 import time
-import loggable
+from . import loggable
 import subprocess
 import sys
-import urllib2
+import urllib.request, urllib.error, urllib.parse
 
 logcat = "httpserver"
 
@@ -42,10 +42,10 @@ class HTTPServer(loggable.Loggable):
         start = time.time()
         while True:
             try:
-                response = urllib2.urlopen('http://127.0.0.1:%s' % (
+                response = urllib.request.urlopen('http://127.0.0.1:%s' % (
                     self.options.http_server_port))
                 return True
-            except urllib2.URLError as e:
+            except urllib.error.URLError as e:
                 pass
 
             if time.time() - start > timeout:
@@ -61,7 +61,7 @@ class HTTPServer(loggable.Loggable):
             if self._check_is_up(timeout=2):
                 return True
 
-            print "Starting Server"
+            print("Starting Server")
             try:
                 self.debug("Launching http server")
                 cmd = "%s %s %d %s" % (sys.executable, os.path.join(os.path.dirname(__file__),
@@ -85,14 +85,14 @@ class HTTPServer(loggable.Loggable):
                 time.sleep(1)
 
                 if self._check_is_up():
-                    print "Started"
+                    print("Started")
                     return True
                 else:
-                    print "Failed starting server"
+                    print("Failed starting server")
                     self._process.terminate()
                     self._process = None
             except OSError as ex:
-                print "Failed starting server"
+                print("Failed starting server")
                 self.warning(logcat, "Could not launch server %s" % ex)
 
         return False
index d033ece..9ed389d 100644 (file)
@@ -1,5 +1,5 @@
-# CC'd from  'pitivi/log/loggable.py'
-#
+# -*- coding: utf-8 -*-
+# Pitivi video editor
 # Copyright (c) 2009, Alessandro Decina <alessandro.decina@collabora.co.uk>
 #
 # This program is free software; you can redistribute it and/or
 # 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 collections
 import errno
-import sys
-import re
-import os
 import fnmatch
+import os
+import re
+import sys
+import threading
 import time
-import types
 import traceback
-import thread
+import types
 
 
 # environment variables controlling levels for each category
@@ -43,11 +43,11 @@ _log_handlers = []
 _log_handlers_limited = []
 
 _initialized = False
-_enableCrackOutput = False
 
 _stdout = None
 _stderr = None
 _old_hup_handler = None
+_outfile = None
 
 
 # public log levels
@@ -56,7 +56,7 @@ _old_hup_handler = None
  FIXME,
  INFO,
  DEBUG,
- LOG) = range(1, 7)
+ LOG) = list(range(1, 7))
 
 COLORS = {ERROR: 'RED',
           WARN: 'YELLOW',
@@ -69,11 +69,8 @@ _FORMATTED_LEVELS = []
 _LEVEL_NAMES = ['ERROR', 'WARN', 'FIXME', 'INFO', 'DEBUG', 'LOG']
 
 
-class TerminalController(object):
-
-    """
-    A class that can be used to portably generate formatted output to
-    a terminal.
+class TerminalController:
+    """A class for generating formatted output to a terminal.
 
     `TerminalController` defines a set of instance variables whose
     values are initialized to the control sequence necessary to
@@ -81,13 +78,13 @@ class TerminalController(object):
     output to the terminal:
 
         >>> term = TerminalController()
-        >>> print 'This is '+term.GREEN+'green'+term.NORMAL
+        >>> print('This is '+term.GREEN+'green'+term.NORMAL)
 
     Alternatively, the `render()` method can used, which replaces
     '${action}' with the string required to perform 'action':
 
         >>> term = TerminalController()
-        >>> print term.render('This is ${GREEN}green${NORMAL}')
+        >>> print(term.render('This is ${GREEN}green${NORMAL}'))
 
     If the terminal doesn't support a given action, then the value of
     the corresponding instance variable will be set to ''.  As a
@@ -99,10 +96,15 @@ class TerminalController(object):
 
         >>> term = TerminalController()
         >>> if term.CLEAR_SCREEN:
-        ...     print 'This terminal supports clearning the screen.'
+        ...     print('This terminal supports clearning the screen.')
 
     Finally, if the width and height of the terminal are known, then
     they will be stored in the `COLS` and `LINES` attributes.
+
+    Args:
+        term_stream (Optional): The stream that will be used for terminal
+            output; if this stream is not a tty, then the terminal is
+            assumed to be a dumb terminal (i.e., have no capabilities).
     """
     # Cursor movement:
     BOL = ''             # : Move the cursor to the beginning of the line
@@ -148,13 +150,6 @@ class TerminalController(object):
     _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
 
     def __init__(self, term_stream=sys.stdout):
-        """
-        Create a `TerminalController` and initialize its attributes
-        with appropriate values for the current terminal.
-        `term_stream` is the stream that will be used for terminal
-        output; if this stream is not a tty, then the terminal is
-        assumed to be a dumb terminal (i.e., have no capabilities).
-        """
         # Curses isn't available on all platforms
         try:
             import curses
@@ -179,42 +174,42 @@ class TerminalController(object):
         # Look up string capabilities.
         for capability in self._STRING_CAPABILITIES:
             (attrib, cap_name) = capability.split('=')
-            setattr(self, attrib, self._tigetstr(cap_name) or '')
+            setattr(self, attrib, self._tigetstr(cap_name) or b'')
 
         # Colors
         set_fg = self._tigetstr('setf')
         if set_fg:
-            for i, color in zip(range(len(self._COLORS)), self._COLORS):
-                setattr(self, color, curses.tparm(set_fg, i) or '')
+            for i, color in zip(list(range(len(self._COLORS))), self._COLORS):
+                setattr(self, color, curses.tparm(set_fg, i) or b'')
         set_fg_ansi = self._tigetstr('setaf')
         if set_fg_ansi:
-            for i, color in zip(range(len(self._ANSICOLORS)),
+            for i, color in zip(list(range(len(self._ANSICOLORS))),
                                 self._ANSICOLORS):
-                setattr(self, color, curses.tparm(set_fg_ansi, i) or '')
+                setattr(self, color, curses.tparm(set_fg_ansi, i) or b'')
         set_bg = self._tigetstr('setb')
         if set_bg:
-            for i, color in zip(range(len(self._COLORS)), self._COLORS):
-                setattr(self, 'BG_' + color, curses.tparm(set_bg, i) or '')
+            for i, color in zip(list(range(len(self._COLORS))), self._COLORS):
+                setattr(self, 'BG_' + color, curses.tparm(set_bg, i) or b'')
         set_bg_ansi = self._tigetstr('setab')
         if set_bg_ansi:
-            for i, color in zip(range(len(self._ANSICOLORS)),
+            for i, color in zip(list(range(len(self._ANSICOLORS))),
                                 self._ANSICOLORS):
                 setattr(
-                    self, 'BG_' + color, curses.tparm(set_bg_ansi, i) or '')
+                    self, 'BG_' + color, curses.tparm(set_bg_ansi, i) or b'')
 
     def _tigetstr(self, cap_name):
         # String capabilities can include "delays" of the form "$<2>".
         # For any modern terminal, we should be able to just ignore
         # these, so strip them out.
         import curses
-        cap = curses.tigetstr(cap_name) or ''
-        return re.sub(r'\$<\d+>[/*]?', '', cap)
+        cap = curses.tigetstr(cap_name) or b''
+        return re.sub(r'\$<\d+>[/*]?', '', cap.decode()).encode()
 
     def render(self, template):
-        """
-        Replace each $-substitutions in the given template string with
-        the corresponding terminal control string (if it's defined) or
-        '' (if it's not).
+        """Replaces each $-substitutions in the specified template string.
+
+        The placeholders are replaced with the corresponding terminal control
+        string (if it's defined) or '' (if it's not).
         """
         return re.sub(r'\$\$|\${\w+}', self._render_sub, template)
 
@@ -231,9 +226,9 @@ class TerminalController(object):
 
 
 class ProgressBar:
+    """A 3-line progress bar.
 
-    """
-    A 3-line progress bar, which looks like::
+    Looks like this:
 
                                 Header
         20% [===========----------------------------------]
@@ -242,6 +237,7 @@ class ProgressBar:
     The progress bar is colored, if the terminal supports color
     output; and adjusts to the width of the terminal.
     """
+
     BAR = '%3d%% ${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}\n'
     HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n'
 
@@ -275,34 +271,36 @@ class ProgressBar:
 
 
 def getLevelName(level):
+    """Returns the name of the specified log level.
+
+    Args:
+        level (int): The level we want to know the name.
+
+    Returns:
+        str: The name of the level.
     """
-    Return the name of a log level.
-    @param level: The level we want to know the name
-    @type level: int
-    @return: The name of the level
-    @rtype: str
-    """
-    assert isinstance(level, int) and level > 0 and level < 6, \
+    assert isinstance(level, int) and level > 0 and level < 7, \
         TypeError("Bad debug level")
     return getLevelNames()[level - 1]
 
 
 def getLevelNames():
-    """
-    Return a list with the level names
-    @return: A list with the level names
-    @rtype: list of str
+    """Returns a list with the level names.
+
+    Returns:
+        List[str]: A list with the level names.
     """
     return _LEVEL_NAMES
 
 
 def getLevelInt(levelName):
-    """
-    Return the integer value of the levelName.
-    @param levelName: The string value of the level name
-    @type levelName: str
-    @return: The value of the level name we are interested in.
-    @rtype: int
+    """Returns the integer value of the levelName.
+
+    Args:
+        levelName (str): The string value of the level name.
+
+    Returns:
+        int: The value of the level name we are interested in.
     """
     assert isinstance(levelName, str) and levelName in getLevelNames(), \
         "Bad debug level name"
@@ -316,8 +314,8 @@ def getFormattedLevelName(level):
 
 
 def registerCategory(category):
-    """
-    Register a given category in the debug system.
+    """Registers the specified category in the debug system.
+
     A level will be assigned to it based on previous calls to setDebug.
     """
     # parse what level it is set to based on _DEBUG
@@ -352,11 +350,13 @@ def registerCategory(category):
 
 
 def getCategoryLevel(category):
-    """
-    @param category: string
+    """Gets the debug level at which the specified category is being logged.
+
+    Registers the category and thus assigns a log level if it wasn't registered
+    yet.
 
-    Get the debug level at which this category is being logged, adding it
-    if it wasn't registered yet.
+    Args:
+        category (string): The category we are interested in.
     """
     global _categories
     if category not in _categories:
@@ -365,10 +365,13 @@ def getCategoryLevel(category):
 
 
 def setLogSettings(state):
-    """Update the current log settings.
+    """Updates the current log settings.
+
     This can restore an old saved log settings object returned by
-    getLogSettings
-    @param state: the settings to set
+    getLogSettings.
+
+    Args:
+        state: The settings to set.
     """
 
     global _DEBUG
@@ -386,9 +389,12 @@ def setLogSettings(state):
 
 def getLogSettings():
     """Fetches the current log settings.
+
     The returned object can be sent to setLogSettings to restore the
     returned settings
-    @returns: the current settings
+
+    Returns:
+        The current settings.
     """
     return (_DEBUG,
             _categories,
@@ -419,29 +425,27 @@ def scrubFilename(filename):
 
 
 def getFileLine(where=-1):
-    """
-    Return the filename and line number for the given location.
-
-    If where is a negative integer, look for the code entry in the current
-    stack that is the given number of frames above this module.
-    If where is a function, look for the code entry of the function.
+    """Returns the filename and line number for the specified location.
 
-    @param where: how many frames to go back up, or function
-    @type  where: int (negative) or function
+    Args:
+        where(int or function): If it's a (negative) integer, looks for
+            the code entry in the current stack that is the given number
+            of frames above this module.
+            If it's a function, look for the code entry of the function.
 
-    @return: tuple of (file, line)
-    @rtype:  tuple of (str, int)
+    Returns:
+        str, int, str: file, line, function_name.
     """
     co = None
     lineno = None
     name = None
 
     if isinstance(where, types.FunctionType):
-        co = where.func_code
+        co = where.__code__
         lineno = co.co_firstlineno
         name = co.co_name
     elif isinstance(where, types.MethodType):
-        co = where.im_func.func_code
+        co = where.__func__.__code__
         lineno = co.co_firstlineno
         name = co.co_name
     else:
@@ -449,10 +453,6 @@ def getFileLine(where=-1):
         while stackFrame:
             co = stackFrame.f_code
             if not co.co_filename.endswith('loggable.py'):
-                # wind up the stack according to frame
-                while where < -1:
-                    stackFrame = stackFrame.f_back
-                    where += 1
                 co = stackFrame.f_code
                 lineno = stackFrame.f_lineno
                 name = co.co_name
@@ -460,15 +460,13 @@ def getFileLine(where=-1):
             stackFrame = stackFrame.f_back
 
     if not co:
-        return "<unknown file>", 0
+        return "<unknown file>", 0, None
 
     return scrubFilename(co.co_filename), lineno, name
 
 
 def ellipsize(o):
-    """
-    Ellipsize the representation of the given object.
-    """
+    """Ellipsizes the representation of the given object."""
     r = repr(o)
     if len(r) < 800:
         return r
@@ -478,15 +476,15 @@ def ellipsize(o):
 
 
 def getFormatArgs(startFormat, startArgs, endFormat, endArgs, args, kwargs):
-    """
-    Helper function to create a format and args to use for logging.
+    """Creates a format and args to use for logging.
+
     This avoids needlessly interpolating variables.
     """
     debugArgs = startArgs[:]
     for a in args:
         debugArgs.append(ellipsize(a))
 
-    for items in kwargs.items():
+    for items in list(kwargs.items()):
         debugArgs.extend(items)
     debugArgs.extend(endArgs)
     format = startFormat \
@@ -498,22 +496,22 @@ def getFormatArgs(startFormat, startArgs, endFormat, endArgs, args, kwargs):
 
 
 def doLog(level, object, category, format, args, where=-1, filePath=None, line=None):
-    """
-    @param where:     what to log file and line number for;
-                      -1 for one frame above log.py; -2 and down for higher up;
-                      a function for a (future) code object
-    @type  where:     int or callable
-    @param filePath:  file to show the message as coming from, if caller
-                      knows best
-    @type  filePath:  str
-    @param line:      line to show the message as coming from, if caller
-                      knows best
-    @type  line:      int
-
-    @return: dict of calculated variables, if they needed calculating.
-             currently contains file and line; this prevents us from
-             doing this work in the caller when it isn't needed because
-             of the debug level
+    """Logs something.
+
+    Args:
+        where (int or function): What to log file and line number for;
+            -1 for one frame above log.py; -2 and down for higher up;
+            a function for a (future) code object.
+        filePath (Optional[str]): The file to show the message as coming from,
+            if caller knows best.
+        line (Optional[int]): The line to show the message as coming from,
+            if caller knows best.
+
+    Returns:
+        A dict of calculated variables, if they needed calculating.
+        currently contains file and line; this prevents us from
+        doing this work in the caller when it isn't needed because
+        of the debug level.
     """
     ret = {}
 
@@ -521,105 +519,80 @@ def doLog(level, object, category, format, args, where=-1, filePath=None, line=N
         message = format % args
     else:
         message = format
-
-    # first all the unlimited ones
-    if _log_handlers:
-        if filePath is None and line is None:
-            (filePath, line, funcname) = getFileLine(where=where)
-        ret['filePath'] = filePath
-        ret['line'] = line
-        if funcname:
-            message = "\033[00m\033[32;01m%s:\033[00m %s" % (funcname, message)
-        for handler in _log_handlers:
-            try:
-                handler(level, object, category, file, line, message)
-            except TypeError, e:
-                raise SystemError("handler %r raised a TypeError: %s" % (
-                    handler, getExceptionMessage(e)))
+    funcname = None
 
     if level > getCategoryLevel(category):
-        return ret
+        handlers = _log_handlers
+    else:
+        handlers = _log_handlers + _log_handlers_limited
 
-    if _log_handlers_limited:
+    if handlers:
         if filePath is None and line is None:
             (filePath, line, funcname) = getFileLine(where=where)
         ret['filePath'] = filePath
         ret['line'] = line
         if funcname:
             message = "\033[00m\033[32;01m%s:\033[00m %s" % (funcname, message)
-        for handler in _log_handlers_limited:
-            # set this a second time, just in case there weren't unlimited
-            # loggers there before
+        for handler in handlers:
             try:
                 handler(level, object, category, filePath, line, message)
-            except TypeError:
-                raise SystemError("handler %r raised a TypeError" % handler)
+            except TypeError as e:
+                raise SystemError("handler %r raised a TypeError: %s" % (
+                    handler, getExceptionMessage(e)))
 
-        return ret
+    return ret
 
 
 def errorObject(object, cat, format, *args):
-    """
-    Log a fatal error message in the given category.
-    This will also raise a L{SystemExit}.
+    """Logs a fatal error message in the specified category.
+
+    This will also raise a `SystemExit`.
     """
     doLog(ERROR, object, cat, format, args)
 
-    # we do the import here because having it globally causes weird import
-    # errors if our gstreactor also imports .log, which brings in errors
-    # and pb stuff
-    if args:
-        raise SystemExit(format % args)
-    else:
-        raise SystemExit(format)
-
 
 def warningObject(object, cat, format, *args):
-    """
-    Log a warning message in the given category.
+    """Logs a warning message in the specified category.
+
     This is used for non-fatal problems.
     """
     doLog(WARN, object, cat, format, args)
 
 
 def fixmeObject(object, cat, format, *args):
-    """
-    Log a fixme message in the given category.
-    This is used for not implemented codepaths or known issues in the code
+    """Logs a fixme message in the specified category.
+
+    This is used for not implemented codepaths or known issues in the code.
     """
     doLog(FIXME, object, cat, format, args)
 
 
 def infoObject(object, cat, format, *args):
-    """
-    Log an informational message in the given category.
-    """
+    """Logs an informational message in the specified category."""
     doLog(INFO, object, cat, format, args)
 
 
 def debugObject(object, cat, format, *args):
-    """
-    Log a debug message in the given category.
-    """
+    """Logs a debug message in the specified category."""
     doLog(DEBUG, object, cat, format, args)
 
 
 def logObject(object, cat, format, *args):
-    """
-    Log a log message.  Used for debugging recurring events.
+    """Logs a log message.
+
+    Used for debugging recurring events.
     """
     doLog(LOG, object, cat, format, args)
 
 
 def safeprintf(file, format, *args):
-    """Write to a file object, ignoring errors.
-    """
+    """Writes to a file object, ignoring errors."""
     try:
         if args:
             file.write(format % args)
         else:
             file.write(format)
-    except IOError, e:
+    except IOError as e:
         if e.errno == errno.EPIPE:
             # if our output is closed, exit; e.g. when logging over an
             # ssh connection and the ssh connection is closed
@@ -627,17 +600,19 @@ def safeprintf(file, format, *args):
         # otherwise ignore it, there's nothing you can do
 
 
-def stderrHandler(level, object, category, file, line, message):
-    """
-    A log handler that writes to stderr.
+def printHandler(level, object, category, file, line, message):
+    """Writes to stderr.
+
     The output will be different depending the value of "_enableCrackOutput";
     in Pitivi's case, that is True when the GST_DEBUG env var is defined.
 
-    @type level:    string
-    @type object:   string (or None)
-    @type category: string
-    @type message:  string
+    Args:
+        level (str):
+        object (str): Can be None.
+        category (str):
+        message (str):
     """
+    global _outfile
 
     # Make the file path more compact for readability
     file = os.path.relpath(file)
@@ -646,9 +621,9 @@ def stderrHandler(level, object, category, file, line, message):
     # If GST_DEBUG is not set, we can assume only PITIVI_DEBUG is set, so don't
     # show a bazillion of debug details that are not relevant to Pitivi.
     if not _enableCrackOutput:
-        safeprintf(sys.stderr, '%s %-8s %-17s %-2s %s %s\n',
+        safeprintf(_outfile, '%s %-8s %-17s %-2s %s %s\n',
                    getFormattedLevelName(level), time.strftime("%H:%M:%S"),
-                   category, "", message, where)
+                   category, object, message, where)
     else:
         o = ""
         if object:
@@ -656,49 +631,55 @@ def stderrHandler(level, object, category, file, line, message):
         # level   pid     object   cat      time
         # 5 + 1 + 7 + 1 + 32 + 1 + 17 + 1 + 15 == 80
         safeprintf(
-            sys.stderr, '%s [%5d] [0x%12x] %-32s %-17s %-15s %-4s %s %s\n',
-            getFormattedLevelName(level), os.getpid(), thread.get_ident(),
+            _outfile, '%s [%5d] [0x%12x] %-32s %-17s %-15s %-4s %s %s\n',
+            getFormattedLevelName(level), os.getpid(),
+            threading.current_thread().ident,
             o[:32], category, time.strftime("%b %d %H:%M:%S"), "",
             message, where)
-    sys.stderr.flush()
+    _outfile.flush()
 
 
-def _colored_formatter(level):
-    format = '%-5s'
-
-    t = TerminalController()
-    return ''.join((t.BOLD, getattr(t, COLORS[level]),
-                    format % (_LEVEL_NAMES[level - 1], ), t.NORMAL))
-
-
-def _formatter(level):
+def logLevelName(level):
     format = '%-5s'
     return format % (_LEVEL_NAMES[level - 1], )
 
 
-def _preformatLevels(noColorEnvVarName):
-    if (noColorEnvVarName is not None
-        and (noColorEnvVarName not in os.environ
-             or not os.environ[noColorEnvVarName])):
-        formatter = _colored_formatter
-    else:
-        formatter = _formatter
-
+def _preformatLevels(enableColorOutput):
+    terminal_controller = TerminalController()
     for level in ERROR, WARN, FIXME, INFO, DEBUG, LOG:
-        _FORMATTED_LEVELS.append(formatter(level))
+        if enableColorOutput:
+            if type(terminal_controller.BOLD) == bytes:
+                formatter = ''.join(
+                    (terminal_controller.BOLD.decode(),
+                     getattr(terminal_controller, COLORS[level]).decode(),
+                     logLevelName(level),
+                     terminal_controller.NORMAL.decode()))
+            else:
+                formatter = ''.join(
+                    (terminal_controller.BOLD,
+                     getattr(terminal_controller, COLORS[level]),
+                     logLevelName(level),
+                     terminal_controller.NORMAL))
+        else:
+            formatter = logLevelName(level)
+        _FORMATTED_LEVELS.append(formatter)
 
 # "public" useful API
 
 # setup functions
 
 
-def init(envVarName, enableColorOutput=False, enableCrackOutput=True):
-    """
-    Initialize the logging system and parse the environment variable
-    of the given name.
-    Needs to be called before starting the actual application.
+def init(envVarName, enableColorOutput=True, enableCrackOutput=True):
+    """Initializes the logging system.
+
+    Needs to be called before using the log methods.
+
+    Args:
+        envVarName (str): The name of the environment variable with additional
+            settings.
     """
     global _initialized
+    global _outfile
     global _enableCrackOutput
     _enableCrackOutput = enableCrackOutput
 
@@ -708,21 +689,29 @@ def init(envVarName, enableColorOutput=False, enableCrackOutput=True):
     global _ENV_VAR_NAME
     _ENV_VAR_NAME = envVarName
 
-    if enableColorOutput:
-        _preformatLevels(envVarName + "_NO_COLOR")
-    else:
-        _preformatLevels(None)
+    _preformatLevels(enableColorOutput)
 
     if envVarName in os.environ:
         # install a log handler that uses the value of the environment var
         setDebug(os.environ[envVarName])
-    addLimitedLogHandler(stderrHandler)
+    filenameEnvVarName = envVarName + "_FILE"
+
+    if filenameEnvVarName in os.environ:
+        # install a log handler that uses the value of the environment var
+        _outfile = open(os.environ[filenameEnvVarName], "w+")
+    else:
+        _outfile = sys.stderr
+
+    addLimitedLogHandler(printHandler)
 
     _initialized = True
 
 
 def setDebug(string):
-    """Set the DEBUG string.  This controls the log output."""
+    """Sets the DEBUG string.
+
+    This controls the log output.
+    """
     global _DEBUG
     global _ENV_VAR_NAME
     global _categories
@@ -736,30 +725,26 @@ def setDebug(string):
 
 
 def getDebug():
-    """
-    Returns the currently active DEBUG string.
-    @rtype: str
-    """
+    """Returns the currently active DEBUG string."""
     global _DEBUG
     return _DEBUG
 
 
 def setPackageScrubList(*packages):
-    """
-    Set the package names to scrub from filenames.
+    """Sets the package names to scrub from filenames.
+
     Filenames from these paths in log messages will be scrubbed to their
     relative file path instead of the full absolute path.
 
-    @type packages: list of str
+    Args:
+        *packages (List[str]): The packages names to scrub.
     """
     global _PACKAGE_SCRUB_LIST
     _PACKAGE_SCRUB_LIST = packages
 
 
 def reset():
-    """
-    Resets the logging system, removing all log handlers.
-    """
+    """Resets the logging system, removing all log handlers."""
     global _log_handlers, _log_handlers_limited, _initialized
 
     _log_handlers = []
@@ -768,19 +753,22 @@ def reset():
 
 
 def addLogHandler(func):
-    """
-    Add a custom log handler.
+    """Adds a custom log handler.
 
-    @param func: a function object with prototype (level, object, category,
-                 message) where level is either ERROR, WARN, INFO, DEBUG, or
-                 LOG, and the rest of the arguments are strings or None. Use
-                 getLevelName(level) to get a printable name for the log level.
-    @type func:  a callable function
+    The log handler receives all the log messages.
 
-    @raises TypeError: if func is not a callable
+    Args:
+        func (function): A function object with prototype
+            (level, object, category, message) where level is either
+            ERROR, WARN, INFO, DEBUG, or LOG, and the rest of the arguments are
+            strings or None. Use getLevelName(level) to get a printable name
+            for the log level.
+
+    Raises:
+        TypeError: When func is not a callable.
     """
 
-    if not callable(func):
+    if not isinstance(func, collections.Callable):
         raise TypeError("func must be callable")
 
     if func not in _log_handlers:
@@ -788,18 +776,21 @@ def addLogHandler(func):
 
 
 def addLimitedLogHandler(func):
-    """
-    Add a custom log handler.
+    """Adds a custom limited log handler.
+
+    The log handler receives only the messages passing the filter.
 
-    @param func: a function object with prototype (level, object, category,
-                 message) where level is either ERROR, WARN, INFO, DEBUG, or
-                 LOG, and the rest of the arguments are strings or None. Use
-                 getLevelName(level) to get a printable name for the log level.
-    @type func:  a callable function
+    Args:
+        func (function): A function object with prototype
+            (level, object, category, message) where level is either
+            ERROR, WARN, INFO, DEBUG, or LOG, and the rest of the arguments are
+            strings or None. Use getLevelName(level) to get a printable name
+            for the log level.
 
-    @raises TypeError: TypeError if func is not a callable
+    Raises:
+        TypeError: When func is not a callable.
     """
-    if not callable(func):
+    if not isinstance(func, collections.Callable):
         raise TypeError("func must be callable")
 
     if func not in _log_handlers_limited:
@@ -807,31 +798,19 @@ def addLimitedLogHandler(func):
 
 
 def removeLogHandler(func):
-    """
-    Remove a registered log handler.
-
-    @param func: a function object with prototype (level, object, category,
-                 message) where level is either ERROR, WARN, INFO, DEBUG, or
-                 LOG, and the rest of the arguments are strings or None. Use
-                 getLevelName(level) to get a printable name for the log level.
-    @type func:  a callable function
+    """Removes a registered log handler.
 
-    @raises ValueError: if func is not registered
+    Raises:
+        ValueError: When func is not registered.
     """
     _log_handlers.remove(func)
 
 
 def removeLimitedLogHandler(func):
-    """
-    Remove a registered limited log handler.
+    """Removes a registered limited log handler.
 
-    @param func: a function object with prototype (level, object, category,
-                 message) where level is either ERROR, WARN, INFO, DEBUG, or
-                 LOG, and the rest of the arguments are strings or None. Use
-                 getLevelName(level) to get a printable name for the log level.
-    @type func:  a callable function
-
-    @raises ValueError: if func is not registered
+    Raises:
+        ValueError: When func is not registered.
     """
     _log_handlers_limited.remove(func)
 
@@ -865,8 +844,9 @@ def log(cat, format, *args):
 
 
 def getExceptionMessage(exception, frame=-1, filename=None):
-    """
-    Return a short message based on an exception, useful for debugging.
+    """Returns a short message based on an exception.
+
+    Useful for debugging.
     Tries to find where the exception was triggered.
     """
     stack = traceback.extract_tb(sys.exc_info()[2])
@@ -886,16 +866,13 @@ def getExceptionMessage(exception, frame=-1, filename=None):
 
 
 def reopenOutputFiles():
-    """
-    Reopens the stdout and stderr output files, as set by
-    L{outputToFiles}.
-    """
+    """Reopens the stdout and stderr output files, as set by `outputToFiles`."""
     if not _stdout and not _stderr:
         debug('log', 'told to reopen log files, but log files not set')
         return
 
     def reopen(name, fileno, *args):
-        oldmask = os.umask(0026)
+        oldmask = os.umask(0o026)
         try:
             f = open(name, 'a+', *args)
         finally:
@@ -912,8 +889,7 @@ def reopenOutputFiles():
 
 
 def outputToFiles(stdout=None, stderr=None):
-    """
-    Redirect stdout and stderr to named files.
+    """Redirects stdout and stderr to the specified files.
 
     Records the file names so that a future call to reopenOutputFiles()
     can open the same files. Installs a SIGHUP handler that will reopen
@@ -935,7 +911,7 @@ def outputToFiles(stdout=None, stderr=None):
             _old_hup_handler(signum, frame)
 
     debug('log', 'installing SIGHUP handler')
-    import signal
+    from . import signal
     handler = signal.signal(signal.SIGHUP, sighup)
     if handler == signal.SIG_DFL or handler == signal.SIG_IGN:
         _old_hup_handler = None
@@ -947,94 +923,93 @@ def outputToFiles(stdout=None, stderr=None):
 
 
 class BaseLoggable(object):
+    """Base class for objects that want to be able to log messages.
 
-    """
-    Base class for objects that want to be able to log messages with
-    different level of severity.  The levels are, in order from least
+    The levels of severity for the messages are, in order from least
     to most: log, debug, info, warning, error.
 
-    @cvar logCategory: Implementors can provide a category to log their
-       messages under.
+    Attributes:
+        logCategory (str): The category under which the messages will be filed.
+            Can be used to set a display filter.
     """
 
-    def writeMarker(self, marker, level):
-        """
-        Sets a marker that written to the logs. Setting this
-        marker to multiple elements at a time helps debugging.
-        @param marker: A string write to the log.
-        @type marker: str
-        @param level: The log level. It can be log.WARN, log.INFO,
-        log.DEBUG, log.ERROR or log.LOG.
-        @type  level: int
-        """
-        logHandlers = {WARN: self.warning,
-                       INFO: self.info,
-                       DEBUG: self.debug,
-                       ERROR: self.error,
-                       LOG: self.log}
-        logHandler = logHandlers.get(level)
-        if logHandler:
-            logHandler('%s', marker)
-
     def error(self, *args):
-        """Log an error.  By default this will also raise an exception."""
+        """Logs an error.
+
+        By default this will also raise an exception.
+        """
         if _canShortcutLogging(self.logCategory, ERROR):
             return
         errorObject(self.logObjectName(),
                     self.logCategory, *self.logFunction(*args))
 
     def warning(self, *args):
-        """Log a warning.  Used for non-fatal problems."""
+        """Logs a warning.
+
+        Used for non-fatal problems.
+        """
         if _canShortcutLogging(self.logCategory, WARN):
             return
         warningObject(
             self.logObjectName(), self.logCategory, *self.logFunction(*args))
 
     def fixme(self, *args):
-        """Log a fixme.  Used for FIXMEs ."""
+        """Logs a fixme.
+
+        Used for FIXMEs.
+        """
         if _canShortcutLogging(self.logCategory, FIXME):
             return
         fixmeObject(self.logObjectName(),
                     self.logCategory, *self.logFunction(*args))
 
     def info(self, *args):
-        """Log an informational message.  Used for normal operation."""
+        """Logs an informational message.
+
+        Used for normal operation.
+        """
         if _canShortcutLogging(self.logCategory, INFO):
             return
         infoObject(self.logObjectName(),
                    self.logCategory, *self.logFunction(*args))
 
     def debug(self, *args):
-        """Log a debug message.  Used for debugging."""
+        """Logs a debug message.
+
+        Used for debugging.
+        """
         if _canShortcutLogging(self.logCategory, DEBUG):
             return
         debugObject(self.logObjectName(),
                     self.logCategory, *self.logFunction(*args))
 
     def log(self, *args):
-        """Log a log message.  Used for debugging recurring events."""
+        """Logs a log message.
+
+        Used for debugging recurring events.
+        """
         if _canShortcutLogging(self.logCategory, LOG):
             return
         logObject(self.logObjectName(),
                   self.logCategory, *self.logFunction(*args))
 
     def doLog(self, level, where, format, *args, **kwargs):
-        """
-        Log a message at the given level, with the possibility of going
+        """Logs a message at the specified level, with the possibility of going
         higher up in the stack.
 
-        @param level: log level
-        @type  level: int
-        @param where: how many frames to go back from the last log frame;
-                      or a function (to log for a future call)
-        @type  where: int (negative), or function
-
-        @param kwargs: a dict of pre-calculated values from a previous
-                       doLog call
-
-        @return: a dict of calculated variables, to be reused in a
-                 call to doLog that should show the same location
-        @rtype:  dict
+        Args:
+            level (int): The log level.
+            where (int or function): How many frames to go back from
+                the last log frame, must be negative; or a function
+                (to log for a future call).
+            format (str): The string template for the message.
+            *args: The arguments used when converting the `format`
+                string template to the message.
+            **kwargs: The pre-calculated values from a previous doLog call.
+
+        Returns:
+            dict: The calculated variables, to be reused in a
+                 call to doLog that should show the same location.
         """
         if _canShortcutLogging(self.logCategory, level):
             return {}
@@ -1042,29 +1017,15 @@ class BaseLoggable(object):
         return doLog(level, self.logObjectName(), self.logCategory,
                      format, args, where=where, **kwargs)
 
-    def warningFailure(self, failure, swallow=True):
-        """
-        Log a warning about a Twisted Failure. Useful as an errback handler:
-        d.addErrback(self.warningFailure)
+    def logFunction(self, *args):
+        """Processes the arguments applied to the message template.
 
-        @param swallow: whether to swallow the failure or not
-        @type  swallow: bool
+        Default just returns the arguments unchanged.
         """
-        if _canShortcutLogging(self.logCategory, WARN):
-            if swallow:
-                return
-            return failure
-        warningObject(self.logObjectName(), self.logCategory,
-                      *self.logFunction(getFailureMessage(failure)))
-        if not swallow:
-            return failure
-
-    def logFunction(self, *args):
-        """Overridable log function.  Default just returns passed message."""
         return args
 
     def logObjectName(self):
-        """Overridable object name function."""
+        """Gets the name of this object."""
         # cheat pychecker
         for name in ['logName', 'name']:
             if hasattr(self, name):
@@ -1075,146 +1036,6 @@ class BaseLoggable(object):
     def handleException(self, exc):
         self.warning(getExceptionMessage(exc))
 
-# Twisted helper stuff
-
-# private stuff
-_initializedTwisted = False
-
-# make a singleton
-__theTwistedLogObserver = None
-
-
-def _getTheTwistedLogObserver():
-    # used internally and in test
-    global __theTwistedLogObserver
-
-    if not __theTwistedLogObserver:
-        __theTwistedLogObserver = TwistedLogObserver()
-
-    return __theTwistedLogObserver
-
-
-# public helper methods
-
-
-def getFailureMessage(failure):
-    """
-    Return a short message based on L{twisted.python.failure.Failure}.
-    Tries to find where the exception was triggered.
-    """
-    exc = str(failure.type)
-    msg = failure.getErrorMessage()
-    if len(failure.frames) == 0:
-        return "failure %(exc)s: %(msg)s" % locals()
-
-    (func, filename, line, some, other) = failure.frames[-1]
-    filename = scrubFilename(filename)
-    return "failure %(exc)s at %(filename)s:%(line)s: %(func)s(): %(msg)s" % locals()
-
-
-def warningFailure(failure, swallow=True):
-    """
-    Log a warning about a Failure. Useful as an errback handler:
-    d.addErrback(warningFailure)
-
-    @param swallow: whether to swallow the failure or not
-    @type  swallow: bool
-    """
-    warning('', getFailureMessage(failure))
-    if not swallow:
-        return failure
-
-
-def logTwisted():
-    """
-    Integrate twisted's logger with our logger.
-
-    This is done in a separate method because calling this imports and sets
-    up a reactor.  Since we want basic logging working before choosing a
-    reactor, we need to separate these.
-    """
-    global _initializedTwisted
-
-    if _initializedTwisted:
-        return
-
-    debug('log', 'Integrating twisted logger')
-
-    # integrate twisted's logging with us
-    from twisted.python import log as tlog
-
-    # this call imports the reactor
-    # that is why we do this in a separate method
-    from twisted.spread import pb
-
-    # we don't want logs for pb.Error types since they
-    # are specifically raised to be handled on the other side
-    observer = _getTheTwistedLogObserver()
-    observer.ignoreErrors([pb.Error, ])
-    tlog.startLoggingWithObserver(observer.emit, False)
-
-    _initializedTwisted = True
-
-
-# we need an object as the observer because startLoggingWithObserver
-# expects a bound method
-
-
-class TwistedLogObserver(BaseLoggable):
-
-    """
-    Twisted log observer that integrates with our logging.
-    """
-    logCategory = "logobserver"
-
-    def __init__(self):
-        self._ignoreErrors = []  # Failure types
-
-    def emit(self, eventDict):
-        method = log  # by default, lowest level
-        edm = eventDict['message']
-        if not edm:
-            if eventDict['isError'] and 'failure' in eventDict:
-                f = eventDict['failure']
-                for failureType in self._ignoreErrors:
-                    r = f.check(failureType)
-                    if r:
-                        self.debug("Failure of type %r, ignoring", failureType)
-                        return
-
-                self.log("Failure %r" % f)
-
-                method = debug  # tracebacks from errors at debug level
-                msg = "A twisted traceback occurred."
-                if getCategoryLevel("twisted") < WARN:
-                    msg += "  Run with debug level >= 2 to see the traceback."
-                # and an additional warning
-                warning('twisted', msg)
-                text = f.getTraceback()
-                safeprintf(sys.stderr, "\nTwisted traceback:\n")
-                safeprintf(sys.stderr, text + '\n')
-            elif 'format' in eventDict:
-                text = eventDict['format'] % eventDict
-            else:
-                # we don't know how to log this
-                return
-        else:
-            text = ' '.join(map(str, edm))
-
-        fmtDict = {'system': eventDict['system'],
-                   'text': text.replace("\n", "\n\t")}
-        msgStr = " [%(system)s] %(text)s\n" % fmtDict
-        # because msgstr can contain %, as in a backtrace, make sure we
-        # don't try to splice it
-        method('twisted', msgStr)
-
-    def ignoreErrors(self, *types):
-        for failureType in types:
-            self._ignoreErrors.append(failureType)
-
-    def clearIgnores(self):
-        self._ignoreErrors = []
-
 
 class Loggable(BaseLoggable):
 
index cde6604..0a8d5f0 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 #
 # Copyright (c) 2014,Thibault Saunier <thibault.saunier@collabora.com>
 #
 # Boston, MA 02110-1301, USA.
 import os
 import sys
-import utils
-import urlparse
-import loggable
+from . import utils
+import urllib.parse
+from . import loggable
 import argparse
 import tempfile
-import reporters
+from . import reporters
 import subprocess
 
 
-from loggable import Loggable
-from httpserver import HTTPServer
-from vfb_server import get_virual_frame_buffer_server
-from baseclasses import _TestsLauncher, ScenarioManager
-from utils import printc, path2url, DEFAULT_MAIN_DIR, launch_command, Colors, Protocols, which
+from .loggable import Loggable
+from .httpserver import HTTPServer
+from .vfb_server import get_virual_frame_buffer_server
+from .baseclasses import _TestsLauncher, ScenarioManager
+from .utils import printc, path2url, DEFAULT_MAIN_DIR, launch_command, Colors, Protocols, which
 
 
 LESS = "less"
@@ -264,7 +264,7 @@ class LauncherConfig(Loggable):
                    % self.redirect_logs, Colors.FAIL, True)
             return False
 
-        if urlparse.urlparse(self.dest).scheme == "":
+        if urllib.parse.urlparse(self.dest).scheme == "":
             self.dest = path2url(self.dest)
 
         if self.no_color:
@@ -506,7 +506,7 @@ Note that all testsuite should be inside python modules, so the directory should
     tests_launcher.add_options(parser)
 
     if _help_message == HELP and which(LESS):
-        tmpf = tempfile.NamedTemporaryFile()
+        tmpf = tempfile.NamedTemporaryFile(mode='r+')
 
         parser.print_help(file=tmpf)
         exit(os.system("%s %s" % (LESS, tmpf.name)))
@@ -560,17 +560,18 @@ Note that all testsuite should be inside python modules, so the directory should
     # Also happened here: https://cgit.freedesktop.org/gstreamer/gst-plugins-good/commit/tests/check/Makefile.am?id=8e2c1d1de56bddbff22170f8b17473882e0e63f9
     os.environ['GSETTINGS_BACKEND'] = "memory"
 
-    e = None
+    exception = None
     try:
         tests_launcher.run_tests()
     except Exception as e:
+        exception = e
         pass
     finally:
         tests_launcher.final_report()
         tests_launcher.clean_tests()
         httpsrv.stop()
         vfb_server.stop()
-        if e is not None:
-            raise
+        if exception is not None:
+            raise exception
 
     return 0
index 9ee58aa..486276b 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 #
 # Copyright (c) 2013,Thibault Saunier <thibault.saunier@collabora.com>
 #
@@ -25,11 +25,11 @@ import time
 import codecs
 import datetime
 import tempfile
-from loggable import Loggable
+from .loggable import Loggable
 from xml.sax import saxutils
-from utils import Result, printc, Colors
+from .utils import Result, printc, Colors
 
-UNICODE_STRINGS = (type(unicode()) == type(str()))  # noqa
+UNICODE_STRINGS = (type(str()) == type(str()))  # noqa
 
 
 class UnknownResult(Exception):
@@ -91,14 +91,14 @@ class Reporter(Loggable):
         self.add_results(test)
 
     def final_report(self):
-        print "\n"
+        print("\n")
         printc("Final Report:", title=True)
         for test in sorted(self.results, key=lambda test: test.result):
             printc(test)
             if test.result != Result.PASSED:
-                print "\n"
+                print("\n")
 
-        print "\n"
+        print("\n")
         lenstat = (len("Statistics") + 1)
         printc("Statistics:\n%s" % (lenstat * "-"), Colors.OKBLUE)
         printc("\n%sTotal time spent: %s seconds\n" %
@@ -157,7 +157,7 @@ class XunitReporter(Reporter):
     def _quoteattr(self, attr):
         """Escape an XML attribute. Value can be unicode."""
         attr = xml_safe(attr)
-        if isinstance(attr, unicode) and not UNICODE_STRINGS:
+        if isinstance(attr, str) and not UNICODE_STRINGS:
             attr = attr.encode(self.encoding)
         return saxutils.quoteattr(attr)
 
@@ -175,10 +175,10 @@ class XunitReporter(Reporter):
         self.stats['total'] = (self.stats['timeout'] + self.stats['failures'] +
                                self.stats['passed'] + self.stats['skipped'])
 
-        xml_file.write(u'<?xml version="1.0" encoding="%(encoding)s"?>'
-                       u'<testsuite name="gst-validate-launcher" tests="%(total)d" '
-                       u'errors="%(timeout)d" failures="%(failures)d" '
-                       u'skip="%(skipped)d">' % self.stats)
+        xml_file.write('<?xml version="1.0" encoding="%(encoding)s"?>'
+                       '<testsuite name="gst-validate-launcher" tests="%(total)d" '
+                       'errors="%(timeout)d" failures="%(failures)d" '
+                       'skip="%(skipped)d">' % self.stats)
 
         tmp_xml_file = codecs.open(self.tmp_xml_file.name, 'r',
                                    self.encoding, 'replace')
@@ -186,7 +186,7 @@ class XunitReporter(Reporter):
         for l in tmp_xml_file:
             xml_file.write(l)
 
-        xml_file.write(u'</testsuite>')
+        xml_file.write('</testsuite>')
         xml_file.close()
         tmp_xml_file.close()
         os.remove(self.tmp_xml_file.name)
index 03b3a3f..6d27fc0 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 #
 # Copyright (c) 2013,Thibault Saunier <thibault.saunier@collabora.com>
 #
@@ -22,14 +22,14 @@ import config
 import os
 import re
 import sys
-import urllib
-import urlparse
+import urllib.request, urllib.parse, urllib.error
+import urllib.parse
 import subprocess
 
 from operator import itemgetter
 
 
-GST_SECOND = long(1000000000)
+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")
@@ -86,7 +86,7 @@ def mkdir(directory):
 
 
 def which(name, extra_path=None):
-    exts = filter(None, os.environ.get('PATHEXT', '').split(os.pathsep))
+    exts = [_f for _f in os.environ.get('PATHEXT', '').split(os.pathsep) if _f]
     path = os.environ.get('PATH', '')
     if extra_path:
         path = extra_path + os.pathsep + path
@@ -142,20 +142,20 @@ def launch_command(command, color=None, fails=False):
 
 
 def path2url(path):
-    return urlparse.urljoin('file:', urllib.pathname2url(path))
+    return urllib.parse.urljoin('file:', urllib.request.pathname2url(path))
 
 
 def url2path(url):
-    path = urlparse.urlparse(url).path
+    path = urllib.parse.urlparse(url).path
     if "win32" in sys.platform:
         if path[0] == '/':
             return path[1:]  # We need to remove the first '/' on windows
-    path = urllib.unquote(path)
+    path = urllib.parse.unquote(path)
     return path
 
 
 def isuri(string):
-    url = urlparse.urlparse(string)
+    url = urllib.parse.urlparse(string)
     if url.scheme != "" and url.scheme != "":
         return True
 
@@ -169,7 +169,7 @@ def touch(fname, times=None):
 
 def get_subclasses(klass, env):
     subclasses = []
-    for symb in env.iteritems():
+    for symb in env.items():
         try:
             if issubclass(symb[1], klass) and not symb[1] is klass:
                 subclasses.append(symb[1])
@@ -216,15 +216,15 @@ def get_data_file(subdir, name):
 
 
 def gsttime_from_tuple(stime):
-    return long((int(stime[0]) * 3600 + int(stime[1]) * 60 + int(stime[2])) * GST_SECOND + int(stime[3]))
+    return int((int(stime[0]) * 3600 + int(stime[1]) * 60 + int(stime[2])) * GST_SECOND + int(stime[3]))
 
 timeregex = re.compile(r'(?P<_0>.+):(?P<_1>.+):(?P<_2>.+)\.(?P<_3>.+)')
 
 
 def parse_gsttimeargs(time):
-    stime = map(itemgetter(1), sorted(
-        timeregex.match(time).groupdict().items()))
-    return long((int(stime[0]) * 3600 + int(stime[1]) * 60 + int(stime[2])) * GST_SECOND + int(stime[3]))
+    stime = list(map(itemgetter(1), sorted(
+        timeregex.match(time).groupdict().items())))
+    return int((int(stime[0]) * 3600 + int(stime[1]) * 60 + int(stime[2])) * GST_SECOND + int(stime[3]))
 
 
 def get_duration(media_file):
@@ -232,7 +232,7 @@ def get_duration(media_file):
     duration = 0
     res = ''
     try:
-        res = subprocess.check_output([DISCOVERER_COMMAND, media_file])
+        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.
index 3b119ea..fd9433c 100644 (file)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 #
 # Copyright (c) 2015,Thibault Saunier <tsaunier@gnome.org>
 #
@@ -19,7 +19,7 @@
 
 import os
 import time
-import loggable
+from . import loggable
 import subprocess
 
 
@@ -55,7 +55,7 @@ class Xvfb(VirtualFrameBufferServer):
                 os.environ["DISPLAY"] = self.display_id
                 subprocess.check_output(["xset", "q"],
                                         stderr=self._logsfile)
-                print("DISPLAY set to %s" % self.display_id)
+                print(("DISPLAY set to %s" % self.display_id))
                 return True
             except subprocess.CalledProcessError:
                 pass
index 3c6d114..8f4bcf8 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 #
 # Copyright (c) 2015, Edward Hervey <edward@centricular.com>
 #
index 1787f67..40013bc 100755 (executable)
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 #
 # Copyright (c) 2014,Thibault Saunier <thibault.saunier@collabora.com>
 #
@@ -31,7 +31,7 @@ def _get_git_first_hash(path):
     cdir = os.path.abspath(os.curdir)
     try:
         os.chdir(path)
-        res = subprocess.check_output(['git', 'rev-list', '--max-parents=0', 'HEAD']).rstrip('\n')
+        res = subprocess.check_output(['git', 'rev-list', '--max-parents=0', 'HEAD']).decode().rstrip('\n')
     except (subprocess.CalledProcessError, OSError):
         res = ''
     finally:
@@ -48,7 +48,7 @@ def _in_devel():
 
 def _add_gst_launcher_path():
     if _in_devel():
-        print "Running with development path"
+        print("Running with development path")
         dir_ = os.path.dirname(os.path.abspath(__file__))
         root = os.path.split(dir_)[0]
     elif __file__.startswith(BUILDDIR):