Make various Python scripts more compatible with Python 3.

- Switch to print() in many places.
- Pick between itertools.imap() and map().
- Convert bytes to strings in a few places.

Bug: pdfium:1674
Change-Id: I46136989311c8b714f4a58f3621c34c50e32b67c
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/79490
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/testing/tools/api_check.py b/testing/tools/api_check.py
index 66b3077..f340243 100755
--- a/testing/tools/api_check.py
+++ b/testing/tools/api_check.py
@@ -12,6 +12,8 @@
 
 """
 
+from __future__ import print_function
+
 import os
 import re
 import sys
@@ -106,9 +108,9 @@
   if not failure_list:
     return True
 
-  print '%s:' % failure_message
+  print('%s:' % failure_message)
   for f in sorted(failure_list):
-    print f
+    print(f)
   return False
 
 
diff --git a/testing/tools/common.py b/testing/tools/common.py
index 108fcfd..f086b13 100755
--- a/testing/tools/common.py
+++ b/testing/tools/common.py
@@ -3,6 +3,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+from __future__ import print_function
+
 import datetime
 import glob
 import os
@@ -139,20 +141,21 @@
   cwd = os.getcwd()
   os.chdir(build_dir)
   gn_args_output = subprocess.check_output(
-      ['gn', 'args', '.', '--list=%s' % arg_name, '--short'])
+      ['gn', 'args', '.', '--list=%s' % arg_name, '--short']).decode('utf8')
   os.chdir(cwd)
   arg_match_output = re.search('%s = (.*)' % arg_name, gn_args_output).group(1)
   if verbose:
-    print >> sys.stderr, "Found '%s' for value of %s" % (arg_match_output,
-                                                         arg_name)
+    print(
+        "Found '%s' for value of %s" % (arg_match_output, arg_name),
+        file=sys.stderr)
   return arg_match_output == 'true'
 
 
 def PrintWithTime(s):
   """Prints s prepended by a timestamp."""
-  print '[%s] %s' % (datetime.datetime.now().strftime("%Y%m%d %H:%M:%S"), s)
+  print('[%s] %s' % (datetime.datetime.now().strftime("%Y%m%d %H:%M:%S"), s))
 
 
 def PrintErr(s):
   """Prints s to stderr."""
-  print >> sys.stderr, s
+  print(s, file=sys.stderr)
diff --git a/testing/tools/coverage/coverage_report.py b/testing/tools/coverage/coverage_report.py
index 7261390..71ab401 100755
--- a/testing/tools/coverage/coverage_report.py
+++ b/testing/tools/coverage/coverage_report.py
@@ -8,6 +8,8 @@
 Prefers that 'is_component_build = false' is set in args.gn.
 """
 
+from __future__ import print_function
+
 import argparse
 from collections import namedtuple
 import fnmatch
@@ -115,38 +117,38 @@
   def check_output(self, args, dry_run=False, env=None):
     """Dry run aware wrapper of subprocess.check_output()"""
     if dry_run:
-      print "Would have run '%s'" % ' '.join(args)
+      print("Would have run '%s'" % ' '.join(args))
       return ''
 
     output = subprocess.check_output(args, env=env)
 
     if self.verbose:
-      print "check_output(%s) returned '%s'" % (args, output)
+      print("check_output(%s) returned '%s'" % (args, output))
     return output
 
   def call(self, args, dry_run=False, env=None):
     """Dry run aware wrapper of subprocess.call()"""
     if dry_run:
-      print "Would have run '%s'" % ' '.join(args)
+      print("Would have run '%s'" % ' '.join(args))
       return 0
 
     output = subprocess.call(args, env=env)
 
     if self.verbose:
-      print 'call(%s) returned %s' % (args, output)
+      print('call(%s) returned %s' % (args, output))
     return output
 
   def call_silent(self, args, dry_run=False, env=None):
     """Dry run aware wrapper of subprocess.call() that eats output from call"""
     if dry_run:
-      print "Would have run '%s'" % ' '.join(args)
+      print("Would have run '%s'" % ' '.join(args))
       return 0
 
     with open(os.devnull, 'w') as f:
       output = subprocess.call(args, env=env, stdout=f)
 
     if self.verbose:
-      print 'call_silent(%s) returned %s' % (args, output)
+      print('call_silent(%s) returned %s' % (args, output))
     return output
 
   def calculate_coverage_tests(self, args):
