CodeHealth: Migrate PRESUBMITs from Python 2 to Python 3

This CL includes:
* set Pylint version to 2.7
* pass skip_shebang_check=True to RunUnitTestsInDirectory

And fixes some Pylint warnings:
* W0406: fix import-self
* E1101: Set name and aliases as an abstract property in
  encode_pdf_filter.py since name and aliases are introduced in
  subclasses.
* E0012: Remove Py2 hint of # pylint: disable=relative-import
* R0205: Replace all "class ClassName(objects)" with "class ClassName"
* E1120: Ignore this error. see
  https://github.com/PyCQA/pylint/issues/3563
* W0707: explicitly re-raising using the 'from' keyword

Bug: pdfium:1674, chromium:1262354, chromium:1262297
Change-Id: I205d254514f1ae667af818c789b5e6d89a061c68
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/87270
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 55d417c..fa7241f 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -496,6 +496,9 @@
                 input_api,
                 output_api,
                 full_path,
-                files_to_check=[r'^PRESUBMIT_test\.py$']))
+                files_to_check=[r'^PRESUBMIT_test\.py$'],
+                run_on_python2=not USE_PYTHON3,
+                run_on_python3=USE_PYTHON3,
+                skip_shebang_check=True))
 
   return results
diff --git a/testing/tools/PRESUBMIT.py b/testing/tools/PRESUBMIT.py
index 02c256b..0a65565 100644
--- a/testing/tools/PRESUBMIT.py
+++ b/testing/tools/PRESUBMIT.py
@@ -13,7 +13,8 @@
 
 def _CommonChecks(input_api, output_api):
   tests = []
-  tests.extend(input_api.canned_checks.GetPylint(input_api, output_api))
+  tests.extend(
+      input_api.canned_checks.GetPylint(input_api, output_api, version='2.7'))
   return tests
 
 
diff --git a/testing/tools/api_check.py b/testing/tools/api_check.py
index f340243..5b582b5 100755
--- a/testing/tools/api_check.py
+++ b/testing/tools/api_check.py
@@ -101,7 +101,7 @@
 
 
 def _FindDuplicates(functions):
-  return set([f for f in functions if functions.count(f) > 1])
+  return {f for f in functions if functions.count(f) > 1}
 
 
 def _CheckAndPrintFailures(failure_list, failure_message):
diff --git a/testing/tools/coverage/coverage_report.py b/testing/tools/coverage/coverage_report.py
index 06c03e4..32016fc 100755
--- a/testing/tools/coverage/coverage_report.py
+++ b/testing/tools/coverage/coverage_report.py
@@ -60,7 +60,7 @@
 }
 
 
-class CoverageExecutor(object):
+class CoverageExecutor:
 
   def __init__(self, parser, args):
     """Initialize executor based on the current script environment
@@ -276,7 +276,7 @@
       print('Failed to successfully build binaries')
       return False
 
-    for name in self.coverage_tests.keys():
+    for name in self.coverage_tests:
       if not self.generate_coverage(name, self.coverage_tests[name]):
         print('Failed to successfully generate coverage data')
         return False
diff --git a/testing/tools/encode_pdf_filter.py b/testing/tools/encode_pdf_filter.py
index 5f29c82..3e0be9c 100755
--- a/testing/tools/encode_pdf_filter.py
+++ b/testing/tools/encode_pdf_filter.py
@@ -13,6 +13,7 @@
 """
 
 import argparse
+from abc import ABCMeta, abstractmethod
 import base64
 import collections
 import collections.abc
@@ -21,10 +22,20 @@
 import zlib
 
 
-class _PdfStream:
+class _PdfStream(metaclass=ABCMeta):
   _unique_filter_classes = []
   _filter_classes = {}
 
+  @property
+  @abstractmethod
+  def name(self):
+    pass
+
+  @property
+  @abstractmethod
+  def aliases(self):
+    pass
+
   @staticmethod
   def GetFilterByName(name):
     # Tolerate any case-insensitive match for "/Name" or "Name", or an alias.
