Make PRESUBMIT.py accept SkiaPaths expected results.

Using Skia and using SkiaPaths sometimes lead to different rendering
results. For a pixel/corpus test case, if both Skia's and SkiaPaths'
rendering results are acceptable, a SkiaPaths specific expected result
is needed for this test case besides its Skia expected result.

This CL modifies PRESUBMIT.py to accept SkiaPaths specific expected
results with names in the form of
"NAME_expected_skiapaths(_(win|mac|linux))?.pdf.#.png", adds a
test for _CheckPNGFormat() and hooks up the test to run as part of the
presubmit process.

Bug: pdfium:1501
Change-Id: Ie8def15b9a18e86445e49620567d54b193d6bc0f
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/68230
Reviewed-by: Lei Zhang <thestig@chromium.org>
Commit-Queue: Hui Yingst <nigi@chromium.org>
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index b56d75e..303f415 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -294,9 +294,9 @@
 def _CheckPNGFormat(input_api, output_api):
   """Checks that .png files have a format that will be considered valid by our
   test runners. If a file ends with .png, then it must be of the form
-  NAME_expected(_skia)?(_(win|mac|linux))?.pdf.#.png"""
+  NAME_expected(_(skia|skiapaths))?(_(win|mac|linux))?.pdf.#.png"""
   expected_pattern = input_api.re.compile(
-      r'.+_expected(_skia)?(_(win|mac|linux))?\.pdf\.\d+.png')
+      r'.+_expected(_(skia|skiapaths))?(_(win|mac|linux))?\.pdf\.\d+.png')
   results = []
   for f in input_api.AffectedFiles(include_deletes=False):
     if not f.LocalPath().endswith('.png'):
@@ -320,4 +320,20 @@
   results += _CheckTestDuplicates(input_api, output_api)
   results += _CheckPNGFormat(input_api, output_api)
 
+  for f in input_api.AffectedFiles():
+    path, name = input_api.os_path.split(f.LocalPath())
+    if name == 'PRESUBMIT.py':
+      full_path = input_api.os_path.join(input_api.PresubmitLocalPath(), path)
+      test_file = input_api.os_path.join(path, 'PRESUBMIT_test.py')
+      if f.Action() != 'D' and input_api.os_path.exists(test_file):
+        # The PRESUBMIT.py file (and the directory containing it) might
+        # have been affected by being moved or removed, so only try to
+        # run the tests if they still exist.
+        results.extend(
+            input_api.canned_checks.RunUnitTestsInDirectory(
+                input_api,
+                output_api,
+                full_path,
+                whitelist=[r'^PRESUBMIT_test\.py$']))
+
   return results
diff --git a/PRESUBMIT_test.py b/PRESUBMIT_test.py
new file mode 100755
index 0000000..925db9b
--- /dev/null
+++ b/PRESUBMIT_test.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import unittest
+
+import PRESUBMIT
+from PRESUBMIT_test_mocks import MockInputApi, MockOutputApi, MockFile
+
+
+class CheckChangeOnUploadTest(unittest.TestCase):
+
+  def testCheckPNGFormat(self):
+    correct_paths = [
+        'test_expected.pdf.0.png',
+        'test_expected_win.pdf.1.png',
+        'test_expected_skia.pdf.2.png',
+        'test_expected_skiapaths.pdf.3.png',
+        'test_expected_skia_mac.pdf.4.png',
+        'test_expected_skiapaths_win.pdf.5.png',
+        'notpng.cc',  # Check will be skipped for non-PNG files
+    ]
+    wrong_paths = [
+        'expected.pdf.0.png',  # Missing '_expected'
+        'test1_expected.0.png',  # Missing '.pdf'
+        'test2_expected.pdf.png',  # Missing page number
+        'test3_expected.pdf.x.png',  # Wrong character for page number
+        'test4_expected_mac_skia.pdf.0.png',  # Wrong order of keywords
+        'test5_expected_useskia.pdf.0.png',  # Wrong keyword
+    ]
+    mock_input_api = MockInputApi()
+    mock_output_api = MockOutputApi()
+    mock_input_api.files = map(MockFile, correct_paths + wrong_paths)
+    errors = map(str, PRESUBMIT._CheckPNGFormat(mock_input_api,
+                                                mock_output_api))
+
+    self.assertEqual(len(wrong_paths), len(errors))
+    self.assertFalse('notpng.cc' in errors[0])
+    for path, error in zip(wrong_paths, errors):
+      self.assertIn(path, error)
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/PRESUBMIT_test_mocks.py b/PRESUBMIT_test_mocks.py
new file mode 100644
index 0000000..ce9fcb0
--- /dev/null
+++ b/PRESUBMIT_test_mocks.py
@@ -0,0 +1,69 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import re
+
+
+class MockInputApi(object):
+  """Mock class for the InputApi class.
+
+  This class can be used for unittests for presubmit by initializing the files
+  attribute as the list of changed files.
+  """
+
+  def __init__(self):
+    self.files = []
+    self.re = re
+
+  def AffectedFiles(self, file_filter=None, include_deletes=False):
+    # pylint: disable=unused-argument
+    return self.files
+
+
+class MockOutputApi(object):
+  """Mock class for the OutputApi class.
+
+  An instance of this class can be passed to presubmit unittests for outputting
+  various types of results.
+  """
+
+  class PresubmitResult(object):
+
+    def __init__(self, message, items=None, long_text=''):
+      self.message = message
+      self.items = items
+      self.long_text = long_text
+
+    def __repr__(self):
+      return self.message
+
+  class PresubmitError(PresubmitResult):
+
+    def __init__(self, message, items=None, long_text=''):
+      MockOutputApi.PresubmitResult.__init__(self, message, items, long_text)
+      self.type = 'error'
+
+
+class MockFile(object):
+  """Mock class for the File class.
+
+  This class can be used to form the mock list of changed files in
+  MockInputApi for presubmit unittests.
+  """
+
+  def __init__(self,
+               local_path,
+               new_contents=None,
+               old_contents=None,
+               action='A'):
+    self._local_path = local_path
+    if new_contents is None:
+      new_contents = []
+    self._new_contents = new_contents
+    self._changed_contents = [(i + 1, l) for i, l in enumerate(new_contents)]
+    self._action = action
+    self._old_contents = old_contents
+
+  def LocalPath(self):
+    return self._local_path