@@ -192,10 +194,10 @@
         spec: Tuple containing the TestSpec.
     """
     if self.verbose:
-      print "Generating coverage for test '%s', using data '%s'" % (name, spec)
+      print("Generating coverage for test '%s', using data '%s'" % (name, spec))
     if not os.path.exists(spec.binary):
       print('Unable to generate coverage for %s, since it appears to not exist'
-            ' @ %s') % (name, spec.binary)
+            ' @ %s' % (name, spec.binary))
       return False
 
     binary_args = [spec.binary]
@@ -218,7 +220,7 @@
       binary_args.extend(['-j', '8', '--build-dir', self.build_directory])
     if self.call(binary_args, dry_run=self.dry_run, env=env) and self.verbose:
       print('Running %s appears to have failed, which might affect '
-            'results') % spec.binary
+            'results' % spec.binary)
 
     return True
 
@@ -267,24 +269,24 @@
   def run(self):
     """Setup environment, execute the tests and generate coverage report"""
     if not self.fetch_profiling_tools():
-      print 'Unable to fetch profiling tools'
+      print('Unable to fetch profiling tools')
       return False
 
     if not self.build_binaries():
-      print 'Failed to successfully build binaries'
+      print('Failed to successfully build binaries')
       return False
 
     for name in self.coverage_tests.keys():
       if not self.generate_coverage(name, self.coverage_tests[name]):
-        print 'Failed to successfully generate coverage data'
+        print('Failed to successfully generate coverage data')
         return False
 
     if not self.merge_raw_coverage_results():
-      print 'Failed to successfully merge raw coverage results'
+      print('Failed to successfully merge raw coverage results')
       return False
 
     if not self.generate_html_report():
-      print 'Failed to successfully generate HTML report'
+      print('Failed to successfully generate HTML report')
       return False
 
     return True
diff --git a/testing/tools/fixup_pdf_template.py b/testing/tools/fixup_pdf_template.py
index 808c0bb..91d9cf0 100755
--- a/testing/tools/fixup_pdf_template.py
+++ b/testing/tools/fixup_pdf_template.py
@@ -17,6 +17,9 @@
   {{streamlen}} - expands to |/Length n|.
 """
 
+from __future__ import print_function
+
+# TODO(thestig): Figure out what to do with cStringIO.
 import cStringIO
 import optparse
 import os
@@ -146,13 +149,13 @@
       for line in preprocessed:
         outfile.write(processor.process_line(line))
   except IOError:
-    print >> sys.stderr, 'failed to process %s' % input_path
+    print('failed to process %s' % input_path, file=sys.stderr)
 
 
 def insert_includes(input_path, output_file, visited_set):
   input_path = os.path.normpath(input_path)
   if input_path in visited_set:
-    print >> sys.stderr, 'Circular inclusion %s, ignoring' % input_path
+    print('Circular inclusion %s, ignoring' % input_path, file=sys.stderr)
     return
   visited_set.add(input_path)
   try:
@@ -170,7 +173,7 @@
             line = line.replace(WINDOWS_LINE_ENDING, UNIX_LINE_ENDING)
           output_file.write(line)
   except IOError:
-    print >> sys.stderr, 'failed to include %s' % input_path
+    print('failed to include %s' % input_path, file=sys.stderr)
     raise
   visited_set.discard(input_path)
 
diff --git a/testing/tools/pngdiffer.py b/testing/tools/pngdiffer.py
index 63315c9..911da10 100755
--- a/testing/tools/pngdiffer.py
+++ b/testing/tools/pngdiffer.py
@@ -3,6 +3,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+from __future__ import print_function
+
 import distutils.spawn
 import itertools
 import os
@@ -56,6 +58,16 @@
     else:
       self.max_path_mode = PathMode.DEFAULT
 
+  @staticmethod
+  def _GetMapFunc():
+    try:
+      # Only exists in Python 2.
+      func = itertools.imap
+    except AttributeError:
+      # Python 3's map returns an iterator.
+      func = map
+    return func
+
   def CheckMissingTools(self, regenerate_expected):
     if (regenerate_expected and self.os_name == 'linux' and
         not distutils.spawn.find_executable('optipng')):
@@ -70,7 +82,7 @@
     for page in itertools.count():
       actual_path = path_templates.GetActualPath(page)
       expected_paths = path_templates.GetExpectedPaths(page)