@@ -99,6 +110,20 @@
   def __init__(self):
     super().__init__(io.BytesIO())
 
+  @property
+  def name(self):
+    # Return an invalid name, so as to ensure _SinkPdfStream.Register()
+    # cannot be called. This method has to be implemented, because this
+    # script create `_SinkPdfStream` instances.
+    return ''
+
+  @property
+  def aliases(self):
+    # Return an invalid aliases, so as to ensure _SinkPdfStream.Register()
+    # cannot be called. This method has to be implemented, because this
+    # script create `_SinkPdfStream` instances.
+    return ()
+
   def close(self):
     # Don't call io.BytesIO.close(); this deallocates the written data.
     self.flush()
@@ -114,6 +139,16 @@
     self.wrapcol = wrapcol
     self.column = 0
 
+  @property
+  @abstractmethod
+  def name(self):
+    pass
+
+  @property
+  @abstractmethod
+  def aliases(self):
+    pass
+
   def write(self, data):
     if not self.wrapcol:
       self.buffer.write(data)
@@ -134,8 +169,16 @@
 
 
 class _Ascii85DecodePdfStream(_AsciiPdfStream):
-  name = '/ASCII85Decode'
-  aliases = ('ascii85', 'base85')
+  _name = '/ASCII85Decode'
+  _aliases = ('ascii85', 'base85')
+
+  @property
+  def name(self):
+    return self._name
+
+  @property
+  def aliases(self):
+    return self._aliases
 
   def __init__(self, out_buffer, **kwargs):
     super().__init__(out_buffer, **kwargs)
@@ -158,8 +201,16 @@
 
 
 class _AsciiHexDecodePdfStream(_AsciiPdfStream):
-  name = '/ASCIIHexDecode'
-  aliases = ('base16', 'hex', 'hexadecimal')
+  _name = '/ASCIIHexDecode'
+  _aliases = ('base16', 'hex', 'hexadecimal')
+
+  @property
+  def name(self):
+    return self._name
+
+  @property
+  def aliases(self):
+    return self._aliases
 
   def __init__(self, out_buffer, **kwargs):
     super().__init__(out_buffer, **kwargs)
@@ -169,13 +220,21 @@
 
 
 class _FlateDecodePdfStream(_PdfStream):
-  name = '/FlateDecode'
-  aliases = ('deflate', 'flate', 'zlib')
+  _name = '/FlateDecode'
+  _aliases = ('deflate', 'flate', 'zlib')
 
   def __init__(self, out_buffer, **kwargs):
     super().__init__(out_buffer, **kwargs)
     self.deflate = zlib.compressobj(level=9, memLevel=9)
 
+  @property
+  def name(cls):
+    return cls._name
+
+  @property
+  def aliases(self):
+    return self._aliases
+
   def write(self, data):
     self.buffer.write(self.deflate.compress(data))
 
@@ -189,6 +248,16 @@
 
 class _VirtualPdfStream(_PdfStream):
 
+  @property
+  @abstractmethod
+  def name(self):
+    pass
+
+  @property
+  @abstractmethod
+  def aliases(self):
+    pass
+
   @classmethod
   def RegisterByName(cls):
     pass
@@ -199,13 +268,21 @@
 
 
 class _PassthroughPdfStream(_VirtualPdfStream):
-  name = '(virtual) passthrough'
-  aliases = ('noop', 'passthrough')
+  _name = '(virtual) passthrough'
+  _aliases = ('noop', 'passthrough')
+
+  @property
+  def name(self):
+    return self._name
+
+  @property
+  def aliases(self):
+    return self._aliases
 
 
 class _PngIdatPdfStream(_VirtualPdfStream):
-  name = '(virtual) PNG IDAT'
-  aliases = ('png',)
+  _name = '(virtual) PNG IDAT'
+  _aliases = ('png',)
 
   _EXPECT_HEADER = -1
   _EXPECT_LENGTH = -2
