2 # Copyright (C) 2010 Google Inc. All rights reserved.
3 # Copyright (C) 2010 Gabor Rapcsanyi (rgabor@inf.u-szeged.hu), University of Szeged
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are
9 # * Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 # * Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following disclaimer
13 # in the documentation and/or other materials provided with the
15 # * Neither the name of Google Inc. nor the names of its
16 # contributors may be used to endorse or promote products derived from
17 # this software without specific prior written permission.
19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 This is a port of the existing webkit test script run-webkit-tests.
35 The TestRunner class runs a series of tests (TestType interface) against a set
36 of test files. If a test file fails a TestType, it returns a list TestFailure
37 objects to the TestRunner. The TestRunner then aggregates the TestFailures to
38 create a final report.
40 This script reads several files, if they exist in the test_lists subdirectory
41 next to this script itself. Each should contain a list of paths to individual
42 tests or entire subdirectories of tests, relative to the outermost test
43 directory. Entire lines starting with '//' (comments) will be ignored.
45 For details of the files' contents and purposes, see test_lists/README.
48 from __future__ import with_statement
67 from layout_package import dump_render_tree_thread
68 from layout_package import json_layout_results_generator
69 from layout_package import message_broker
70 from layout_package import printing
71 from layout_package import test_expectations
72 from layout_package import test_failures
73 from layout_package import test_results
74 from layout_package import test_results_uploader
76 from webkitpy.common.system import user
77 from webkitpy.thirdparty import simplejson
78 from webkitpy.tool import grammar
82 _log = logging.getLogger("webkitpy.layout_tests.run_webkit_tests")
84 # Builder base URL where we have the archived test results.
85 BUILDER_BASE_URL = "http://build.chromium.org/buildbot/layout_test_results/"
87 LAYOUT_TESTS_DIRECTORY = "LayoutTests" + os.sep
89 TestExpectationsFile = test_expectations.TestExpectationsFile
93 """Groups information about a test for easy passing of data."""
95 def __init__(self, filename, timeout):
96 """Holds the input parameters for a test.
98 filename: Full path to the test.
99 timeout: Timeout in msecs the driver should use while running the test
101 # FIXME: filename should really be test_name as a relative path.
102 self.filename = filename
103 self.timeout = timeout
104 # The image_hash is used to avoid doing an image dump if the
105 # checksums match. The image_hash is set later, and only if it is needed
107 self.image_hash = None
110 class TestRunInterruptedException(Exception):
111 """Raised when a test run should be stopped immediately."""
112 def __init__(self, reason):
116 class ResultSummary(object):
117 """A class for partitioning the test results we get into buckets.
119 This class is basically a glorified struct and it's private to this file
120 so we don't bother with any information hiding."""
122 def __init__(self, expectations, test_files):
123 self.total = len(test_files)
124 self.remaining = self.total
125 self.expectations = expectations
128 self.unexpected_failures = 0
129 self.unexpected_crashes_or_timeouts = 0
130 self.tests_by_expectation = {}
131 self.tests_by_timeline = {}
133 self.unexpected_results = {}
135 self.tests_by_expectation[test_expectations.SKIP] = set()
136 for expectation in TestExpectationsFile.EXPECTATIONS.values():
137 self.tests_by_expectation[expectation] = set()
138 for timeline in TestExpectationsFile.TIMELINES.values():
139 self.tests_by_timeline[timeline] = (
140 expectations.get_tests_with_timeline(timeline))
142 def add(self, result, expected):
143 """Add a TestResult into the appropriate bin.
146 result: TestResult from dump_render_tree_thread.
147 expected: whether the result was what we expected it to be.
150 self.tests_by_expectation[result.type].add(result.filename)
151 self.results[result.filename] = result
153 if len(result.failures):
154 self.failures[result.filename] = result.failures
158 self.unexpected_results[result.filename] = result.type
160 if len(result.failures):
161 self.unexpected_failures += 1
162 if result.type == test_expectations.CRASH or result.type == test_expectations.TIMEOUT:
163 self.unexpected_crashes_or_timeouts += 1
166 def summarize_unexpected_results(port_obj, expectations, result_summary,
168 """Summarize any unexpected results as a dict.
170 FIXME: split this data structure into a separate class?
173 port_obj: interface to port-specific hooks
174 expectations: test_expectations.TestExpectations object
175 result_summary: summary object from initial test runs
176 retry_summary: summary object from final test run of retried tests
178 A dictionary containing a summary of the unexpected results from the
179 run, with the following fields:
180 'version': a version indicator (1 in this version)
181 'fixable': # of fixable tests (NOW - PASS)
182 'skipped': # of skipped tests (NOW & SKIPPED)
183 'num_regressions': # of non-flaky failures
184 'num_flaky': # of flaky failures
185 'num_passes': # of unexpected passes
186 'tests': a dict of tests -> {'expected': '...', 'actual': '...'}
189 results['version'] = 1
191 tbe = result_summary.tests_by_expectation
192 tbt = result_summary.tests_by_timeline
193 results['fixable'] = len(tbt[test_expectations.NOW] -
194 tbe[test_expectations.PASS])
195 results['skipped'] = len(tbt[test_expectations.NOW] &
196 tbe[test_expectations.SKIP])
202 for k, v in TestExpectationsFile.EXPECTATIONS.iteritems():
203 keywords[v] = k.upper()
206 for filename, result in result_summary.unexpected_results.iteritems():
207 # Note that if a test crashed in the original run, we ignore
208 # whether or not it crashed when we retried it (if we retried it),
209 # and always consider the result not flaky.
210 test = port_obj.relative_test_filename(filename)
211 expected = expectations.get_expectations_string(filename)
212 actual = [keywords[result]]
214 if result == test_expectations.PASS:
216 elif result == test_expectations.CRASH:
219 if filename not in retry_summary.unexpected_results:
220 actual.extend(expectations.get_expectations_string(
221 filename).split(" "))
224 retry_result = retry_summary.unexpected_results[filename]
225 if result != retry_result:
226 actual.append(keywords[retry_result])
232 tests[test]['expected'] = expected
233 tests[test]['actual'] = " ".join(actual)
235 results['tests'] = tests
236 results['num_passes'] = num_passes
237 results['num_flaky'] = num_flaky
238 results['num_regressions'] = num_regressions
244 """A class for managing running a series of tests on a series of layout
247 HTTP_SUBDIR = os.sep.join(['', 'http', ''])
248 WEBSOCKET_SUBDIR = os.sep.join(['', 'websocket', ''])
250 # The per-test timeout in milliseconds, if no --time-out-ms option was
251 # given to run_webkit_tests. This should correspond to the default timeout
253 DEFAULT_TEST_TIMEOUT_MS = 6 * 1000
255 def __init__(self, port, options, printer, message_broker):
256 """Initialize test runner data structures.
259 port: an object implementing port-specific
260 options: a dictionary of command line options
261 printer: a Printer object to record updates to.
262 message_broker: object used to communicate with workers.
265 self._options = options
266 self._printer = printer
267 self._message_broker = message_broker
269 # disable wss server. need to install pyOpenSSL on buildbots.
270 # self._websocket_secure_server = websocket_server.PyWebSocket(
271 # options.results_directory, use_tls=True, port=9323)
273 # a set of test files, and the same tests as a list
274 self._test_files = set()
275 self._test_files_list = None
276 self._result_queue = Queue.Queue()
277 self._retrying = False
279 def collect_tests(self, args, last_unexpected_results):
280 """Find all the files to test.
283 args: list of test arguments from the command line
284 last_unexpected_results: list of unexpected results to retest, if any
287 paths = [self._strip_test_dir_prefix(arg) for arg in args if arg and arg != '']
288 paths += last_unexpected_results
289 if self._options.test_list:
290 paths += read_test_files(self._options.test_list)
291 self._test_files = self._port.tests(paths)
293 def _strip_test_dir_prefix(self, path):
294 if path.startswith(LAYOUT_TESTS_DIRECTORY):
295 return path[len(LAYOUT_TESTS_DIRECTORY):]
301 # Creating the expecations for each platform/configuration pair does
302 # all the test list parsing and ensures it's correct syntax (e.g. no
304 for platform_name in self._port.test_platform_names():
306 self.parse_expectations(platform_name, is_debug_mode=True)
307 except test_expectations.ParseError:
310 self.parse_expectations(platform_name, is_debug_mode=False)
311 except test_expectations.ParseError:
314 self._printer.write("")
316 _log.error("Lint failed.")
319 _log.info("Lint succeeded.")
322 def parse_expectations(self, test_platform_name, is_debug_mode):
323 """Parse the expectations from the test_list files and return a data
324 structure holding them. Throws an error if the test_list files have
326 if self._options.lint_test_files:
329 test_files = self._test_files
331 expectations_str = self._port.test_expectations()
332 overrides_str = self._port.test_expectations_overrides()
333 self._expectations = test_expectations.TestExpectations(
334 self._port, test_files, expectations_str, test_platform_name,
335 is_debug_mode, self._options.lint_test_files,
336 overrides=overrides_str)
337 return self._expectations
340 def prepare_lists_and_print_output(self):
341 """Create appropriate subsets of test lists and returns a
342 ResultSummary object. Also prints expected test counts.
345 # Remove skipped - both fixable and ignored - files from the
346 # top-level list of files to test.
347 num_all_test_files = len(self._test_files)
348 self._printer.print_expected("Found: %d tests" %
349 (len(self._test_files)))
350 if not num_all_test_files:
351 _log.critical('No tests to run.')
355 if num_all_test_files > 1 and not self._options.force:
356 skipped = self._expectations.get_tests_with_result_type(
357 test_expectations.SKIP)
358 self._test_files -= skipped
360 # Create a sorted list of test files so the subset chunk,
361 # if used, contains alphabetically consecutive tests.
362 self._test_files_list = list(self._test_files)
363 if self._options.randomize_order:
364 random.shuffle(self._test_files_list)
366 self._test_files_list.sort()
368 # If the user specifies they just want to run a subset of the tests,
369 # just grab a subset of the non-skipped tests.
370 if self._options.run_chunk or self._options.run_part:
371 chunk_value = self._options.run_chunk or self._options.run_part
372 test_files = self._test_files_list
374 (chunk_num, chunk_len) = chunk_value.split(":")
375 chunk_num = int(chunk_num)
376 assert(chunk_num >= 0)
377 test_size = int(chunk_len)
378 assert(test_size > 0)
380 _log.critical("invalid chunk '%s'" % chunk_value)
383 # Get the number of tests
384 num_tests = len(test_files)
386 # Get the start offset of the slice.
387 if self._options.run_chunk:
388 chunk_len = test_size
389 # In this case chunk_num can be really large. We need
390 # to make the slave fit in the current number of tests.
391 slice_start = (chunk_num * chunk_len) % num_tests
394 assert(test_size <= num_tests)
395 assert(chunk_num <= test_size)
397 # To count the chunk_len, and make sure we don't skip
398 # some tests, we round to the next value that fits exactly
400 rounded_tests = num_tests
401 if rounded_tests % test_size != 0:
402 rounded_tests = (num_tests + test_size -
403 (num_tests % test_size))
405 chunk_len = rounded_tests / test_size
406 slice_start = chunk_len * (chunk_num - 1)
407 # It does not mind if we go over test_size.
409 # Get the end offset of the slice.
410 slice_end = min(num_tests, slice_start + chunk_len)
412 files = test_files[slice_start:slice_end]
414 tests_run_msg = 'Running: %d tests (chunk slice [%d:%d] of %d)' % (
415 (slice_end - slice_start), slice_start, slice_end, num_tests)
416 self._printer.print_expected(tests_run_msg)
418 # If we reached the end and we don't have enough tests, we run some
419 # from the beginning.
420 if slice_end - slice_start < chunk_len:
421 extra = chunk_len - (slice_end - slice_start)
422 extra_msg = (' last chunk is partial, appending [0:%d]' %
424 self._printer.print_expected(extra_msg)
425 tests_run_msg += "\n" + extra_msg
426 files.extend(test_files[0:extra])
427 tests_run_filename = os.path.join(self._options.results_directory,
429 with codecs.open(tests_run_filename, "w", "utf-8") as file:
430 file.write(tests_run_msg + "\n")
432 len_skip_chunk = int(len(files) * len(skipped) /
433 float(len(self._test_files)))
434 skip_chunk_list = list(skipped)[0:len_skip_chunk]
435 skip_chunk = set(skip_chunk_list)
437 # Update expectations so that the stats are calculated correctly.
438 # We need to pass a list that includes the right # of skipped files
439 # to ParseExpectations so that ResultSummary() will get the correct
440 # stats. So, we add in the subset of skipped files, and then
441 # subtract them back out.
442 self._test_files_list = files + skip_chunk_list
443 self._test_files = set(self._test_files_list)
445 self._expectations = self.parse_expectations(
446 self._port.test_platform_name(),
447 self._options.configuration == 'Debug')
449 self._test_files = set(files)
450 self._test_files_list = files
454 result_summary = ResultSummary(self._expectations,
455 self._test_files | skip_chunk)
456 self._print_expected_results_of_type(result_summary,
457 test_expectations.PASS, "passes")
458 self._print_expected_results_of_type(result_summary,
459 test_expectations.FAIL, "failures")
460 self._print_expected_results_of_type(result_summary,
461 test_expectations.FLAKY, "flaky")
462 self._print_expected_results_of_type(result_summary,
463 test_expectations.SKIP, "skipped")
465 if self._options.force:
466 self._printer.print_expected('Running all tests, including '
469 # Note that we don't actually run the skipped tests (they were
470 # subtracted out of self._test_files, above), but we stub out the
471 # results here so the statistics can remain accurate.
472 for test in skip_chunk:
473 result = test_results.TestResult(test,
474 failures=[], test_run_time=0, total_time_for_all_diffs=0,
476 result.type = test_expectations.SKIP
477 result_summary.add(result, expected=True)
478 self._printer.print_expected('')
480 return result_summary
482 def _get_dir_for_test_file(self, test_file):
483 """Returns the highest-level directory by which to shard the given
485 index = test_file.rfind(os.sep + LAYOUT_TESTS_DIRECTORY)
487 test_file = test_file[index + len(LAYOUT_TESTS_DIRECTORY):]
488 test_file_parts = test_file.split(os.sep, 1)
489 directory = test_file_parts[0]
490 test_file = test_file_parts[1]
492 # The http tests are very stable on mac/linux.
493 # TODO(ojan): Make the http server on Windows be apache so we can
494 # turn shard the http tests there as well. Switching to apache is
495 # what made them stable on linux/mac.
496 return_value = directory
497 while ((directory != 'http' or sys.platform in ('darwin', 'linux2'))
498 and test_file.find(os.sep) >= 0):
499 test_file_parts = test_file.split(os.sep, 1)
500 directory = test_file_parts[0]
501 return_value = os.path.join(return_value, directory)
502 test_file = test_file_parts[1]
506 def _get_test_input_for_file(self, test_file):
507 """Returns the appropriate TestInput object for the file. Mostly this
508 is used for looking up the timeout value (in ms) to use for the given
510 if self._test_is_slow(test_file):
511 return TestInput(test_file, self._options.slow_time_out_ms)
512 return TestInput(test_file, self._options.time_out_ms)
514 def _test_requires_lock(self, test_file):
515 """Return True if the test needs to be locked when
516 running multiple copies of NRWTs."""
517 split_path = test_file.split(os.sep)
518 return 'http' in split_path or 'websocket' in split_path
520 def _test_is_slow(self, test_file):
521 return self._expectations.has_modifier(test_file,
522 test_expectations.SLOW)
524 def _shard_tests(self, test_files, use_real_shards):
525 """Groups tests into batches.
526 This helps ensure that tests that depend on each other (aka bad tests!)
527 continue to run together as most cross-tests dependencies tend to
528 occur within the same directory. If use_real_shards is false, we
529 put each (non-HTTP/websocket) test into its own shard for maximum
530 concurrency instead of trying to do any sort of real sharding.
533 A list of lists of TestInput objects.
535 # FIXME: when we added http locking, we changed how this works such
536 # that we always lump all of the HTTP threads into a single shard.
537 # That will slow down experimental-fully-parallel, but it's unclear
538 # what the best alternative is completely revamping how we track
539 # when to grab the lock.
542 tests_to_http_lock = []
543 if not use_real_shards:
544 for test_file in test_files:
545 test_input = self._get_test_input_for_file(test_file)
546 if self._test_requires_lock(test_file):
547 tests_to_http_lock.append(test_input)
549 test_lists.append((".", [test_input]))
552 for test_file in test_files:
553 directory = self._get_dir_for_test_file(test_file)
554 test_input = self._get_test_input_for_file(test_file)
555 if self._test_requires_lock(test_file):
556 tests_to_http_lock.append(test_input)
558 tests_by_dir.setdefault(directory, [])
559 tests_by_dir[directory].append(test_input)
560 # Sort by the number of tests in the dir so that the ones with the
561 # most tests get run first in order to maximize parallelization.
562 # Number of tests is a good enough, but not perfect, approximation
563 # of how long that set of tests will take to run. We can't just use
564 # a PriorityQueue until we move to Python 2.6.
565 for directory in tests_by_dir:
566 test_list = tests_by_dir[directory]
567 # Keep the tests in alphabetical order.
568 # FIXME: Remove once tests are fixed so they can be run in any
571 test_list_tuple = (directory, test_list)
572 test_lists.append(test_list_tuple)
573 test_lists.sort(lambda a, b: cmp(len(b[1]), len(a[1])))
575 # Put the http tests first. There are only a couple hundred of them,
576 # but each http test takes a very long time to run, so sorting by the
577 # number of tests doesn't accurately capture how long they take to run.
578 if tests_to_http_lock:
579 tests_to_http_lock.reverse()
580 test_lists.insert(0, ("tests_to_http_lock", tests_to_http_lock))
584 def _contains_tests(self, subdir):
585 for test_file in self._test_files:
586 if test_file.find(subdir) >= 0:
590 def _num_workers(self):
591 return int(self._options.child_processes)
593 def _run_tests(self, file_list, result_summary):
594 """Runs the tests in the file_list.
596 Return: A tuple (interrupted, keyboard_interrupted, thread_timings,
597 test_timings, individual_test_timings)
598 interrupted is whether the run was interrupted
599 keyboard_interrupted is whether the interruption was because someone
601 thread_timings is a list of dicts with the total runtime
602 of each thread with 'name', 'num_tests', 'total_time' properties
603 test_timings is a list of timings for each sharded subdirectory
604 of the form [time, directory_name, num_tests]
605 individual_test_timings is a list of run times for each test
606 in the form {filename:filename, test_run_time:test_run_time}
607 result_summary: summary object to populate with the results
610 self._printer.print_update('Sharding tests ...')
611 num_workers = self._num_workers()
612 test_lists = self._shard_tests(file_list,
613 num_workers > 1 and not self._options.experimental_fully_parallel)
614 filename_queue = Queue.Queue()
615 for item in test_lists:
616 filename_queue.put(item)
618 self._printer.print_update('Starting %s ...' %
619 grammar.pluralize('worker', num_workers))
620 message_broker = self._message_broker
621 self._current_filename_queue = filename_queue
622 self._current_result_summary = result_summary
624 if not self._options.dry_run:
625 threads = message_broker.start_workers(self)
629 self._printer.print_update("Starting testing ...")
630 keyboard_interrupted = False
632 if not self._options.dry_run:
634 message_broker.run_message_loop()
635 except KeyboardInterrupt:
636 _log.info("Interrupted, exiting")
637 message_broker.cancel_workers()
638 keyboard_interrupted = True
640 except TestRunInterruptedException, e:
642 message_broker.cancel_workers()
645 # Unexpected exception; don't try to clean up workers.
646 _log.info("Exception raised, exiting")
649 thread_timings, test_timings, individual_test_timings = \
650 self._collect_timing_info(threads)
652 return (interrupted, keyboard_interrupted, thread_timings, test_timings,
653 individual_test_timings)
656 self.update_summary(self._current_result_summary)
658 def _collect_timing_info(self, threads):
660 individual_test_timings = []
663 for thread in threads:
664 thread_timings.append({'name': thread.getName(),
665 'num_tests': thread.get_num_tests(),
666 'total_time': thread.get_total_time()})
667 test_timings.update(thread.get_test_group_timing_stats())
668 individual_test_timings.extend(thread.get_test_results())
670 return (thread_timings, test_timings, individual_test_timings)
672 def needs_http(self):
673 """Returns whether the test runner needs an HTTP server."""
674 return self._contains_tests(self.HTTP_SUBDIR)
676 def needs_websocket(self):
677 """Returns whether the test runner needs a WEBSOCKET server."""
678 return self._contains_tests(self.WEBSOCKET_SUBDIR)
680 def set_up_run(self):
681 """Configures the system to be ready to run tests.
683 Returns a ResultSummary object if we should continue to run tests,
684 or None if we should abort.
687 # This must be started before we check the system dependencies,
688 # since the helper may do things to make the setup correct.
689 self._printer.print_update("Starting helper ...")
690 self._port.start_helper()
692 # Check that the system dependencies (themes, fonts, ...) are correct.
693 if not self._options.nocheck_sys_deps:
694 self._printer.print_update("Checking system dependencies ...")
695 if not self._port.check_sys_deps(self.needs_http()):
696 self._port.stop_helper()
699 if self._options.clobber_old_results:
700 self._clobber_old_results()
702 # Create the output directory if it doesn't already exist.
703 self._port.maybe_make_directory(self._options.results_directory)
705 self._port.setup_test_run()
707 self._printer.print_update("Preparing tests ...")
708 result_summary = self.prepare_lists_and_print_output()
709 if not result_summary:
712 return result_summary
714 def run(self, result_summary):
715 """Run all our tests on all our test files.
717 For each test file, we run each test type. If there are any failures,
718 we collect them for reporting.
721 result_summary: a summary object tracking the test results.
724 The number of unexpected results (0 == success)
726 # gather_test_files() must have been called first to initialize us.
727 # If we didn't find any files to test, we've errored out already in
728 # prepare_lists_and_print_output().
729 assert(len(self._test_files))
731 start_time = time.time()
733 interrupted, keyboard_interrupted, thread_timings, test_timings, \
734 individual_test_timings = (
735 self._run_tests(self._test_files_list, result_summary))
737 # We exclude the crashes from the list of results to retry, because
738 # we want to treat even a potentially flaky crash as an error.
739 failures = self._get_failures(result_summary, include_crashes=False)
740 retry_summary = result_summary
741 while (len(failures) and self._options.retry_failures and
742 not self._retrying and not interrupted):
744 _log.info("Retrying %d unexpected failure(s) ..." % len(failures))
746 self._retrying = True
747 retry_summary = ResultSummary(self._expectations, failures.keys())
748 # Note that we intentionally ignore the return value here.
749 self._run_tests(failures.keys(), retry_summary)
750 failures = self._get_failures(retry_summary, include_crashes=True)
752 end_time = time.time()
754 self._print_timing_statistics(end_time - start_time,
755 thread_timings, test_timings,
756 individual_test_timings,
759 self._print_result_summary(result_summary)
764 self._printer.print_one_line_summary(result_summary.total,
765 result_summary.expected,
766 result_summary.unexpected)
768 unexpected_results = summarize_unexpected_results(self._port,
769 self._expectations, result_summary, retry_summary)
770 self._printer.print_unexpected_results(unexpected_results)
772 if (self._options.record_results and not self._options.dry_run and
774 # Write the same data to log files and upload generated JSON files
775 # to appengine server.
776 self._upload_json_files(unexpected_results, result_summary,
777 individual_test_timings)
779 # Write the summary to disk (results.html) and display it if requested.
780 if not self._options.dry_run:
781 wrote_results = self._write_results_html_file(result_summary)
782 if self._options.show_results and wrote_results:
783 self._show_results_html_file()
785 # Now that we've completed all the processing we can, we re-raise
786 # a KeyboardInterrupt if necessary so the caller can handle it.
787 if keyboard_interrupted:
788 raise KeyboardInterrupt
790 # Ignore flaky failures and unexpected passes so we don't turn the
792 return unexpected_results['num_regressions']
794 def clean_up_run(self):
795 """Restores the system after we're done running tests."""
797 _log.debug("flushing stdout")
799 _log.debug("flushing stderr")
801 _log.debug("stopping helper")
802 self._port.stop_helper()
804 def update_summary(self, result_summary):
805 """Update the summary and print results with any completed tests."""
808 result = test_results.TestResult.loads(self._result_queue.get_nowait())
812 expected = self._expectations.matches_an_expected_result(
813 result.filename, result.type, self._options.pixel_tests)
814 result_summary.add(result, expected)
815 exp_str = self._expectations.get_expectations_string(
817 got_str = self._expectations.expectation_to_string(result.type)
818 self._printer.print_test_result(result, expected, exp_str, got_str)
819 self._printer.print_progress(result_summary, self._retrying,
820 self._test_files_list)
822 def interrupt_if_at_failure_limit(limit, count, message):
823 if limit and count >= limit:
824 raise TestRunInterruptedException(message % count)
826 interrupt_if_at_failure_limit(
827 self._options.exit_after_n_failures,
828 result_summary.unexpected_failures,
829 "Aborting run since %d failures were reached")
830 interrupt_if_at_failure_limit(
831 self._options.exit_after_n_crashes_or_timeouts,
832 result_summary.unexpected_crashes_or_timeouts,
833 "Aborting run since %d crashes or timeouts were reached")
836 def _clobber_old_results(self):
837 # Just clobber the actual test results directories since the other
838 # files in the results directory are explicitly used for cross-run
840 self._printer.print_update("Clobbering old results in %s" %
841 self._options.results_directory)
842 layout_tests_dir = self._port.layout_tests_dir()
843 possible_dirs = self._port.test_dirs()
844 for dirname in possible_dirs:
845 if os.path.isdir(os.path.join(layout_tests_dir, dirname)):
846 shutil.rmtree(os.path.join(self._options.results_directory,
850 def _get_failures(self, result_summary, include_crashes):
851 """Filters a dict of results and returns only the failures.
854 result_summary: the results of the test run
855 include_crashes: whether crashes are included in the output.
856 We use False when finding the list of failures to retry
857 to see if the results were flaky. Although the crashes may also be
858 flaky, we treat them as if they aren't so that they're not ignored.
860 a dict of files -> results
863 for test, result in result_summary.unexpected_results.iteritems():
864 if (result == test_expectations.PASS or
865 result == test_expectations.CRASH and not include_crashes):
867 failed_results[test] = result
869 return failed_results
871 def _upload_json_files(self, unexpected_results, result_summary,
872 individual_test_timings):
873 """Writes the results of the test run as JSON files into the results
874 dir and upload the files to the appengine server.
876 There are three different files written into the results dir:
877 unexpected_results.json: A short list of any unexpected results.
878 This is used by the buildbots to display results.
879 expectations.json: This is used by the flakiness dashboard.
880 results.json: A full list of the results - used by the flakiness
881 dashboard and the aggregate results dashboard.
884 unexpected_results: dict of unexpected results
885 result_summary: full summary object
886 individual_test_timings: list of test times (used by the flakiness
889 results_directory = self._options.results_directory
890 _log.debug("Writing JSON files in %s." % results_directory)
891 unexpected_json_path = os.path.join(results_directory, "unexpected_results.json")
892 with codecs.open(unexpected_json_path, "w", "utf-8") as file:
893 simplejson.dump(unexpected_results, file, sort_keys=True, indent=2)
895 # Write a json file of the test_expectations.txt file for the layout
897 expectations_path = os.path.join(results_directory, "expectations.json")
898 expectations_json = \
899 self._expectations.get_expectations_json_for_all_platforms()
900 with codecs.open(expectations_path, "w", "utf-8") as file:
901 file.write(u"ADD_EXPECTATIONS(%s);" % expectations_json)
903 generator = json_layout_results_generator.JSONLayoutResultsGenerator(
904 self._port, self._options.builder_name, self._options.build_name,
905 self._options.build_number, self._options.results_directory,
906 BUILDER_BASE_URL, individual_test_timings,
907 self._expectations, result_summary, self._test_files_list,
908 not self._options.upload_full_results,
909 self._options.test_results_server,
911 self._options.master_name)
913 _log.debug("Finished writing JSON files.")
915 json_files = ["expectations.json"]
916 if self._options.upload_full_results:
917 json_files.append("results.json")
919 json_files.append("incremental_results.json")
921 generator.upload_json_files(json_files)
923 def _print_config(self):
924 """Prints the configuration for the test run."""
926 p.print_config("Using port '%s'" % self._port.name())
927 p.print_config("Placing test results in %s" %
928 self._options.results_directory)
929 if self._options.new_baseline:
930 p.print_config("Placing new baselines in %s" %
931 self._port.baseline_path())
932 p.print_config("Using %s build" % self._options.configuration)
933 if self._options.pixel_tests:
934 p.print_config("Pixel tests enabled")
936 p.print_config("Pixel tests disabled")
938 p.print_config("Regular timeout: %s, slow test timeout: %s" %
939 (self._options.time_out_ms,
940 self._options.slow_time_out_ms))
942 if self._num_workers() == 1:
943 p.print_config("Running one %s" % self._port.driver_name())
945 p.print_config("Running %s %ss in parallel" %
946 (self._options.child_processes,
947 self._port.driver_name()))
948 p.print_config('Command line: ' +
949 ' '.join(self._port.driver_cmd_line()))
950 p.print_config("Worker model: %s" % self._options.worker_model)
953 def _print_expected_results_of_type(self, result_summary,
954 result_type, result_type_str):
955 """Print the number of the tests in a given result class.
958 result_summary - the object containing all the results to report on
959 result_type - the particular result type to report in the summary.
960 result_type_str - a string description of the result_type.
962 tests = self._expectations.get_tests_with_result_type(result_type)
963 now = result_summary.tests_by_timeline[test_expectations.NOW]
964 wontfix = result_summary.tests_by_timeline[test_expectations.WONTFIX]
966 # We use a fancy format string in order to print the data out in a
967 # nicely-aligned table.
968 fmtstr = ("Expect: %%5d %%-8s (%%%dd now, %%%dd wontfix)"
969 % (self._num_digits(now), self._num_digits(wontfix)))
970 self._printer.print_expected(fmtstr %
971 (len(tests), result_type_str, len(tests & now), len(tests & wontfix)))
973 def _num_digits(self, num):
974 """Returns the number of digits needed to represent the length of a
978 ndigits = int(math.log10(len(num))) + 1
981 def _print_timing_statistics(self, total_time, thread_timings,
982 directory_test_timings, individual_test_timings,
984 """Record timing-specific information for the test run.
987 total_time: total elapsed time (in seconds) for the test run
988 thread_timings: wall clock time each thread ran for
989 directory_test_timings: timing by directory
990 individual_test_timings: timing by file
991 result_summary: summary object for the test run
993 self._printer.print_timing("Test timing:")
994 self._printer.print_timing(" %6.2f total testing time" % total_time)
995 self._printer.print_timing("")
996 self._printer.print_timing("Thread timing:")
998 for t in thread_timings:
999 self._printer.print_timing(" %10s: %5d tests, %6.2f secs" %
1000 (t['name'], t['num_tests'], t['total_time']))
1001 cuml_time += t['total_time']
1002 self._printer.print_timing(" %6.2f cumulative, %6.2f optimal" %
1003 (cuml_time, cuml_time / int(self._options.child_processes)))
1004 self._printer.print_timing("")
1006 self._print_aggregate_test_statistics(individual_test_timings)
1007 self._print_individual_test_times(individual_test_timings,
1009 self._print_directory_timings(directory_test_timings)
1011 def _print_aggregate_test_statistics(self, individual_test_timings):
1012 """Prints aggregate statistics (e.g. median, mean, etc.) for all tests.
1014 individual_test_timings: List of dump_render_tree_thread.TestStats
1017 test_types = [] # Unit tests don't actually produce any timings.
1018 if individual_test_timings:
1019 test_types = individual_test_timings[0].time_for_diffs.keys()
1020 times_for_dump_render_tree = []
1021 times_for_diff_processing = []
1022 times_per_test_type = {}
1023 for test_type in test_types:
1024 times_per_test_type[test_type] = []
1026 for test_stats in individual_test_timings:
1027 times_for_dump_render_tree.append(test_stats.test_run_time)
1028 times_for_diff_processing.append(
1029 test_stats.total_time_for_all_diffs)
1030 time_for_diffs = test_stats.time_for_diffs
1031 for test_type in test_types:
1032 times_per_test_type[test_type].append(
1033 time_for_diffs[test_type])
1035 self._print_statistics_for_test_timings(
1036 "PER TEST TIME IN TESTSHELL (seconds):",
1037 times_for_dump_render_tree)
1038 self._print_statistics_for_test_timings(
1039 "PER TEST DIFF PROCESSING TIMES (seconds):",
1040 times_for_diff_processing)
1041 for test_type in test_types:
1042 self._print_statistics_for_test_timings(
1043 "PER TEST TIMES BY TEST TYPE: %s" % test_type,
1044 times_per_test_type[test_type])
1046 def _print_individual_test_times(self, individual_test_timings,
1048 """Prints the run times for slow, timeout and crash tests.
1050 individual_test_timings: List of dump_render_tree_thread.TestStats
1052 result_summary: summary object for test run
1054 # Reverse-sort by the time spent in DumpRenderTree.
1055 individual_test_timings.sort(lambda a, b:
1056 cmp(b.test_run_time, a.test_run_time))
1060 timeout_or_crash_tests = []
1061 unexpected_slow_tests = []
1062 for test_tuple in individual_test_timings:
1063 filename = test_tuple.filename
1064 is_timeout_crash_or_slow = False
1065 if self._test_is_slow(filename):
1066 is_timeout_crash_or_slow = True
1067 slow_tests.append(test_tuple)
1069 if filename in result_summary.failures:
1070 result = result_summary.results[filename].type
1071 if (result == test_expectations.TIMEOUT or
1072 result == test_expectations.CRASH):
1073 is_timeout_crash_or_slow = True
1074 timeout_or_crash_tests.append(test_tuple)
1076 if (not is_timeout_crash_or_slow and
1077 num_printed < printing.NUM_SLOW_TESTS_TO_LOG):
1078 num_printed = num_printed + 1
1079 unexpected_slow_tests.append(test_tuple)
1081 self._printer.print_timing("")
1082 self._print_test_list_timing("%s slowest tests that are not "
1083 "marked as SLOW and did not timeout/crash:" %
1084 printing.NUM_SLOW_TESTS_TO_LOG, unexpected_slow_tests)
1085 self._printer.print_timing("")
1086 self._print_test_list_timing("Tests marked as SLOW:", slow_tests)
1087 self._printer.print_timing("")
1088 self._print_test_list_timing("Tests that timed out or crashed:",
1089 timeout_or_crash_tests)
1090 self._printer.print_timing("")
1092 def _print_test_list_timing(self, title, test_list):
1093 """Print timing info for each test.
1096 title: section heading
1097 test_list: tests that fall in this section
1099 if self._printer.disabled('slowest'):
1102 self._printer.print_timing(title)
1103 for test_tuple in test_list:
1104 filename = test_tuple.filename[len(
1105 self._port.layout_tests_dir()) + 1:]
1106 filename = filename.replace('\\', '/')
1107 test_run_time = round(test_tuple.test_run_time, 1)
1108 self._printer.print_timing(" %s took %s seconds" %
1109 (filename, test_run_time))
1111 def _print_directory_timings(self, directory_test_timings):
1112 """Print timing info by directory for any directories that
1113 take > 10 seconds to run.
1116 directory_test_timing: time info for each directory
1119 for directory in directory_test_timings:
1120 num_tests, time_for_directory = directory_test_timings[directory]
1121 timings.append((round(time_for_directory, 1), directory,
1125 self._printer.print_timing("Time to process slowest subdirectories:")
1126 min_seconds_to_print = 10
1127 for timing in timings:
1128 if timing[0] > min_seconds_to_print:
1129 self._printer.print_timing(
1130 " %s took %s seconds to run %s tests." % (timing[1],
1131 timing[0], timing[2]))
1132 self._printer.print_timing("")
1134 def _print_statistics_for_test_timings(self, title, timings):
1135 """Prints the median, mean and standard deviation of the values in
1139 title: Title for these timings.
1140 timings: A list of floats representing times.
1142 self._printer.print_timing(title)
1145 num_tests = len(timings)
1148 percentile90 = timings[int(.9 * num_tests)]
1149 percentile99 = timings[int(.99 * num_tests)]
1151 if num_tests % 2 == 1:
1152 median = timings[((num_tests - 1) / 2) - 1]
1154 lower = timings[num_tests / 2 - 1]
1155 upper = timings[num_tests / 2]
1156 median = (float(lower + upper)) / 2
1158 mean = sum(timings) / num_tests
1160 for time in timings:
1161 sum_of_deviations = math.pow(time - mean, 2)
1163 std_deviation = math.sqrt(sum_of_deviations / num_tests)
1164 self._printer.print_timing(" Median: %6.3f" % median)
1165 self._printer.print_timing(" Mean: %6.3f" % mean)
1166 self._printer.print_timing(" 90th percentile: %6.3f" % percentile90)
1167 self._printer.print_timing(" 99th percentile: %6.3f" % percentile99)
1168 self._printer.print_timing(" Standard dev: %6.3f" % std_deviation)
1169 self._printer.print_timing("")
1171 def _print_result_summary(self, result_summary):
1172 """Print a short summary about how many tests passed.
1175 result_summary: information to log
1177 failed = len(result_summary.failures)
1179 result_summary.tests_by_expectation[test_expectations.SKIP])
1180 total = result_summary.total
1181 passed = total - failed - skipped
1184 pct_passed = float(passed) * 100 / total
1186 self._printer.print_actual("")
1187 self._printer.print_actual("=> Results: %d/%d tests passed (%.1f%%)" %
1188 (passed, total, pct_passed))
1189 self._printer.print_actual("")
1190 self._print_result_summary_entry(result_summary,
1191 test_expectations.NOW, "Tests to be fixed")
1193 self._printer.print_actual("")
1194 self._print_result_summary_entry(result_summary,
1195 test_expectations.WONTFIX,
1196 "Tests that will only be fixed if they crash (WONTFIX)")
1197 self._printer.print_actual("")
1199 def _print_result_summary_entry(self, result_summary, timeline,
1201 """Print a summary block of results for a particular timeline of test.
1204 result_summary: summary to print results for
1205 timeline: the timeline to print results for (NOT, WONTFIX, etc.)
1206 heading: a textual description of the timeline
1208 total = len(result_summary.tests_by_timeline[timeline])
1209 not_passing = (total -
1210 len(result_summary.tests_by_expectation[test_expectations.PASS] &
1211 result_summary.tests_by_timeline[timeline]))
1212 self._printer.print_actual("=> %s (%d):" % (heading, not_passing))
1214 for result in TestExpectationsFile.EXPECTATION_ORDER:
1215 if result == test_expectations.PASS:
1217 results = (result_summary.tests_by_expectation[result] &
1218 result_summary.tests_by_timeline[timeline])
1219 desc = TestExpectationsFile.EXPECTATION_DESCRIPTIONS[result]
1220 if not_passing and len(results):
1221 pct = len(results) * 100.0 / not_passing
1222 self._printer.print_actual(" %5d %-24s (%4.1f%%)" %
1223 (len(results), desc[len(results) != 1], pct))
1225 def _results_html(self, test_files, failures, title="Test Failures", override_time=None):
1227 test_files = a list of file paths
1228 failures = dictionary mapping test paths to failure objects
1229 title = title printed at top of test
1230 override_time = current time (used by unit tests)
1234 <title>Layout Test Results (%(time)s)</title>
1237 <h2>%(title)s (%(time)s)</h2>
1238 """ % {'title': title, 'time': override_time or time.asctime()}
1240 for test_file in sorted(test_files):
1241 test_name = self._port.relative_test_filename(test_file)
1242 test_url = self._port.filename_to_uri(test_file)
1243 page += u"<p><a href='%s'>%s</a><br />\n" % (test_url, test_name)
1244 test_failures = failures.get(test_file, [])
1245 for failure in test_failures:
1246 page += (u" %s<br/>" %
1247 failure.result_html_output(test_name))
1249 page += "</body></html>\n"
1252 def _write_results_html_file(self, result_summary):
1253 """Write results.html which is a summary of tests that failed.
1256 result_summary: a summary of the results :)
1259 True if any results were written (since expected failures may be
1263 if self._options.full_results_html:
1264 results_title = "Test Failures"
1265 test_files = result_summary.failures.keys()
1267 results_title = "Unexpected Test Failures"
1268 unexpected_failures = self._get_failures(result_summary,
1269 include_crashes=True)
1270 test_files = unexpected_failures.keys()
1271 if not len(test_files):
1274 out_filename = os.path.join(self._options.results_directory,
1276 with codecs.open(out_filename, "w", "utf-8") as results_file:
1277 html = self._results_html(test_files, result_summary.failures, results_title)
1278 results_file.write(html)
1282 def _show_results_html_file(self):
1283 """Shows the results.html page."""
1284 results_filename = os.path.join(self._options.results_directory,
1286 self._port.show_results_html_file(results_filename)
1289 def read_test_files(files):
1293 with codecs.open(file, 'r', 'utf-8') as file_contents:
1294 # FIXME: This could be cleaner using a list comprehension.
1295 for line in file_contents:
1296 line = test_expectations.strip_comments(line)
1300 if e.errno == errno.ENOENT:
1302 _log.critical('--test-list file "%s" not found' % file)
1307 def run(port, options, args, regular_output=sys.stderr,
1308 buildbot_output=sys.stdout):
1312 port: Port object for port-specific behavior
1313 options: a dictionary of command line options
1314 args: a list of sub directories or files to test
1315 regular_output: a stream-like object that we can send logging/debug
1317 buildbot_output: a stream-like object that we can write all output that
1318 is intended to be parsed by the buildbot to
1320 the number of unexpected results that occurred, or -1 if there is an
1324 _set_up_derived_options(port, options)
1326 printer = printing.Printer(port, options, regular_output, buildbot_output,
1327 int(options.child_processes), options.experimental_fully_parallel)
1328 if options.help_printing:
1329 printer.help_printing()
1333 last_unexpected_results = _gather_unexpected_results(options)
1334 if options.print_last_failures:
1335 printer.write("\n".join(last_unexpected_results) + "\n")
1339 broker = message_broker.get(port, options)
1341 # We wrap any parts of the run that are slow or likely to raise exceptions
1342 # in a try/finally to ensure that we clean up the logging configuration.
1343 num_unexpected_results = -1
1345 test_runner = TestRunner(port, options, printer, broker)
1346 test_runner._print_config()
1348 printer.print_update("Collecting tests ...")
1350 test_runner.collect_tests(args, last_unexpected_results)
1352 if e.errno == errno.ENOENT:
1356 printer.print_update("Parsing expectations ...")
1357 if options.lint_test_files:
1358 return test_runner.lint()
1359 test_runner.parse_expectations(port.test_platform_name(),
1360 options.configuration == 'Debug')
1362 printer.print_update("Checking build ...")
1363 if not port.check_build(test_runner.needs_http()):
1364 _log.error("Build check failed")
1367 result_summary = test_runner.set_up_run()
1369 num_unexpected_results = test_runner.run(result_summary)
1370 test_runner.clean_up_run()
1371 _log.debug("Testing completed, Exit status: %d" %
1372 num_unexpected_results)
1377 return num_unexpected_results
1380 def _set_up_derived_options(port_obj, options):
1381 """Sets the options values that depend on other options values."""
1383 if options.worker_model == 'inline':
1384 if options.child_processes and int(options.child_processes) > 1:
1385 _log.warning("--worker-model=inline overrides --child-processes")
1386 options.child_processes = "1"
1387 if not options.child_processes:
1388 options.child_processes = os.environ.get("WEBKIT_TEST_CHILD_PROCESSES",
1389 str(port_obj.default_child_processes()))
1391 if not options.configuration:
1392 options.configuration = port_obj.default_configuration()
1394 if options.pixel_tests is None:
1395 options.pixel_tests = True
1397 if not options.use_apache:
1398 options.use_apache = sys.platform in ('darwin', 'linux2')
1400 if not os.path.isabs(options.results_directory):
1401 # This normalizes the path to the build dir.
1402 # FIXME: how this happens is not at all obvious; this is a dumb
1403 # interface and should be cleaned up.
1404 options.results_directory = port_obj.results_directory()
1406 if not options.time_out_ms:
1407 if options.configuration == "Debug":
1408 options.time_out_ms = str(2 * TestRunner.DEFAULT_TEST_TIMEOUT_MS)
1410 options.time_out_ms = str(TestRunner.DEFAULT_TEST_TIMEOUT_MS)
1412 options.slow_time_out_ms = str(5 * int(options.time_out_ms))
1415 def _gather_unexpected_results(options):
1416 """Returns the unexpected results from the previous run, if any."""
1417 last_unexpected_results = []
1418 if options.print_last_failures or options.retest_last_failures:
1419 unexpected_results_filename = os.path.join(
1420 options.results_directory, "unexpected_results.json")
1421 with codecs.open(unexpected_results_filename, "r", "utf-8") as file:
1422 results = simplejson.load(file)
1423 last_unexpected_results = results['tests'].keys()
1424 return last_unexpected_results
1427 def _compat_shim_callback(option, opt_str, value, parser):
1428 print "Ignoring unsupported option: %s" % opt_str
1431 def _compat_shim_option(option_name, **kwargs):
1432 return optparse.make_option(option_name, action="callback",
1433 callback=_compat_shim_callback,
1434 help="Ignored, for old-run-webkit-tests compat only.", **kwargs)
1437 def parse_args(args=None):
1438 """Provides a default set of command line args.
1440 Returns a tuple of options, args from optparse"""
1442 # FIXME: All of these options should be stored closer to the code which
1443 # FIXME: actually uses them. configuration_options should move
1444 # FIXME: to WebKitPort and be shared across all scripts.
1445 configuration_options = [
1446 optparse.make_option("-t", "--target", dest="configuration",
1447 help="(DEPRECATED)"),
1448 # FIXME: --help should display which configuration is default.
1449 optparse.make_option('--debug', action='store_const', const='Debug',
1450 dest="configuration",
1451 help='Set the configuration to Debug'),
1452 optparse.make_option('--release', action='store_const',
1453 const='Release', dest="configuration",
1454 help='Set the configuration to Release'),
1455 # old-run-webkit-tests also accepts -c, --configuration CONFIGURATION.
1458 print_options = printing.print_options()
1460 # FIXME: These options should move onto the ChromiumPort.
1461 chromium_options = [
1462 optparse.make_option("--chromium", action="store_true", default=False,
1463 help="use the Chromium port"),
1464 optparse.make_option("--startup-dialog", action="store_true",
1465 default=False, help="create a dialog on DumpRenderTree startup"),
1466 optparse.make_option("--gp-fault-error-box", action="store_true",
1467 default=False, help="enable Windows GP fault error box"),
1468 optparse.make_option("--multiple-loads",
1469 type="int", help="turn on multiple loads of each test"),
1470 optparse.make_option("--js-flags",
1471 type="string", help="JavaScript flags to pass to tests"),
1472 optparse.make_option("--nocheck-sys-deps", action="store_true",
1474 help="Don't check the system dependencies (themes)"),
1475 optparse.make_option("--use-test-shell", action="store_true",
1477 help="Use test_shell instead of DRT"),
1478 optparse.make_option("--accelerated-compositing",
1479 action="store_true",
1480 help="Use hardware-accelated compositing for rendering"),
1481 optparse.make_option("--no-accelerated-compositing",
1482 action="store_false",
1483 dest="accelerated_compositing",
1484 help="Don't use hardware-accelerated compositing for rendering"),
1485 optparse.make_option("--accelerated-2d-canvas",
1486 action="store_true",
1487 help="Use hardware-accelerated 2D Canvas calls"),
1488 optparse.make_option("--no-accelerated-2d-canvas",
1489 action="store_false",
1490 dest="accelerated_2d_canvas",
1491 help="Don't use hardware-accelerated 2D Canvas calls"),
1494 # Missing Mac-specific old-run-webkit-tests options:
1495 # FIXME: Need: -g, --guard for guard malloc support on Mac.
1496 # FIXME: Need: -l --leaks Enable leaks checking.
1497 # FIXME: Need: --sample-on-timeout Run sample on timeout
1499 old_run_webkit_tests_compat = [
1500 # NRWT doesn't generate results by default anyway.
1501 _compat_shim_option("--no-new-test-results"),
1502 # NRWT doesn't sample on timeout yet anyway.
1503 _compat_shim_option("--no-sample-on-timeout"),
1504 # FIXME: NRWT needs to support remote links eventually.
1505 _compat_shim_option("--use-remote-links-to-tests"),
1509 # NEED for bots: --use-remote-links-to-tests Link to test files
1510 # within the SVN repository in the results.
1511 optparse.make_option("-p", "--pixel-tests", action="store_true",
1512 dest="pixel_tests", help="Enable pixel-to-pixel PNG comparisons"),
1513 optparse.make_option("--no-pixel-tests", action="store_false",
1514 dest="pixel_tests", help="Disable pixel-to-pixel PNG comparisons"),
1515 optparse.make_option("--tolerance",
1516 help="Ignore image differences less than this percentage (some "
1517 "ports may ignore this option)", type="float"),
1518 optparse.make_option("--results-directory",
1519 default="layout-test-results",
1520 help="Output results directory source dir, relative to Debug or "
1522 optparse.make_option("--new-baseline", action="store_true",
1523 default=False, help="Save all generated results as new baselines "
1524 "into the platform directory, overwriting whatever's "
1526 optparse.make_option("--reset-results", action="store_true",
1527 default=False, help="Reset any existing baselines to the "
1528 "generated results"),
1529 optparse.make_option("--no-show-results", action="store_false",
1530 default=True, dest="show_results",
1531 help="Don't launch a browser with results after the tests "
1533 # FIXME: We should have a helper function to do this sort of
1534 # deprectated mapping and automatically log, etc.
1535 optparse.make_option("--noshow-results", action="store_false",
1536 dest="show_results",
1537 help="Deprecated, same as --no-show-results."),
1538 optparse.make_option("--no-launch-safari", action="store_false",
1539 dest="show_results",
1540 help="old-run-webkit-tests compat, same as --noshow-results."),
1541 # old-run-webkit-tests:
1542 # --[no-]launch-safari Launch (or do not launch) Safari to display
1543 # test results (default: launch)
1544 optparse.make_option("--full-results-html", action="store_true",
1546 help="Show all failures in results.html, rather than only "
1548 optparse.make_option("--clobber-old-results", action="store_true",
1549 default=False, help="Clobbers test results from previous runs."),
1550 optparse.make_option("--platform",
1551 help="Override the platform for expected results"),
1552 optparse.make_option("--no-record-results", action="store_false",
1553 default=True, dest="record_results",
1554 help="Don't record the results."),
1555 # old-run-webkit-tests also has HTTP toggle options:
1556 # --[no-]http Run (or do not run) http tests
1561 optparse.make_option("--build", dest="build",
1562 action="store_true", default=True,
1563 help="Check to ensure the DumpRenderTree build is up-to-date "
1565 optparse.make_option("--no-build", dest="build",
1566 action="store_false", help="Don't check to see if the "
1567 "DumpRenderTree build is up-to-date."),
1568 optparse.make_option("-n", "--dry-run", action="store_true",
1570 help="Do everything but actually run the tests or upload results."),
1571 # old-run-webkit-tests has --valgrind instead of wrapper.
1572 optparse.make_option("--wrapper",
1573 help="wrapper command to insert before invocations of "
1574 "DumpRenderTree; option is split on whitespace before "
1575 "running. (Example: --wrapper='valgrind --smc-check=all')"),
1576 # old-run-webkit-tests:
1577 # -i|--ignore-tests Comma-separated list of directories
1578 # or tests to ignore
1579 optparse.make_option("--test-list", action="append",
1580 help="read list of tests to run from file", metavar="FILE"),
1581 # old-run-webkit-tests uses --skipped==[default|ignore|only]
1582 # instead of --force:
1583 optparse.make_option("--force", action="store_true", default=False,
1584 help="Run all tests, even those marked SKIP in the test list"),
1585 optparse.make_option("--use-apache", action="store_true",
1586 default=False, help="Whether to use apache instead of lighttpd."),
1587 optparse.make_option("--time-out-ms",
1588 help="Set the timeout for each test"),
1589 # old-run-webkit-tests calls --randomize-order --random:
1590 optparse.make_option("--randomize-order", action="store_true",
1591 default=False, help=("Run tests in random order (useful "
1592 "for tracking down corruption)")),
1593 optparse.make_option("--run-chunk",
1594 help=("Run a specified chunk (n:l), the nth of len l, "
1595 "of the layout tests")),
1596 optparse.make_option("--run-part", help=("Run a specified part (n:m), "
1597 "the nth of m parts, of the layout tests")),
1598 # old-run-webkit-tests calls --batch-size: --nthly n
1599 # Restart DumpRenderTree every n tests (default: 1000)
1600 optparse.make_option("--batch-size",
1601 help=("Run a the tests in batches (n), after every n tests, "
1602 "DumpRenderTree is relaunched."), type="int", default=0),
1603 # old-run-webkit-tests calls --run-singly: -1|--singly
1604 # Isolate each test case run (implies --nthly 1 --verbose)
1605 optparse.make_option("--run-singly", action="store_true",
1606 default=False, help="run a separate DumpRenderTree for each test"),
1607 optparse.make_option("--child-processes",
1608 help="Number of DumpRenderTrees to run in parallel."),
1609 # FIXME: Display default number of child processes that will run.
1610 optparse.make_option("--worker-model", action="store",
1611 default="threads", help=("controls worker model. Valid values are "
1612 "'inline' and 'threads' (default).")),
1613 optparse.make_option("--experimental-fully-parallel",
1614 action="store_true", default=False,
1615 help="run all tests in parallel"),
1616 optparse.make_option("--exit-after-n-failures", type="int", nargs=1,
1617 help="Exit after the first N failures instead of running all "
1619 optparse.make_option("--exit-after-n-crashes-or-timeouts", type="int",
1620 nargs=1, help="Exit after the first N crashes instead of running "
1622 # FIXME: consider: --iterations n
1623 # Number of times to run the set of tests (e.g. ABCABCABC)
1624 optparse.make_option("--print-last-failures", action="store_true",
1625 default=False, help="Print the tests in the last run that "
1626 "had unexpected failures (or passes) and then exit."),
1627 optparse.make_option("--retest-last-failures", action="store_true",
1628 default=False, help="re-test the tests in the last run that "
1629 "had unexpected failures (or passes)."),
1630 optparse.make_option("--retry-failures", action="store_true",
1632 help="Re-try any tests that produce unexpected results (default)"),
1633 optparse.make_option("--no-retry-failures", action="store_false",
1634 dest="retry_failures",
1635 help="Don't re-try any tests that produce unexpected results."),
1639 optparse.make_option("--lint-test-files", action="store_true",
1640 default=False, help=("Makes sure the test files parse for all "
1641 "configurations. Does not run any tests.")),
1644 # FIXME: Move these into json_results_generator.py
1645 results_json_options = [
1646 optparse.make_option("--master-name", help="The name of the buildbot master."),
1647 optparse.make_option("--builder-name", default="DUMMY_BUILDER_NAME",
1648 help=("The name of the builder shown on the waterfall running "
1649 "this script e.g. WebKit.")),
1650 optparse.make_option("--build-name", default="DUMMY_BUILD_NAME",
1651 help=("The name of the builder used in its path, e.g. "
1653 optparse.make_option("--build-number", default="DUMMY_BUILD_NUMBER",
1654 help=("The build number of the builder running this script.")),
1655 optparse.make_option("--test-results-server", default="",
1656 help=("If specified, upload results json files to this appengine "
1658 optparse.make_option("--upload-full-results",
1659 action="store_true",
1661 help="If true, upload full json results to server."),
1664 option_list = (configuration_options + print_options +
1665 chromium_options + results_options + test_options +
1666 misc_options + results_json_options +
1667 old_run_webkit_tests_compat)
1668 option_parser = optparse.OptionParser(option_list=option_list)
1670 return option_parser.parse_args(args)
1674 options, args = parse_args()
1675 port_obj = port.get(options.platform, options)
1676 return run(port_obj, options, args)
1679 if '__main__' == __name__:
1682 except KeyboardInterrupt:
1683 # this mirrors what the shell normally does
1684 sys.exit(signal.SIGINT + 128)