7from dataclasses
import dataclass, field
8from datetime
import timedelta
21from skia_gold
import skia_gold
24pdfium_root.add_source_directory_to_import_path(os.path.join(
'build',
'util'))
25from lib.results
import result_sink, result_types
31TEST_SEED_TIME =
'1399672130'
34TEXT_TESTS = [
'javascript']
39TEST_TIMEOUT = timedelta(minutes=5).total_seconds()
50 test_dir=dirname, test_type=dirname)
57 return (self.
options.run_skia_gold
and
69 input_filename = os.path.basename(test_case.input_path)
73 if test_result.status == result_types.UNKNOWN:
76 elif test_result.status == result_types.SKIP:
78 elif not test_result.IsPass():
81 for artifact
in test_result.image_artifacts:
82 if artifact.skia_gold_status == result_types.PASS:
87 elif artifact.skia_gold_status == result_types.FAIL:
91 print(f
'{test_result.status}: {test_result.test_id}')
92 if not test_result.IsPass():
93 if test_result.reason:
94 print(f
'Failure reason: {test_result.reason}')
96 print(f
'Test output:\n{test_result.log}')
97 for artifact
in test_result.image_artifacts:
98 if artifact.skia_gold_status == result_types.FAIL:
99 print(f
'Failed Skia Gold: {artifact.image_path}')
100 if artifact.image_diff:
101 print(f
'Failed image diff: {artifact.image_diff.reason}')
105 only_artifacts =
None
106 only_failure_reason = test_result.reason
107 if len(test_result.image_artifacts) == 1:
108 only = test_result.image_artifacts[0]
109 only_artifacts = only.GetDiffArtifacts()
110 if only.GetDiffReason():
111 only_failure_reason += f
': {only.GetDiffReason()}'
113 test_id=test_result.test_id,
114 status=test_result.status,
115 duration=test_result.duration_milliseconds,
116 test_log=test_result.log,
118 artifacts=only_artifacts,
119 failure_reason=only_failure_reason)
123 if len(test_result.image_artifacts) > 1:
124 for page, artifact
in enumerate(test_result.image_artifacts):
126 test_id=f
'{test_result.test_id}/{page}',
128 artifact.GetDiffStatus()),
132 artifacts=artifact.GetDiffArtifacts(),
133 failure_reason=artifact.GetDiffReason())
139 if status == result_types.PASS:
144 return result_types.UNKNOWN
148 return result_types.SKIP
151 if status != result_types.FAIL:
154 if test_result.status != result_types.SKIP:
157 return result_types.SKIP
164 if relative_test_dir !=
'corpus':
165 relative_test_dir = os.path.join(
'resources', relative_test_dir)
167 parser = argparse.ArgumentParser()
171 default=os.path.join(
'out',
'Debug'),
172 help=
'relative path from the base source directory')
176 default=multiprocessing.cpu_count(),
179 help=
'run NUM_WORKERS jobs in parallel')
182 '--disable-javascript',
184 help=
'Prevents JavaScript from executing in PDF files.')
189 help=
'Prevents processing XFA forms.')
194 help=
'Sets whether to use the oneshot renderer.')
200 help=
'When flag is on, skia gold tests will be run.')
203 '--regenerate_expected',
205 help=
'Regenerates expected images. For each failing image diff, this '
206 'will regenerate the most specific expected image file that exists. '
207 'This also will suggest removals of unnecessary expected image files '
208 'by renaming them with an additional ".bak" extension, although these '
209 'removals should be reviewed manually. Use "git clean" to quickly deal '
210 'with any ".bak" files.')
213 '--reverse-byte-order',
215 help=
'Run image-based tests using --reverse-byte-order.')
220 help=
'Prevents the return value from being non-zero '
221 'when image comparison fails.')
225 choices=(
'agg',
'gdi',
'skia'),
226 help=
'Forces the renderer to use.')
229 'inputted_file_paths',
231 help=
'Path to test files to run, relative to '
232 f
'testing/{relative_test_dir}. If omitted, runs all test files under '
233 f
'testing/{relative_test_dir}.',
234 metavar=
'relative/test/path')
236 skia_gold.add_skia_gold_args(parser)
242 if not os.path.exists(pdfium_test_path):
243 print(f
"FAILURE: Can't find test executable '{pdfium_test_path}'")
244 print(
'Use --build-dir to specify its location.')
249 print(
'FAILURE:', error_message)
257 self.
options.regenerate_expected)
259 print(
'FAILURE:', error_message)
262 self.
resultdb = result_sink.TryInitClient()
264 print(
'Detected ResultSink environment')
267 walk_from_dir = finder.TestingDir(relative_test_dir)
271 input_file_re = re.compile(
'^.+[.](in|pdf)$')
272 if self.
options.inputted_file_paths:
273 for file_name
in self.
options.inputted_file_paths:
274 input_path = os.path.join(walk_from_dir, file_name)
275 if not os.path.isfile(input_path):
276 print(f
"Can't find test file '{file_name}'")
281 for file_dir, _, filename_list
in os.walk(walk_from_dir):
282 for input_filename
in filename_list:
283 if input_file_re.match(input_filename):
284 input_path = os.path.join(file_dir, input_filename)
288 if not os.path.isfile(input_path):
302 assert self.
options.gold_output_dir
304 skia_gold.clear_gold_output_dir(self.
options.gold_output_dir)
306 with multiprocessing.Pool(
307 processes=self.
options.num_workers,
308 initializer=_InitializePerProcessState,
311 test_function = _RunTextTest
313 test_function = _RunPixelTest
314 for result
in pool.imap(test_function, self.
test_cases):
320 print(
'\nUnexpected Successes:')
326 print(
'\nSummary of Failures:')
332 print(
'\nUnexpected Skia Gold Successes:')
338 print(
'\nSummary of Skia Gold Failures:')
345 if not self.
options.ignore_errors:
352 number_failures = len(self.
failures)
354 number_successes = number_test_cases - number_failures - number_suppressed
356 print(
'\nTest cases executed:', number_test_cases)
357 print(
' Successes:', number_successes)
358 print(
' Suppressed:', number_suppressed)
359 print(
' Surprises:', number_surprises)
360 print(
' Failures:', number_failures)
365 number_total_gold_tests =
sum(
366 [number_gold_failures, number_gold_successes, number_gold_surprises])
367 print(
'\nSkia Gold Test cases executed:', number_total_gold_tests)
368 print(
' Skia Gold Successes:', number_gold_successes)
369 print(
' Skia Gold Surprises:', number_gold_surprises)
370 print(
' Skia Gold Failures:', number_gold_failures)
373 cl_triage_link = skia_tester.GetCLTriageLink()
374 print(
' Triage link for CL:', cl_triage_link)
375 skia_tester.WriteCLTriageLink(cl_triage_link)
380 """Set whether to delete generated output if the test passes."""
384 """Set whether to enforce that each test case provide an expected image."""
389 """Runs a text test case."""
391 with test_case_runner:
392 test_case_runner.test_result = test_case_runner.GenerateAndTest(
393 test_case_runner.TestText)
394 return test_case_runner.test_result
398 """Runs a pixel test case."""
400 with test_case_runner:
401 test_case_runner.test_result = test_case_runner.GenerateAndTest(
402 test_case_runner.TestPixel)
403 return test_case_runner.test_result
409_per_process_state =
None
413 """Initializes the `_per_process_state` singleton."""
414 global _per_process_state
415 assert not _per_process_state
421 """Configuration for initializing `_PerProcessState`.
424 test_dir: The name of the test directory.
425 test_type: The test type.
426 delete_output_on_success: Whether to delete output on success.
427 enforce_expected_images: Whether to enforce expected images.
428 options: The dictionary of command line options.
429 features: The set of features supported by `pdfium_test`.
430 rendering_option: The renderer to use (agg, gdi, or skia).
434 delete_output_on_success: bool =
False
435 enforce_expected_images: bool =
False
438 default_renderer: str =
None
439 rendering_option: str =
None
445 return finder.ExecutablePath(
'pdfium_test')
448 output = subprocess.check_output([pdfium_test_path,
'--show-config'],
449 timeout=TEST_TIMEOUT)
458 if self.
options.use_renderer ==
'agg':
460 elif self.
options.use_renderer ==
'gdi':
462 return 'pdfium_test missing GDI renderer support'
464 elif self.
options.use_renderer ==
'skia':
466 return 'pdfium_test missing Skia renderer support'
473 """State defined per process."""
483 finder = config.NewFinder()
487 self.
font_dir = os.path.join(finder.TestingDir(),
'resources',
'fonts')
495 self.
options.disable_xfa, config.rendering_option)
497 self.
options.reverse_byte_order,
498 config.rendering_option,
499 config.default_renderer)
505 raise RuntimeError(
'Cannot pickle per-process state')
508 """Gets the `SkiaGoldTester` singleton for this worker."""
518 """Runner for a single test case."""
534 def __exit__(self, exc_type, exc_value, traceback):
537 result_types.UNKNOWN, reason=
'No test result recorded')
543 return _per_process_state.options
551 return _per_process_state.working_dir
554 return _per_process_state.test_suppressor.IsResultSuppressed(
558 return _per_process_state.test_suppressor.IsImageDiffSuppressed(
562 return _per_process_state.test_suppressor.GetImageMatchingAlgorithm(
566 """Runs a test command.
569 command: The list of command arguments.
570 stdout: Optional `file`-like object to send standard output.
579 assert stdout != subprocess.PIPE
585 original_stdout = stdout
586 stdout = subprocess.PIPE
587 stderr = subprocess.PIPE
589 stdout = subprocess.PIPE
590 stderr = subprocess.STDOUT
592 test_result = self.
test_case.NewResult(result_types.PASS)
594 run_result = subprocess.run(
598 timeout=TEST_TIMEOUT,
600 if run_result.returncode != 0:
601 test_result.status = result_types.FAIL
602 test_result.reason =
'Command {} exited with code {}'.format(
603 run_result.args, run_result.returncode)
604 except subprocess.TimeoutExpired
as timeout_expired:
605 run_result = timeout_expired
606 test_result.status = result_types.TIMEOUT
607 test_result.reason =
'Command {} timed out'.format(run_result.cmd)
609 if stdout == subprocess.PIPE
and stderr == subprocess.PIPE:
611 if run_result.stdout:
612 original_stdout.write(run_result.stdout)
614 if not test_result.IsPass():
616 if stderr == subprocess.STDOUT:
617 test_result.log = run_result.stdout
619 test_result.log = run_result.stderr
620 test_result.log = test_result.log.decode(errors=
'backslashreplace')
624 """Generate test input and run pdfium_test."""
626 if not test_result.IsPass():
629 return test_function()
632 if not self.
options.regenerate_expected:
636 _per_process_state.image_differ.Regenerate(
643 input_event_path = os.path.join(self.
source_dir, f
'{self.test_id}.evt')
644 if os.path.exists(input_event_path):
645 output_event_path = f
'{os.path.splitext(self.pdf_path)[0]}.evt'
646 shutil.copyfile(input_event_path, output_event_path)
648 template_path = os.path.join(self.
source_dir, f
'{self.test_id}.in')
649 if not os.path.exists(template_path):
650 if os.path.exists(self.
test_case.input_path):
652 return self.
test_case.NewResult(result_types.PASS)
655 sys.executable, _per_process_state.fixup_path,
656 f
'--output-dir={self.working_dir}', template_path
661 with open(txt_path,
'w')
as outfile:
663 _per_process_state.pdfium_test_path,
'--send-events',
664 f
'--time={TEST_SEED_TIME}'
667 if self.
options.disable_javascript:
668 cmd_to_run.append(
'--disable-javascript')
671 cmd_to_run.append(
'--disable-xfa')
674 test_result = self.
RunCommand(cmd_to_run, stdout=outfile)
675 if not test_result.IsPass():
679 expected_txt_path = os.path.join(self.
source_dir,
680 f
'{self.test_id}_expected.txt')
681 if not os.path.exists(expected_txt_path):
691 sys.executable, _per_process_state.text_diff_path, expected_txt_path,
696 with open(txt_path,
'rb')
as txt_file:
697 txt_data = txt_file.read()
702 log=txt_data.decode(errors=
'backslashreplace'),
703 reason=f
'{txt_path} should be empty')
705 return self.
test_case.NewResult(result_types.PASS)
717 _per_process_state.pdfium_test_path,
'--send-events',
'--png',
'--md5',
718 f
'--time={TEST_SEED_TIME}'
722 font_path = os.path.join(_per_process_state.font_dir,
'ahem')
723 cmd_to_run.append(f
'--font-dir={font_path}')
725 font_path = os.path.join(_per_process_state.font_dir,
'symbolneu')
726 cmd_to_run.append(f
'--font-dir={font_path}')
728 cmd_to_run.append(f
'--font-dir={_per_process_state.third_party_font_dir}')
729 cmd_to_run.append(
'--croscore-font-names')
731 if self.
options.disable_javascript:
732 cmd_to_run.append(
'--disable-javascript')
735 cmd_to_run.append(
'--disable-xfa')
737 if self.
options.render_oneshot:
738 cmd_to_run.append(
'--render-oneshot')
740 if self.
options.reverse_byte_order:
741 cmd_to_run.append(
'--reverse-byte-order')
744 cmd_to_run.append(f
'--use-renderer={self.options.use_renderer}')
748 with BytesIO()
as command_output:
749 test_result = self.
RunCommand(cmd_to_run, stdout=command_output)
750 if not test_result.IsPass():
753 test_result.image_artifacts = []
754 for line
in command_output.getvalue().splitlines():
756 line = bytes.decode(line).strip()
757 if line.startswith(
'MD5:'):
758 image_path, md5_hash = line[4:].rsplit(
':', 1)
759 test_result.image_artifacts.append(
761 image_path=image_path.strip(), md5_hash=md5_hash.strip()))
764 image_diffs = _per_process_state.image_differ.ComputeDifferences(
770 test_result.status = result_types.FAIL
771 test_result.reason =
'Images differ'
776 for diff
in image_diffs:
777 diff_map[diff.actual_path] = diff
778 diff_log.append(f
'{os.path.basename(diff.actual_path)} vs. ')
779 if diff.expected_path:
780 diff_log.append(f
'{os.path.basename(diff.expected_path)}\n')
782 diff_log.append(
'missing expected file\n')
784 for artifact
in test_result.image_artifacts:
785 artifact.image_diff = diff_map.get(artifact.image_path)
786 test_result.log =
''.join(diff_log)
788 elif _per_process_state.enforce_expected_images:
790 test_result.status = result_types.FAIL
791 test_result.reason =
'Missing expected images'
793 if not test_result.IsPass():
797 if _per_process_state.delete_output_on_success:
802 artifact =
ImageArtifact(image_path=image_path, md5_hash=md5_hash)
805 if _per_process_state.GetSkiaGoldTester().UploadTestResultToSkiaGold(
806 artifact.GetSkiaGoldId(), artifact.image_path):
807 artifact.skia_gold_status = result_types.PASS
809 artifact.skia_gold_status = result_types.FAIL
815 if os.path.exists(image_file):
816 os.remove(image_file)
821 """Description of a test case to run.
824 test_id: A unique identifier for the test.
825 input_path: The absolute path to the test file.
831 """Derives a new test result corresponding to this test case."""
832 return TestResult(test_id=self.test_id, status=status, **kwargs)
837 """Results from running a test case.
840 test_id: The corresponding test case ID.
841 status: The overall `result_types` status.
842 duration_milliseconds: Test time in milliseconds.
843 log: Optional log of the test's output.
844 image_artfacts: Optional list of image artifacts.
845 reason: Optional reason why the test failed.
849 duration_milliseconds: float =
None
851 image_artifacts: list = field(default_factory=list)
855 """Whether the test passed."""
861 """Image artifact for a test result.
864 image_path: The absolute path to the image file.
865 md5_hash: The MD5 hash of the pixel buffer.
866 skia_gold_status: Optional Skia Gold status.
867 image_diff: Optional image diff.
871 skia_gold_status: str =
None
881 return result_types.FAIL
if self.
image_diff else result_types.PASS
902 """Manages a collection of test cases."""
914 """Creates and registers a new test case."""
915 input_basename = os.path.basename(input_path)
919 f
'Test ID "{test_id}" derived from "{input_basename}" must be unique')
921 test_case =
TestCase(test_id=test_id, input_path=input_path, **kwargs)
926 """Looks up a test case previously registered by `NewTestCase()`."""
931 """Constructs a test ID by stripping the last extension from the basename."""
932 return os.path.splitext(input_basename)[0]
936 """Constructs a ResultSink artifact from a file path."""
937 return {
'filePath': file_path}
list append(new Employee("Blackpool", "Stephen"))
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter * iter
static void split(QT_FT_Vector *b)
QDebug print(QDebug debug, QSslError::SslError error)
QFuture< QSet< QChar > > set
[10]
file open(QIODevice::ReadOnly)