@@ -215,6 +292,14 @@
   _PNG_HEADER = 0x89504E470D0A1A0A
   _PNG_CHUNK_IDAT = 0x49444154
 
+  @property
+  def name(self):
+    return self._name
+
+  @property
+  def aliases(self):
+    return self._aliases
+
   @classmethod
   def AddEntries(cls, entries):
     # Technically only true for compression method 0 (zlib), but no other
diff --git a/testing/tools/githelper.py b/testing/tools/githelper.py
index 4637ed7..a8d62e7 100644
--- a/testing/tools/githelper.py
+++ b/testing/tools/githelper.py
@@ -5,11 +5,10 @@
 
 import subprocess
 
-# pylint: disable=relative-import
 from common import RunCommandPropagateErr
 
 
-class GitHelper(object):
+class GitHelper:
   """Issues git commands. Stateful."""
 
   def __init__(self):
diff --git a/testing/tools/pngdiffer.py b/testing/tools/pngdiffer.py
index 911da10..1da4c46 100755
--- a/testing/tools/pngdiffer.py
+++ b/testing/tools/pngdiffer.py
@@ -11,7 +11,6 @@
 import shutil
 import sys
 
-# pylint: disable=relative-import
 import common
 
 
@@ -42,7 +41,6 @@
 
 class NotFoundError(Exception):
   """Raised when file doesn't exist"""
-  pass
 
 
 class PNGDiffer():
@@ -165,7 +163,8 @@
 
 ACTUAL_TEMPLATE = '.pdf.%d.png'
 
-class PathTemplates(object):
+
+class PathTemplates:
 
   def __init__(self, input_filename, source_dir, working_dir, os_name,
                max_path_mode):
diff --git a/testing/tools/run_corpus_tests.py b/testing/tools/run_corpus_tests.py
index c81aea3..edc4f02 100755
--- a/testing/tools/run_corpus_tests.py
+++ b/testing/tools/run_corpus_tests.py
@@ -5,7 +5,6 @@
 
 import sys
 
-# pylint: disable=relative-import
 import test_runner
 
 
diff --git a/testing/tools/run_javascript_tests.py b/testing/tools/run_javascript_tests.py
index 948c1af..cb6e69a 100755
--- a/testing/tools/run_javascript_tests.py
+++ b/testing/tools/run_javascript_tests.py
@@ -5,7 +5,6 @@
 
 import sys
 
-# pylint: disable=relative-import
 import test_runner
 
 
diff --git a/testing/tools/run_pixel_tests.py b/testing/tools/run_pixel_tests.py
index 0992e0a..92523d1 100755
--- a/testing/tools/run_pixel_tests.py
+++ b/testing/tools/run_pixel_tests.py
@@ -5,7 +5,6 @@
 
 import sys
 
-# pylint: disable=relative-import
 import test_runner
 
 
diff --git a/testing/tools/safetynet_compare.py b/testing/tools/safetynet_compare.py
index 8a5942d..7ce7d5a 100755
--- a/testing/tools/safetynet_compare.py
+++ b/testing/tools/safetynet_compare.py
@@ -18,7 +18,6 @@
 import sys
 import tempfile
 
-# pylint: disable=relative-import
 from common import GetBooleanGnArg
 from common import PrintErr
 from common import RunCommandPropagateErr
@@ -35,7 +34,7 @@
   return (test_case, result)
 
 
-class CompareRun(object):
+class CompareRun:
   """A comparison between two branches of pdfium."""
 
   def __init__(self, args):
@@ -529,8 +528,7 @@
       output_filename = (
           'callgrind.out.%s.%s' % (test_case.replace('/', '_'), run_label))
       return os.path.join(self.args.output_dir, output_filename)
