blob: b13f7ee57bb1818e61e91ff2ce58415170676d77 [file] [log] [blame]
Lei Zhangc5568a22022-01-08 06:42:01 +00001#!/usr/bin/env python3
dsinclair2a8a20c2016-04-25 09:46:17 -07002# Copyright 2016 The PDFium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
Stephanie Kim606d0852021-03-04 16:02:41 +00006from __future__ import print_function
Stephanie Kim34cbd922021-03-17 20:25:12 +00007from __future__ import division
Stephanie Kim606d0852021-03-04 16:02:41 +00008
9import argparse
dsinclair849284d2016-05-17 06:13:36 -070010import functools
11import multiprocessing
dsinclair2a8a20c2016-04-25 09:46:17 -070012import os
13import re
dsinclair849284d2016-05-17 06:13:36 -070014import shutil
dsinclair2a8a20c2016-04-25 09:46:17 -070015import subprocess
16import sys
17
18import common
19import pngdiffer
20import suppressor
Stephanie Kim606d0852021-03-04 16:02:41 +000021from skia_gold import skia_gold
22
dsinclair2a8a20c2016-04-25 09:46:17 -070023
Ryan Harrison70cca362018-08-10 18:55:46 +000024# Arbitrary timestamp, expressed in seconds since the epoch, used to make sure
25# that tests that depend on the current time are stable. Happens to be the
26# timestamp of the first commit to repo, 2014/5/9 17:48:50.
27TEST_SEED_TIME = "1399672130"
28
Lei Zhangfe3ab672019-11-19 19:09:40 +000029# List of test types that should run text tests instead of pixel tests.
30TEXT_TESTS = ['javascript']
31
Lei Zhang30543372019-11-19 19:02:30 +000032
33class KeyboardInterruptError(Exception):
34 pass
35
dsinclair849284d2016-05-17 06:13:36 -070036
dsinclair2a8a20c2016-04-25 09:46:17 -070037# Nomenclature:
38# x_root - "x"
39# x_filename - "x.ext"
40# x_path - "path/to/a/b/c/x.ext"
41# c_dir - "path/to/a/b/c"
42
Lei Zhang30543372019-11-19 19:02:30 +000043
dsinclair849284d2016-05-17 06:13:36 -070044def TestOneFileParallel(this, test_case):
45 """Wrapper to call GenerateAndTest() and redirect output to stdout."""
46 try:
47 input_filename, source_dir = test_case
Lei Zhang30543372019-11-19 19:02:30 +000048 result = this.GenerateAndTest(input_filename, source_dir)
dsinclair849284d2016-05-17 06:13:36 -070049 return (result, input_filename, source_dir)
50 except KeyboardInterrupt:
Lingqi Chied293672021-11-24 20:53:01 +000051 # TODO(https://crbug.com/pdfium/1674): Re-enable this check after try bots
52 # can run with Python3.
53 # pylint: disable=raise-missing-from
dsinclair849284d2016-05-17 06:13:36 -070054 raise KeyboardInterruptError()
55
56
Stephanie Kim34cbd922021-03-17 20:25:12 +000057def RunSkiaWrapper(this, input_chunk):
Stephanie Kim606d0852021-03-04 16:02:41 +000058 """Wrapper to call RunSkia() and redirect output to stdout"""
Stephanie Kim606d0852021-03-04 16:02:41 +000059 try:
Stephanie Kim34cbd922021-03-17 20:25:12 +000060 results = []
61 for img_path_input_filename in input_chunk:
62 img_path, input_filename = img_path_input_filename
63 multiprocessing_name = multiprocessing.current_process().name
64
65 test_name, skia_success = this.RunSkia(img_path, multiprocessing_name)
66 results.append((test_name, skia_success, input_filename))
67 return results
Stephanie Kim606d0852021-03-04 16:02:41 +000068 except KeyboardInterrupt:
Lingqi Chied293672021-11-24 20:53:01 +000069 # TODO(https://crbug.com/pdfium/1674): Re-enable this check after try bots
70 # can run with Python3.
71 # pylint: disable=raise-missing-from
Stephanie Kim606d0852021-03-04 16:02:41 +000072 raise KeyboardInterruptError()
73
74
Lei Zhang5767aca2018-12-05 19:57:46 +000075def DeleteFiles(files):
76 """Utility function to delete a list of files"""
77 for f in files:
78 if os.path.exists(f):
79 os.remove(f)
80
81
dsinclair2a8a20c2016-04-25 09:46:17 -070082class TestRunner:
Lei Zhang30543372019-11-19 19:02:30 +000083
dsinclair2a8a20c2016-04-25 09:46:17 -070084 def __init__(self, dirname):
Ryan Harrison80302c72018-05-10 18:27:25 +000085 # Currently the only used directories are corpus, javascript, and pixel,
86 # which all correspond directly to the type for the test being run. In the
87 # future if there are tests that don't have this clean correspondence, then
88 # an argument for the type will need to be added.
dsinclair2a8a20c2016-04-25 09:46:17 -070089 self.test_dir = dirname
Ryan Harrison80302c72018-05-10 18:27:25 +000090 self.test_type = dirname
Lei Zhang5767aca2018-12-05 19:57:46 +000091 self.delete_output_on_success = False
Henrique Nakashima3bcabf32017-06-27 09:48:24 -040092 self.enforce_expected_images = False
Stephanie Kim606d0852021-03-04 16:02:41 +000093 self.skia_tester = None
94
95 def GetSkiaGoldTester(self, process_name=None):
96 if not self.skia_tester:
97 self.skia_tester = skia_gold.SkiaGoldTester(
98 source_type=self.test_type,
99 skia_gold_args=self.options,
100 process_name=process_name)
101 return self.skia_tester
102
103 def RunSkia(self, img_path, process_name=None):
104 skia_tester = self.GetSkiaGoldTester(process_name=process_name)
105 # The output filename without image extension becomes the test name.
106 # For example, "/path/to/.../testing/corpus/example_005.pdf.0.png"
107 # becomes "example_005.pdf.0".
108 test_name = os.path.splitext(os.path.split(img_path)[1])[0]
109 skia_success = skia_tester.UploadTestResultToSkiaGold(test_name, img_path)
110 sys.stdout.flush()
111 return test_name, skia_success
dsinclair2a8a20c2016-04-25 09:46:17 -0700112
stephanafa05e972017-01-02 06:19:41 -0800113 # GenerateAndTest returns a tuple <success, outputfiles> where
114 # success is a boolean indicating whether the tests passed comparison
115 # tests and outputfiles is a list tuples:
116 # (path_to_image, md5_hash_of_pixelbuffer)
dsinclair2a8a20c2016-04-25 09:46:17 -0700117 def GenerateAndTest(self, input_filename, source_dir):
118 input_root, _ = os.path.splitext(input_filename)
dsinclair2a8a20c2016-04-25 09:46:17 -0700119 pdf_path = os.path.join(self.working_dir, input_root + '.pdf')
120
121 # Remove any existing generated images from previous runs.
122 actual_images = self.image_differ.GetActualFiles(input_filename, source_dir,
123 self.working_dir)
Lei Zhang5767aca2018-12-05 19:57:46 +0000124 DeleteFiles(actual_images)
dsinclair2a8a20c2016-04-25 09:46:17 -0700125
126 sys.stdout.flush()
127
128 raised_exception = self.Generate(source_dir, input_filename, input_root,
129 pdf_path)
130
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400131 if raised_exception is not None:
Stephanie Kim606d0852021-03-04 16:02:41 +0000132 print('FAILURE: {}; {}'.format(input_filename, raised_exception))
stephanafa05e972017-01-02 06:19:41 -0800133 return False, []
dsinclair2a8a20c2016-04-25 09:46:17 -0700134
stephanafa05e972017-01-02 06:19:41 -0800135 results = []
Lei Zhangfe3ab672019-11-19 19:09:40 +0000136 if self.test_type in TEXT_TESTS:
137 expected_txt_path = os.path.join(source_dir, input_root + '_expected.txt')
Daniel Hosseinian77b3a432019-12-18 17:54:37 +0000138 raised_exception = self.TestText(input_filename, input_root,
139 expected_txt_path, pdf_path)
dsinclair2a8a20c2016-04-25 09:46:17 -0700140 else:
Lei Zhang17b67222022-04-01 18:02:29 +0000141 raised_exception, results = self.TestPixel(pdf_path, source_dir)
dsinclair2a8a20c2016-04-25 09:46:17 -0700142
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400143 if raised_exception is not None:
Stephanie Kim606d0852021-03-04 16:02:41 +0000144 print('FAILURE: {}; {}'.format(input_filename, raised_exception))
stephanafa05e972017-01-02 06:19:41 -0800145 return False, results
dsinclair2a8a20c2016-04-25 09:46:17 -0700146
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400147 if actual_images:
dsinclair2a8a20c2016-04-25 09:46:17 -0700148 if self.image_differ.HasDifferences(input_filename, source_dir,
149 self.working_dir):
Henrique Nakashima15bc9742018-04-26 15:55:07 +0000150 self.RegenerateIfNeeded_(input_filename, source_dir)
stephanafa05e972017-01-02 06:19:41 -0800151 return False, results
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400152 else:
Lei Zhang30543372019-11-19 19:02:30 +0000153 if (self.enforce_expected_images and
154 not self.test_suppressor.IsImageDiffSuppressed(input_filename)):
Henrique Nakashima15bc9742018-04-26 15:55:07 +0000155 self.RegenerateIfNeeded_(input_filename, source_dir)
Stephanie Kim606d0852021-03-04 16:02:41 +0000156 print('FAILURE: {}; Missing expected images'.format(input_filename))
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400157 return False, results
158
Lei Zhang5767aca2018-12-05 19:57:46 +0000159 if self.delete_output_on_success:
160 DeleteFiles(actual_images)
stephanafa05e972017-01-02 06:19:41 -0800161 return True, results
dsinclair2a8a20c2016-04-25 09:46:17 -0700162
Hui Yingstb8b3f5f2020-04-11 00:20:36 +0000163 # TODO(crbug.com/pdfium/1508): Add support for an option to automatically
164 # generate Skia/SkiaPaths specific expected results.
Henrique Nakashima15bc9742018-04-26 15:55:07 +0000165 def RegenerateIfNeeded_(self, input_filename, source_dir):
Lei Zhang30543372019-11-19 19:02:30 +0000166 if (not self.options.regenerate_expected or
167 self.test_suppressor.IsResultSuppressed(input_filename) or
168 self.test_suppressor.IsImageDiffSuppressed(input_filename)):
Henrique Nakashima15bc9742018-04-26 15:55:07 +0000169 return
170
171 platform_only = (self.options.regenerate_expected == 'platform')
Lei Zhang30543372019-11-19 19:02:30 +0000172 self.image_differ.Regenerate(input_filename, source_dir, self.working_dir,
173 platform_only)
Henrique Nakashima15bc9742018-04-26 15:55:07 +0000174
dsinclair2a8a20c2016-04-25 09:46:17 -0700175 def Generate(self, source_dir, input_filename, input_root, pdf_path):
176 original_path = os.path.join(source_dir, input_filename)
177 input_path = os.path.join(source_dir, input_root + '.in')
178
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400179 input_event_path = os.path.join(source_dir, input_root + '.evt')
dsinclair849284d2016-05-17 06:13:36 -0700180 if os.path.exists(input_event_path):
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400181 output_event_path = os.path.splitext(pdf_path)[0] + '.evt'
dsinclair849284d2016-05-17 06:13:36 -0700182 shutil.copyfile(input_event_path, output_event_path)
183
dsinclair2a8a20c2016-04-25 09:46:17 -0700184 if not os.path.exists(input_path):
185 if os.path.exists(original_path):
186 shutil.copyfile(original_path, pdf_path)
187 return None
188
189 sys.stdout.flush()
dsinclair849284d2016-05-17 06:13:36 -0700190
Lei Zhang30543372019-11-19 19:02:30 +0000191 return common.RunCommand([
192 sys.executable, self.fixup_path, '--output-dir=' + self.working_dir,
193 input_path
194 ])
dsinclair2a8a20c2016-04-25 09:46:17 -0700195
Daniel Hosseinian77b3a432019-12-18 17:54:37 +0000196 def TestText(self, input_filename, input_root, expected_txt_path, pdf_path):
dsinclair2a8a20c2016-04-25 09:46:17 -0700197 txt_path = os.path.join(self.working_dir, input_root + '.txt')
198
199 with open(txt_path, 'w') as outfile:
Lei Zhang30543372019-11-19 19:02:30 +0000200 cmd_to_run = [
Daniel Hosseinian77b3a432019-12-18 17:54:37 +0000201 self.pdfium_test_path, '--send-events', '--time=' + TEST_SEED_TIME
Lei Zhang30543372019-11-19 19:02:30 +0000202 ]
Daniel Hosseinian77b3a432019-12-18 17:54:37 +0000203
204 if self.options.disable_javascript:
205 cmd_to_run.append('--disable-javascript')
206
Daniel Hosseinian09dbeac2020-01-24 19:41:31 +0000207 if self.options.disable_xfa:
208 cmd_to_run.append('--disable-xfa')
209
Daniel Hosseinian77b3a432019-12-18 17:54:37 +0000210 cmd_to_run.append(pdf_path)
K. Moon5d075e42021-04-06 22:57:11 +0000211 try:
212 subprocess.check_call(cmd_to_run, stdout=outfile)
213 except subprocess.CalledProcessError as e:
214 return e
dsinclair2a8a20c2016-04-25 09:46:17 -0700215
Daniel Hosseinian77b3a432019-12-18 17:54:37 +0000216 # If the expected file does not exist, the output is expected to be empty.
Lei Zhangfe3ab672019-11-19 19:09:40 +0000217 if not os.path.exists(expected_txt_path):
218 return self._VerifyEmptyText(txt_path)
219
Daniel Hosseinian77b3a432019-12-18 17:54:37 +0000220 # If JavaScript is disabled, the output should be empty.
221 # However, if the test is suppressed and JavaScript is disabled, do not
222 # verify that the text is empty so the suppressed test does not surprise.
223 if (self.options.disable_javascript and
224 not self.test_suppressor.IsResultSuppressed(input_filename)):
225 return self._VerifyEmptyText(txt_path)
226
dsinclair2a8a20c2016-04-25 09:46:17 -0700227 cmd = [sys.executable, self.text_diff_path, expected_txt_path, txt_path]
228 return common.RunCommand(cmd)
229
Lei Zhangfe3ab672019-11-19 19:09:40 +0000230 def _VerifyEmptyText(self, txt_path):
231 try:
232 with open(txt_path, "r") as txt_file:
233 txt_data = txt_file.readlines()
Lingqi Chied293672021-11-24 20:53:01 +0000234 if not txt_data:
Lei Zhangfe3ab672019-11-19 19:09:40 +0000235 return None
236 sys.stdout.write('Unexpected output:\n')
237 for line in txt_data:
238 sys.stdout.write(line)
239 raise Exception('%s should be empty.' % txt_path)
240 except Exception as e:
241 return e
242
Stephanie Kim0314ade2021-03-11 00:27:35 +0000243 # TODO(crbug.com/pdfium/1656): Remove when ready to fully switch over to
244 # Skia Gold
Lei Zhang17b67222022-04-01 18:02:29 +0000245 def TestPixel(self, pdf_path, source_dir):
Lei Zhang30543372019-11-19 19:02:30 +0000246 cmd_to_run = [
247 self.pdfium_test_path, '--send-events', '--png', '--md5',
248 '--time=' + TEST_SEED_TIME
249 ]
Ryan Harrison1118a662018-05-31 19:26:52 +0000250
Lei Zhang17b67222022-04-01 18:02:29 +0000251 if 'use_ahem' in source_dir or 'use_symbolneu' in source_dir:
Ryan Harrison1118a662018-05-31 19:26:52 +0000252 cmd_to_run.append('--font-dir=%s' % self.font_dir)
Lei Zhang17b67222022-04-01 18:02:29 +0000253 else:
254 cmd_to_run.append('--font-dir=%s' % self.third_party_font_dir)
255 cmd_to_run.append('--croscore-font-names')
Ryan Harrison1118a662018-05-31 19:26:52 +0000256
Daniel Hosseinian77b3a432019-12-18 17:54:37 +0000257 if self.options.disable_javascript:
258 cmd_to_run.append('--disable-javascript')
259
Daniel Hosseinian09dbeac2020-01-24 19:41:31 +0000260 if self.options.disable_xfa:
261 cmd_to_run.append('--disable-xfa')
262
Hui Yingstc511fd22021-10-25 18:03:10 +0000263 if self.options.render_oneshot:
264 cmd_to_run.append('--render-oneshot')
265
Lei Zhangafce8532019-11-20 18:09:41 +0000266 if self.options.reverse_byte_order:
267 cmd_to_run.append('--reverse-byte-order')
268
stephanafa05e972017-01-02 06:19:41 -0800269 cmd_to_run.append(pdf_path)
270 return common.RunCommandExtractHashedFiles(cmd_to_run)
dsinclair2a8a20c2016-04-25 09:46:17 -0700271
272 def HandleResult(self, input_filename, input_path, result):
Stephanie Kim606d0852021-03-04 16:02:41 +0000273 success, _ = result
stephanafa05e972017-01-02 06:19:41 -0800274
dsinclair2a8a20c2016-04-25 09:46:17 -0700275 if self.test_suppressor.IsResultSuppressed(input_filename):
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400276 self.result_suppressed_cases.append(input_filename)
dan sinclair00d40642017-01-30 19:48:54 -0800277 if success:
dsinclair2a8a20c2016-04-25 09:46:17 -0700278 self.surprises.append(input_path)
279 else:
dan sinclair00d40642017-01-30 19:48:54 -0800280 if not success:
dsinclair2a8a20c2016-04-25 09:46:17 -0700281 self.failures.append(input_path)
282
dsinclair2a8a20c2016-04-25 09:46:17 -0700283 def Run(self):
K Moon8de7d57d2019-12-05 19:32:33 +0000284 # Running a test defines a number of attributes on the fly.
285 # pylint: disable=attribute-defined-outside-init
286
Stephanie Kim606d0852021-03-04 16:02:41 +0000287 parser = argparse.ArgumentParser()
stephanafa05e972017-01-02 06:19:41 -0800288
Stephanie Kim606d0852021-03-04 16:02:41 +0000289 parser.add_argument(
Lei Zhang30543372019-11-19 19:02:30 +0000290 '--build-dir',
291 default=os.path.join('out', 'Debug'),
292 help='relative path from the base source directory')
stephanafa05e972017-01-02 06:19:41 -0800293
Stephanie Kim606d0852021-03-04 16:02:41 +0000294 parser.add_argument(
Lei Zhang30543372019-11-19 19:02:30 +0000295 '-j',
296 default=multiprocessing.cpu_count(),
297 dest='num_workers',
Stephanie Kim606d0852021-03-04 16:02:41 +0000298 type=int,
Lei Zhang30543372019-11-19 19:02:30 +0000299 help='run NUM_WORKERS jobs in parallel')
stephanafa05e972017-01-02 06:19:41 -0800300
Stephanie Kim606d0852021-03-04 16:02:41 +0000301 parser.add_argument(
Daniel Hosseinian77b3a432019-12-18 17:54:37 +0000302 '--disable-javascript',
303 action="store_true",
304 dest="disable_javascript",
305 help='Prevents JavaScript from executing in PDF files.')
306
Stephanie Kim606d0852021-03-04 16:02:41 +0000307 parser.add_argument(
Daniel Hosseinian09dbeac2020-01-24 19:41:31 +0000308 '--disable-xfa',
309 action="store_true",
310 dest="disable_xfa",
311 help='Prevents processing XFA forms.')
312
Stephanie Kim606d0852021-03-04 16:02:41 +0000313 parser.add_argument(
Hui Yingstc511fd22021-10-25 18:03:10 +0000314 '--render-oneshot',
315 action="store_true",
316 dest="render_oneshot",
317 help='Sets whether to use the oneshot renderer.')
318
319 parser.add_argument(
Stephanie Kim606d0852021-03-04 16:02:41 +0000320 '--run-skia-gold',
321 action='store_true',
322 default=False,
323 help='When flag is on, skia gold tests will be run.')
324
325 # TODO: Remove when pdfium recipe stops passing this argument
326 parser.add_argument(
Lei Zhang30543372019-11-19 19:02:30 +0000327 '--gold_properties',
328 default='',
329 dest="gold_properties",
330 help='Key value pairs that are written to the top level '
331 'of the JSON file that is ingested by Gold.')
stephanafa05e972017-01-02 06:19:41 -0800332
Stephanie Kim606d0852021-03-04 16:02:41 +0000333 # TODO: Remove when pdfium recipe stops passing this argument
334 parser.add_argument(
Lei Zhang30543372019-11-19 19:02:30 +0000335 '--gold_ignore_hashes',
336 default='',
337 dest="gold_ignore_hashes",
338 help='Path to a file with MD5 hashes we wish to ignore.')
stephanad5320362017-01-26 15:18:54 -0800339
Stephanie Kim606d0852021-03-04 16:02:41 +0000340 parser.add_argument(
Lei Zhang30543372019-11-19 19:02:30 +0000341 '--regenerate_expected',
342 default='',
343 dest="regenerate_expected",
344 help='Regenerates expected images. Valid values are '
345 '"all" to regenerate all expected pngs, and '
346 '"platform" to regenerate only platform-specific '
347 'expected pngs.')
Henrique Nakashima06673ed2017-10-25 17:31:13 -0400348
Stephanie Kim606d0852021-03-04 16:02:41 +0000349 parser.add_argument(
Lei Zhangafce8532019-11-20 18:09:41 +0000350 '--reverse-byte-order',
351 action='store_true',
352 dest="reverse_byte_order",
353 help='Run image-based tests using --reverse-byte-order.')
354
Stephanie Kim606d0852021-03-04 16:02:41 +0000355 parser.add_argument(
Lei Zhang30543372019-11-19 19:02:30 +0000356 '--ignore_errors',
357 action="store_true",
358 dest="ignore_errors",
359 help='Prevents the return value from being non-zero '
360 'when image comparison fails.')
stephanafa05e972017-01-02 06:19:41 -0800361
Stephanie Kim606d0852021-03-04 16:02:41 +0000362 skia_gold.add_skia_gold_args(parser)
363
364 self.options, self.inputted_file_paths = parser.parse_known_args()
dsinclair2a8a20c2016-04-25 09:46:17 -0700365
Lei Zhang30543372019-11-19 19:02:30 +0000366 if (self.options.regenerate_expected and
367 self.options.regenerate_expected not in ['all', 'platform']):
Stephanie Kim606d0852021-03-04 16:02:41 +0000368 print('FAILURE: --regenerate_expected must be "all" or "platform"')
Henrique Nakashima352e2512017-10-26 11:22:52 -0400369 return 1
370
Henrique Nakashima06673ed2017-10-25 17:31:13 -0400371 finder = common.DirectoryFinder(self.options.build_dir)
dsinclair2a8a20c2016-04-25 09:46:17 -0700372 self.fixup_path = finder.ScriptPath('fixup_pdf_template.py')
373 self.text_diff_path = finder.ScriptPath('text_diff.py')
Ryan Harrison1118a662018-05-31 19:26:52 +0000374 self.font_dir = os.path.join(finder.TestingDir(), 'resources', 'fonts')
Lei Zhang17b67222022-04-01 18:02:29 +0000375 self.third_party_font_dir = finder.ThirdPartyFontsDir()
dsinclair2a8a20c2016-04-25 09:46:17 -0700376
dsinclair2a8a20c2016-04-25 09:46:17 -0700377 self.source_dir = finder.TestingDir()
dsinclair849284d2016-05-17 06:13:36 -0700378 if self.test_dir != 'corpus':
379 test_dir = finder.TestingDir(os.path.join('resources', self.test_dir))
380 else:
381 test_dir = finder.TestingDir(self.test_dir)
382
dsinclair2a8a20c2016-04-25 09:46:17 -0700383 self.pdfium_test_path = finder.ExecutablePath('pdfium_test')
384 if not os.path.exists(self.pdfium_test_path):
Stephanie Kim606d0852021-03-04 16:02:41 +0000385 print("FAILURE: Can't find test executable '{}'".format(
386 self.pdfium_test_path))
387 print('Use --build-dir to specify its location.')
dsinclair2a8a20c2016-04-25 09:46:17 -0700388 return 1
389
390 self.working_dir = finder.WorkingDir(os.path.join('testing', self.test_dir))
Lei Zhang96eff7c2019-09-19 21:08:58 +0000391 shutil.rmtree(self.working_dir, ignore_errors=True)
392 os.makedirs(self.working_dir)
dsinclair2a8a20c2016-04-25 09:46:17 -0700393
Lei Zhang76f95692020-04-01 22:16:25 +0000394 self.features = subprocess.check_output(
Lei Zhangae578532021-04-15 20:32:56 +0000395 [self.pdfium_test_path,
396 '--show-config']).decode('utf-8').strip().split(',')
Daniel Hosseinian77b3a432019-12-18 17:54:37 +0000397 self.test_suppressor = suppressor.Suppressor(
Lei Zhang76f95692020-04-01 22:16:25 +0000398 finder, self.features, self.options.disable_javascript,
Daniel Hosseinian09dbeac2020-01-24 19:41:31 +0000399 self.options.disable_xfa)
Hui Yingstb8b3f5f2020-04-11 00:20:36 +0000400 self.image_differ = pngdiffer.PNGDiffer(finder, self.features,
Lei Zhangafce8532019-11-20 18:09:41 +0000401 self.options.reverse_byte_order)
Henrique Nakashima40be5052018-10-10 23:18:14 +0000402 error_message = self.image_differ.CheckMissingTools(
403 self.options.regenerate_expected)
404 if error_message:
Stephanie Kim606d0852021-03-04 16:02:41 +0000405 print('FAILURE:', error_message)
Henrique Nakashima40be5052018-10-10 23:18:14 +0000406 return 1
dsinclair2a8a20c2016-04-25 09:46:17 -0700407
Henrique Nakashimaea4a56d2017-11-29 19:34:19 +0000408
Lei Zhang30543372019-11-19 19:02:30 +0000409 walk_from_dir = finder.TestingDir(test_dir)
dsinclair2a8a20c2016-04-25 09:46:17 -0700410
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400411 self.test_cases = []
412 self.execution_suppressed_cases = []
Henrique Nakashima62d50762017-06-27 13:06:23 -0400413 input_file_re = re.compile('^.+[.](in|pdf)$')
Stephanie Kim606d0852021-03-04 16:02:41 +0000414 if self.inputted_file_paths:
415 for file_name in self.inputted_file_paths:
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400416 file_name.replace('.pdf', '.in')
dsinclair2a8a20c2016-04-25 09:46:17 -0700417 input_path = os.path.join(walk_from_dir, file_name)
418 if not os.path.isfile(input_path):
Stephanie Kim606d0852021-03-04 16:02:41 +0000419 print("Can't find test file '{}'".format(file_name))
dsinclair2a8a20c2016-04-25 09:46:17 -0700420 return 1
421
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400422 self.test_cases.append((os.path.basename(input_path),
Lei Zhang30543372019-11-19 19:02:30 +0000423 os.path.dirname(input_path)))
dsinclair2a8a20c2016-04-25 09:46:17 -0700424 else:
425 for file_dir, _, filename_list in os.walk(walk_from_dir):
426 for input_filename in filename_list:
427 if input_file_re.match(input_filename):
428 input_path = os.path.join(file_dir, input_filename)
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400429 if self.test_suppressor.IsExecutionSuppressed(input_path):
430 self.execution_suppressed_cases.append(input_path)
431 else:
dsinclair2a8a20c2016-04-25 09:46:17 -0700432 if os.path.isfile(input_path):
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400433 self.test_cases.append((input_filename, file_dir))
dsinclair2a8a20c2016-04-25 09:46:17 -0700434
Lei Zhang1ee96012018-04-09 17:31:14 +0000435 self.test_cases.sort()
dsinclair2a8a20c2016-04-25 09:46:17 -0700436 self.failures = []
437 self.surprises = []
Stephanie Kim606d0852021-03-04 16:02:41 +0000438 self.skia_gold_successes = []
439 self.skia_gold_unexpected_successes = []
440 self.skia_gold_failures = []
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400441 self.result_suppressed_cases = []
dsinclair2a8a20c2016-04-25 09:46:17 -0700442
Stephanie Kim606d0852021-03-04 16:02:41 +0000443 gold_results = []
Stephanie Kim0314ade2021-03-11 00:27:35 +0000444 if self.test_type not in TEXT_TESTS and self.options.run_skia_gold:
445 assert self.options.gold_output_dir
Stephanie Kimebe79c12021-03-05 21:14:49 +0000446 # Clear out and create top level gold output directory before starting
447 skia_gold.clear_gold_output_dir(self.options.gold_output_dir)
448
Henrique Nakashima06673ed2017-10-25 17:31:13 -0400449 if self.options.num_workers > 1 and len(self.test_cases) > 1:
Stephanie Kim0314ade2021-03-11 00:27:35 +0000450 skia_gold_parallel_inputs = []
dsinclair849284d2016-05-17 06:13:36 -0700451 try:
Henrique Nakashima06673ed2017-10-25 17:31:13 -0400452 pool = multiprocessing.Pool(self.options.num_workers)
dsinclair849284d2016-05-17 06:13:36 -0700453 worker_func = functools.partial(TestOneFileParallel, self)
454
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400455 worker_results = pool.imap(worker_func, self.test_cases)
dsinclair849284d2016-05-17 06:13:36 -0700456 for worker_result in worker_results:
457 result, input_filename, source_dir = worker_result
458 input_path = os.path.join(source_dir, input_filename)
459
460 self.HandleResult(input_filename, input_path, result)
461
Stephanie Kim606d0852021-03-04 16:02:41 +0000462 if self.test_type not in TEXT_TESTS and self.options.run_skia_gold:
463 _, image_paths = result
464 if image_paths:
Stephanie Kim0314ade2021-03-11 00:27:35 +0000465 path_filename_tuples = [
466 (path, input_filename) for path, _ in image_paths
467 ]
468 skia_gold_parallel_inputs.extend(path_filename_tuples)
Stephanie Kim606d0852021-03-04 16:02:41 +0000469
dsinclair849284d2016-05-17 06:13:36 -0700470 except KeyboardInterrupt:
471 pool.terminate()
472 finally:
473 pool.close()
474 pool.join()
Stephanie Kim0314ade2021-03-11 00:27:35 +0000475
476 if skia_gold_parallel_inputs and self.test_type not in TEXT_TESTS:
477 try:
478 pool = multiprocessing.Pool(self.options.num_workers)
479 gold_worker_func = functools.partial(RunSkiaWrapper, self)
Stephanie Kim34cbd922021-03-17 20:25:12 +0000480
481 def chunk_input(whole_list):
482 chunked = []
483 size = len(whole_list) // self.options.num_workers
484 for i in range(0, len(whole_list), size):
485 chunked.append(whole_list[i:i + size])
486 return chunked
487
488 chunked_input = chunk_input(skia_gold_parallel_inputs)
489 pool_results = pool.imap(gold_worker_func, chunked_input)
490 for r in pool_results:
491 gold_results.extend(r)
Stephanie Kim0314ade2021-03-11 00:27:35 +0000492 except KeyboardInterrupt:
493 pool.terminate()
494 finally:
495 pool.close()
496 pool.join()
dsinclair849284d2016-05-17 06:13:36 -0700497 else:
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400498 for test_case in self.test_cases:
dsinclair849284d2016-05-17 06:13:36 -0700499 input_filename, input_file_dir = test_case
500 result = self.GenerateAndTest(input_filename, input_file_dir)
501 self.HandleResult(input_filename,
502 os.path.join(input_file_dir, input_filename), result)
dsinclair2a8a20c2016-04-25 09:46:17 -0700503
Stephanie Kim606d0852021-03-04 16:02:41 +0000504 _, image_paths = result
Stephanie Kimebe79c12021-03-05 21:14:49 +0000505 if image_paths and self.test_type not in TEXT_TESTS and \
506 self.options.run_skia_gold:
Stephanie Kim606d0852021-03-04 16:02:41 +0000507 for img_path, _ in image_paths:
508 test_name, skia_success = self.RunSkia(img_path)
509 gold_results.append((test_name, skia_success, input_filename))
510
511 for r in gold_results:
512 test_name, skia_success, input_filename = r
513 if skia_success:
514 if self.test_suppressor.IsResultSuppressed(input_filename):
515 self.skia_gold_unexpected_successes.append(test_name)
516 else:
517 self.skia_gold_successes.append(test_name)
518 else:
519 self.skia_gold_failures.append(test_name)
stephanafa05e972017-01-02 06:19:41 -0800520
Stephanie Kim0314ade2021-03-11 00:27:35 +0000521 # For some reason, summary will be cut off from stdout on windows if
522 # _PrintSummary() is called at the end
523 # TODO(crbug.com/pdfium/1657): Once resolved, move _PrintSummary() back
524 # down to the end
525 self._PrintSummary()
526
dsinclair2a8a20c2016-04-25 09:46:17 -0700527 if self.surprises:
528 self.surprises.sort()
Stephanie Kim606d0852021-03-04 16:02:41 +0000529 print('\nUnexpected Successes:')
dsinclair2a8a20c2016-04-25 09:46:17 -0700530 for surprise in self.surprises:
Stephanie Kim606d0852021-03-04 16:02:41 +0000531 print(surprise)
dsinclair2a8a20c2016-04-25 09:46:17 -0700532
533 if self.failures:
534 self.failures.sort()
Stephanie Kim606d0852021-03-04 16:02:41 +0000535 print('\nSummary of Failures:')
dsinclair2a8a20c2016-04-25 09:46:17 -0700536 for failure in self.failures:
Stephanie Kim606d0852021-03-04 16:02:41 +0000537 print(failure)
538
539 if self.skia_gold_unexpected_successes:
Stephanie Kim0314ade2021-03-11 00:27:35 +0000540 self.skia_gold_unexpected_successes.sort()
Stephanie Kim606d0852021-03-04 16:02:41 +0000541 print('\nUnexpected Skia Gold Successes:')
542 for surprise in self.skia_gold_unexpected_successes:
543 print(surprise)
544
545 if self.skia_gold_failures:
546 self.skia_gold_failures.sort()
547 print('\nSummary of Skia Gold Failures:')
548 for failure in self.skia_gold_failures:
549 print(failure)
dan sinclair00d40642017-01-30 19:48:54 -0800550
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400551 if self.failures:
Henrique Nakashima06673ed2017-10-25 17:31:13 -0400552 if not self.options.ignore_errors:
dan sinclair00d40642017-01-30 19:48:54 -0800553 return 1
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400554
dsinclair2a8a20c2016-04-25 09:46:17 -0700555 return 0
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400556
557 def _PrintSummary(self):
558 number_test_cases = len(self.test_cases)
559 number_failures = len(self.failures)
560 number_suppressed = len(self.result_suppressed_cases)
561 number_successes = number_test_cases - number_failures - number_suppressed
562 number_surprises = len(self.surprises)
Stephanie Kim606d0852021-03-04 16:02:41 +0000563 print('\nTest cases executed:', number_test_cases)
564 print(' Successes:', number_successes)
565 print(' Suppressed:', number_suppressed)
566 print(' Surprises:', number_surprises)
567 print(' Failures:', number_failures)
Stephanie Kimebe79c12021-03-05 21:14:49 +0000568 if self.test_type not in TEXT_TESTS and self.options.run_skia_gold:
Stephanie Kim606d0852021-03-04 16:02:41 +0000569 number_gold_failures = len(self.skia_gold_failures)
570 number_gold_successes = len(self.skia_gold_successes)
571 number_gold_surprises = len(self.skia_gold_unexpected_successes)
572 number_total_gold_tests = sum(
573 [number_gold_failures, number_gold_successes, number_gold_surprises])
574 print('\nSkia Gold Test cases executed:', number_total_gold_tests)
575 print(' Skia Gold Successes:', number_gold_successes)
576 print(' Skia Gold Surprises:', number_gold_surprises)
577 print(' Skia Gold Failures:', number_gold_failures)
578 skia_tester = self.GetSkiaGoldTester()
579 if self.skia_gold_failures and skia_tester.IsTryjobRun():
580 cl_triage_link = skia_tester.GetCLTriageLink()
581 print(' Triage link for CL:', cl_triage_link)
582 skia_tester.WriteCLTriageLink(cl_triage_link)
583 print()
584 print('Test cases not executed:', len(self.execution_suppressed_cases))
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400585
Lei Zhang5767aca2018-12-05 19:57:46 +0000586 def SetDeleteOutputOnSuccess(self, new_value):
587 """Set whether to delete generated output if the test passes."""
588 self.delete_output_on_success = new_value
589
Henrique Nakashima3bcabf32017-06-27 09:48:24 -0400590 def SetEnforceExpectedImages(self, new_value):
591 """Set whether to enforce that each test case provide an expected image."""
592 self.enforce_expected_images = new_value