--- tests/testHarness.orig	2021-08-10 22:46:49 UTC
+++ tests/testHarness
@@ -1,4 +1,4 @@
-#!/usr/bin/env python2
+#! %%PYTHON_CMD%%
 # -*- mode: python -*-
 #
 #  This software is available as Open-Source software under the
@@ -50,9 +50,14 @@ import sqlite3
 import copy
 import operator
 import json
-from StringIO import StringIO
+from six import string_types
 
 try:
+  from io import StringIO
+except:
+  from StringIO import StringIO
+
+try:
   from collections import OrderedDict
 except:
   def OrderedDict():
@@ -105,8 +110,8 @@ def bar():
 
 def labeled_bar(label):
   length = len(label) + 2
-  width1 = (screen_width - length) / 2
-  width2 = screen_width - length - width1
+  width1 = int((screen_width - length) / 2)
+  width2 = int(screen_width - length - width1)
 
   str = width1 * '*' + ' ' + label + ' ' + width2 * '*' + '\n'
 
@@ -135,7 +140,7 @@ def ensure_parent_dir(file):
 
 def copy_pattern(pattern, dst, ignores = []):
   # shutil.ignore_patterns() is only in v2.6
-  ignore = lambda dir, files: filter(lambda f: f in ignores, files)
+  ignore = lambda dir, files: list(filter(lambda f: f in ignores, files))
 
   for path in glob.glob(pattern):
     dir, file = os.path.split(path)
@@ -155,7 +160,7 @@ def read_file(path):
   f = None
 
   try:
-    f = open(path, 'r')
+    f = open(path, 'rt')
     return f.read()
 
   finally:
@@ -174,7 +179,7 @@ def write_file(path, data):
 
 
 def split_cmd_line(cmd):
-  if isinstance(cmd, basestring): return shlex.split(str(cmd))
+  if isinstance(cmd, string_types): return shlex.split(str(cmd))
   return cmd
 
 
@@ -207,21 +212,20 @@ def execute_proc(command, timeout, **kwargs):
       if timeout and timeout < start - time.time():
         if sys.platform == 'win32': proc.kill()
         else: os.killpg(proc.pid)
-        raise Exception, 'Timedout'
+        raise Exception('Timedout')
       time.sleep(0.1)
 
     return proc.returncode
 
-  except Exception, e:
-    raise Exception, 'Running: ' + ' '.join(command) + \
-        '\nCaused by:' + str(e)
+  except Exception as e:
+    raise Exception('Running: ' + ' '.join(command) + '\nCaused by:' + str(e))
 
 
 def remove_tree(path):
   mode = os.stat(path).st_mode
 
   if not os.path.islink(path) and os.path.isdir(path):
-    if (mode & 0700) != 0700: os.chmod(path, mode | 0700)
+    if (mode & 0o0700) != 0o0700: os.chmod(path, mode | 0o0700)
 
     for name in os.listdir(path):
       remove_tree(path + '/' + name)
@@ -229,8 +233,8 @@ def remove_tree(path):
     os.rmdir(path)
 
   else:
-    if sys.platform == 'win32' and (mode & 0200) != 0200:
-      os.chmod(path, mode | 0200)
+    if sys.platform == 'win32' and (mode & 0o0200) != 0o0200:
+      os.chmod(path, mode | 0o0200)
     os.remove(path)
 
 
@@ -258,13 +262,13 @@ class Logger:
 
 
   def push(self, key):
-    #print 'JSON push(%s)' % key
+    #print('JSON push(%s)' % key)
     self.back()[key] = OrderedDict()
     self.stack.append(self.back()[key])
 
 
   def pop(self, key):
-    #print 'JSON pop(%s)' % key
+    #print('JSON pop(%s)' % key)
     self.stack.pop()
 
 
@@ -556,7 +560,7 @@ class ReplaceFilter:
     if replace is None: replace = ['']
     if isinstance(replace, list): self.replace = replace
     else: self.replace = list([replace])
-    self.replace = map(str, self.replace)
+    self.replace = list(map(str, self.replace))
 
 
   def __call__(self, line):
@@ -585,10 +589,10 @@ class CheckFile(Check):
     self.reason = filename
     self.exists = exists
 
-    if isinstance(filter, basestring): filter = MatchFilter(filter)
+    if isinstance(filter, string_types): filter = MatchFilter(filter)
     if isinstance(filter, (list, tuple)):
       if not filter[0] in filter_classes:
-        raise 'Invalid filter "%s"' % filter[0]
+        raise Exception('Invalid filter "%s"' % filter[0])
 
       klass = filter_classes[filter[0]]
       filter = klass(*filter[1:])
@@ -782,7 +786,7 @@ class CheckFilePattern(CheckFile):
 
       return CheckFile.check(self, test)
 
-    except Exception, e:
+    except Exception as e:
       if options.verbose: output_stream.write(str(e))
       return FAILED
 
@@ -810,7 +814,7 @@ class Table:
     self.number = number
     self.header = header
 
-    self.widths = map(lambda x: 0, range(columns))
+    self.widths = list(map(lambda x: 0, range(columns)))
     self.rows = []
 
 
@@ -862,8 +866,8 @@ class Table:
 
     # Check row width
     if self.columns != len(row):
-      raise Exception, \
-          'Row has ' + len(row) + ' columns, expected ' + self.columns
+      raise Exception('Row has ' + len(row) + ' columns, expected ' +
+                      self.columns)
 
     self.rows.append(row)
 
@@ -879,8 +883,8 @@ class Table:
 
 
   def bar(self):
-    return self.do_row(map(lambda x: x * self.row_sep, self.widths),
-                       self.corner, self.row_sep)
+    return self.do_row(list(map(lambda x: x * self.row_sep, self.widths),
+                            self.corner, self.row_sep))
 
 
   def __str__(self):
@@ -892,8 +896,8 @@ class Table:
 
     first = True
     for row in self.rows:
-      row = map(lambda cell, width:
-                  cell.center(width, self.padding), row, self.widths)
+      row = list(map(lambda cell, width:
+                     cell.center(width, self.padding), row, self.widths))
       s += self.do_row(row, self.col_sep, self.padding)
       if self.separate_rows or (self.header and first):
         s += self.bar()
@@ -913,8 +917,8 @@ class CheckDB(CheckFile):
       if col[0] == '"': return col
       else: return '"%s"' % col
 
-    if cols is not None: cols = map(quote_column, cols)
-    if orderBy is not None: orderBy = map(quote_column, orderBy)
+    if cols is not None: cols = list(map(quote_column, cols))
+    if orderBy is not None: orderBy = list(map(quote_column, orderBy))
 
     self.db = db
     self.sql = sql
@@ -923,7 +927,7 @@ class CheckDB(CheckFile):
 
     if sql is None:
       if cols is None or table is None:
-        raise Exception, 'Must set either "sql" or "cols" and "table"'
+        raise Exception('Must set either "sql" or "cols" and "table"')
       if orderBy is None: orderBy = [cols[0]]
       self.sql = 'SELECT ' + ', '.join(cols) + ' FROM "' + table + \
           '" ORDER BY ' + ', '.join(orderBy)
@@ -931,8 +935,8 @@ class CheckDB(CheckFile):
 
   def check(self, test):
     # Clean up column names
-    header = map(lambda x: x.split()[len(x.split()) - 1], self.cols)
-    header = map(lambda x: x.strip(' "\''), header)
+    header = list(map(lambda x: x.split()[len(x.split()) - 1], self.cols))
+    header = list(map(lambda x: x.strip(' "\''), header))
 
     table = Table(len(header), 0, separate_rows = False, number = True)
     table.add_row(header)
@@ -946,9 +950,9 @@ class CheckDB(CheckFile):
       conn = sqlite3.connect(db)
       c = conn.cursor()
       c.execute(self.sql)
-      for row in c: table.add_row(map(str, row))
+      for row in c: table.add_row(list(map(str, row)))
       c.close()
-    except Exception, e: return FAILED
+    except Exception as e: return FAILED
 
     # Write
     write_file(self.get_output(test),
@@ -1027,7 +1031,7 @@ class Unit:
       if os.path.exists(path):
         self.config.update(json.loads(read_file(path)))
 
-    except Exception, e:
+    except Exception as e:
       raise HarnessError('Failed to parse test.json file in %s: %s' % (
          self.get_full_name(), e))
 
@@ -1041,12 +1045,13 @@ class Unit:
       for check in checks:
         if isinstance(check, Check): self.check.add(check)
         else:
-          if isinstance(check, basestring):
+          if isinstance(check, string_types):
             check = [check]
 
           if isinstance(check, (list, tuple)) and len(check):
             if not check[0] in check_classes:
-              raise 'Invalid check "%s" in %s' % (check[0], self.name)
+              raise Exception('Invalid check "%s" in %s' %
+                              (check[0], self.name))
 
             klass = check_classes[check[0]]
             self.check.add(klass(*check[1:]))
@@ -1064,11 +1069,11 @@ class Unit:
 
   def config_get_list(self, name, default = None, split = str.split):
     value = self.config.get(name, default)
-    if isinstance(value, basestring): value = split(value)
+    if isinstance(value, string_types): value = split(value)
 
     try:
-      return map(lambda item: item % self.config, value)
-    except Exception, e:
+      return list(map(lambda item: item % self.config, value))
+    except Exception as e:
       raise HarnessError('Error resolving %s "%s": %s' % (name, value, e))
 
 
@@ -1141,11 +1146,11 @@ class Unit:
   def get_stdin_file(self):
     stdin = self.config.get('stdin', None)
 
-    if isinstance(stdin, basestring): return StringIO(stdin)
+    if isinstance(stdin, string_types): return StringIO(stdin)
 
     if stdin is None and 'stdin_file' in self.config:
       path = self.config_get('stdin_file')
-      if os.path.exists(path): return open(path, 'r')
+      if os.path.exists(path): return open(path, 'rb')
 
     return stdin
 
@@ -1170,8 +1175,8 @@ class Unit:
 
     # Resolve command vars refs
     try:
-      command = map(lambda s: s % self.config, command)
-    except Exception, e:
+      command = list(map(lambda s: s % self.config, command))
+    except Exception as e:
       raise HarnessError('Error resolving command "%s": %s' % (command, e))
 
     return command
@@ -1245,7 +1250,7 @@ class Unit:
       self.status = self.check.check(self)
 
       # Check status
-      if self.status is None: raise Exception, 'Test result not set'
+      if self.status is None: raise Exception('Test result not set')
       self.reason = self.check.reason
 
     # Logging
@@ -1343,12 +1348,12 @@ class Test(Unit):
 
   def initialize(self):
     if self.units is None: Unit.initialize(self)
-    else: map(Unit.initialize, self.units)
+    else: list(map(Unit.initialize, self.units))
 
 
   def reset(self):
     if self.units is None: Unit.reset(self)
-    else: map(Unit.reset, self.units)
+    else: list(map(Unit.reset, self.units))
 
 
   def view(self):
@@ -1455,7 +1460,7 @@ class TestSuite:
     if os.path.exists(path):
       try:
         self.config.update(json.loads(read_file(path)))
-      except Exception, e:
+      except Exception as e:
         raise HarnessError('Failed to parse suite test.json: ' + str(e))
 
       if 'command' in self.config:
@@ -1575,8 +1580,8 @@ class TestSuite:
       env.update(globals())
 
       try: execfile(script, env)
-      except Exception, e:
-        raise Exception, 'Exception in "%s": %s' % (script, str(e))
+      except Exception as e:
+        raise Exception('Exception in "%s": %s' % (script, str(e)))
 
       if not 'Suite' in env:
         raise HarnessError(
@@ -1655,7 +1660,7 @@ class TestSuite:
     errStr = "'%s' not found and variable '%s' not set" % (name, conf_name)
     if home_var is not None and home_var in os.environ:
       errStr += ' and ' + home_var + '=' + os.environ[home_var]
-    raise Exception, errStr
+    raise Exception(errStr)
 
 
 class TestResults:
@@ -1893,7 +1898,7 @@ options, args = parser.parse_args()
 # Change dir
 if options.chdir is not None:
   os.chdir(options.chdir)
-  if options.verbose: print "Changed directory: " + os.getcwd()
+  if options.verbose: print("Changed directory: " + os.getcwd())
 
 # Load config file
 if options.config is not None: config_file = options.config
@@ -1925,14 +1930,14 @@ if options.valgrind:
 
 if options.dump_config:
   for name, value in config.items():
-    print "%s=%s" % (name, value)
+    print("%s=%s" % (name, value))
   sys.exit(0)
 
 # Run
 try:
   sys.exit(TestHarness(config).run(cmd_args))
 
-except HarnessError, e:
+except HarnessError as e:
   sys.stdout.flush()
   if e.show_usage: parser.error(e.msg)
   else: sys.stderr.write('ERROR: ' + e.msg + '\n')
