Tighten expected image selection

Makes expected image selection stricter by only allowing the most
specific expected path that exists to match. An image diff test now will
fail if a less specific expected path matches.

Bug: pdfium:1987
Change-Id: Ie881e344db046a1eff242e759b60581c165c3f89
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/103750
Commit-Queue: K. Moon <kmoon@chromium.org>
Reviewed-by: Nigi <nigi@chromium.org>
diff --git a/testing/tools/pngdiffer.py b/testing/tools/pngdiffer.py
index 39ced20..b2e70d2 100755
--- a/testing/tools/pngdiffer.py
+++ b/testing/tools/pngdiffer.py
@@ -95,53 +95,47 @@
     for page in itertools.count():
       page_diff = ImageDiff(actual_path=path_templates.GetActualPath(page))
       if not os.path.exists(page_diff.actual_path):
+        # No more actual pages.
         break
 
-      last_not_found_expected_path = None
-      compare_error = None
-      for expected_path in path_templates.GetExpectedPaths(page):
-        if not os.path.exists(expected_path):
-          last_not_found_expected_path = expected_path
-          continue
+      expected_path = path_templates.GetExpectedPath(page)
+      if os.path.exists(expected_path):
         page_diff.expected_path = expected_path
 
         compare_error = self._RunImageCompareCommand(page_diff)
-        if not compare_error:
-          # Found a match.
-          break
+        if compare_error:
+          page_diff.reason = str(compare_error)
 
-      if page_diff.expected_path:
-        if not compare_error:
-          # Proceed to next page
-          continue
-        page_diff.reason = str(compare_error)
-
-        # TODO(crbug.com/pdfium/1925): Compare and diff in a single invocation.
-        page_diff.diff_path = path_templates.GetDiffPath(page)
-        if not self._RunImageDiffCommand(page_diff):
-          print(f'WARNING: No diff for {page_diff.actual_path}')
-          page_diff.diff_path = None
+          # TODO(crbug.com/pdfium/1925): Compare and diff simultaneously.
+          page_diff.diff_path = path_templates.GetDiffPath(page)
+          if not self._RunImageDiffCommand(page_diff):
+            print(f'WARNING: No diff for {page_diff.actual_path}')
+            page_diff.diff_path = None
+        else:
+          # Validate that no other paths match.
+          for unexpected_path in path_templates.GetExpectedPaths(page)[1:]:
+            page_diff.expected_path = unexpected_path
+            if not self._RunImageCompareCommand(page_diff):
+              page_diff.reason = f'Also matches {unexpected_path}'
+              break
+          page_diff.expected_path = expected_path
       else:
         if page == 0:
           print(f'WARNING: no expected results files for {input_filename}')
-        if last_not_found_expected_path:
-          page_diff.reason = f'{last_not_found_expected_path} does not exist'
-        else:
-          page_diff.reason = f'Missing expected result for 0-based page {page}'
+        page_diff.reason = f'{expected_path} does not exist'
 
-      image_diffs.append(page_diff)
+      if page_diff.reason:
+        image_diffs.append(page_diff)
 
     return image_diffs
 
   def Regenerate(self, input_filename, source_dir, working_dir):
     path_templates = _PathTemplates(input_filename, source_dir, working_dir,
                                     self.os_name, self.suffix_order)
-
     for page in itertools.count():
-      # Loop through the generated page images. Stop when there is a page
-      # missing a png, which means the document ended.
-      actual_path = path_templates.GetActualPath(page)
-      if not os.path.isfile(actual_path):
+      page_diff = ImageDiff(actual_path=path_templates.GetActualPath(page))
+      if not os.path.isfile(page_diff.actual_path):
+        # No more actual pages.
         break
 
       # Regenerate the most specific expected path that exists. If there are no
@@ -149,7 +143,7 @@
       #
       # TODO(crbug.com/pdfium/1987): Clean up redundant expectations.
       expected_path = path_templates.GetExpectedPath(page)
-      shutil.copyfile(actual_path, expected_path)
+      shutil.copyfile(page_diff.actual_path, expected_path)
       self._RunCommand([_PNG_OPTIMIZER, expected_path])
 
 
@@ -184,13 +178,16 @@
   def GetDiffPath(self, page):
     return self.diff_path_template % page
 
-  def GetExpectedPaths(self, page):
+  def _GetPossibleExpectedPaths(self, page):
     return [template % page for template in self.expected_templates]
 
+  def GetExpectedPaths(self, page):
+    return list(filter(os.path.exists, self._GetPossibleExpectedPaths(page)))
+
   def GetExpectedPath(self, page, default_to_base=True):
     """Returns the most specific expected path that exists."""
     last_not_found_expected_path = None
-    for expected_path in self.GetExpectedPaths(page):
+    for expected_path in self._GetPossibleExpectedPaths(page):
       if os.path.exists(expected_path):
         return expected_path
       last_not_found_expected_path = expected_path
diff --git a/testing/tools/test_runner.py b/testing/tools/test_runner.py
index 7fe2ee9..d72ca33 100644
--- a/testing/tools/test_runner.py
+++ b/testing/tools/test_runner.py
@@ -104,8 +104,12 @@
     # Report test result to ResultDB.
     if self.resultdb:
       only_artifacts = None
+      only_failure_reason = test_result.reason
       if len(test_result.image_artifacts) == 1:
-        only_artifacts = test_result.image_artifacts[0].GetDiffArtifacts()
+        only = test_result.image_artifacts[0]
+        only_artifacts = only.GetDiffArtifacts()
+        if only.GetDiffReason():
+          only_failure_reason += f': {only.GetDiffReason()}'
       self.resultdb.Post(
           test_id=test_result.test_id,
           status=test_result.status,
@@ -113,7 +117,7 @@
           test_log=test_result.log,
           test_file=None,
           artifacts=only_artifacts,
-          failure_reason=test_result.reason)
+          failure_reason=only_failure_reason)
 
       # Milo only supports a single diff per test, so if we have multiple pages,
       # report each page as its own "test."