[Piglit] [PATCH 1/5] framework: Port framework to python3

Dylan Baker baker.dylan.c at gmail.com
Thu Jul 9 14:25:41 PDT 2015


This is a pretty substantial patch, but it would be a lot of work to
port this incrementally with a lot of code churn, much of which wouldn't
receive thorough testing.  And honestly, while it touches every python
file in the tree, it doesn't touch them substantially.

Due to a lot of work put in in the last few months/years the code base
is pretty modern, and most of what needs to be changed are intentional
changes to language form upstream: dicts don't have iter* methods, more
functions return iterators so they need to be wrapped in list() if they
need to be accessed by index or walked multiple times. __future__
imports are removed, and the largest change is the transition from str
-> bytes and unicode -> str.

This also changes the shbang lines in the scripts, and a small updated
to cmake to search for python3 instead of python2.

This makes two changes that I would like to split out into a separate
change, but can't figure out how to do without breaking things in
between.

The first is that it replaces bz2.BZ2File with bz2.open, this is a
requirement to get the 'wt' and 'rt' modes (which use strings rather
than bytes), and piglit uses str not bytes.

The second is that the xz code is vastly simplified. The problem is that
python3 has builtin xz support, so as soon as backends.lzma is replaced
with the builtin lzma code the rest becomes dead code, and it doesn't
make sense to me to leave dead code in place.

Signed-off-by: Dylan Baker <dylanx.c.baker at intel.com>
---
 CMakeLists.txt                               |   2 +-
 framework/backends/__init__.py               |   2 +-
 framework/backends/abstract.py               |  11 +--
 framework/backends/compression.py            | 103 +++------------------------
 framework/backends/json.py                   |  15 ++--
 framework/backends/junit.py                  |  12 ++--
 framework/core.py                            |  18 ++---
 framework/dmesg.py                           |   6 +-
 framework/grouptools.py                      |   2 +-
 framework/log.py                             |   9 ++-
 framework/profile.py                         |  26 +++----
 framework/programs/run.py                    |  10 +--
 framework/programs/summary.py                |   3 +-
 framework/results.py                         |   3 +-
 framework/status.py                          |  22 +++---
 framework/summary.py                         |  19 +++--
 framework/test/__init__.py                   |   2 +-
 framework/test/base.py                       |  49 +++++--------
 framework/test/deqp.py                       |   2 +-
 framework/test/gleantest.py                  |   2 +-
 framework/test/glsl_parser_test.py           |   4 +-
 framework/test/gtest.py                      |   2 +-
 framework/test/oclconform.py                 |   2 +-
 framework/test/opencv.py                     |   2 +-
 framework/test/piglit_test.py                |   2 +-
 framework/test/shader_test.py                |   1 -
 framework/tests/backends_tests.py            |   4 +-
 framework/tests/base_tests.py                |   2 +-
 framework/tests/core_tests.py                |   6 +-
 framework/tests/deqp_tests.py                |   2 +-
 framework/tests/dmesg_tests.py               |  16 ++---
 framework/tests/gleantest_tests.py           |   2 +-
 framework/tests/glsl_parser_test_tests.py    |   8 +--
 framework/tests/grouptools_tests.py          |   2 -
 framework/tests/gtest_tests.py               |   2 +-
 framework/tests/integration_tests.py         |   2 +-
 framework/tests/json_backend_tests.py        |   1 -
 framework/tests/json_results_update_tests.py |   7 +-
 framework/tests/json_tests.py                |   2 +-
 framework/tests/junit_backends_tests.py      |   1 -
 framework/tests/log_tests.py                 |   4 +-
 framework/tests/opencv_tests.py              |   2 +-
 framework/tests/piglit_test_tests.py         |   2 +-
 framework/tests/profile_tests.py             |   1 -
 framework/tests/results_tests.py             |   1 -
 framework/tests/run_parser_tests.py          |   2 +-
 framework/tests/shader_test_tests.py         |   2 +-
 framework/tests/status_tests.py              |   4 +-
 framework/tests/summary_tests.py             |   3 +-
 framework/tests/test_lists.py                |   2 +-
 framework/tests/utils.py                     |  20 ++++--
 piglit                                       |   4 +-
 piglit-print-commands.py                     |   4 +-
 piglit-resume.py                             |   2 +-
 piglit-run.py                                |   2 +-
 piglit-summary-html.py                       |   2 +-
 piglit-summary.py                            |   2 +-
 templates/index.mako                         |   2 +-
 templates/testrun_info.mako                  |   4 +-
 tests/all.py                                 |   5 +-
 tests/cl.py                                  |   2 +-
 tests/igt.py                                 |   3 +-
 tests/xts.py                                 |   2 +-
 63 files changed, 178 insertions(+), 287 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3ae892d..392b8cf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -203,7 +203,7 @@ IF(PIGLIT_BUILD_GLX_TESTS)
 	pkg_check_modules(GLPROTO REQUIRED glproto)
 ENDIF()
 
-find_package(PythonInterp 2.7 REQUIRED)
+find_package(PythonInterp 3.3 REQUIRED)
 find_package(PythonNumpy 1.6.2 REQUIRED)
 find_package(PythonMako 0.8.0 REQUIRED)
 find_package(PythonSix 1.4.0 REQUIRED)
diff --git a/framework/backends/__init__.py b/framework/backends/__init__.py
index 2950b33..a0723fa 100644
--- a/framework/backends/__init__.py
+++ b/framework/backends/__init__.py
@@ -157,7 +157,7 @@ def load(file_path):
 
     extension, compression = get_extension(file_path)
 
-    for backend in BACKENDS.itervalues():
+    for backend in BACKENDS.values():
         if extension in backend.extensions:
             loader = backend.load
 
diff --git a/framework/backends/abstract.py b/framework/backends/abstract.py
index 6147e84..e905d9b 100644
--- a/framework/backends/abstract.py
+++ b/framework/backends/abstract.py
@@ -25,7 +25,6 @@ This module provides mixins and base classes for backend modules.
 
 """
 
-from __future__ import print_function, absolute_import
 import abc
 import contextlib
 import itertools
@@ -54,7 +53,7 @@ def write_compressed(filename):
         yield f
 
 
-class Backend(object):
+class Backend(object, metaclass=abc.ABCMeta):
     """ Abstract base class for summary backends
 
     This class provides an abstract ancestor for classes implementing backends,
@@ -68,7 +67,6 @@ class Backend(object):
     be thread safe and not need to be locked during write)
 
     """
-    __metaclass__ = abc.ABCMeta
 
     @abc.abstractmethod
     def __init__(self, dest, metadata, **options):
@@ -165,6 +163,9 @@ class FileBackend(Backend):
         self._file_sync = file_fsync
         self._write_final = write_compressed
 
+    # Defaults to 'w', for some applications this may need to be set to 'wb'
+    # (for writing bytes instead of strings)
+    _write_mode = 'w'
     __INCOMPLETE = TestResult({'result': INCOMPLETE})
 
     def __fsync(self, file_):
@@ -201,7 +202,7 @@ class FileBackend(Backend):
         """
         def finish(val):
             tfile = file_ + '.tmp'
-            with open(tfile, 'w') as f:
+            with open(tfile, self._write_mode) as f:
                 self._write(f, name, val)
                 self.__fsync(f)
             shutil.move(tfile, file_)
@@ -209,7 +210,7 @@ class FileBackend(Backend):
         file_ = os.path.join(self._dest, 'tests', '{}.{}'.format(
             next(self._counter), self._file_extension))
 
-        with open(file_, 'w') as f:
+        with open(file_, mode=self._write_mode) as f:
             self._write(f, name, self.__INCOMPLETE)
             self.__fsync(f)
 
diff --git a/framework/backends/compression.py b/framework/backends/compression.py
index 7bc488e..06237dc 100644
--- a/framework/backends/compression.py
+++ b/framework/backends/compression.py
@@ -43,121 +43,34 @@ they're passing unicode and not bytes.
 
 from __future__ import print_function, absolute_import, division
 import bz2
-import errno
 import functools
 import gzip
+import lzma
 import os
-import subprocess
 
 from framework import exceptions
 from framework.core import PIGLIT_CONFIG
 
 
 # TODO: in python3 the bz2 module has an open function
-COMPRESSION_SUFFIXES = ['.gz', '.bz2']
+COMPRESSION_SUFFIXES = ['.gz', '.bz2', '.xz']
 
 DEFAULT = 'bz2'
 
 COMPRESSORS = {
-    'bz2': functools.partial(bz2.BZ2File, mode='w'),
-    'gz': functools.partial(gzip.open, mode='w'),
+    'bz2': functools.partial(bz2.open, mode='wt'),
+    'gz': functools.partial(gzip.open, mode='wt'),
     'none': functools.partial(open, mode='w'),
+    'xz': functools.partial(lzma.open, mode='wt'),
 }
 
 DECOMPRESSORS = {
-    'bz2': functools.partial(bz2.BZ2File, mode='r'),
-    'gz': functools.partial(gzip.open, mode='r'),
+    'bz2': functools.partial(bz2.open, mode='rt'),
+    'gz': functools.partial(gzip.open, mode='rt'),
     'none': functools.partial(open, mode='r'),
+    'xz': functools.partial(lzma.open, mode='rt'),
 }
 
-# TODO: in python3 there is builtin xz support, and doesn't need this madness
-# First try to use backports.lzma, that's the easiest solution. If that fails
-# then go to trying the shell. If that fails then piglit won't have xz support,
-# and will raise an error if xz is used
-try:
-    import backports.lzma
-
-    COMPRESSORS['xz'] = functools.partial(backports.lzma.open, mode='w')
-    DECOMPRESSORS['xz'] = functools.partial(backports.lzma.open, mode='r')
-    COMPRESSION_SUFFIXES += ['.xz']
-except ImportError:
-    try:
-        with open(os.devnull, 'w') as d:
-            subprocess.check_call(['xz'], stderr=d)
-    except subprocess.CalledProcessError as e:
-        if e.returncode == 1:
-            import contextlib
-            try:
-                import cStringIO as StringIO
-            except ImportError:
-                import StringIO
-
-            @contextlib.contextmanager
-            def _compress_xz(filename):
-                """Emulates an open function in write mode for xz.
-
-                Python 2.x doesn't support xz, but it's dang useful. This
-                function calls out to the shell and tries to use xz from the
-                environment to get xz compression.
-
-                This obviously won't work without a working xz binary.
-
-                This function tries to emulate the default values of the lzma
-                module in python3 as much as possible
-
-                """
-                if filename.endswith('.xz'):
-                    filename = filename[:-2]
-
-                with open(filename, 'w') as f:
-                    yield f
-
-                try:
-                    subprocess.check_call(['xz', '--compress', '-9', filename])
-                except OSError as e:
-                    if e.errno == errno.ENOENT:
-                        raise exceptions.PiglitFatalError(
-                            'No xz binary available')
-                    raise
-
-            @contextlib.contextmanager
-            def _decompress_xz(filename):
-                """Eumlates an option function in read mode for xz.
-
-                See the comment in _compress_xz for more information.
-
-                This function tries to emulate the lzma module as much as
-                possible
-
-                """
-                if not filename.endswith('.xz'):
-                    filename = '{}.xz'.format(filename)
-
-                try:
-                    string = subprocess.check_output(
-                        ['xz', '--decompress', '--stdout', filename])
-                except OSError as e:
-                    if e.errno == errno.ENOENT:
-                        raise exceptions.PiglitFatalError(
-                            'No xz binary available')
-                    raise
-
-                # We need a file-like object, so the contents must be placed in
-                # a StringIO object.
-                io = StringIO.StringIO()
-                io.write(string)
-                io.seek(0)
-
-                yield io
-
-                io.close()
-
-            COMPRESSORS['xz'] = _compress_xz
-            DECOMPRESSORS['xz'] = _decompress_xz
-            COMPRESSION_SUFFIXES += ['.xz']
-    except OSError:
-        pass
-
 
 def _set_mode():
     """Set the compression mode.
diff --git a/framework/backends/json.py b/framework/backends/json.py
index b40eda7..21f11c9 100644
--- a/framework/backends/json.py
+++ b/framework/backends/json.py
@@ -20,7 +20,7 @@
 
 """ Module providing json backend for piglit """
 
-from __future__ import print_function, absolute_import
+
 import os
 import sys
 import shutil
@@ -58,6 +58,11 @@ def piglit_encoder(obj):
         return str(obj)
     elif isinstance(obj, set):
         return list(obj)
+    elif isinstance(obj, bytes):
+        # simplejson handles bytes correctly, but the builtin json module runs
+        # into some bizarre errors I don't really understand when passed bytes
+        # objects, converting them to strings fixes the issue.
+        return str(obj, encoding='utf-8')
     return obj
 
 
@@ -317,7 +322,7 @@ def _update_results(results, filepath):
 def _write(results, file_):
     """WRite the values of the results out to a file."""
     with write_compressed(file_) as f:
-        json.dump({k:v for k, v in results.__dict__.iteritems()},
+        json.dump({k:v for k, v in results.__dict__.items()},
                   f,
                   default=piglit_encoder,
                   indent=INDENT)
@@ -344,7 +349,7 @@ def _update_zero_to_one(results):
     updated_results = {}
     remove = set()
 
-    for name, test in results.tests.iteritems():
+    for name, test in results.tests.items():
         # fix dmesg errors if any
         if isinstance(test.get('dmesg'), list):
             test['dmesg'] = '\n'.join(test['dmesg'])
@@ -393,7 +398,7 @@ def _update_zero_to_one(results):
         #
         # this must be the last thing done in this loop, or there will be pain
         if test.get('subtest'):
-            for sub in test['subtest'].iterkeys():
+            for sub in test['subtest'].keys():
                 # adding the leading / ensures that we get exactly what we
                 # expect, since endswith does a character by chacter match, if
                 # the subtest name is duplicated it wont match, and if there
@@ -521,7 +526,7 @@ def _update_four_to_five(results):
     """Updates json results from version 4 to version 5."""
     new_tests = {}
 
-    for name, test in results.tests.iteritems():
+    for name, test in results.tests.items():
         new_tests[name.replace('/', '@').replace('\\', '@')] = test
 
     results.tests = new_tests
diff --git a/framework/backends/junit.py b/framework/backends/junit.py
index f052007..bd5383e 100644
--- a/framework/backends/junit.py
+++ b/framework/backends/junit.py
@@ -20,7 +20,7 @@
 
 """ Module implementing a JUnitBackend for piglit """
 
-from __future__ import print_function, absolute_import
+
 import os.path
 import shutil
 
@@ -57,6 +57,7 @@ class JUnitBackend(FileBackend):
     https://svn.jenkins-ci.org/trunk/hudson/dtkit/dtkit-format/dtkit-junit-model/src/main/resources/com/thalesgroup/dtkit/junit/model/xsd/junit-7.xsd
 
     """
+    _write_mode = 'wb'
     _file_extension = 'xml'
 
     def __init__(self, dest, junit_suffix='', **options):
@@ -107,13 +108,12 @@ class JUnitBackend(FileBackend):
         # This must be bytes or unicode
         piglit.attrib['tests'] = str(len(piglit))
 
-        with open(os.path.join(self._dest, 'results.xml'), 'w') as f:
-            f.write("<?xml version='1.0' encoding='utf-8'?>\n")
+        with open(os.path.join(self._dest, 'results.xml'), 'wb') as f:
             # lxml has a pretty print we want to use
             if etree.__name__ == 'lxml.etree':
-                f.write(etree.tostring(root, pretty_print=True))
+                f.write(etree.tostring(root, encoding='utf-8', pretty_print=True))
             else:
-                f.write(etree.tostring(root))
+                f.write(etree.tostring(root, encoding='utf-8'))
 
         shutil.rmtree(os.path.join(self._dest, 'tests'))
 
@@ -216,7 +216,7 @@ class JUnitBackend(FileBackend):
         else:
             etree.SubElement(element, 'failure', message='Incomplete run.')
 
-        f.write(etree.tostring(element))
+        f.write(etree.tostring(element, encoding='utf-8'))
 
 
 def _load(results_file):
diff --git a/framework/core.py b/framework/core.py
index f9cdbfe..8796403 100644
--- a/framework/core.py
+++ b/framework/core.py
@@ -22,13 +22,13 @@
 
 # Piglit core
 
-from __future__ import print_function, absolute_import
+
 import errno
 import os
 import re
 import subprocess
 import sys
-import ConfigParser
+import configparser
 
 from framework import exceptions
 
@@ -44,18 +44,18 @@ __all__ = [
 PLATFORMS = ["glx", "x11_egl", "wayland", "gbm", "mixed_glx_egl"]
 
 
-class PiglitConfig(ConfigParser.SafeConfigParser):
+class PiglitConfig(configparser.SafeConfigParser):
     """Custom Config parser that provides a few extra helpers."""
     def __init__(self, *args, **kwargs):
         # In Python2 the ConfigParser classes are old style, you can't use
         # super() on them. sigh
-        ConfigParser.SafeConfigParser.__init__(self, *args, **kwargs)
+        configparser.SafeConfigParser.__init__(self, *args, **kwargs)
         self.filename = None
 
     def readfp(self, fp, filename=None):
         # In Python2 the ConfigParser classes are old style, you can't use
         # super() on them. sigh
-        ConfigParser.SafeConfigParser.readfp(self, fp, filename)
+        configparser.SafeConfigParser.readfp(self, fp, filename)
         self.filename = os.path.abspath(filename or fp.name)
 
     def safe_get(self, *args, **kwargs):
@@ -68,7 +68,7 @@ class PiglitConfig(ConfigParser.SafeConfigParser):
         """
         try:
             return self.get(*args, **kwargs)
-        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
+        except (configparser.NoOptionError, configparser.NoSectionError):
             return None
 
     def required_get(self, section, option, **kwargs):
@@ -80,12 +80,12 @@ class PiglitConfig(ConfigParser.SafeConfigParser):
         """
         try:
             return self.get(section, option, **kwargs)
-        except ConfigParser.NoSectionError:
+        except configparser.NoSectionError:
             raise exceptions.PiglitFatalError(
                 'No Section "{}" in file "{}".\n'
                 'This section is required.'.format(
                     section, self.filename))
-        except ConfigParser.NoOptionError:
+        except configparser.NoOptionError:
             raise exceptions.PiglitFatalError(
                 'No option "{}"  from section "{}" in file "{}".\n'
                 'This option is required.'.format(
@@ -173,7 +173,7 @@ class Options(object):
         }
 
     def __iter__(self):
-        for key, values in self.__dict__.iteritems():
+        for key, values in self.__dict__.items():
             # If the values are regex compiled then yield their pattern
             # attribute, which is the original plaintext they were compiled
             # from, otherwise yield them normally.
diff --git a/framework/dmesg.py b/framework/dmesg.py
index 1a5f629..f1cb976 100644
--- a/framework/dmesg.py
+++ b/framework/dmesg.py
@@ -35,7 +35,7 @@ dmesg implementation for their OS.
 
 """
 
-from __future__ import print_function, absolute_import
+
 import re
 import sys
 import subprocess
@@ -136,7 +136,7 @@ class BaseDmesg(object):
 
             # Replace the results of any subtests
             if 'subtest' in result:
-                for key, value in result['subtest'].iteritems():
+                for key, value in result['subtest'].items():
                     result['subtest'][key] = replace(value)
 
             # Add the dmesg values to the result
@@ -171,7 +171,7 @@ class LinuxDmesg(BaseDmesg):
             warnings.warn("Cannot check dmesg for timestamp support. If you "
                           "do not have timestamps enabled in your kernel you "
                           "get incomplete dmesg captures", RuntimeWarning)
-        elif not re.match(r'\[\s*\d+\.\d+\]', self._last_message):
+        elif not re.match(b'\[\s*\d+\.\d+\]', self._last_message):
             # Do an initial check to ensure that dmesg has timestamps, we need
             # timestamps to work correctly. A proper linux dmesg timestamp
             # looks like this: [    0.00000]
diff --git a/framework/grouptools.py b/framework/grouptools.py
index 81e6091..a664b07 100644
--- a/framework/grouptools.py
+++ b/framework/grouptools.py
@@ -137,7 +137,7 @@ def from_path(path):
     This safely handles both Windows and Unix style paths.
 
     """
-    assert isinstance(path, (str, unicode)), 'Type must be string or unicode'
+    assert isinstance(path, str), 'Type must be type str'
 
     if '\\' in path:
         path = path.replace('\\', SEPARATOR)
diff --git a/framework/log.py b/framework/log.py
index 759974a..4072326 100644
--- a/framework/log.py
+++ b/framework/log.py
@@ -26,7 +26,7 @@ returning BaseLog derived instances to individual tests.
 
 """
 
-from __future__ import print_function, absolute_import
+
 import sys
 import abc
 import itertools
@@ -36,7 +36,7 @@ import collections
 __all__ = ['LogManager']
 
 
-class BaseLog(object):
+class BaseLog(object, metaclass=abc.ABCMeta):
     """ Abstract base class for Log objects
 
     It provides a lock, which should be used to lock whever the shared state is
@@ -46,7 +46,6 @@ class BaseLog(object):
     state -- the state dict from LogManager
 
     """
-    __metaclass__ = abc.ABCMeta
 
     SUMMARY_KEYS = set([
         'pass', 'fail', 'warn', 'crash', 'skip', 'dmesg-warn', 'dmesg-fail',
@@ -109,7 +108,7 @@ class QuietLog(BaseLog):
         else:
             self._endcode = '\n'
 
-        self.__counter = self._test_counter.next()
+        self.__counter = next(self._test_counter)
         self._state['running'].append(self.__counter)
 
     def start(self, name):
@@ -154,7 +153,7 @@ class QuietLog(BaseLog):
             done=str(self._state['complete']).zfill(self._pad),
             total=str(self._state['total']).zfill(self._pad),
             status=', '.join('{0}: {1}'.format(k, v) for k, v in
-                             sorted(self._state['summary'].iteritems())),
+                             sorted(self._state['summary'].items())),
             running=''.join('|/-\\'[x % 4] for x in self._state['running'])
         )
 
diff --git a/framework/profile.py b/framework/profile.py
index bce28b9..ec586d8 100644
--- a/framework/profile.py
+++ b/framework/profile.py
@@ -26,7 +26,7 @@ are represented by a TestProfile or a TestProfile derived object.
 
 """
 
-from __future__ import print_function, absolute_import
+
 import os
 import multiprocessing
 import multiprocessing.dummy
@@ -73,7 +73,7 @@ class TestDict(dict):  # pylint: disable=too-few-public-methods
 
         """
         # keys should be strings
-        if not isinstance(key, basestring):
+        if not isinstance(key, str):
             raise exceptions.PiglitFatalError(
                 "TestDict keys must be strings, but was {}".format(type(key)))
 
@@ -103,7 +103,7 @@ class TestDict(dict):  # pylint: disable=too-few-public-methods
                 "A test has already been asigned the name: {}\n{}".format(
                     key, error))
 
-        super(TestDict, self).__setitem__(key, value)
+        super().__setitem__(key, value)
 
     def __getitem__(self, key):
         """Lower the value before returning."""
@@ -208,7 +208,7 @@ class TestProfile(object):
             return True
 
         # Filter out unwanted tests
-        self.test_list = dict(item for item in self.test_list.iteritems()
+        self.test_list = dict(item for item in self.test_list.items()
                               if check_all(item))
 
         if not self.test_list:
@@ -289,15 +289,15 @@ class TestProfile(object):
         multi = multiprocessing.dummy.Pool()
 
         if opts.concurrent == "all":
-            run_threads(multi, self.test_list.iteritems())
+            run_threads(multi, iter(self.test_list.items()))
         elif opts.concurrent == "none":
-            run_threads(single, self.test_list.iteritems())
+            run_threads(single, iter(self.test_list.items()))
         else:
             # Filter and return only thread safe tests to the threaded pool
-            run_threads(multi, (x for x in self.test_list.iteritems()
+            run_threads(multi, (x for x in self.test_list.items()
                                 if x[1].run_concurrent))
             # Filter and return the non thread safe tests to the single pool
-            run_threads(single, (x for x in self.test_list.iteritems()
+            run_threads(single, (x for x in self.test_list.items()
                                  if not x[1].run_concurrent))
 
         log.get().summary()
@@ -363,7 +363,7 @@ class TestProfile(object):
         ...     g(['power', 'test'], 'powertest')
 
         """
-        assert isinstance(group, basestring), type(group)
+        assert isinstance(group, str), type(group)
 
         def adder(args, name=None, **kwargs):
             """Helper function that actually adds the tests.
@@ -389,16 +389,16 @@ class TestProfile(object):
                 if isinstance(args, list):
                     name = ' '.join(args)
                 else:
-                    assert isinstance(args, basestring)
+                    assert isinstance(args, str)
                     name = args
 
-            assert isinstance(name, basestring)
+            assert isinstance(name, str)
             lgroup = grouptools.join(group, name)
 
             self.test_list[lgroup] = test_class(
                 args,
-                **{k: v for k, v in itertools.chain(default_args.iteritems(),
-                                                    kwargs.iteritems())})
+                **{k:v for k, v in itertools.chain(default_args.items(),
+                                                   kwargs.items())})
 
         yield adder
 
diff --git a/framework/programs/run.py b/framework/programs/run.py
index 2981ffa..cc85aeb 100644
--- a/framework/programs/run.py
+++ b/framework/programs/run.py
@@ -20,13 +20,13 @@
 # DEALINGS IN THE SOFTWARE.
 
 
-from __future__ import print_function
+
 import argparse
 import sys
 import os
 import os.path as path
 import time
-import ConfigParser
+import configparser
 import ctypes
 
 from framework import core, backends, exceptions
@@ -63,7 +63,7 @@ def _default_platform():
                     'Platform is not valid\nvalid platforms are: {}'.format(
                         core.PLATFORMS))
             return plat
-        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
+        except (configparser.NoOptionError, configparser.NoSectionError):
             return 'mixed_glx_egl'
 
 
@@ -81,7 +81,7 @@ def _default_backend():
                 'Backend is not valid\nvalid backends are: {}'.format(
                       ' '.join(backends.BACKENDS.keys())))
         return backend
-    except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
+    except (configparser.NoOptionError, configparser.NoSectionError):
         return 'json'
 
 
@@ -343,7 +343,7 @@ def resume(input_):
 
     # Don't re-run tests that have already completed, incomplete status tests
     # have obviously not completed.
-    for name, result in results.tests.iteritems():
+    for name, result in results.tests.items():
         if args.no_retry or result['result'] != 'incomplete':
             opts.exclude_tests.add(name)
 
diff --git a/framework/programs/summary.py b/framework/programs/summary.py
index 069aed9..dfc2c2e 100644
--- a/framework/programs/summary.py
+++ b/framework/programs/summary.py
@@ -19,7 +19,6 @@
 # OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 
-from __future__ import print_function, absolute_import
 import argparse
 import shutil
 import os
@@ -168,7 +167,7 @@ def csv(input_):
     testrun = backends.load(args.testResults)
 
     def write_results(output):
-        for name, result in testrun.tests.iteritems():
+        for name, result in testrun.tests.items():
             output.write("{},{},{},{}\n".format(name, result.get('time', ""),
                                                 result.get('returncode', ""),
                                                 result['result']))
diff --git a/framework/results.py b/framework/results.py
index dd1a645..e075833 100644
--- a/framework/results.py
+++ b/framework/results.py
@@ -21,7 +21,6 @@
 
 """ Module for results generation """
 
-from __future__ import print_function, absolute_import
 import framework.status as status
 
 __all__ = [
@@ -54,7 +53,7 @@ class TestResult(dict):
 
         """
         def update(d, u, check):
-            for k, v in u.iteritems():
+            for k, v in u.items():
                 if isinstance(v, dict):
                     d[k] = update(d.get(k, {}), v, True)
                 else:
diff --git a/framework/status.py b/framework/status.py
index 98a9e3d..aebba07 100644
--- a/framework/status.py
+++ b/framework/status.py
@@ -56,7 +56,6 @@ The formula for determining fixes is:
 
 """
 
-from __future__ import print_function, absolute_import
 
 __all__ = ['NOTRUN',
            'PASS',
@@ -79,6 +78,9 @@ def status_lookup(status):
     does not correspond to a key it will raise an exception
 
     """
+    if isinstance(status, Status):
+        return status
+
     status_dict = {
         'skip': SKIP,
         'pass': PASS,
@@ -165,9 +167,6 @@ class Status(object):
     def __str__(self):
         return str(self.name)
 
-    def __unicode__(self):
-        return unicode(self.name)
-
     def __lt__(self, other):
         return not self.__ge__(other)
 
@@ -179,8 +178,8 @@ class Status(object):
         # the __int__ magic method
         if isinstance(other, (int, Status)):
             return int(self) == int(other)
-        elif isinstance(other, (str, unicode)):
-            return unicode(self) == unicode(other)
+        elif isinstance(other, str):
+            return str(self) == str(other)
         raise TypeError("Cannot compare type: {}".format(type(other)))
 
     def __ne__(self, other):
@@ -211,15 +210,18 @@ class NoChangeStatus(Status):
         super(NoChangeStatus, self).__init__(name, value, fraction)
 
     def __eq__(self, other):
-        if isinstance(other, (str, unicode, Status)):
-            return unicode(self) == unicode(other)
+        if isinstance(other, (str, Status)):
+            return str(self) == str(other)
         raise TypeError("Cannot compare type: {}".format(type(other)))
 
     def __ne__(self, other):
-        if isinstance(other, (str, unicode, Status)):
-            return unicode(self) != unicode(other)
+        if isinstance(other, (str, Status)):
+            return str(self) != str(other)
         raise TypeError("Cannot compare type: {}".format(type(other)))
 
+    def __hash__(self):
+        return hash(self.name)
+
 
 NOTRUN = NoChangeStatus('Not Run')
 
diff --git a/framework/summary.py b/framework/summary.py
index 8d5ad10..2169e0d 100644
--- a/framework/summary.py
+++ b/framework/summary.py
@@ -19,7 +19,6 @@
 # OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 # DEALINGS IN THE SOFTWARE.
 
-from __future__ import print_function, absolute_import
 import os
 import os.path as path
 import itertools
@@ -100,7 +99,7 @@ class HTMLIndex(list):
             if open == close:
                 return [], []
             else:
-                for i, j in itertools.izip_longest(open, close):
+                for i, j in itertools.zip_longest(open, close):
                     if i != j:
                         for k in common:
                             open.remove(k)
@@ -341,7 +340,7 @@ class Summary:
             # loop will cause a RuntimeError
             temp_results = {}
 
-            for key, value in results.tests.iteritems():
+            for key, value in results.tests.items():
                 # if the first character of key is a / then our while loop will
                 # become an infinite loop. Beyond that / should never be the
                 # leading character, if it is then there is a bug in one of the
@@ -352,7 +351,7 @@ class Summary:
                 # subtests' statuses and fractions down to the test, and then
                 # proceed like normal.
                 if 'subtest' in value:
-                    for (subt, subv) in value['subtest'].iteritems():
+                    for (subt, subv) in value['subtest'].items():
                         subt = grouptools.join(key, subt)
                         subv = so.status_lookup(subv)
 
@@ -386,7 +385,7 @@ class Summary:
             # Update the the results.tests dictionary with the subtests so that
             # they are entered into the appropriate pages other than all.
             # Updating it in the loop will raise a RuntimeError
-            for key, value in temp_results.iteritems():
+            for key, value in temp_results.items():
                 results.tests[key] = value
 
         # Create the lists of statuses like problems, regressions, fixes,
@@ -412,7 +411,7 @@ class Summary:
                 self.tests['incomplete'].add(test)
 
             # find fixes, regressions, and changes
-            for i in xrange(len(status) - 1):
+            for i in range(len(status) - 1):
                 first = status[i]
                 last = status[i + 1]
                 if first in [so.SKIP, so.NOTRUN] and \
@@ -439,7 +438,7 @@ class Summary:
                        'timeout': 0, 'warn': 0, 'dmesg-warn': 0,
                        'dmesg-fail': 0, 'incomplete': 0,}
 
-        for test in results.tests.itervalues():
+        for test in results.tests.values():
             self.totals[str(test['result'])] += 1
 
     def generate_html(self, destination, exclude):
@@ -506,7 +505,7 @@ class Summary:
                     lspci=each.lspci))
 
             # Then build the individual test results
-            for key, value in each.tests.iteritems():
+            for key, value in each.tests.items():
                 html_path = path.join(destination, name,
                                       escape_filename(key + ".html"))
                 temp_path = path.dirname(html_path)
@@ -600,9 +599,9 @@ class Summary:
                 print("    changes: {changes}\n"
                       "      fixes: {fixes}\n"
                       "regressions: {regressions}".format(
-                          **{k: len(v) for k, v in self.tests.iteritems()}))
+                          **{k: len(v) for k, v in self.tests.items()}))
 
-            print("      total: {}".format(sum(self.totals.itervalues())))
+            print("      total: {}".format(sum(self.totals.values())))
 
         # Print the name of the test and the status from each test run
         if mode == 'all':
diff --git a/framework/test/__init__.py b/framework/test/__init__.py
index a9b5f4e..1de27e6 100644
--- a/framework/test/__init__.py
+++ b/framework/test/__init__.py
@@ -24,7 +24,7 @@
 # create a general use API, but allow it to be controlled by setting the
 # __all__ in each module
 
-from __future__ import print_function, absolute_import
+
 from .base import *
 from .piglit_test import *
 from .gleantest import *
diff --git a/framework/test/base.py b/framework/test/base.py
index b4ee4ad..29d66d9 100644
--- a/framework/test/base.py
+++ b/framework/test/base.py
@@ -22,7 +22,7 @@
 
 """ Module provides a base class for Tests """
 
-from __future__ import print_function, absolute_import
+
 import errno
 import os
 import subprocess
@@ -110,10 +110,11 @@ class ProcessTimeout(threading.Thread):
 
 
 def _is_crash_returncode(returncode):
-    """Determine whether the given process return code correspond to a
-    crash.
+    """Determine whether the given process return code correspond to a crash.  
     """
-    if sys.platform == 'win32':
+    if returncode is None:
+        return False
+    elif sys.platform == 'win32':
         # On Windows:
         # - For uncaught exceptions the process terminates with the exception
         # code, which is usually negative
@@ -123,7 +124,7 @@ def _is_crash_returncode(returncode):
         return returncode < 0
 
 
-class Test(object):
+class Test(object, metaclass=abc.ABCMeta):
     """ Abstract base class for Test classes
 
     This class provides the framework for running tests, with several methods
@@ -143,7 +144,6 @@ class Test(object):
 
     """
     OPTS = Options()
-    __metaclass__ = abc.ABCMeta
     __slots__ = ['run_concurrent', 'env', 'result', 'cwd', '_command',
                  '_test_hook_execute_run', '__proc_timeout']
     timeout = 0
@@ -220,13 +220,13 @@ class Test(object):
         self.result['command'] = ' '.join(self.command)
         self.result['environment'] = " ".join(
             '{0}="{1}"'.format(k, v) for k, v in itertools.chain(
-                self.OPTS.env.iteritems(), self.env.iteritems()))
+                iter(self.OPTS.env.items()), iter(self.env.items())))
 
         try:
             self.is_skip()
         except TestIsSkip as e:
             self.result['result'] = 'skip'
-            self.result['out'] = unicode(e)
+            self.result['out'] = str(e)
             self.result['err'] = u""
             self.result['returncode'] = None
             return
@@ -234,9 +234,9 @@ class Test(object):
         try:
             self._run_command()
         except TestRunError as e:
-            self.result['result'] = unicode(e.status)
-            self.result['out'] = unicode(e)
-            self.result['err'] = u""
+            self.result['result'] = e.status
+            self.result['out'] = str(e)
+            self.result['err'] = ""
             self.result['returncode'] = None
             return
 
@@ -297,9 +297,9 @@ class Test(object):
         # requirements.
         #
         fullenv = dict()
-        for key, value in itertools.chain(os.environ.iteritems(),
-                                          self.OPTS.env.iteritems(),
-                                          self.env.iteritems()):
+        for key, value in itertools.chain(iter(os.environ.items()),
+                                          iter(self.OPTS.env.items()),
+                                          iter(self.env.items())):
             fullenv[key] = str(value)
 
         # preexec_fn is not supported on Windows platforms
@@ -337,22 +337,9 @@ class Test(object):
             else:
                 raise e
 
-        # proc.communicate() returns 8-bit strings, but we need
-        # unicode strings.  In Python 2.x, this is because we
-        # will eventually be serializing the strings as JSON,
-        # and the JSON library expects unicode.  In Python 3.x,
-        # this is because all string operations require
-        # unicode.  So translate the strings into unicode,
-        # assuming they are using UTF-8 encoding.
-        #
-        # If the subprocess output wasn't properly UTF-8
-        # encoded, we don't want to raise an exception, so
-        # translate the strings using 'replace' mode, which
-        # replaces erroneous charcters with the Unicode
-        # "replacement character" (a white question mark inside
-        # a black diamond).
-        self.result['out'] = out.decode('utf-8', 'replace')
-        self.result['err'] = err.decode('utf-8', 'replace')
+        # Popen returns bytes, but the rest of the code assumes unicode
+        self.result['out'] = str(out)
+        self.result['err'] = str(err)
         self.result['returncode'] = returncode
 
     def __eq__(self, other):
@@ -382,7 +369,7 @@ class WindowResizeMixin(object):
         Test.run() to return early.
 
         """
-        for _ in xrange(5):
+        for _ in range(5):
             super(WindowResizeMixin, self)._run_command()
             if "Got spurious window resize" not in self.result['out']:
                 return
diff --git a/framework/test/deqp.py b/framework/test/deqp.py
index 1462ca2..b407e0c 100644
--- a/framework/test/deqp.py
+++ b/framework/test/deqp.py
@@ -146,7 +146,7 @@ class DEQPBaseTest(Test):
 
         for line in self.result['out'].split('\n'):
             line = line.lstrip()
-            for k, v in self.__RESULT_MAP.iteritems():
+            for k, v in self.__RESULT_MAP.items():
                 if line.startswith(k):
                     self.result['result'] = v
                     return
diff --git a/framework/test/gleantest.py b/framework/test/gleantest.py
index 539212f..41ef60d 100644
--- a/framework/test/gleantest.py
+++ b/framework/test/gleantest.py
@@ -22,7 +22,7 @@
 
 """ Glean support """
 
-from __future__ import print_function, absolute_import
+
 import os
 from .base import Test, TestIsSkip
 from .piglit_test import TEST_BIN_DIR
diff --git a/framework/test/glsl_parser_test.py b/framework/test/glsl_parser_test.py
index e5cd542..0b18784 100644
--- a/framework/test/glsl_parser_test.py
+++ b/framework/test/glsl_parser_test.py
@@ -21,7 +21,7 @@
 
 """ This module enables the running of GLSL parser tests. """
 
-from __future__ import print_function, absolute_import
+
 import os
 import re
 
@@ -65,7 +65,7 @@ class GLSLParserTest(PiglitBaseTest):
         self.__found_keys = set()
 
         # Parse the config file and get the config section, then write this