-    else:
-      return None
+    return None
 
   def _DrawConclusions(self, times_before_branch, times_after_branch):
     """Draws conclusions comparing results of test runs in two branches.
diff --git a/testing/tools/safetynet_conclusions.py b/testing/tools/safetynet_conclusions.py
index dc6e3dd..fb563b5 100644
--- a/testing/tools/safetynet_conclusions.py
+++ b/testing/tools/safetynet_conclusions.py
@@ -33,7 +33,7 @@
 }
 
 
-class ComparisonConclusions(object):
+class ComparisonConclusions:
   """All conclusions drawn from a comparison.
 
   This is initialized empty and then processes pairs of results for each test
@@ -184,7 +184,7 @@
     return output_dict
 
 
-class ComparisonSummary(object):
+class ComparisonSummary:
   """Totals computed for a comparison."""
 
   def __init__(self):
@@ -209,7 +209,7 @@
     return result
 
 
-class CaseResult(object):
+class CaseResult:
   """The conclusion for the comparison of a single test case."""
 
   def __init__(self, case_name, before, after, ratio, rating):
diff --git a/testing/tools/safetynet_image.py b/testing/tools/safetynet_image.py
index 3161178..9232098 100644
--- a/testing/tools/safetynet_image.py
+++ b/testing/tools/safetynet_image.py
@@ -15,7 +15,6 @@
 import sys
 import webbrowser
 
-# pylint: disable=relative-import
 from common import DirectoryFinder
 
 
@@ -23,7 +22,7 @@
   return image_comparison.GenerateOneDiff(image)
 
 
-class ImageComparison(object):
+class ImageComparison:
   """Compares pairs of page images and generates an HTML to look at differences.
 
   The images are all assumed to have the same name and be in two directories:
@@ -241,7 +240,7 @@
       f.write('</td></tr>')
 
 
-class ImageLocations(object):
+class ImageLocations:
   """Contains the locations of input and output image files.
   """
 
diff --git a/testing/tools/safetynet_job.py b/testing/tools/safetynet_job.py
index c1361c1..ba1d055 100755
--- a/testing/tools/safetynet_job.py
+++ b/testing/tools/safetynet_job.py
@@ -15,14 +15,13 @@
 import os
 import sys
 
-# pylint: disable=relative-import
 from common import PrintWithTime
 from common import RunCommandPropagateErr
 from githelper import GitHelper
 from safetynet_conclusions import PrintConclusionsDictHumanReadable
 
 
-class JobContext(object):
+class JobContext:
   """Context for a single run, including name and directory paths."""
 
   def __init__(self, args):
@@ -36,7 +35,7 @@
                                             '%s.log' % self.datetime)
 
 
-class JobRun(object):
+class JobRun:
   """A single run looking for regressions since the last one."""
 
   def __init__(self, args, context):
diff --git a/testing/tools/safetynet_measure.py b/testing/tools/safetynet_measure.py
index be61aa8..55738cb 100755
--- a/testing/tools/safetynet_measure.py
+++ b/testing/tools/safetynet_measure.py
@@ -15,7 +15,6 @@
 import subprocess
 import sys
 
-# pylint: disable=relative-import
 from common import PrintErr
 
 CALLGRIND_PROFILER = 'callgrind'
@@ -25,7 +24,7 @@
 PDFIUM_TEST = 'pdfium_test'
 
 
-class PerformanceRun(object):
+class PerformanceRun:
   """A single measurement of a test case."""
 
   def __init__(self, args):
diff --git a/testing/tools/skia_gold/__init__.py b/testing/tools/skia_gold/__init__.py
index a16cacc..1c98a89 100644
--- a/testing/tools/skia_gold/__init__.py
+++ b/testing/tools/skia_gold/__init__.py
@@ -1,3 +1,3 @@
-from . import path_util
+from .path_util import AddDirToPathIfNeeded, GetPDFiumDir
 
-path_util.AddDirToPathIfNeeded(path_util.GetPDFiumDir(), 'build')
+AddDirToPathIfNeeded(GetPDFiumDir(), 'build')
diff --git a/testing/tools/skia_gold/path_util.py b/testing/tools/skia_gold/path_util.py
index 94c9297..e9fb04a 100644
--- a/testing/tools/skia_gold/path_util.py
+++ b/testing/tools/skia_gold/path_util.py
@@ -8,6 +8,7 @@
 
 
 def AddDirToPathIfNeeded(*path_parts):
+  # pylint: disable=no-value-for-parameter
   path = os.path.abspath(os.path.join(*path_parts))
   if os.path.isdir(path) and path not in sys.path:
     sys.path.append(path)
diff --git a/testing/tools/skia_gold/pdfium_skia_gold_session.py b/testing/tools/skia_gold/pdfium_skia_gold_session.py
index 9f7d198..8b6d3d6 100644
--- a/testing/tools/skia_gold/pdfium_skia_gold_session.py
+++ b/testing/tools/skia_gold/pdfium_skia_gold_session.py
@@ -4,13 +4,12 @@
 # found in the LICENSE file.
 """PDFium implementation of //build/skia_gold_common/skia_gold_session.py."""
 
-# pylint: disable=relative-import
 from skia_gold_common import output_managerless_skia_gold_session as omsgs
 
 
 # ComparisonResults nested inside the SkiaGoldSession causes issues with
 # multiprocessing and pickling, so it was moved out here.
-class PDFiumComparisonResults(object):
+class PDFiumComparisonResults:
   """Struct-like object for storing results of an image comparison."""
 
   def __init__(self):
diff --git a/testing/tools/skia_gold/skia_gold.py b/testing/tools/skia_gold/skia_gold.py
index 112a560..136beb1 100644
--- a/testing/tools/skia_gold/skia_gold.py
+++ b/testing/tools/skia_gold/skia_gold.py
@@ -97,7 +97,7 @@
   os.makedirs(output_dir)
 
 
-class SkiaGoldTester(object):
+class SkiaGoldTester:
 
   def __init__(self, source_type, skia_gold_args, process_name=None):
     """
@@ -175,7 +175,7 @@
         self.GetSkiaGoldSessionManager().GetSessionClass().StatusCodes
     if status == status_codes.SUCCESS:
       return True
-    elif status == status_codes.AUTH_FAILURE:
+    if status == status_codes.AUTH_FAILURE:
       logging.error('Gold authentication failed with output %s', error)
     elif status == status_codes.INIT_FAILURE:
       logging.error('Gold initialization failed with output %s', error)
diff --git a/testing/tools/suppressor.py b/testing/tools/suppressor.py
index 9eec78b..c59941a 100755
--- a/testing/tools/suppressor.py
+++ b/testing/tools/suppressor.py
@@ -7,7 +7,6 @@
 
 import os
 
-# pylint: disable=relative-import
 import common
 
 
diff --git a/testing/tools/test_runner.py b/testing/tools/test_runner.py
index 3e935d9..f64d7a6 100644
--- a/testing/tools/test_runner.py
+++ b/testing/tools/test_runner.py
@@ -15,7 +15,6 @@
 import subprocess
 import sys
 
-# pylint: disable=relative-import
 import common
 import pngdiffer
 import suppressor
@@ -49,6 +48,9 @@
     result = this.GenerateAndTest(input_filename, source_dir)
     return (result, input_filename, source_dir)
   except KeyboardInterrupt:
+    # TODO(https://crbug.com/pdfium/1674): Re-enable this check after try bots
+    # can run with Python3.
+    # pylint: disable=raise-missing-from
     raise KeyboardInterruptError()
 
 
@@ -64,6 +66,9 @@
       results.append((test_name, skia_success, input_filename))
     return results
   except KeyboardInterrupt:
+    # TODO(https://crbug.com/pdfium/1674): Re-enable this check after try bots
+    # can run with Python3.
+    # pylint: disable=raise-missing-from
     raise KeyboardInterruptError()
 
 
@@ -227,7 +232,7 @@
     try:
       with open(txt_path, "r") as txt_file:
         txt_data = txt_file.readlines()
-      if not len(txt_data):
+      if not txt_data:
         return None
       sys.stdout.write('Unexpected output:\n')
       for line in txt_data: