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
81 _log = logging.getLogger("webkitpy.layout_tests.run_webkit_tests")
83 # Builder base URL where we have the archived test results.
84 BUILDER_BASE_URL = "http://build.chromium.org/buildbot/layout_test_results/"
86 LAYOUT_TESTS_DIRECTORY = "LayoutTests" + os.sep
88 TestExpectationsFile = test_expectations.TestExpectationsFile
92 """Groups information about a test for easy passing of data."""
94 def __init__(self, filename, timeout):
95 """Holds the input parameters for a test.
97 filename: Full path to the test.
98 timeout: Timeout in msecs the driver should use while running the test
100 # FIXME: filename should really be test_name as a relative path.
101 self.filename = filename
102 self.timeout = timeout
103 # The image_hash is used to avoid doing an image dump if the
104 # checksums match. The image_hash is set later, and only if it is needed
106 self.image_hash = None
109 class ResultSummary(object):
110 """A class for partitioning the test results we get into buckets.
112 This class is basically a glorified struct and it's private to this file
113 so we don't bother with any information hiding."""
115 def __init__(self, expectations, test_files):
116 self.total = len(test_files)
117 self.remaining = self.total
118 self.expectations = expectations
121 self.tests_by_expectation = {}
122 self.tests_by_timeline = {}
124 self.unexpected_results = {}
126 self.tests_by_expectation[test_expectations.SKIP] = set()
127 for expectation in TestExpectationsFile.EXPECTATIONS.values():
128 self.tests_by_expectation[expectation] = set()
129 for timeline in TestExpectationsFile.TIMELINES.values():
130 self.tests_by_timeline[timeline] = (
131 expectations.get_tests_with_timeline(timeline))
133 def add(self, result, expected):
134 """Add a TestResult into the appropriate bin.
137 result: TestResult from dump_render_tree_thread.
138 expected: whether the result was what we expected it to be.
141 self.tests_by_expectation[result.type].add(result.filename)
142 self.results[result.filename] = result
144 if len(result.failures):
145 self.failures[result.filename] = result.failures
149 self.unexpected_results[result.filename] = result.type
153 def summarize_unexpected_results(port_obj, expectations, result_summary,
155 """Summarize any unexpected results as a dict.
157 FIXME: split this data structure into a separate class?
160 port_obj: interface to port-specific hooks
161 expectations: test_expectations.TestExpectations object
162 result_summary: summary object from initial test runs
163 retry_summary: summary object from final test run of retried tests
165 A dictionary containing a summary of the unexpected results from the
166 run, with the following fields:
167 'version': a version indicator (1 in this version)
168 'fixable': # of fixable tests (NOW - PASS)
169 'skipped': # of skipped tests (NOW & SKIPPED)
170 'num_regressions': # of non-flaky failures
171 'num_flaky': # of flaky failures
172 'num_passes': # of unexpected passes
173 'tests': a dict of tests -> {'expected': '...', 'actual': '...'}
176 results['version'] = 1
178 tbe = result_summary.tests_by_expectation
179 tbt = result_summary.tests_by_timeline
180 results['fixable'] = len(tbt[test_expectations.NOW] -
181 tbe[test_expectations.PASS])
182 results['skipped'] = len(tbt[test_expectations.NOW] &
183 tbe[test_expectations.SKIP])
189 for k, v in TestExpectationsFile.EXPECTATIONS.iteritems():
190 keywords[v] = k.upper()
193 for filename, result in result_summary.unexpected_results.iteritems():
194 # Note that if a test crashed in the original run, we ignore
195 # whether or not it crashed when we retried it (if we retried it),
196 # and always consider the result not flaky.
197 test = port_obj.relative_test_filename(filename)
198 expected = expectations.get_expectations_string(filename)
199 actual = [keywords[result]]
201 if result == test_expectations.PASS:
203 elif result == test_expectations.CRASH:
206 if filename not in retry_summary.unexpected_results:
207 actual.extend(expectations.get_expectations_string(
208 filename).split(" "))
211 retry_result = retry_summary.unexpected_results[filename]
212 if result != retry_result:
213 actual.append(keywords[retry_result])
219 tests[test]['expected'] = expected
220 tests[test]['actual'] = " ".join(actual)
222 results['tests'] = tests
223 results['num_passes'] = num_passes
224 results['num_flaky'] = num_flaky
225 results['num_regressions'] = num_regressions
231 """A class for managing running a series of tests on a series of layout
234 HTTP_SUBDIR = os.sep.join(['', 'http', ''])
235 WEBSOCKET_SUBDIR = os.sep.join(['', 'websocket', ''])
237 # The per-test timeout in milliseconds, if no --time-out-ms option was
238 # given to run_webkit_tests. This should correspond to the default timeout
240 DEFAULT_TEST_TIMEOUT_MS = 6 * 1000
242 def __init__(self, port, options, printer):
243 """Initialize test runner data structures.
246 port: an object implementing port-specific
247 options: a dictionary of command line options
248 printer: a Printer object to record updates to.
251 self._options = options
252 self._printer = printer
254 # disable wss server. need to install pyOpenSSL on buildbots.
255 # self._websocket_secure_server = websocket_server.PyWebSocket(
256 # options.results_directory, use_tls=True, port=9323)
258 # a set of test files, and the same tests as a list
259 self._test_files = set()
260 self._test_files_list = None
261 self._result_queue = Queue.Queue()
262 self._retrying = False
264 def collect_tests(self, args, last_unexpected_results):
265 """Find all the files to test.
268 args: list of test arguments from the command line
269 last_unexpected_results: list of unexpected results to retest, if any
272 paths = [self._strip_test_dir_prefix(arg) for arg in args if arg and arg != '']
273 paths += last_unexpected_results
274 if self._options.test_list:
275 paths += read_test_files(self._options.test_list)
276 self._test_files = self._port.tests(paths)
278 def _strip_test_dir_prefix(self, path):
279 if path.startswith(LAYOUT_TESTS_DIRECTORY):
280 return path[len(LAYOUT_TESTS_DIRECTORY):]
284 # Creating the expecations for each platform/configuration pair does
285 # all the test list parsing and ensures it's correct syntax (e.g. no
287 for platform_name in self._port.test_platform_names():
288 self.parse_expectations(platform_name, is_debug_mode=True)
289 self.parse_expectations(platform_name, is_debug_mode=False)
290 self._printer.write("")
291 _log.info("If there are no fail messages, errors or exceptions, "
292 "then the lint succeeded.")
295 def parse_expectations(self, test_platform_name, is_debug_mode):
296 """Parse the expectations from the test_list files and return a data
297 structure holding them. Throws an error if the test_list files have
299 if self._options.lint_test_files:
302 test_files = self._test_files
305 expectations_str = self._port.test_expectations()
306 overrides_str = self._port.test_expectations_overrides()
307 self._expectations = test_expectations.TestExpectations(
308 self._port, test_files, expectations_str, test_platform_name,
309 is_debug_mode, self._options.lint_test_files,
310 overrides=overrides_str)
311 return self._expectations
312 except SyntaxError, err:
313 if self._options.lint_test_files:
318 def prepare_lists_and_print_output(self):
319 """Create appropriate subsets of test lists and returns a
320 ResultSummary object. Also prints expected test counts.
323 # Remove skipped - both fixable and ignored - files from the
324 # top-level list of files to test.
325 num_all_test_files = len(self._test_files)
326 self._printer.print_expected("Found: %d tests" %
327 (len(self._test_files)))
328 if not num_all_test_files:
329 _log.critical('No tests to run.')
333 if num_all_test_files > 1 and not self._options.force:
334 skipped = self._expectations.get_tests_with_result_type(
335 test_expectations.SKIP)
336 self._test_files -= skipped
338 # Create a sorted list of test files so the subset chunk,
339 # if used, contains alphabetically consecutive tests.
340 self._test_files_list = list(self._test_files)
341 if self._options.randomize_order:
342 random.shuffle(self._test_files_list)
344 self._test_files_list.sort()
346 # If the user specifies they just want to run a subset of the tests,
347 # just grab a subset of the non-skipped tests.
348 if self._options.run_chunk or self._options.run_part:
349 chunk_value = self._options.run_chunk or self._options.run_part
350 test_files = self._test_files_list
352 (chunk_num, chunk_len) = chunk_value.split(":")
353 chunk_num = int(chunk_num)
354 assert(chunk_num >= 0)
355 test_size = int(chunk_len)
356 assert(test_size > 0)
358 _log.critical("invalid chunk '%s'" % chunk_value)
361 # Get the number of tests
362 num_tests = len(test_files)
364 # Get the start offset of the slice.
365 if self._options.run_chunk:
366 chunk_len = test_size
367 # In this case chunk_num can be really large. We need
368 # to make the slave fit in the current number of tests.
369 slice_start = (chunk_num * chunk_len) % num_tests
372 assert(test_size <= num_tests)
373 assert(chunk_num <= test_size)
375 # To count the chunk_len, and make sure we don't skip
376 # some tests, we round to the next value that fits exactly
378 rounded_tests = num_tests
379 if rounded_tests % test_size != 0:
380 rounded_tests = (num_tests + test_size -
381 (num_tests % test_size))
383 chunk_len = rounded_tests / test_size
384 slice_start = chunk_len * (chunk_num - 1)
385 # It does not mind if we go over test_size.
387 # Get the end offset of the slice.
388 slice_end = min(num_tests, slice_start + chunk_len)
390 files = test_files[slice_start:slice_end]
392 tests_run_msg = 'Running: %d tests (chunk slice [%d:%d] of %d)' % (
393 (slice_end - slice_start), slice_start, slice_end, num_tests)
394 self._printer.print_expected(tests_run_msg)
396 # If we reached the end and we don't have enough tests, we run some
397 # from the beginning.
398 if slice_end - slice_start < chunk_len:
399 extra = chunk_len - (slice_end - slice_start)
400 extra_msg = (' last chunk is partial, appending [0:%d]' %
402 self._printer.print_expected(extra_msg)
403 tests_run_msg += "\n" + extra_msg
404 files.extend(test_files[0:extra])
405 tests_run_filename = os.path.join(self._options.results_directory,
407 with codecs.open(tests_run_filename, "w", "utf-8") as file:
408 file.write(tests_run_msg + "\n")
410 len_skip_chunk = int(len(files) * len(skipped) /
411 float(len(self._test_files)))
412 skip_chunk_list = list(skipped)[0:len_skip_chunk]
413 skip_chunk = set(skip_chunk_list)
415 # Update expectations so that the stats are calculated correctly.
416 # We need to pass a list that includes the right # of skipped files
417 # to ParseExpectations so that ResultSummary() will get the correct
418 # stats. So, we add in the subset of skipped files, and then
419 # subtract them back out.
420 self._test_files_list = files + skip_chunk_list
421 self._test_files = set(self._test_files_list)
423 self._expectations = self.parse_expectations(
424 self._port.test_platform_name(),
425 self._options.configuration == 'Debug')
427 self._test_files = set(files)
428 self._test_files_list = files
432 result_summary = ResultSummary(self._expectations,
433 self._test_files | skip_chunk)
434 self._print_expected_results_of_type(result_summary,
435 test_expectations.PASS, "passes")
436 self._print_expected_results_of_type(result_summary,
437 test_expectations.FAIL, "failures")
438 self._print_expected_results_of_type(result_summary,
439 test_expectations.FLAKY, "flaky")
440 self._print_expected_results_of_type(result_summary,
441 test_expectations.SKIP, "skipped")
443 if self._options.force:
444 self._printer.print_expected('Running all tests, including '
447 # Note that we don't actually run the skipped tests (they were
448 # subtracted out of self._test_files, above), but we stub out the
449 # results here so the statistics can remain accurate.
450 for test in skip_chunk:
451 result = test_results.TestResult(test,
452 failures=[], test_run_time=0, total_time_for_all_diffs=0,
454 result.type = test_expectations.SKIP
455 result_summary.add(result, expected=True)
456 self._printer.print_expected('')
458 return result_summary
460 def _get_dir_for_test_file(self, test_file):
461 """Returns the highest-level directory by which to shard the given
463 index = test_file.rfind(os.sep + LAYOUT_TESTS_DIRECTORY)
465 test_file = test_file[index + len(LAYOUT_TESTS_DIRECTORY):]
466 test_file_parts = test_file.split(os.sep, 1)
467 directory = test_file_parts[0]
468 test_file = test_file_parts[1]
470 # The http tests are very stable on mac/linux.
471 # TODO(ojan): Make the http server on Windows be apache so we can
472 # turn shard the http tests there as well. Switching to apache is
473 # what made them stable on linux/mac.
474 return_value = directory
475 while ((directory != 'http' or sys.platform in ('darwin', 'linux2'))
476 and test_file.find(os.sep) >= 0):
477 test_file_parts = test_file.split(os.sep, 1)
478 directory = test_file_parts[0]
479 return_value = os.path.join(return_value, directory)
480 test_file = test_file_parts[1]
484 def _get_test_input_for_file(self, test_file):
485 """Returns the appropriate TestInput object for the file. Mostly this
486 is used for looking up the timeout value (in ms) to use for the given
488 if self._test_is_slow(test_file):
489 return TestInput(test_file, self._options.slow_time_out_ms)
490 return TestInput(test_file, self._options.time_out_ms)
492 def _test_requires_lock(self, test_file):
493 """Return True if the test needs to be locked when
494 running multiple copies of NRWTs."""
495 split_path = test_file.split(os.sep)
496 return 'http' in split_path or 'websocket' in split_path
498 def _test_is_slow(self, test_file):
499 return self._expectations.has_modifier(test_file,
500 test_expectations.SLOW)
502 def _shard_tests(self, test_files, use_real_shards):
503 """Groups tests into batches.
504 This helps ensure that tests that depend on each other (aka bad tests!)
505 continue to run together as most cross-tests dependencies tend to
506 occur within the same directory. If use_real_shards is false, we
507 put each (non-HTTP/websocket) test into its own shard for maximum
508 concurrency instead of trying to do any sort of real sharding.
511 A list of lists of TestInput objects.
513 # FIXME: when we added http locking, we changed how this works such
514 # that we always lump all of the HTTP threads into a single shard.
515 # That will slow down experimental-fully-parallel, but it's unclear
516 # what the best alternative is completely revamping how we track
517 # when to grab the lock.
520 tests_to_http_lock = []
521 if not use_real_shards:
522 for test_file in test_files:
523 test_input = self._get_test_input_for_file(test_file)
524 if self._test_requires_lock(test_file):
525 tests_to_http_lock.append(test_input)
527 test_lists.append((".", [test_input]))
530 for test_file in test_files:
531 directory = self._get_dir_for_test_file(test_file)
532 test_input = self._get_test_input_for_file(test_file)
533 if self._test_requires_lock(test_file):
534 tests_to_http_lock.append(test_input)
536 tests_by_dir.setdefault(directory, [])
537 tests_by_dir[directory].append(test_input)
538 # Sort by the number of tests in the dir so that the ones with the
539 # most tests get run first in order to maximize parallelization.
540 # Number of tests is a good enough, but not perfect, approximation
541 # of how long that set of tests will take to run. We can't just use
542 # a PriorityQueue until we move to Python 2.6.
543 for directory in tests_by_dir:
544 test_list = tests_by_dir[directory]
545 # Keep the tests in alphabetical order.
546 # FIXME: Remove once tests are fixed so they can be run in any
549 test_list_tuple = (directory, test_list)
550 test_lists.append(test_list_tuple)
551 test_lists.sort(lambda a, b: cmp(len(b[1]), len(a[1])))
553 # Put the http tests first. There are only a couple hundred of them,
554 # but each http test takes a very long time to run, so sorting by the
555 # number of tests doesn't accurately capture how long they take to run.
556 if tests_to_http_lock:
557 tests_to_http_lock.reverse()
558 test_lists.insert(0, ("tests_to_http_lock", tests_to_http_lock))
562 def _contains_tests(self, subdir):
563 for test_file in self._test_files:
564 if test_file.find(subdir) >= 0:
568 def _instantiate_dump_render_tree_threads(self, test_files,
570 """Instantitates and starts the TestShellThread(s).
575 num_workers = self._num_workers()
576 test_lists = self._shard_tests(test_files,
577 num_workers > 1 and not self._options.experimental_fully_parallel)
578 filename_queue = Queue.Queue()
579 for item in test_lists:
580 filename_queue.put(item)
582 # Instantiate TestShellThreads and start them.
584 for worker_number in xrange(num_workers):
585 thread = dump_render_tree_thread.TestShellThread(self._port,
586 self._options, worker_number,
587 filename_queue, self._result_queue)
591 thread.run_in_main_thread(self, result_summary)
592 threads.append(thread)
596 def _num_workers(self):
597 return int(self._options.child_processes)
599 def _run_tests(self, file_list, result_summary):
600 """Runs the tests in the file_list.
602 Return: A tuple (keyboard_interrupted, thread_timings, test_timings,
603 individual_test_timings)
604 keyboard_interrupted is whether someone typed Ctrl^C
605 thread_timings is a list of dicts with the total runtime
606 of each thread with 'name', 'num_tests', 'total_time' properties
607 test_timings is a list of timings for each sharded subdirectory
608 of the form [time, directory_name, num_tests]
609 individual_test_timings is a list of run times for each test
610 in the form {filename:filename, test_run_time:test_run_time}
611 result_summary: summary object to populate with the results
614 if self._num_workers() > 1:
616 self._printer.print_update('Starting %s%s ...' %
617 (self._port.driver_name(), plural))
618 threads = self._instantiate_dump_render_tree_threads(file_list,
620 self._printer.print_update("Starting testing ...")
622 keyboard_interrupted = self._wait_for_threads_to_finish(threads,
624 (thread_timings, test_timings, individual_test_timings) = \
625 self._collect_timing_info(threads)
627 return (keyboard_interrupted, thread_timings, test_timings,
628 individual_test_timings)
630 def _wait_for_threads_to_finish(self, threads, result_summary):
631 keyboard_interrupted = False
633 # Loop through all the threads waiting for them to finish.
634 some_thread_is_alive = True
635 while some_thread_is_alive:
636 some_thread_is_alive = False
638 for thread in threads:
639 exception_info = thread.exception_info()
640 if exception_info is not None:
641 # Re-raise the thread's exception here to make it
642 # clear that testing was aborted. Otherwise,
643 # the tests that did not run would be assumed
645 raise exception_info[0], exception_info[1], exception_info[2]
648 some_thread_is_alive = True
649 next_timeout = thread.next_timeout()
650 if (next_timeout and t > next_timeout):
651 message_broker.log_wedged_thread(thread.id())
652 thread.clear_next_timeout()
654 self.update_summary(result_summary)
656 if some_thread_is_alive:
659 except KeyboardInterrupt:
660 keyboard_interrupted = True
661 for thread in threads:
664 return keyboard_interrupted
666 def _collect_timing_info(self, threads):
668 individual_test_timings = []
671 for thread in threads:
672 thread_timings.append({'name': thread.getName(),
673 'num_tests': thread.get_num_tests(),
674 'total_time': thread.get_total_time()})
675 test_timings.update(thread.get_test_group_timing_stats())
676 individual_test_timings.extend(thread.get_test_results())
678 return (thread_timings, test_timings, individual_test_timings)
680 def needs_http(self):
681 """Returns whether the test runner needs an HTTP server."""
682 return self._contains_tests(self.HTTP_SUBDIR)
684 def needs_websocket(self):
685 """Returns whether the test runner needs a WEBSOCKET server."""
686 return self._contains_tests(self.WEBSOCKET_SUBDIR)
688 def set_up_run(self):
689 """Configures the system to be ready to run tests.
691 Returns a ResultSummary object if we should continue to run tests,
692 or None if we should abort.
695 # This must be started before we check the system dependencies,
696 # since the helper may do things to make the setup correct.
697 self._printer.print_update("Starting helper ...")
698 self._port.start_helper()
700 # Check that the system dependencies (themes, fonts, ...) are correct.
701 if not self._options.nocheck_sys_deps:
702 self._printer.print_update("Checking system dependencies ...")
703 if not self._port.check_sys_deps(self.needs_http()):
704 self._port.stop_helper()
707 if self._options.clobber_old_results:
708 self._clobber_old_results()
710 # Create the output directory if it doesn't already exist.
711 self._port.maybe_make_directory(self._options.results_directory)
713 self._port.setup_test_run()
715 self._printer.print_update("Preparing tests ...")
716 result_summary = self.prepare_lists_and_print_output()
717 if not result_summary:
720 return result_summary
722 def run(self, result_summary):
723 """Run all our tests on all our test files.
725 For each test file, we run each test type. If there are any failures,
726 we collect them for reporting.
729 result_summary: a summary object tracking the test results.
732 The number of unexpected results (0 == success)
734 # gather_test_files() must have been called first to initialize us.
735 # If we didn't find any files to test, we've errored out already in
736 # prepare_lists_and_print_output().
737 assert(len(self._test_files))
739 start_time = time.time()
741 keyboard_interrupted, thread_timings, test_timings, \
742 individual_test_timings = (
743 self._run_tests(self._test_files_list, result_summary))
745 # We exclude the crashes from the list of results to retry, because
746 # we want to treat even a potentially flaky crash as an error.
747 failures = self._get_failures(result_summary, include_crashes=False)
748 retry_summary = result_summary
749 while (len(failures) and self._options.retry_failures and
750 not self._retrying and not keyboard_interrupted):
752 _log.info("Retrying %d unexpected failure(s) ..." % len(failures))
754 self._retrying = True
755 retry_summary = ResultSummary(self._expectations, failures.keys())
756 # Note that we intentionally ignore the return value here.
757 self._run_tests(failures.keys(), retry_summary)
758 failures = self._get_failures(retry_summary, include_crashes=True)
760 end_time = time.time()
762 self._print_timing_statistics(end_time - start_time,
763 thread_timings, test_timings,
764 individual_test_timings,
767 self._print_result_summary(result_summary)
772 self._printer.print_one_line_summary(result_summary.total,
773 result_summary.expected,
774 result_summary.unexpected)
776 unexpected_results = summarize_unexpected_results(self._port,
777 self._expectations, result_summary, retry_summary)
778 self._printer.print_unexpected_results(unexpected_results)
780 if self._options.record_results:
781 # Write the same data to log files and upload generated JSON files
782 # to appengine server.
783 self._upload_json_files(unexpected_results, result_summary,
784 individual_test_timings)
786 # Write the summary to disk (results.html) and display it if requested.
787 wrote_results = self._write_results_html_file(result_summary)
788 if self._options.show_results and wrote_results:
789 self._show_results_html_file()
791 # Now that we've completed all the processing we can, we re-raise
792 # a KeyboardInterrupt if necessary so the caller can handle it.
793 if keyboard_interrupted:
794 raise KeyboardInterrupt
796 # Ignore flaky failures and unexpected passes so we don't turn the
798 return unexpected_results['num_regressions']
800 def clean_up_run(self):
801 """Restores the system after we're done running tests."""
803 _log.debug("flushing stdout")
805 _log.debug("flushing stderr")
807 _log.debug("stopping helper")
808 self._port.stop_helper()
810 def update_summary(self, result_summary):
811 """Update the summary and print results with any completed tests."""
814 result = test_results.TestResult.loads(self._result_queue.get_nowait())
818 expected = self._expectations.matches_an_expected_result(
819 result.filename, result.type, self._options.pixel_tests)
820 result_summary.add(result, expected)
821 exp_str = self._expectations.get_expectations_string(
823 got_str = self._expectations.expectation_to_string(result.type)
824 self._printer.print_test_result(result, expected, exp_str, got_str)
825 self._printer.print_progress(result_summary, self._retrying,
826 self._test_files_list)
828 def _clobber_old_results(self):
829 # Just clobber the actual test results directories since the other
830 # files in the results directory are explicitly used for cross-run
832 self._printer.print_update("Clobbering old results in %s" %
833 self._options.results_directory)
834 layout_tests_dir = self._port.layout_tests_dir()
835 possible_dirs = self._port.test_dirs()
836 for dirname in possible_dirs:
837 if os.path.isdir(os.path.join(layout_tests_dir, dirname)):
838 shutil.rmtree(os.path.join(self._options.results_directory,
842 def _get_failures(self, result_summary, include_crashes):
843 """Filters a dict of results and returns only the failures.
846 result_summary: the results of the test run
847 include_crashes: whether crashes are included in the output.
848 We use False when finding the list of failures to retry
849 to see if the results were flaky. Although the crashes may also be
850 flaky, we treat them as if they aren't so that they're not ignored.
852 a dict of files -> results
855 for test, result in result_summary.unexpected_results.iteritems():
856 if (result == test_expectations.PASS or
857 result == test_expectations.CRASH and not include_crashes):
859 failed_results[test] = result
861 return failed_results
863 def _upload_json_files(self, unexpected_results, result_summary,
864 individual_test_timings):
865 """Writes the results of the test run as JSON files into the results
866 dir and upload the files to the appengine server.
868 There are three different files written into the results dir:
869 unexpected_results.json: A short list of any unexpected results.
870 This is used by the buildbots to display results.
871 expectations.json: This is used by the flakiness dashboard.
872 results.json: A full list of the results - used by the flakiness
873 dashboard and the aggregate results dashboard.
876 unexpected_results: dict of unexpected results
877 result_summary: full summary object
878 individual_test_timings: list of test times (used by the flakiness
881 results_directory = self._options.results_directory
882 _log.debug("Writing JSON files in %s." % results_directory)
883 unexpected_json_path = os.path.join(results_directory, "unexpected_results.json")
884 with codecs.open(unexpected_json_path, "w", "utf-8") as file:
885 simplejson.dump(unexpected_results, file, sort_keys=True, indent=2)
887 # Write a json file of the test_expectations.txt file for the layout
889 expectations_path = os.path.join(results_directory, "expectations.json")
890 expectations_json = \
891 self._expectations.get_expectations_json_for_all_platforms()
892 with codecs.open(expectations_path, "w", "utf-8") as file:
893 file.write(u"ADD_EXPECTATIONS(%s);" % expectations_json)
895 generator = json_layout_results_generator.JSONLayoutResultsGenerator(
896 self._port, self._options.builder_name, self._options.build_name,
897 self._options.build_number, self._options.results_directory,
898 BUILDER_BASE_URL, individual_test_timings,
899 self._expectations, result_summary, self._test_files_list,
900 not self._options.upload_full_results,
901 self._options.test_results_server,
903 self._options.master_name)
905 _log.debug("Finished writing JSON files.")
907 json_files = ["expectations.json"]
908 if self._options.upload_full_results:
909 json_files.append("results.json")
911 json_files.append("incremental_results.json")
913 generator.upload_json_files(json_files)
915 def _print_config(self):
916 """Prints the configuration for the test run."""
918 p.print_config("Using port '%s'" % self._port.name())
919 p.print_config("Placing test results in %s" %
920 self._options.results_directory)
921 if self._options.new_baseline:
922 p.print_config("Placing new baselines in %s" %
923 self._port.baseline_path())
924 p.print_config("Using %s build" % self._options.configuration)
925 if self._options.pixel_tests:
926 p.print_config("Pixel tests enabled")
928 p.print_config("Pixel tests disabled")
930 p.print_config("Regular timeout: %s, slow test timeout: %s" %
931 (self._options.time_out_ms,
932 self._options.slow_time_out_ms))
934 if self._num_workers() == 1:
935 p.print_config("Running one %s" % self._port.driver_name())
937 p.print_config("Running %s %ss in parallel" %
938 (self._options.child_processes,
939 self._port.driver_name()))
940 p.print_config("Worker model: %s" % self._options.worker_model)
943 def _print_expected_results_of_type(self, result_summary,
944 result_type, result_type_str):
945 """Print the number of the tests in a given result class.
948 result_summary - the object containing all the results to report on
949 result_type - the particular result type to report in the summary.
950 result_type_str - a string description of the result_type.
952 tests = self._expectations.get_tests_with_result_type(result_type)
953 now = result_summary.tests_by_timeline[test_expectations.NOW]
954 wontfix = result_summary.tests_by_timeline[test_expectations.WONTFIX]
956 # We use a fancy format string in order to print the data out in a
957 # nicely-aligned table.
958 fmtstr = ("Expect: %%5d %%-8s (%%%dd now, %%%dd wontfix)"
959 % (self._num_digits(now), self._num_digits(wontfix)))
960 self._printer.print_expected(fmtstr %
961 (len(tests), result_type_str, len(tests & now), len(tests & wontfix)))
963 def _num_digits(self, num):
964 """Returns the number of digits needed to represent the length of a
968 ndigits = int(math.log10(len(num))) + 1
971 def _print_timing_statistics(self, total_time, thread_timings,
972 directory_test_timings, individual_test_timings,
974 """Record timing-specific information for the test run.
977 total_time: total elapsed time (in seconds) for the test run
978 thread_timings: wall clock time each thread ran for
979 directory_test_timings: timing by directory
980 individual_test_timings: timing by file
981 result_summary: summary object for the test run
983 self._printer.print_timing("Test timing:")
984 self._printer.print_timing(" %6.2f total testing time" % total_time)
985 self._printer.print_timing("")
986 self._printer.print_timing("Thread timing:")
988 for t in thread_timings:
989 self._printer.print_timing(" %10s: %5d tests, %6.2f secs" %
990 (t['name'], t['num_tests'], t['total_time']))
991 cuml_time += t['total_time']
992 self._printer.print_timing(" %6.2f cumulative, %6.2f optimal" %
993 (cuml_time, cuml_time / int(self._options.child_processes)))
994 self._printer.print_timing("")
996 self._print_aggregate_test_statistics(individual_test_timings)
997 self._print_individual_test_times(individual_test_timings,
999 self._print_directory_timings(directory_test_timings)
1001 def _print_aggregate_test_statistics(self, individual_test_timings):
1002 """Prints aggregate statistics (e.g. median, mean, etc.) for all tests.
1004 individual_test_timings: List of dump_render_tree_thread.TestStats
1007 test_types = [] # Unit tests don't actually produce any timings.
1008 if individual_test_timings:
1009 test_types = individual_test_timings[0].time_for_diffs.keys()
1010 times_for_dump_render_tree = []
1011 times_for_diff_processing = []
1012 times_per_test_type = {}
1013 for test_type in test_types:
1014 times_per_test_type[test_type] = []
1016 for test_stats in individual_test_timings:
1017 times_for_dump_render_tree.append(test_stats.test_run_time)
1018 times_for_diff_processing.append(
1019 test_stats.total_time_for_all_diffs)
1020 time_for_diffs = test_stats.time_for_diffs
1021 for test_type in test_types:
1022 times_per_test_type[test_type].append(
1023 time_for_diffs[test_type])
1025 self._print_statistics_for_test_timings(
1026 "PER TEST TIME IN TESTSHELL (seconds):",
1027 times_for_dump_render_tree)
1028 self._print_statistics_for_test_timings(
1029 "PER TEST DIFF PROCESSING TIMES (seconds):",
1030 times_for_diff_processing)
1031 for test_type in test_types:
1032 self._print_statistics_for_test_timings(
1033 "PER TEST TIMES BY TEST TYPE: %s" % test_type,
1034 times_per_test_type[test_type])
1036 def _print_individual_test_times(self, individual_test_timings,
1038 """Prints the run times for slow, timeout and crash tests.
1040 individual_test_timings: List of dump_render_tree_thread.TestStats
1042 result_summary: summary object for test run
1044 # Reverse-sort by the time spent in DumpRenderTree.
1045 individual_test_timings.sort(lambda a, b:
1046 cmp(b.test_run_time, a.test_run_time))
1050 timeout_or_crash_tests = []
1051 unexpected_slow_tests = []
1052 for test_tuple in individual_test_timings:
1053 filename = test_tuple.filename
1054 is_timeout_crash_or_slow = False
1055 if self._test_is_slow(filename):
1056 is_timeout_crash_or_slow = True
1057 slow_tests.append(test_tuple)
1059 if filename in result_summary.failures:
1060 result = result_summary.results[filename].type
1061 if (result == test_expectations.TIMEOUT or
1062 result == test_expectations.CRASH):
1063 is_timeout_crash_or_slow = True
1064 timeout_or_crash_tests.append(test_tuple)
1066 if (not is_timeout_crash_or_slow and
1067 num_printed < printing.NUM_SLOW_TESTS_TO_LOG):
1068 num_printed = num_printed + 1
1069 unexpected_slow_tests.append(test_tuple)
1071 self._printer.print_timing("")
1072 self._print_test_list_timing("%s slowest tests that are not "
1073 "marked as SLOW and did not timeout/crash:" %
1074 printing.NUM_SLOW_TESTS_TO_LOG, unexpected_slow_tests)
1075 self._printer.print_timing("")
1076 self._print_test_list_timing("Tests marked as SLOW:", slow_tests)
1077 self._printer.print_timing("")
1078 self._print_test_list_timing("Tests that timed out or crashed:",
1079 timeout_or_crash_tests)
1080 self._printer.print_timing("")
1082 def _print_test_list_timing(self, title, test_list):
1083 """Print timing info for each test.
1086 title: section heading
1087 test_list: tests that fall in this section
1089 if self._printer.disabled('slowest'):
1092 self._printer.print_timing(title)
1093 for test_tuple in test_list:
1094 filename = test_tuple.filename[len(
1095 self._port.layout_tests_dir()) + 1:]
1096 filename = filename.replace('\\', '/')
1097 test_run_time = round(test_tuple.test_run_time, 1)
1098 self._printer.print_timing(" %s took %s seconds" %
1099 (filename, test_run_time))
1101 def _print_directory_timings(self, directory_test_timings):
1102 """Print timing info by directory for any directories that
1103 take > 10 seconds to run.
1106 directory_test_timing: time info for each directory
1109 for directory in directory_test_timings:
1110 num_tests, time_for_directory = directory_test_timings[directory]
1111 timings.append((round(time_for_directory, 1), directory,
1115 self._printer.print_timing("Time to process slowest subdirectories:")
1116 min_seconds_to_print = 10
1117 for timing in timings:
1118 if timing[0] > min_seconds_to_print:
1119 self._printer.print_timing(
1120 " %s took %s seconds to run %s tests." % (timing[1],
1121 timing[0], timing[2]))
1122 self._printer.print_timing("")
1124 def _print_statistics_for_test_timings(self, title, timings):
1125 """Prints the median, mean and standard deviation of the values in
1129 title: Title for these timings.
1130 timings: A list of floats representing times.
1132 self._printer.print_timing(title)
1135 num_tests = len(timings)
1138 percentile90 = timings[int(.9 * num_tests)]
1139 percentile99 = timings[int(.99 * num_tests)]
1141 if num_tests % 2 == 1:
1142 median = timings[((num_tests - 1) / 2) - 1]
1144 lower = timings[num_tests / 2 - 1]
1145 upper = timings[num_tests / 2]
1146 median = (float(lower + upper)) / 2
1148 mean = sum(timings) / num_tests
1150 for time in timings:
1151 sum_of_deviations = math.pow(time - mean, 2)
1153 std_deviation = math.sqrt(sum_of_deviations / num_tests)
1154 self._printer.print_timing(" Median: %6.3f" % median)
1155 self._printer.print_timing(" Mean: %6.3f" % mean)
1156 self._printer.print_timing(" 90th percentile: %6.3f" % percentile90)
1157 self._printer.print_timing(" 99th percentile: %6.3f" % percentile99)
1158 self._printer.print_timing(" Standard dev: %6.3f" % std_deviation)
1159 self._printer.print_timing("")
1161 def _print_result_summary(self, result_summary):
1162 """Print a short summary about how many tests passed.
1165 result_summary: information to log
1167 failed = len(result_summary.failures)
1169 result_summary.tests_by_expectation[test_expectations.SKIP])
1170 total = result_summary.total
1171 passed = total - failed - skipped
1174 pct_passed = float(passed) * 100 / total
1176 self._printer.print_actual("")
1177 self._printer.print_actual("=> Results: %d/%d tests passed (%.1f%%)" %
1178 (passed, total, pct_passed))
1179 self._printer.print_actual("")
1180 self._print_result_summary_entry(result_summary,
1181 test_expectations.NOW, "Tests to be fixed")
1183 self._printer.print_actual("")
1184 self._print_result_summary_entry(result_summary,
1185 test_expectations.WONTFIX,
1186 "Tests that will only be fixed if they crash (WONTFIX)")
1187 self._printer.print_actual("")
1189 def _print_result_summary_entry(self, result_summary, timeline,
1191 """Print a summary block of results for a particular timeline of test.
1194 result_summary: summary to print results for
1195 timeline: the timeline to print results for (NOT, WONTFIX, etc.)
1196 heading: a textual description of the timeline
1198 total = len(result_summary.tests_by_timeline[timeline])
1199 not_passing = (total -
1200 len(result_summary.tests_by_expectation[test_expectations.PASS] &
1201 result_summary.tests_by_timeline[timeline]))
1202 self._printer.print_actual("=> %s (%d):" % (heading, not_passing))
1204 for result in TestExpectationsFile.EXPECTATION_ORDER:
1205 if result == test_expectations.PASS:
1207 results = (result_summary.tests_by_expectation[result] &
1208 result_summary.tests_by_timeline[timeline])
1209 desc = TestExpectationsFile.EXPECTATION_DESCRIPTIONS[result]
1210 if not_passing and len(results):
1211 pct = len(results) * 100.0 / not_passing
1212 self._printer.print_actual(" %5d %-24s (%4.1f%%)" %
1213 (len(results), desc[len(results) != 1], pct))
1215 def _results_html(self, test_files, failures, title="Test Failures", override_time=None):
1217 test_files = a list of file paths
1218 failures = dictionary mapping test paths to failure objects
1219 title = title printed at top of test
1220 override_time = current time (used by unit tests)
1224 <title>Layout Test Results (%(time)s)</title>
1227 <h2>%(title)s (%(time)s)</h2>
1228 """ % {'title': title, 'time': override_time or time.asctime()}
1230 for test_file in sorted(test_files):
1231 test_name = self._port.relative_test_filename(test_file)
1232 test_url = self._port.filename_to_uri(test_file)
1233 page += u"<p><a href='%s'>%s</a><br />\n" % (test_url, test_name)
1234 test_failures = failures.get(test_file, [])
1235 for failure in test_failures:
1236 page += (u" %s<br/>" %
1237 failure.result_html_output(test_name))
1239 page += "</body></html>\n"
1242 def _write_results_html_file(self, result_summary):
1243 """Write results.html which is a summary of tests that failed.
1246 result_summary: a summary of the results :)
1249 True if any results were written (since expected failures may be
1253 if self._options.full_results_html:
1254 results_title = "Test Failures"
1255 test_files = result_summary.failures.keys()
1257 results_title = "Unexpected Test Failures"
1258 unexpected_failures = self._get_failures(result_summary,
1259 include_crashes=True)
1260 test_files = unexpected_failures.keys()
1261 if not len(test_files):
1264 out_filename = os.path.join(self._options.results_directory,
1266 with codecs.open(out_filename, "w", "utf-8") as results_file:
1267 html = self._results_html(test_files, result_summary.failures, results_title)
1268 results_file.write(html)
1272 def _show_results_html_file(self):
1273 """Shows the results.html page."""
1274 results_filename = os.path.join(self._options.results_directory,
1276 self._port.show_results_html_file(results_filename)
1279 def read_test_files(files):
1283 with codecs.open(file, 'r', 'utf-8') as file_contents:
1284 # FIXME: This could be cleaner using a list comprehension.
1285 for line in file_contents:
1286 line = test_expectations.strip_comments(line)
1290 if e.errno == errno.ENOENT:
1292 _log.critical('--test-list file "%s" not found' % file)
1297 def run(port, options, args, regular_output=sys.stderr,
1298 buildbot_output=sys.stdout):
1302 port: Port object for port-specific behavior
1303 options: a dictionary of command line options
1304 args: a list of sub directories or files to test
1305 regular_output: a stream-like object that we can send logging/debug
1307 buildbot_output: a stream-like object that we can write all output that
1308 is intended to be parsed by the buildbot to
1310 the number of unexpected results that occurred, or -1 if there is an
1314 _set_up_derived_options(port, options)
1316 printer = printing.Printer(port, options, regular_output, buildbot_output,
1317 int(options.child_processes), options.experimental_fully_parallel)
1318 if options.help_printing:
1319 printer.help_printing()
1323 last_unexpected_results = _gather_unexpected_results(options)
1324 if options.print_last_failures:
1325 printer.write("\n".join(last_unexpected_results) + "\n")
1329 # We wrap any parts of the run that are slow or likely to raise exceptions
1330 # in a try/finally to ensure that we clean up the logging configuration.
1331 num_unexpected_results = -1
1333 test_runner = TestRunner(port, options, printer)
1334 test_runner._print_config()
1336 printer.print_update("Collecting tests ...")
1338 test_runner.collect_tests(args, last_unexpected_results)
1340 if e.errno == errno.ENOENT:
1344 printer.print_update("Parsing expectations ...")
1345 if options.lint_test_files:
1346 return test_runner.lint()
1347 test_runner.parse_expectations(port.test_platform_name(),
1348 options.configuration == 'Debug')
1350 printer.print_update("Checking build ...")
1351 if not port.check_build(test_runner.needs_http()):
1352 _log.error("Build check failed")
1355 result_summary = test_runner.set_up_run()
1357 num_unexpected_results = test_runner.run(result_summary)
1358 test_runner.clean_up_run()
1359 _log.debug("Testing completed, Exit status: %d" %
1360 num_unexpected_results)
1364 return num_unexpected_results
1367 def _set_up_derived_options(port_obj, options):
1368 """Sets the options values that depend on other options values."""
1370 if options.worker_model == 'inline':
1371 if options.child_processes and int(options.child_processes) > 1:
1372 _log.warning("--worker-model=inline overrides --child-processes")
1373 options.child_processes = "1"
1374 if not options.child_processes:
1375 options.child_processes = os.environ.get("WEBKIT_TEST_CHILD_PROCESSES",
1376 str(port_obj.default_child_processes()))
1378 if not options.configuration:
1379 options.configuration = port_obj.default_configuration()
1381 if options.pixel_tests is None:
1382 options.pixel_tests = True
1384 if not options.use_apache:
1385 options.use_apache = sys.platform in ('darwin', 'linux2')
1387 if not os.path.isabs(options.results_directory):
1388 # This normalizes the path to the build dir.
1389 # FIXME: how this happens is not at all obvious; this is a dumb
1390 # interface and should be cleaned up.
1391 options.results_directory = port_obj.results_directory()
1393 if not options.time_out_ms:
1394 if options.configuration == "Debug":
1395 options.time_out_ms = str(2 * TestRunner.DEFAULT_TEST_TIMEOUT_MS)
1397 options.time_out_ms = str(TestRunner.DEFAULT_TEST_TIMEOUT_MS)
1399 options.slow_time_out_ms = str(5 * int(options.time_out_ms))
1402 def _gather_unexpected_results(options):
1403 """Returns the unexpected results from the previous run, if any."""
1404 last_unexpected_results = []
1405 if options.print_last_failures or options.retest_last_failures:
1406 unexpected_results_filename = os.path.join(
1407 options.results_directory, "unexpected_results.json")
1408 with codecs.open(unexpected_results_filename, "r", "utf-8") as file:
1409 results = simplejson.load(file)
1410 last_unexpected_results = results['tests'].keys()
1411 return last_unexpected_results
1414 def _compat_shim_callback(option, opt_str, value, parser):
1415 print "Ignoring unsupported option: %s" % opt_str
1418 def _compat_shim_option(option_name, **kwargs):
1419 return optparse.make_option(option_name, action="callback",
1420 callback=_compat_shim_callback,
1421 help="Ignored, for old-run-webkit-tests compat only.", **kwargs)
1424 def parse_args(args=None):
1425 """Provides a default set of command line args.
1427 Returns a tuple of options, args from optparse"""
1429 # FIXME: All of these options should be stored closer to the code which
1430 # FIXME: actually uses them. configuration_options should move
1431 # FIXME: to WebKitPort and be shared across all scripts.
1432 configuration_options = [
1433 optparse.make_option("-t", "--target", dest="configuration",
1434 help="(DEPRECATED)"),
1435 # FIXME: --help should display which configuration is default.
1436 optparse.make_option('--debug', action='store_const', const='Debug',
1437 dest="configuration",
1438 help='Set the configuration to Debug'),
1439 optparse.make_option('--release', action='store_const',
1440 const='Release', dest="configuration",
1441 help='Set the configuration to Release'),
1442 # old-run-webkit-tests also accepts -c, --configuration CONFIGURATION.
1445 print_options = printing.print_options()
1447 # FIXME: These options should move onto the ChromiumPort.
1448 chromium_options = [
1449 optparse.make_option("--chromium", action="store_true", default=False,
1450 help="use the Chromium port"),
1451 optparse.make_option("--startup-dialog", action="store_true",
1452 default=False, help="create a dialog on DumpRenderTree startup"),
1453 optparse.make_option("--gp-fault-error-box", action="store_true",
1454 default=False, help="enable Windows GP fault error box"),
1455 optparse.make_option("--multiple-loads",
1456 type="int", help="turn on multiple loads of each test"),
1457 optparse.make_option("--js-flags",
1458 type="string", help="JavaScript flags to pass to tests"),
1459 optparse.make_option("--nocheck-sys-deps", action="store_true",
1461 help="Don't check the system dependencies (themes)"),
1462 optparse.make_option("--use-drt", action="store_true",
1464 help="Use DumpRenderTree instead of test_shell"),
1465 optparse.make_option("--accelerated-compositing",
1466 action="store_true",
1467 help="Use hardware-accelated compositing for rendering"),
1468 optparse.make_option("--no-accelerated-compositing",
1469 action="store_false",
1470 dest="accelerated_compositing",
1471 help="Don't use hardware-accelerated compositing for rendering"),
1472 optparse.make_option("--accelerated-2d-canvas",
1473 action="store_true",
1474 help="Use hardware-accelerated 2D Canvas calls"),
1475 optparse.make_option("--no-accelerated-2d-canvas",
1476 action="store_false",
1477 dest="accelerated_2d_canvas",
1478 help="Don't use hardware-accelerated 2D Canvas calls"),
1481 # Missing Mac-specific old-run-webkit-tests options:
1482 # FIXME: Need: -g, --guard for guard malloc support on Mac.
1483 # FIXME: Need: -l --leaks Enable leaks checking.
1484 # FIXME: Need: --sample-on-timeout Run sample on timeout
1486 old_run_webkit_tests_compat = [
1487 # NRWT doesn't generate results by default anyway.
1488 _compat_shim_option("--no-new-test-results"),
1489 # NRWT doesn't sample on timeout yet anyway.
1490 _compat_shim_option("--no-sample-on-timeout"),
1491 # FIXME: NRWT needs to support remote links eventually.
1492 _compat_shim_option("--use-remote-links-to-tests"),
1493 # FIXME: NRWT doesn't need this option as much since failures are
1494 # designed to be cheap. We eventually plan to add this support.
1495 _compat_shim_option("--exit-after-n-failures", nargs=1, type="int"),
1499 # NEED for bots: --use-remote-links-to-tests Link to test files
1500 # within the SVN repository in the results.
1501 optparse.make_option("-p", "--pixel-tests", action="store_true",
1502 dest="pixel_tests", help="Enable pixel-to-pixel PNG comparisons"),
1503 optparse.make_option("--no-pixel-tests", action="store_false",
1504 dest="pixel_tests", help="Disable pixel-to-pixel PNG comparisons"),
1505 optparse.make_option("--tolerance",
1506 help="Ignore image differences less than this percentage (some "
1507 "ports may ignore this option)", type="float"),
1508 optparse.make_option("--results-directory",
1509 default="layout-test-results",
1510 help="Output results directory source dir, relative to Debug or "
1512 optparse.make_option("--new-baseline", action="store_true",
1513 default=False, help="Save all generated results as new baselines "
1514 "into the platform directory, overwriting whatever's "
1516 optparse.make_option("--reset-results", action="store_true",
1517 default=False, help="Reset any existing baselines to the "
1518 "generated results"),
1519 optparse.make_option("--no-show-results", action="store_false",
1520 default=True, dest="show_results",
1521 help="Don't launch a browser with results after the tests "
1523 # FIXME: We should have a helper function to do this sort of
1524 # deprectated mapping and automatically log, etc.
1525 optparse.make_option("--noshow-results", action="store_false",
1526 dest="show_results",
1527 help="Deprecated, same as --no-show-results."),
1528 optparse.make_option("--no-launch-safari", action="store_false",
1529 dest="show_results",
1530 help="old-run-webkit-tests compat, same as --noshow-results."),
1531 # old-run-webkit-tests:
1532 # --[no-]launch-safari Launch (or do not launch) Safari to display
1533 # test results (default: launch)
1534 optparse.make_option("--full-results-html", action="store_true",
1536 help="Show all failures in results.html, rather than only "
1538 optparse.make_option("--clobber-old-results", action="store_true",
1539 default=False, help="Clobbers test results from previous runs."),
1540 optparse.make_option("--platform",
1541 help="Override the platform for expected results"),
1542 optparse.make_option("--no-record-results", action="store_false",
1543 default=True, dest="record_results",
1544 help="Don't record the results."),
1545 # old-run-webkit-tests also has HTTP toggle options:
1546 # --[no-]http Run (or do not run) http tests
1551 optparse.make_option("--build", dest="build",
1552 action="store_true", default=True,
1553 help="Check to ensure the DumpRenderTree build is up-to-date "
1555 optparse.make_option("--no-build", dest="build",
1556 action="store_false", help="Don't check to see if the "
1557 "DumpRenderTree build is up-to-date."),
1558 # old-run-webkit-tests has --valgrind instead of wrapper.
1559 optparse.make_option("--wrapper",
1560 help="wrapper command to insert before invocations of "
1561 "DumpRenderTree; option is split on whitespace before "
1562 "running. (Example: --wrapper='valgrind --smc-check=all')"),
1563 # old-run-webkit-tests:
1564 # -i|--ignore-tests Comma-separated list of directories
1565 # or tests to ignore
1566 optparse.make_option("--test-list", action="append",
1567 help="read list of tests to run from file", metavar="FILE"),
1568 # old-run-webkit-tests uses --skipped==[default|ignore|only]
1569 # instead of --force:
1570 optparse.make_option("--force", action="store_true", default=False,
1571 help="Run all tests, even those marked SKIP in the test list"),
1572 optparse.make_option("--use-apache", action="store_true",
1573 default=False, help="Whether to use apache instead of lighttpd."),
1574 optparse.make_option("--time-out-ms",
1575 help="Set the timeout for each test"),
1576 # old-run-webkit-tests calls --randomize-order --random:
1577 optparse.make_option("--randomize-order", action="store_true",
1578 default=False, help=("Run tests in random order (useful "
1579 "for tracking down corruption)")),
1580 optparse.make_option("--run-chunk",
1581 help=("Run a specified chunk (n:l), the nth of len l, "
1582 "of the layout tests")),
1583 optparse.make_option("--run-part", help=("Run a specified part (n:m), "
1584 "the nth of m parts, of the layout tests")),
1585 # old-run-webkit-tests calls --batch-size: --nthly n
1586 # Restart DumpRenderTree every n tests (default: 1000)
1587 optparse.make_option("--batch-size",
1588 help=("Run a the tests in batches (n), after every n tests, "
1589 "DumpRenderTree is relaunched."), type="int", default=0),
1590 # old-run-webkit-tests calls --run-singly: -1|--singly
1591 # Isolate each test case run (implies --nthly 1 --verbose)
1592 optparse.make_option("--run-singly", action="store_true",
1593 default=False, help="run a separate DumpRenderTree for each test"),
1594 optparse.make_option("--child-processes",
1595 help="Number of DumpRenderTrees to run in parallel."),
1596 # FIXME: Display default number of child processes that will run.
1597 optparse.make_option("--worker-model", action="store",
1599 help="controls worker model. Valid values are "
1600 "'inline' and 'threads' (default)."),
1601 optparse.make_option("--experimental-fully-parallel",
1602 action="store_true", default=False,
1603 help="run all tests in parallel"),
1604 # FIXME: Need --exit-after-n-failures N
1605 # Exit after the first N failures instead of running all tests
1606 # FIXME: Need --exit-after-n-crashes N
1607 # Exit after the first N crashes instead of running all tests
1608 # FIXME: consider: --iterations n
1609 # Number of times to run the set of tests (e.g. ABCABCABC)
1610 optparse.make_option("--print-last-failures", action="store_true",
1611 default=False, help="Print the tests in the last run that "
1612 "had unexpected failures (or passes)."),
1613 optparse.make_option("--retest-last-failures", action="store_true",
1614 default=False, help="re-test the tests in the last run that "
1615 "had unexpected failures (or passes)."),
1616 optparse.make_option("--retry-failures", action="store_true",
1618 help="Re-try any tests that produce unexpected results (default)"),
1619 optparse.make_option("--no-retry-failures", action="store_false",
1620 dest="retry_failures",
1621 help="Don't re-try any tests that produce unexpected results."),
1625 optparse.make_option("--lint-test-files", action="store_true",
1626 default=False, help=("Makes sure the test files parse for all "
1627 "configurations. Does not run any tests.")),
1630 # FIXME: Move these into json_results_generator.py
1631 results_json_options = [
1632 optparse.make_option("--master-name", help="The name of the buildbot master."),
1633 optparse.make_option("--builder-name", default="DUMMY_BUILDER_NAME",
1634 help=("The name of the builder shown on the waterfall running "
1635 "this script e.g. WebKit.")),
1636 optparse.make_option("--build-name", default="DUMMY_BUILD_NAME",
1637 help=("The name of the builder used in its path, e.g. "
1639 optparse.make_option("--build-number", default="DUMMY_BUILD_NUMBER",
1640 help=("The build number of the builder running this script.")),
1641 optparse.make_option("--test-results-server", default="",
1642 help=("If specified, upload results json files to this appengine "
1644 optparse.make_option("--upload-full-results",
1645 action="store_true",
1647 help="If true, upload full json results to server."),
1650 option_list = (configuration_options + print_options +
1651 chromium_options + results_options + test_options +
1652 misc_options + results_json_options +
1653 old_run_webkit_tests_compat)
1654 option_parser = optparse.OptionParser(option_list=option_list)
1656 options, args = option_parser.parse_args(args)
1658 if options.worker_model not in ('inline', 'threads'):
1659 option_parser.error("unsupported value for --worker-model: %s" %
1660 options.worker_model)
1662 return options, args
1666 options, args = parse_args()
1667 port_obj = port.get(options.platform, options)
1668 return run(port_obj, options, args)
1671 if '__main__' == __name__:
1674 except KeyboardInterrupt:
1675 # this mirrors what the shell normally does
1676 sys.exit(signal.SIGINT + 128)