-      if any(itertools.imap(os.path.exists, expected_paths)):
+      if any(self._GetMapFunc()(os.path.exists, expected_paths)):
         actual_paths.append(actual_path)
       else:
         break
@@ -92,15 +104,15 @@
     for page in itertools.count():
       actual_path = path_templates.GetActualPath(page)
       expected_paths = path_templates.GetExpectedPaths(page)
-      if not any(itertools.imap(os.path.exists, expected_paths)):
+      if not any(self._GetMapFunc()(os.path.exists, expected_paths)):
         if page == 0:
-          print "WARNING: no expected results files for " + input_filename
+          print("WARNING: no expected results files for " + input_filename)
         if os.path.exists(actual_path):
           print('FAILURE: Missing expected result for 0-based page %d of %s' %
                 (page, input_filename))
           return True
         break
-      print "Checking " + actual_path
+      print("Checking " + actual_path)
       sys.stdout.flush()
 
       error = None
@@ -115,7 +127,7 @@
           break
 
       if error:
-        print "FAILURE: " + input_filename + "; " + str(error)
+        print("FAILURE: " + input_filename + "; " + str(error))
         return True
 
     return False
diff --git a/testing/tools/safetynet_compare.py b/testing/tools/safetynet_compare.py
index c76ce44..8a5942d 100755
--- a/testing/tools/safetynet_compare.py
+++ b/testing/tools/safetynet_compare.py
@@ -4,6 +4,8 @@
 # found in the LICENSE file.
 """Compares the performance of two versions of the pdfium code."""
 
+from __future__ import print_function
+
 import argparse
 import functools
 import glob
@@ -564,7 +566,7 @@
           ComparisonConclusions.GetOutputDict().
     """
     if self.args.machine_readable:
-      print json.dumps(conclusions_dict)
+      print(json.dumps(conclusions_dict))
     else:
       PrintConclusionsDictHumanReadable(
           conclusions_dict, colored=True, key=self.args.case_order)
diff --git a/testing/tools/safetynet_conclusions.py b/testing/tools/safetynet_conclusions.py
index 8f0b28c..dc6e3dd 100644
--- a/testing/tools/safetynet_conclusions.py
+++ b/testing/tools/safetynet_conclusions.py
@@ -3,6 +3,8 @@
 # found in the LICENSE file.
 """Classes that draw conclusions out of a comparison and represent them."""
 
+from __future__ import print_function
+
 from collections import Counter
 
 FORMAT_RED = '\033[01;31m{0}\033[00m'
@@ -245,9 +247,9 @@
     key: String with the CaseResult dictionary key to sort the cases.
   """
   # Print header
-  print '=' * 80
-  print '{0:>11s} {1:>15s}  {2}'.format('% Change', 'Time after', 'Test case')
-  print '-' * 80
+  print('=' * 80)
+  print('{0:>11s} {1:>15s}  {2}'.format('% Change', 'Time after', 'Test case'))
+  print('-' * 80)
 
   color = FORMAT_NORMAL
 
@@ -264,18 +266,18 @@
       color = RATING_TO_COLOR[case_dict['rating']]
 
     if case_dict['rating'] == RATING_FAILURE:
-      print u'{} to measure time for {}'.format(
-          color.format('Failed'), case_name).encode('utf-8')
+      print(u'{} to measure time for {}'.format(
+          color.format('Failed'), case_name).encode('utf-8'))
       continue
 
-    print u'{0} {1:15,d}  {2}'.format(
+    print(u'{0} {1:15,d}  {2}'.format(
         color.format('{:+11.4%}'.format(case_dict['ratio'])),
-        case_dict['after'], case_name).encode('utf-8')
+        case_dict['after'], case_name).encode('utf-8'))
 
   # Print totals
   totals = conclusions_dict['summary']
-  print '=' * 80
-  print 'Test cases run: %d' % totals['total']
+  print('=' * 80)
+  print('Test cases run: %d' % totals['total'])
 
   if colored:
     color = FORMAT_MAGENTA if totals[RATING_FAILURE] else FORMAT_GREEN
diff --git a/testing/tools/safetynet_image.py b/testing/tools/safetynet_image.py
index f300615..3161178 100644
--- a/testing/tools/safetynet_image.py
+++ b/testing/tools/safetynet_image.py
@@ -4,6 +4,8 @@
 """Compares pairs of page images and generates an HTML to look at differences.
 """
 
