From: Gordon Ball <gordon@chronitis.net>
Date: Thu, 14 May 2020 10:17:07 +0000
Subject: Port testsuite to python3

---
 src/libshared/test/json_test.t |  8 ++--
 test/basetest/compat.py        |  9 -----
 test/basetest/hooks.py         |  7 +---
 test/basetest/meta.py          |  2 +-
 test/basetest/task.py          |  3 +-
 test/basetest/taskd.py         |  7 +---
 test/basetest/utils.py         | 90 ++++--------------------------------------
 test/encoding.t                |  2 +-
 test/sorting.t                 | 10 +----
 9 files changed, 18 insertions(+), 120 deletions(-)
 delete mode 100644 test/basetest/compat.py

diff --git a/src/libshared/test/json_test.t b/src/libshared/test/json_test.t
index 9d7032a..7669c7e 100755
--- a/src/libshared/test/json_test.t
+++ b/src/libshared/test/json_test.t
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2.7
+#!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 ###############################################################################
 #
@@ -64,15 +64,13 @@ class MetaTestJson(MetaTest):
         return test
 
 
-class TestJsonParsing(TestCase):
-    __metaclass__ = MetaTestJson
-
+class TestJsonParsing(TestCase, metaclass=MetaTestJson):
     EXTRA = {}
     EXTRA["TEST_DIR"] = os.path.abspath(os.path.join(CURRENT_DIR, ".."))
     EXTRA["TEST_BIN"] = os.path.join(EXTRA["TEST_DIR"], "json_test")
 
     TESTS = (
-        zip(glob(os.path.join(EXTRA["TEST_DIR"], "json/*.json")))
+        list(zip(glob(os.path.join(EXTRA["TEST_DIR"], "json/*.json"))))
     )
 
 
diff --git a/test/basetest/compat.py b/test/basetest/compat.py
deleted file mode 100644
index e60cb97..0000000
--- a/test/basetest/compat.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- coding: utf-8 -*-
-
-try:
-    STRING_TYPE = basestring
-except NameError:
-    # Python 3
-    STRING_TYPE = str
-
-# vim: ai sts=4 et sw=4
diff --git a/test/basetest/hooks.py b/test/basetest/hooks.py
index e0f7ea7..b51161a 100644
--- a/test/basetest/hooks.py
+++ b/test/basetest/hooks.py
@@ -1,15 +1,10 @@
 # -*- coding: utf-8 -*-
 
-from __future__ import division
-import errno
 import os
 from sys import stderr
 import shutil
 import stat
-try:
-    import simplejson as json
-except ImportError:
-    import json
+import json
 
 from datetime import datetime
 from .utils import DEFAULT_HOOK_PATH
diff --git a/test/basetest/meta.py b/test/basetest/meta.py
index 2be7866..d9d89e2 100644
--- a/test/basetest/meta.py
+++ b/test/basetest/meta.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-from __future__ import print_function, division
+
 
 
 class MetaTest(type):
diff --git a/test/basetest/task.py b/test/basetest/task.py
index dba7686..c9507b5 100644
--- a/test/basetest/task.py
+++ b/test/basetest/task.py
@@ -11,7 +11,6 @@ import unittest
 from .exceptions import CommandError
 from .hooks import Hooks
 from .utils import run_cmd_wait, run_cmd_wait_nofail, which, task_binary_location
-from .compat import STRING_TYPE
 
 
 class Task(object):
@@ -195,7 +194,7 @@ class Task(object):
         argument. The string is literally the same as if written in the shell.
         """
         # Enable nicer-looking calls by allowing plain strings
-        if isinstance(args, STRING_TYPE):
+        if isinstance(args, str):
             args = shlex.split(args)
 
         return args
diff --git a/test/basetest/taskd.py b/test/basetest/taskd.py
index c4b244b..48f1920 100644
--- a/test/basetest/taskd.py
+++ b/test/basetest/taskd.py
@@ -1,7 +1,5 @@
 # -*- coding: utf-8 -*-
 
-from __future__ import division, print_function
-import errno
 import os
 import tempfile
 import shutil
@@ -14,10 +12,7 @@ from .utils import (find_unused_port, release_port, port_used, run_cmd_wait,
                     taskd_binary_location)
 from .exceptions import CommandError
 
-try:
-    from subprocess import DEVNULL
-except ImportError:
-    DEVNULL = open(os.devnull, 'w')
+from subprocess import DEVNULL
 
 
 class Taskd(object):
diff --git a/test/basetest/utils.py b/test/basetest/utils.py
index a36ade5..74ad728 100644
--- a/test/basetest/utils.py
+++ b/test/basetest/utils.py
@@ -1,6 +1,4 @@
 # -*- coding: utf-8 -*-
-from __future__ import division
-import errno
 import os
 import sys
 import socket
@@ -10,15 +8,10 @@ import atexit
 import tempfile
 from subprocess import Popen, PIPE, STDOUT
 from threading import Thread
-try:
-    from Queue import Queue, Empty
-except ImportError:
-    from queue import Queue, Empty
+
+from queue import Queue, Empty
 from time import sleep
-try:
-    import simplejson as json
-except ImportError:
-    import json
+import json
 from .exceptions import CommandError, TimeoutWaitingFor
 
 USED_PORTS = set()
@@ -86,7 +79,7 @@ def wait_condition(cond, timeout=1, sleeptime=.01):
         timeout = 1
 
     if timeout < sleeptime:
-        print("Warning, timeout cannot be smaller than", sleeptime)
+        print(("Warning, timeout cannot be smaller than", sleeptime))
         timeout = sleeptime
 
     # Max number of attempts until giving up
@@ -148,8 +141,7 @@ def _queue_output(arguments, pidq, outputq):
     # Send input and wait for finish
     out, err = proc.communicate(input_data)
 
-    if sys.version_info > (3,):
-        out, err = out.decode('utf-8'), err.decode('utf-8')
+    out, err = out.decode('utf-8'), err.decode('utf-8')
 
     # Give the output back to the caller
     outputq.put((out, err, proc.returncode))
@@ -323,7 +315,7 @@ def find_unused_port(addr="localhost", start=53589, track=True):
     maxport = 65535
     unused = None
 
-    for port in xrange(start, maxport):
+    for port in range(start, maxport):
         if not port_used(addr, port):
             if track and port in USED_PORTS:
                 continue
@@ -364,74 +356,8 @@ def memoize(obj):
     return memoizer
 
 
-try:
-    from shutil import which
-    which = memoize(which)
-except ImportError:
-    # NOTE: This is shutil.which backported from python-3.3.3
-    @memoize
-    def which(cmd, mode=os.F_OK | os.X_OK, path=None):
-        """Given a command, mode, and a PATH string, return the path which
-        conforms to the given mode on the PATH, or None if there is no such
-        file.
-
-        `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
-        of os.environ.get("PATH"), or can be overridden with a custom search
-        path.
-
-        """
-        # Check that a given file can be accessed with the correct mode.
-        # Additionally check that `file` is not a directory, as on Windows
-        # directories pass the os.access check.
-        def _access_check(fn, mode):
-            return (os.path.exists(fn) and os.access(fn, mode) and
-                    not os.path.isdir(fn))
-
-        # If we're given a path with a directory part, look it up directly
-        # rather than referring to PATH directories. This includes checking
-        # relative to the current directory, e.g. ./script
-        if os.path.dirname(cmd):
-            if _access_check(cmd, mode):
-                return cmd
-            return None
-
-        if path is None:
-            path = os.environ.get("PATH", os.defpath)
-        if not path:
-            return None
-        path = path.split(os.pathsep)
-
-        if sys.platform == "win32":
-            # The current directory takes precedence on Windows.
-            if os.curdir not in path:
-                path.insert(0, os.curdir)
-
-            # PATHEXT is necessary to check on Windows.
-            pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
-            # See if the given file matches any of the expected path
-            # extensions. This will allow us to short circuit when given
-            # "python.exe". If it does match, only test that one, otherwise we
-            # have to try others.
-            if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
-                files = [cmd]
-            else:
-                files = [cmd + ext for ext in pathext]
-        else:
-            # On other platforms you don't have things like PATHEXT to tell you
-            # what file suffixes are executable, so just pass on cmd as-is.
-            files = [cmd]
-
-        seen = set()
-        for dir in path:
-            normdir = os.path.normcase(dir)
-            if normdir not in seen:
-                seen.add(normdir)
-                for thefile in files:
-                    name = os.path.join(dir, thefile)
-                    if _access_check(name, mode):
-                        return name
-        return None
-
+from shutil import which
+which = memoize(which)
 
 def parse_datafile(file):
     """Parse .data files on the client and server treating files as JSON
diff --git a/test/encoding.t b/test/encoding.t
index 9fc3409..6def7ac 100755
--- a/test/encoding.t
+++ b/test/encoding.t
@@ -65,7 +65,7 @@ class TestUtf8(TestCase):
         #                       contains wide utf8 characters
         self.t.config("print.empty.columns", "0")
 
-        self.t(("add", "abc", "pro:Bar\u263a"))
+        self.t(("add", "abc", "pro:Bar\\u263a"))
         self.t("add def pro:Foo")
 
         code, out, err = self.t("ls")
diff --git a/test/sorting.t b/test/sorting.t
index 1443697..e8b1dc6 100755
--- a/test/sorting.t
+++ b/test/sorting.t
@@ -64,9 +64,7 @@ class MetaTestSorting(MetaTest):
         return test
 
 
-class TestSorting(TestCase):
-    __metaclass__ = MetaTestSorting
-
+class TestSorting(TestCase, metaclass=MetaTestSorting):
     @classmethod
     def setUpClass(cls):
         cls.t = Task()
@@ -203,11 +201,7 @@ class TestSorting(TestCase):
     )
 
 
-class TestBug438(TestCase):
-    __metaclass__ = MetaTestSorting
-
-    # Bug #438: Reports sorting by end, start, and entry are ordered
-    #           incorrectly, if time is included.
+class TestBug438(TestCase, metaclass=MetaTestSorting):
     @classmethod
     def setUpClass(cls):
         cls.t = Task()