-        # section to a StringIO and pass that to ConfigParser
+        # section to a StringIO and pass that to configparser
         with open(filepath, 'r') as testfile:
             try:
                 command = self.__get_command(self.__parser(testfile, filepath),
diff --git a/framework/test/gtest.py b/framework/test/gtest.py
index cd80871..9cf2390 100644
--- a/framework/test/gtest.py
+++ b/framework/test/gtest.py
@@ -22,7 +22,7 @@
 # Authors: Tom Stellard <thomas.stellard at amd.com>
 #
 
-from __future__ import print_function, absolute_import 
+ 
 import re
 
 from .base import Test
diff --git a/framework/test/oclconform.py b/framework/test/oclconform.py
index 7f6e27d..064d28e 100644
--- a/framework/test/oclconform.py
+++ b/framework/test/oclconform.py
@@ -22,7 +22,7 @@
 # Authors: Tom Stellard <thomas.stellard at amd.com>
 #
 
-from __future__ import print_function, print_function
+
 import re
 import subprocess
 from os.path import join
diff --git a/framework/test/opencv.py b/framework/test/opencv.py
index 157102e..93fcba9 100644
--- a/framework/test/opencv.py
+++ b/framework/test/opencv.py
@@ -22,7 +22,7 @@
 # Authors: Tom Stellard <thomas.stellard at amd.com>
 #
 
-from __future__ import print_function, absolute_import
+
 import re
 import subprocess
 from os import path
diff --git a/framework/test/piglit_test.py b/framework/test/piglit_test.py
index a4d3c8d..5b5cf63 100644
--- a/framework/test/piglit_test.py
+++ b/framework/test/piglit_test.py
@@ -22,7 +22,7 @@
 
 """ Module provides a base class for Tests """
 
-from __future__ import print_function, absolute_import
+
 import os
 import sys
 import glob
diff --git a/framework/test/shader_test.py b/framework/test/shader_test.py
index d06eb2d..8554504 100644
--- a/framework/test/shader_test.py
+++ b/framework/test/shader_test.py
@@ -23,7 +23,6 @@
 
 """ This module enables running shader tests. """
 
-from __future__ import print_function, absolute_import
 import re
 
 from framework import exceptions
diff --git a/framework/tests/backends_tests.py b/framework/tests/backends_tests.py
index 8084cdf..4f796e9 100644
--- a/framework/tests/backends_tests.py
+++ b/framework/tests/backends_tests.py
@@ -22,7 +22,7 @@
 
 """ Tests for the backend package """
 
-from __future__ import print_function, absolute_import
+
 import os
 
 import nose.tools as nt
@@ -77,7 +77,7 @@ def test_get_backend():
     def check(n, i):
         return nt.assert_is(backends.get_backend(n), i)
 
-    for name, inst in backends_.iteritems():
+    for name, inst in backends_.items():
         check.description = \
             'backends.get_backend({0}): returns {0} backend'.format(name)
         yield check, name, inst
diff --git a/framework/tests/base_tests.py b/framework/tests/base_tests.py
index a9e0e88..41916a8 100644
--- a/framework/tests/base_tests.py
+++ b/framework/tests/base_tests.py
@@ -20,7 +20,7 @@
 
 """ Tests for the exectest module """
 
-from __future__ import print_function, absolute_import
+
 
 import nose.tools as nt
 
diff --git a/framework/tests/core_tests.py b/framework/tests/core_tests.py
index bfbc87b..66a2f7b 100644
--- a/framework/tests/core_tests.py
+++ b/framework/tests/core_tests.py
@@ -20,12 +20,12 @@
 
 """ Module providing tests for the core module """
 
-from __future__ import print_function, absolute_import
-import os
+
 import collections
+import functools
+import os
 import shutil
 import textwrap
-import functools
 
 import nose.tools as nt
 
diff --git a/framework/tests/deqp_tests.py b/framework/tests/deqp_tests.py
index 1225523..ade6483 100644
--- a/framework/tests/deqp_tests.py
+++ b/framework/tests/deqp_tests.py
@@ -173,7 +173,7 @@ def test_DEQPBaseTest_interpret_result_status():
     desc = ('deqp.DEQPBaseTest.interpret_result: '
             'when "{}" in stdout status is set to "{}"')
 
-    _map = deqp.DEQPBaseTest._DEQPBaseTest__RESULT_MAP.iteritems()  # pylint: disable=no-member,protected-access
+    _map = deqp.DEQPBaseTest._DEQPBaseTest__RESULT_MAP.items()  # pylint: disable=no-member,protected-access
 
     for status, expected in _map:
         test.description = desc.format(status, expected)
diff --git a/framework/tests/dmesg_tests.py b/framework/tests/dmesg_tests.py
index c264acb..dc43da1 100644
--- a/framework/tests/dmesg_tests.py
+++ b/framework/tests/dmesg_tests.py
@@ -25,7 +25,7 @@ don't want to run them use '-e sudo' with nosetests
 
 """
 
-from __future__ import print_function, absolute_import
+
 import os
 import sys
 import subprocess
@@ -216,9 +216,10 @@ def test_dmesg_wrap_partial():
     test.DMESG_COMMAND = ['echo', 'b\nc\nd\n']
     test.update_dmesg()
 
-    nt.assert_items_equal(test._new_messages, ['d'],
-                          msg=("_new_messages should be equal to ['d'], but is"
-                               " {} instead.".format(test._new_messages)))
+    nt.ok_(
+        test._new_messages == [b'd'],
+        msg=("_new_messages should be equal to ['d'], but is {} instead.".format(
+            test._new_messages)))
 
 
 def test_dmesg_wrap_complete():
@@ -239,10 +240,9 @@ def test_dmesg_wrap_complete():
     test.DMESG_COMMAND = ['echo', '1\n2\n3\n']
     test.update_dmesg()
 
-    nt.assert_items_equal(test._new_messages, ['1', '2', '3'],
-                          msg=("_new_messages should be equal to "
-                               "['1', '2', '3'], but is {} instead".format(
-                                   test._new_messages)))
+    nt.ok_(test._new_messages == [b'1', b'2', b'3'],
+           msg=("_new_messages should be equal to ['1', '2', '3'], "
+                "but is {} instead".format(test._new_messages)))
 
 
 @utils.nose_generator
diff --git a/framework/tests/gleantest_tests.py b/framework/tests/gleantest_tests.py
index 5cbf427..3d3edbb 100644
--- a/framework/tests/gleantest_tests.py
+++ b/framework/tests/gleantest_tests.py
@@ -20,7 +20,7 @@
 
 """ Tests for the glean class. Requires Nose """
 
-from __future__ import print_function, absolute_import
+
 
 import nose.tools as nt
 
diff --git a/framework/tests/glsl_parser_test_tests.py b/framework/tests/glsl_parser_test_tests.py
index bb5c246..1871357 100644
--- a/framework/tests/glsl_parser_test_tests.py
+++ b/framework/tests/glsl_parser_test_tests.py
@@ -20,7 +20,6 @@
 
 """ Provides tests for the shader_test module """
 
-from __future__ import print_function, absolute_import
 import os
 
 import nose.tools as nt
@@ -39,6 +38,7 @@ def _check_config(content):
         return glsl.GLSLParserTest(tfile), tfile
 
 
+ at nt.raises(glsl.GLSLParserNoConfigError)
 def test_no_config_start():
     """test.glsl_parser_test.GLSLParserTest: exception is raised if [config] section is missing
     """
@@ -46,11 +46,7 @@ def test_no_config_start():
                '// glsl_version: 1.10\n'
                '// [end config]\n')
     with utils.tempfile(content) as tfile:
-        with nt.assert_raises(glsl.GLSLParserNoConfigError) as exc:
-            glsl.GLSLParserTest(tfile)
-            nt.assert_equal(
-                exc.exception, 'No [config] section found!',
-                msg="No config section found, no exception raised")
+        glsl.GLSLParserTest(tfile)
 
 
 @nt.raises(exceptions.PiglitFatalError)
diff --git a/framework/tests/grouptools_tests.py b/framework/tests/grouptools_tests.py
index c3d3d39..64bb0fb 100644
--- a/framework/tests/grouptools_tests.py
+++ b/framework/tests/grouptools_tests.py
@@ -20,8 +20,6 @@
 
 """Module with tests for grouptools."""
 
-from __future__ import print_function
-
 import nose.tools as nt
 
 import framework.grouptools as grouptools
diff --git a/framework/tests/gtest_tests.py b/framework/tests/gtest_tests.py
index d7095de..2868d18 100644
--- a/framework/tests/gtest_tests.py
+++ b/framework/tests/gtest_tests.py
@@ -20,7 +20,7 @@
 
 """ Module providing tests for gtest """
 
-from __future__ import print_function, absolute_import
+
 
 from framework.tests import utils
 from framework.test import GTest
diff --git a/framework/tests/integration_tests.py b/framework/tests/integration_tests.py
index dc584b3..b5ed5a9 100644
--- a/framework/tests/integration_tests.py
+++ b/framework/tests/integration_tests.py
@@ -26,7 +26,7 @@ errors and to ensure that the API hasn't changed without fixing these modules
 
 """
 
-from __future__ import print_function, absolute_import
+
 import importlib
 
 from nose.plugins.skip import SkipTest
diff --git a/framework/tests/json_backend_tests.py b/framework/tests/json_backend_tests.py
index 9eff61c..a6851c8 100644
--- a/framework/tests/json_backend_tests.py
+++ b/framework/tests/json_backend_tests.py
@@ -22,7 +22,6 @@
 
 """ Tests for the backend package """
 
-from __future__ import print_function, absolute_import
 import os
 
 try:
diff --git a/framework/tests/json_results_update_tests.py b/framework/tests/json_results_update_tests.py
index 7774a53..73dd94c 100644
--- a/framework/tests/json_results_update_tests.py
+++ b/framework/tests/json_results_update_tests.py
@@ -20,7 +20,6 @@
 
 """Tests for JSON backend version updates."""
 
-from __future__ import print_function, absolute_import, division
 import os
 import copy
 import tempfile
@@ -175,7 +174,7 @@ class TestV0toV1(object):
 
     def test_info_delete(self):
         """backends.json.update_results (0 -> 1): Remove the info name from results"""
-        for value in self.RESULT.tests.itervalues():
+        for value in self.RESULT.tests.values():
             assert 'info' not in value
 
     def test_returncode_from_info(self):
@@ -226,12 +225,12 @@ class TestV0toV1(object):
 
         expected = 'group2/groupA/test/subtest 1'
         nt.assert_not_in(
-            expected, self.RESULT.tests.iterkeys(),
+            expected, self.RESULT.tests.keys(),
             msg='{0} found in result, when it should not be'.format(expected))
 
     def test_handle_fixed_subtests(self):
         """backends.json.update_results (0 -> 1): Correctly handle new single entry subtests correctly"""
-        assert 'group3/groupA/test' in self.RESULT.tests.iterkeys()
+        assert 'group3/groupA/test' in self.RESULT.tests.keys()
 
     def _load_with_update(self, data=None):
         """If the file is not results.json, it will be renamed.
diff --git a/framework/tests/json_tests.py b/framework/tests/json_tests.py
index 53701ee..142b743 100644
--- a/framework/tests/json_tests.py
+++ b/framework/tests/json_tests.py
@@ -26,7 +26,7 @@ tests and they will change with each version of the json output.
 
 """
 
-from __future__ import print_function, absolute_import
+
 import os
 
 import nose.tools as nt
diff --git a/framework/tests/junit_backends_tests.py b/framework/tests/junit_backends_tests.py
index e2b1dd7..53d03cb 100644
--- a/framework/tests/junit_backends_tests.py
+++ b/framework/tests/junit_backends_tests.py
@@ -22,7 +22,6 @@
 
 """ Tests for the backend package """
 
-from __future__ import print_function, absolute_import
 import os
 
 try:
diff --git a/framework/tests/log_tests.py b/framework/tests/log_tests.py
index 3dafc8b..ebcb9b8 100644
--- a/framework/tests/log_tests.py
+++ b/framework/tests/log_tests.py
@@ -20,7 +20,7 @@
 
 """ Module provides tests for log.py module """
 
-from __future__ import print_function, absolute_import
+
 import sys
 import collections
 
@@ -136,7 +136,7 @@ def check_no_output(func, args, file_=sys.stdout):
     file_.truncate()
 
     func(*args)
-    file_.seek(-1)
+    file_.seek(0, 2)
     assert file_.tell() == 0, 'file.tell() is at {}'.format(file_.tell())
 
 
diff --git a/framework/tests/opencv_tests.py b/framework/tests/opencv_tests.py
index 6e106e3..87c794a 100644
--- a/framework/tests/opencv_tests.py
+++ b/framework/tests/opencv_tests.py
@@ -20,7 +20,7 @@
 
 """ Module for testing opencv """
 
-from __future__ import print_function, absolute_import
+
 
 from framework.tests import utils
 from framework.test import OpenCVTest
diff --git a/framework/tests/piglit_test_tests.py b/framework/tests/piglit_test_tests.py
index c21f1fe..40e0233 100644
--- a/framework/tests/piglit_test_tests.py
+++ b/framework/tests/piglit_test_tests.py
@@ -20,7 +20,7 @@
 
 """ Tests for the exectest module """
 
-from __future__ import print_function, absolute_import
+
 
 import nose.tools as nt
 
diff --git a/framework/tests/profile_tests.py b/framework/tests/profile_tests.py
index 765528e..fa5a716 100644
--- a/framework/tests/profile_tests.py
+++ b/framework/tests/profile_tests.py
@@ -20,7 +20,6 @@
 
 """ Provides test for the framework.profile modules """
 
-from __future__ import print_function, absolute_import
 import sys
 import copy
 
diff --git a/framework/tests/results_tests.py b/framework/tests/results_tests.py
index 825f992..18c2bfe 100644
--- a/framework/tests/results_tests.py
+++ b/framework/tests/results_tests.py
@@ -21,7 +21,6 @@
 """ Module providing tests for the core module """
 
 
-from __future__ import print_function, absolute_import
 
 from framework import results, status
 import framework.tests.utils as utils
diff --git a/framework/tests/run_parser_tests.py b/framework/tests/run_parser_tests.py
index 5f0b21b..d584084 100644
--- a/framework/tests/run_parser_tests.py
+++ b/framework/tests/run_parser_tests.py
@@ -20,7 +20,7 @@
 
 """ Module of tests for the run commandline parser """
 
-from __future__ import print_function, absolute_import
+
 import sys
 import os
 import shutil
diff --git a/framework/tests/shader_test_tests.py b/framework/tests/shader_test_tests.py
index 1c10385..feb911f 100644
--- a/framework/tests/shader_test_tests.py
+++ b/framework/tests/shader_test_tests.py
@@ -20,7 +20,7 @@
 
 """ Provides tests for the shader_test module """
 
-from __future__ import print_function, absolute_import
+
 import os
 
 import nose.tools as nt
diff --git a/framework/tests/status_tests.py b/framework/tests/status_tests.py
index 657f24b..dc0929b 100644
--- a/framework/tests/status_tests.py
+++ b/framework/tests/status_tests.py
@@ -25,7 +25,7 @@ etc
 
 """
 
-from __future__ import print_function, absolute_import
+
 import itertools
 
 import nose.tools as nt
@@ -232,7 +232,6 @@ def test_nochangestatus_magic():
     # generator equality tests
     for comp, type_ in [(obj, 'status.NoChangeStatus'),
                         (stat, 'status.Status'),
-                        (u'Test', 'unicode'),
                         ('Test', 'str')]:
         check_operator_equal.description = (
             'Status.NoChangeStatus: Operator eq works with type: {}'.format(type_)
@@ -253,7 +252,6 @@ def test_status_magic():
 
     for func, name, result in [
             (str, 'str', 'foo'),
-            (unicode, 'unicode', u'foo'),
             (repr, 'repr', 'foo'),
             (int, 'int', 0)]:
         check_operator.description = \
diff --git a/framework/tests/summary_tests.py b/framework/tests/summary_tests.py
index 0c9542d..269df37 100644
--- a/framework/tests/summary_tests.py
+++ b/framework/tests/summary_tests.py
@@ -21,7 +21,7 @@
 
 """ Module providing tests for the summary module """
 
-from __future__ import print_function, absolute_import
+
 import copy
 
 try:
@@ -78,7 +78,6 @@ def check_sets(old, ostat, new, nstat, set_):
         with utils.tempfile(json.dumps(new)) as nfile:
             summ = summary.Summary([ofile, nfile])
 
-            print(summ.tests)
             nt.assert_equal(1, len(summ.tests[set_]),
                             msg="{0} was not appended".format(set_))
 
diff --git a/framework/tests/test_lists.py b/framework/tests/test_lists.py
index f1eac6b..9463334 100644
--- a/framework/tests/test_lists.py
+++ b/framework/tests/test_lists.py
@@ -26,7 +26,7 @@ es3conform, etc)
 
 """
 
-from __future__ import print_function, absolute_import
+
 import importlib
 import os.path as path
 
diff --git a/framework/tests/utils.py b/framework/tests/utils.py
index f91d87a..2b2f653 100644
--- a/framework/tests/utils.py
+++ b/framework/tests/utils.py
@@ -25,7 +25,6 @@ in a single place.
 
 """
 
-from __future__ import print_function, absolute_import
 import os
 import sys
 import shutil
@@ -208,7 +207,8 @@ class GeneratedTestWrapper(object):
 @contextmanager
 def resultfile():
     """ Create a stringio with some json in it and pass that as results """
-    with tempfile_.NamedTemporaryFile(delete=False) as output:
+    # tempfile assumes bytes, but json requires unicode
+    with tempfile_.NamedTemporaryFile(mode='w', delete=False) as output:
         json.dump(JSON_DATA, output)
 
     yield output
@@ -225,12 +225,18 @@ def tempfile(contents):
     returned it closes and removes the tempfile.
 
     Arguments:
-    contests -- This should be a string (unicode or str), in which case it is
+    contests -- This should be a string (bytes or str), in which case it is
                 written directly into the file.
 
     """
+    # Set the mode to allow either bytes or strings to be written
+    if isinstance(contents, bytes):
+        mode = 'wb'
+    else:
+        mode = 'w'
+
     # Do not delete the tempfile as soon as it is closed
-    temp = tempfile_.NamedTemporaryFile(delete=False)
+    temp = tempfile_.NamedTemporaryFile(delete=False, mode=mode)
     temp.write(contents)
     temp.close()
 
@@ -331,7 +337,7 @@ def not_raises(exceptions):
             try:
                 func(*args, **kwargs)
             except exceptions as e:
-                raise TestFailure(str(e))
+                raise TestFailure from e
 
         return _inner
 
@@ -378,7 +384,7 @@ def set_env(**envargs):
         def _inner(*args, **kwargs):
             """The returned function."""
             backup = {}
-            for key, value in envargs.iteritems():
+            for key, value in envargs.items():
                 backup[key] = os.environ.get(key, "__DONOTRESTORE__")
                 if value is not None:
                     os.environ[key] = value
@@ -388,7 +394,7 @@ def set_env(**envargs):
             try:
                 func(*args, **kwargs)
             finally:
-                for key, value in backup.iteritems():
+                for key, value in backup.items():
                     if value == "__DONOTRESTORE__" and key in os.environ:
                         del os.environ[key]
                     elif value != '__DONOTRESTORE__':
diff --git a/piglit b/piglit
index 5ae43e9..b459896 100755
--- a/piglit
+++ b/piglit
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
 # Copyright (c) 2014 Intel Corporation
 
@@ -31,8 +31,6 @@ capture -h/--help and the results will not be useful.
 
 """
 
-from __future__ import print_function
-
 import os
 import os.path as path
 import sys
diff --git a/piglit-print-commands.py b/piglit-print-commands.py
index e32ec70..f54e1dc 100755
--- a/piglit-print-commands.py
+++ b/piglit-print-commands.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 #
 # Permission is hereby granted, free of charge, to any person
 # obtaining a copy of this software and associated documentation
@@ -22,7 +22,7 @@
 # DEALINGS IN THE SOFTWARE.
 
 
-from __future__ import print_function
+
 import argparse
 import sys
 import os
diff --git a/piglit-resume.py b/piglit-resume.py
index 6b78529..8af6665 100755
--- a/piglit-resume.py
+++ b/piglit-resume.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
 # Copyright (c) 2014 Intel Corporation
 
diff --git a/piglit-run.py b/piglit-run.py
index 4c0f878..93c0bcc 100755
--- a/piglit-run.py
+++ b/piglit-run.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
 # Copyright (c) 2014 Intel Corporation
 
diff --git a/piglit-summary-html.py b/piglit-summary-html.py
index 4b5278e..b9bb003 100755
--- a/piglit-summary-html.py
+++ b/piglit-summary-html.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
 # Copyright (c) 2014 Intel Corporation
 
diff --git a/piglit-summary.py b/piglit-summary.py
index d1294be..5a97806 100755
--- a/piglit-summary.py
+++ b/piglit-summary.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#!/usr/bin/env python3
 
 # Copyright (c) 2014 Intel Corporation
 
diff --git a/templates/index.mako b/templates/index.mako
index 1ca46d3..3aca903 100644
--- a/templates/index.mako
+++ b/templates/index.mako
@@ -31,7 +31,7 @@
 
         ## Status columns
         ## Create an additional column for each summary
-        % for _ in xrange(colnum):
+        % for _ in range(colnum):
         <col />
         % endfor
       </colgroup>
diff --git a/templates/testrun_info.mako b/templates/testrun_info.mako
index 9ff5022..30d77dd 100644
--- a/templates/testrun_info.mako
+++ b/templates/testrun_info.mako
@@ -21,10 +21,10 @@
         <td>totals</td>
         <td>
           <table>
-            % for key, value in sorted(totals.iteritems(), key=lambda (k,v): (v,k), reverse=True):
+            % for key, value in sorted(totals.items(), key=lambda t: (t[1], t[0]), reverse=True):
             <tr><td>${key}</td><td>${value}</td></tr>
             % endfor
-            <tr><td>total</td><td>${sum(totals.itervalues())}</td></tr>
+            <tr><td>total</td><td>${sum(totals.values())}</td></tr>
           </table>
         </td>
       </tr>
diff --git a/tests/all.py b/tests/all.py
index f2e1765..27933be 100644
--- a/tests/all.py
+++ b/tests/all.py
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 # All tests that come with piglit, using default settings
 
-from __future__ import print_function, division, absolute_import
 import itertools
 import os
 import platform
@@ -2005,8 +2004,8 @@ with profile.group_manager(
     samplers = ['2D', '2DArray', 'Cube', 'CubeArray', '2DRect']
     for stage, type_, comp, sampler in itertools.product(
             stages, types, comps, samplers):
-        for func in ['textureGather'] if 'Cube' in sampler else ['textureGather', 'textureGatherOffset', 'textureGatherOffsets']:
-            for cs in xrange(len(comp)):
+        for func in ['textureGather'] if 'Cube' in sampler else ['textureGather', 'textureGatherOffset', 'textureGatherOffsets' ]:
+            for cs in range(len(comp)):
                 assert cs <= 3
                 address_mode = 'clamp' if sampler == '2DRect' else 'repeat'
                 cmd = ['textureGather', stage,
diff --git a/tests/cl.py b/tests/cl.py
index c55d3dd..66dd630 100644
--- a/tests/cl.py
+++ b/tests/cl.py
@@ -7,7 +7,7 @@
 # invalid constant names, they're not really fixable, so just hide them.
 # pylint: disable=invalid-name
 
-from __future__ import division, absolute_import, print_function
+
 
 import os
 
diff --git a/tests/igt.py b/tests/igt.py
index 01531d2..ae2e3f3 100644
--- a/tests/igt.py
+++ b/tests/igt.py
@@ -32,7 +32,6 @@ drm. Even if you have rendernode support enabled.
 
 """
 
-from __future__ import print_function, division, absolute_import
 import os
 import re
 import subprocess
@@ -93,7 +92,7 @@ class IGTTestProfile(TestProfile):
             try:
                 check_environment()
             except exceptions.PiglitInternalError as e:
-                raise exceptions.PiglitFatalError(str(e))
+                raise exceptions.PiglitFatalError from e
 
 
 profile = IGTTestProfile()  # pylint: disable=invalid-name
diff --git a/tests/xts.py b/tests/xts.py
index 325475f..973e398 100644
--- a/tests/xts.py
+++ b/tests/xts.py
@@ -23,7 +23,7 @@
 
 """ Test integreation for the X Test Suite """
 
-from __future__ import print_function, division, absolute_import
+
 import os
 import re
 import subprocess
-- 
2.4.5



More information about the Piglit mailing list