+from __future__ import print_function
+
 import functools
 import glob
 import multiprocessing
@@ -63,7 +65,7 @@
     # pylint: disable=attribute-defined-outside-init
 
     if len(self.two_labels) != 2:
-      print >> sys.stderr, 'two_labels must be a tuple of length 2'
+      print('two_labels must be a tuple of length 2', file=sys.stderr)
       return 1
 
     finder = DirectoryFinder(self.build_dir)
@@ -88,7 +90,7 @@
       for image in self.image_locations.Images():
         diff = difference[image]
         if diff is None:
-          print >> sys.stderr, 'Failed to compare image %s' % image
+          print('Failed to compare image %s' % image, file=sys.stderr)
         elif diff > self.threshold:
           self._WriteImageRows(f, image, diff)
         else:
@@ -170,7 +172,7 @@
     except subprocess.CalledProcessError as e:
       return image, percentage_change
     else:
-      print >> sys.stderr, 'Warning: Should have failed the previous diff.'
+      print('Warning: Should have failed the previous diff.', file=sys.stderr)
       return image, 0
 
   def _GetRelativePath(self, absolute_path):
diff --git a/testing/tools/safetynet_measure.py b/testing/tools/safetynet_measure.py
index 3577189..35f1832 100755
--- a/testing/tools/safetynet_measure.py
+++ b/testing/tools/safetynet_measure.py
@@ -7,6 +7,8 @@
 The output is a number that is a metric which depends on the profiler specified.
 """
 
+from __future__ import print_function
+
 import argparse
 import os
 import re
@@ -65,7 +67,7 @@
     if time is None:
       return 1
 
-    print time
+    print(time)
     return 0
 
   def _RunCallgrind(self):
diff --git a/testing/tools/suppressor.py b/testing/tools/suppressor.py
index 38351b5..2464289 100755
--- a/testing/tools/suppressor.py
+++ b/testing/tools/suppressor.py
@@ -3,6 +3,8 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+from __future__ import print_function
+
 import os
 
 # pylint: disable=relative-import
@@ -49,18 +51,18 @@
 
   def IsResultSuppressed(self, input_filename):
     if input_filename in self.suppression_set:
-      print "%s result is suppressed" % input_filename
+      print("%s result is suppressed" % input_filename)
       return True
     return False
 
   def IsExecutionSuppressed(self, input_filepath):
     if "xfa_specific" in input_filepath and not self.has_xfa:
-      print "%s execution is suppressed" % input_filepath
+      print("%s execution is suppressed" % input_filepath)
       return True
     return False
 
   def IsImageDiffSuppressed(self, input_filename):
     if input_filename in self.image_suppression_set:
-      print "%s image diff comparison is suppressed" % input_filename
+      print("%s image diff comparison is suppressed" % input_filename)
       return True
     return False
diff --git a/testing/tools/test_runner.py b/testing/tools/test_runner.py
index 4a34938..a7198cc 100644
--- a/testing/tools/test_runner.py
+++ b/testing/tools/test_runner.py
@@ -379,7 +379,8 @@
     os.makedirs(self.working_dir)
 
     self.features = subprocess.check_output(
-        [self.pdfium_test_path, '--show-config']).strip().split(',')
+        [self.pdfium_test_path,
+         '--show-config']).decode('utf-8').strip().split(',')
     self.test_suppressor = suppressor.Suppressor(
         finder, self.features, self.options.disable_javascript,
         self.options.disable_xfa)
diff --git a/testing/tools/text_diff.py b/testing/tools/text_diff.py
index fdf45a0..b408e6e 100755
--- a/testing/tools/text_diff.py
+++ b/testing/tools/text_diff.py
@@ -3,13 +3,15 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+from __future__ import print_function
+
 import difflib
 import sys
 
 
 def main(argv):
   if len(argv) != 3:
-    print '%s: invalid arguments' % argv[0]
+    print('%s: invalid arguments' % argv[0])
     return 2
   filename1 = argv[1]
   filename2 = argv[2]
@@ -21,7 +23,7 @@
     diffs = difflib.unified_diff(
         str1, str2, fromfile=filename1, tofile=filename2)
   except Exception as e:
-    print "something went astray: %s" % e
+    print("something went astray: %s" % e)
     return 1
   status_code = 0
   for diff in diffs: