summaryrefslogtreecommitdiffstats
path: root/webkit/tools/layout_tests/run_webkit_tests.py
diff options
context:
space:
mode:
Diffstat (limited to 'webkit/tools/layout_tests/run_webkit_tests.py')
-rwxr-xr-xwebkit/tools/layout_tests/run_webkit_tests.py650
1 files changed, 274 insertions, 376 deletions
diff --git a/webkit/tools/layout_tests/run_webkit_tests.py b/webkit/tools/layout_tests/run_webkit_tests.py
index b9244de..33ec3bd 100755
--- a/webkit/tools/layout_tests/run_webkit_tests.py
+++ b/webkit/tools/layout_tests/run_webkit_tests.py
@@ -36,6 +36,7 @@ import time
import traceback
from layout_package import apache_http_server
+from layout_package import compare_failures
from layout_package import test_expectations
from layout_package import http_server
from layout_package import json_results_generator
@@ -49,8 +50,6 @@ from test_types import image_diff
from test_types import test_type_base
from test_types import text_diff
-TestExpectationsFile = test_expectations.TestExpectationsFile
-
class TestInfo:
"""Groups information about a test for easy passing of data."""
def __init__(self, filename, timeout):
@@ -71,40 +70,43 @@ class TestInfo:
self.image_hash = None
-class ResultSummary(object):
- """A class for partitioning the test results we get into buckets.
-
- This class is basically a glorified struct and it's private to this file
- so we don't bother with any information hiding."""
- def __init__(self, expectations, test_files):
- self.total = len(test_files)
- self.expectations = expectations
- self.tests_by_expectation = {}
- self.tests_by_timeline = {}
- self.results = {}
- self.unexpected_results = {}
- self.failures = {}
- self.tests_by_expectation[test_expectations.SKIP] = set()
- for expectation in TestExpectationsFile.EXPECTATIONS.values():
- self.tests_by_expectation[expectation] = set()
- for timeline in TestExpectationsFile.TIMELINES.values():
- self.tests_by_timeline[timeline] = expectations.GetTestsWithTimeline(
- timeline)
-
- def Add(self, test, failures, result):
- """Add a result into the appropriate bin.
+class ResultSummaryEntry:
+ def __init__(self, all, failed, failure_counts, skipped):
+ """Resolves result counts.
Args:
- test: test file name
- failures: list of failure objects from test execution
- result: result of test (PASS, IMAGE, etc.)."""
+ all: list of all tests in this category
+ failed: list of failing tests in this category
+ skipped: list of skipped tests
+ failure_counts: dictionary of (TestFailure -> frequency)
+ """
+ self.skip_count = len(skipped)
+ self.total_count = len(all | skipped)
+ self.pass_count = self.total_count - self.skip_count - len(failed)
+ self.failure_counts = failure_counts;
+
- self.tests_by_expectation[result].add(test)
- self.results[test] = result
- if len(failures):
- self.failures[test] = failures
- if not self.expectations.MatchesAnExpectedResult(test, result):
- self.unexpected_results[test] = result
+class ResultSummary:
+ """Container for all result summaries for this test run.
+
+ Args:
+ deferred: ResultSummary object for deferred tests.
+ wontfix: ResultSummary object for wontfix tests.
+ fixable: ResultSummary object for tests marked in test_expectations.txt as
+ needing fixing but are not deferred/wontfix (i.e. should be fixed
+ for the next release).
+ fixable_count: Count of all fixable and skipped tests. This is essentially
+ a deduped sum of all the failures that are not deferred/wontfix.
+ all_fixable_count: Count of all the tests that are not deferred/wontfix.
+ This includes passing tests.
+ """
+ def __init__(self, deferred, wontfix, fixable, fixable_count,
+ all_fixable_count):
+ self.deferred = deferred
+ self.wontfix = wontfix
+ self.fixable = fixable
+ self.fixable_count = fixable_count
+ self.all_fixable_count = all_fixable_count
class TestRunner:
@@ -157,7 +159,6 @@ class TestRunner:
self._test_files = set()
self._test_files_list = None
self._file_dir = path_utils.GetAbsolutePath(os.path.dirname(sys.argv[0]))
- self._result_queue = Queue.Queue()
def __del__(self):
logging.info("flushing stdout")
@@ -174,7 +175,7 @@ class TestRunner:
def GatherFilePaths(self, paths):
"""Find all the files to test.
- Args:
+ args:
paths: a list of globs to use instead of the defaults."""
self._test_files = test_files.GatherTestFiles(paths)
logging.info('Found: %d tests' % len(self._test_files))
@@ -201,19 +202,21 @@ class TestRunner:
raise err
def PrepareListsAndPrintOutput(self):
- """Create appropriate subsets of test lists and returns a ResultSummary
- object. Also prints expected test counts."""
-
- result_summary = ResultSummary(self._expectations, self._test_files)
+ """Create appropriate subsets of test lists and print test counts.
+ Create appropriate subsets of self._tests_files in
+ self._ignored_failures, self._fixable_failures, and self._fixable_crashes.
+ Also remove skipped files from self._test_files, extract a subset of tests
+ if desired, and create the sorted self._test_files_list.
+ """
# Remove skipped - both fixable and ignored - files from the
# top-level list of files to test.
skipped = set()
+ # If there was only one test file, we'll run it even if it was skipped.
if len(self._test_files) > 1 and not self._options.force:
- skipped = self._expectations.GetTestsWithResultType(
- test_expectations.SKIP)
- for test in skipped:
- result_summary.Add(test, [], test_expectations.SKIP)
+ skipped = (self._expectations.GetFixableSkipped() |
+ self._expectations.GetDeferredSkipped() |
+ self._expectations.GetWontFixSkipped())
self._test_files -= skipped
if self._options.force:
@@ -305,15 +308,23 @@ class TestRunner:
else:
logging.info('Run: %d tests' % len(self._test_files))
- self._PrintExpectedResultsOfType(result_summary, test_expectations.PASS,
- "passes")
- self._PrintExpectedResultsOfType(result_summary, test_expectations.FAIL,
- "failures")
- self._PrintExpectedResultsOfType(result_summary, test_expectations.FLAKY,
- "flaky")
- self._PrintExpectedResultsOfType(result_summary, test_expectations.SKIP,
- "skipped")
- return result_summary
+ logging.info('Deferred: %d tests' % len(self._expectations.GetDeferred()))
+ logging.info('Expected passes: %d tests' %
+ len(self._test_files -
+ self._expectations.GetFixable() -
+ self._expectations.GetWontFix()))
+ logging.info(('Expected failures: %d fixable, %d ignored '
+ 'and %d deferred tests') %
+ (len(self._expectations.GetFixableFailures()),
+ len(self._expectations.GetWontFixFailures()),
+ len(self._expectations.GetDeferredFailures())))
+ logging.info(('Expected timeouts: %d fixable, %d ignored '
+ 'and %d deferred tests') %
+ (len(self._expectations.GetFixableTimeouts()),
+ len(self._expectations.GetWontFixTimeouts()),
+ len(self._expectations.GetDeferredTimeouts())))
+ logging.info('Expected crashes: %d fixable tests' %
+ len(self._expectations.GetFixableCrashes()))
def AddTestType(self, test_type):
"""Add a TestType to the TestRunner."""
@@ -373,7 +384,6 @@ class TestRunner:
Return:
The Queue of lists of TestInfo objects.
"""
-
if self._options.experimental_fully_parallel:
filename_queue = Queue.Queue()
for test_file in test_files:
@@ -474,7 +484,6 @@ class TestRunner:
test_args, shell_args = self._GetTestShellArgs(i)
thread = test_shell_thread.TestShellThread(filename_queue,
- self._result_queue,
test_shell_command,
test_types,
test_args,
@@ -493,7 +502,7 @@ class TestRunner:
proc.stdin.close()
proc.wait()
- def _RunTests(self, test_shell_binary, file_list, result_summary):
+ def _RunTests(self, test_shell_binary, file_list):
"""Runs the tests in the file_list.
Return: A tuple (failures, thread_timings, test_timings,
@@ -505,7 +514,6 @@ class TestRunner:
form [time, directory_name, num_tests]
individual_test_timings is a list of run times for each test in the form
{filename:filename, test_run_time:test_run_time}
- result_summary: summary object to populate with the results
"""
threads = self._InstantiateTestShellThreads(test_shell_binary, file_list)
@@ -522,7 +530,7 @@ class TestRunner:
# suffices to not use an indefinite blocking join for it to
# be interruptible by KeyboardInterrupt.
thread.join(1.0)
- self._UpdateSummary(result_summary)
+ failures.update(thread.GetFailures())
thread_timings.append({ 'name': thread.getName(),
'num_tests': thread.GetNumTests(),
'total_time': thread.GetTotalTime()});
@@ -542,19 +550,14 @@ class TestRunner:
# would be assumed to have passed.
raise exception_info[0], exception_info[1], exception_info[2]
- # Make sure we pick up any remaining tests.
- self._UpdateSummary(result_summary)
- return (thread_timings, test_timings, individual_test_timings)
+ return (failures, thread_timings, test_timings, individual_test_timings)
- def Run(self, result_summary):
+ def Run(self):
"""Run all our tests on all our test files.
For each test file, we run each test type. If there are any failures, we
collect them for reporting.
- Args:
- result_summary: a summary object tracking the test results.
-
Return:
We return nonzero if there are regressions compared to the last run.
"""
@@ -597,36 +600,25 @@ class TestRunner:
self._websocket_server.Start()
# self._websocket_secure_server.Start()
- thread_timings, test_timings, individual_test_timings = (
- self._RunTests(test_shell_binary, self._test_files_list,
- result_summary))
- original_results_by_test, original_results_by_type = self._CompareResults(
- result_summary)
- final_results_by_test = original_results_by_test
- final_results_by_type = original_results_by_type
- failed_results_by_test, failed_results_by_type = self._OnlyFailures(
- original_results_by_test)
- num_passes = len(original_results_by_test) - len(failed_results_by_test)
+ original_failures, thread_timings, test_timings, individual_test_timings = (
+ self._RunTests(test_shell_binary, self._test_files_list))
retries = 0
- while (retries < self.NUM_RETRY_ON_UNEXPECTED_FAILURE and
- len(failed_results_by_test)):
- logging.info("Retrying %d unexpected failure(s)" %
- len(failed_results_by_test))
+ final_failures = original_failures
+ original_regressions = self._CompareFailures(final_failures)
+ regressions = original_regressions
+
+ while retries < self.NUM_RETRY_ON_UNEXPECTED_FAILURE and len(regressions):
+ logging.info("Retrying %d unexpected failure(s)" % len(regressions))
retries += 1
- retry_summary = ResultSummary(self._expectations,
- failed_results_by_test.keys())
- self._RunTests(test_shell_binary, failed_results_by_test.keys(),
- retry_summary)
- final_results_by_test, final_results_by_type = self._CompareResults(
- retry_summary)
- failed_results_by_test, failed_results_by_type = self._OnlyFailures(
- final_results_by_test)
+ final_failures = self._RunTests(test_shell_binary, list(regressions))[0]
+ regressions = self._CompareFailures(final_failures)
self._StopLayoutTestHelper(layout_test_helper_proc)
print
end_time = time.time()
+
logging.info("%6.2f total testing time" % (end_time - start_time))
cuml_time = 0
logging.debug("Thread timing:")
@@ -641,20 +633,20 @@ class TestRunner:
print
self._PrintTimingStatistics(test_timings, individual_test_timings,
- result_summary)
+ original_failures)
- self._PrintUnexpectedResults(original_results_by_test,
- original_results_by_type,
- final_results_by_test,
- final_results_by_type)
+ self._PrintRegressions(original_failures, original_regressions,
+ final_failures)
# Write summaries to stdout.
- # The summaries should include flaky tests, so use the original summary,
- # not the final one.
+ # The summary should include flaky tests, so use original_failures, not
+ # final_failures.
+ result_summary = self._GetResultSummary(original_failures)
self._PrintResultSummary(result_summary, sys.stdout)
if self._options.verbose:
- self._WriteJSONFiles(result_summary, individual_test_timings);
+ self._WriteJSONFiles(original_failures, individual_test_timings,
+ result_summary);
# Write the same data to a log file.
out_filename = os.path.join(self._options.results_directory, "score.txt")
@@ -664,62 +656,40 @@ class TestRunner:
# Write the summary to disk (results.html) and maybe open the test_shell
# to this file.
- wrote_results = self._WriteResultsHtmlFile(result_summary)
+ wrote_results = self._WriteResultsHtmlFile(original_failures,
+ original_regressions)
if not self._options.noshow_results and wrote_results:
self._ShowResultsHtmlFile()
sys.stdout.flush()
sys.stderr.flush()
- # Ignore flaky failures and unexpected passes so we don't turn the
- # bot red for those.
- return len(failed_results_by_test)
-
- def _UpdateSummary(self, result_summary):
- """Update the summary while running tests."""
- while True:
- try:
- (test, fail_list) = self._result_queue.get_nowait()
- result = test_failures.DetermineResultType(fail_list)
- result_summary.Add(test, fail_list, result)
- except Queue.Empty:
- return
-
- def _CompareResults(self, result_summary):
- """Determine if the results in this test run are unexpected.
+ # Ignore flaky failures so we don't turn the bot red for those.
+ return len(regressions)
- Returns:
- A dict of files -> results and a dict of results -> sets of files
+ def _PrintRegressions(self, original_failures, original_regressions,
+ final_failures):
+ """Prints the regressions from the test run.
+ Args:
+ original_failures: Failures from the first test run.
+ original_regressions: Regressions from the first test run.
+ final_failures: Failures after retrying the failures from the first run.
"""
- results_by_test = {}
- results_by_type = {}
- for result in TestExpectationsFile.EXPECTATIONS.values():
- results_by_type[result] = set()
- for test, result in result_summary.unexpected_results.iteritems():
- results_by_test[test] = result
- results_by_type[result].add(test)
- return results_by_test, results_by_type
-
- def _OnlyFailures(self, results_by_test):
- """Filters a dict of results and returns only the failures.
+ print "-" * 78
- Args:
- results_by_test: a dict of files -> results
+ flaky_failures = {}
+ non_flaky_failures = {}
+ for failure in original_failures:
+ if failure not in original_regressions or failure in final_failures:
+ non_flaky_failures[failure] = original_failures[failure]
+ else:
+ flaky_failures[failure] = original_failures[failure]
- Returns:
- a dict of files -> results and results -> sets of files.
- """
- failed_results_by_test = {}
- failed_results_by_type = {}
- for test, result in results_by_test.iteritems():
- if result != test_expectations.PASS:
- failed_results_by_test[test] = result
- if result in failed_results_by_type:
- failed_results_by_type.add(test)
- else:
- failed_results_by_type = set([test])
- return failed_results_by_test, failed_results_by_type
+ self._CompareFailures(non_flaky_failures, print_regressions=True)
+ self._CompareFailures(flaky_failures, print_regressions=True, is_flaky=True)
- def _WriteJSONFiles(self, result_summary, individual_test_timings):
+ print "-" * 78
+
+ def _WriteJSONFiles(self, failures, individual_test_timings, result_summary):
logging.debug("Writing JSON files in %s." % self._options.results_directory)
# Write a json file of the test_expectations.txt file for the layout tests
# dashboard.
@@ -729,47 +699,16 @@ class TestRunner:
expectations_file.write(("ADD_EXPECTATIONS(" + expectations_json + ");"))
expectations_file.close()
- json_results_generator.JSONResultsGenerator(self._options,
- result_summary, individual_test_timings,
- self._options.results_directory, self._test_files_list)
+ json_results_generator.JSONResultsGenerator(self._options, failures,
+ individual_test_timings, self._options.results_directory,
+ self._test_files_list, result_summary)
logging.debug("Finished writing JSON files.")
- def _PrintExpectedResultsOfType(self, result_summary, result_type,
- result_type_str):
- """Print the number of the tests in a given result class.
-
- Args:
- result_summary - the object containg all the results to report on
- result_type - the particular result type to report in the summary.
- result_type_str - a string description of the result_type.
- """
- tests = self._expectations.GetTestsWithResultType(result_type)
- now = result_summary.tests_by_timeline[test_expectations.NOW]
- wontfix = result_summary.tests_by_timeline[test_expectations.WONTFIX]
- defer = result_summary.tests_by_timeline[test_expectations.DEFER]
-
- # We use a fancy format string in order to print the data out in a
- # nicely-aligned table.
- fmtstr = ("Expect: %%5d %%-8s (%%%dd now, %%%dd defer, %%%dd wontfix)" %
- (self._NumDigits(now), self._NumDigits(defer),
- self._NumDigits(wontfix)))
- logging.info(fmtstr %
- (len(tests), result_type_str, len(tests & now), len(tests & defer),
- len(tests & wontfix)))
-
- def _NumDigits(self, num):
- """Returns the number of digits needed to represent the length of a
- sequence."""
- ndigits = 1
- if len(num):
- ndigits = int(math.log10(len(num))) + 1
- return ndigits
-
def _PrintTimingStatistics(self, directory_test_timings,
- individual_test_timings, result_summary):
+ individual_test_timings, failures):
self._PrintAggregateTestStatistics(individual_test_timings)
- self._PrintIndividualTestTimes(individual_test_timings, result_summary)
+ self._PrintIndividualTestTimes(individual_test_timings, failures)
self._PrintDirectoryTimings(directory_test_timings)
def _PrintAggregateTestStatistics(self, individual_test_timings):
@@ -803,12 +742,12 @@ class TestRunner:
"PER TEST TIMES BY TEST TYPE: %s" % test_type,
times_per_test_type[test_type])
- def _PrintIndividualTestTimes(self, individual_test_timings, result_summary):
+ def _PrintIndividualTestTimes(self, individual_test_timings, failures):
"""Prints the run times for slow, timeout and crash tests.
Args:
individual_test_timings: List of test_shell_thread.TestStats for all
tests.
- result_summary: Object containing the results of all the tests.
+ failures: Dictionary mapping test filenames to list of test_failures.
"""
# Reverse-sort by the time spent in test_shell.
individual_test_timings.sort(lambda a, b:
@@ -825,10 +764,13 @@ class TestRunner:
is_timeout_crash_or_slow = True
slow_tests.append(test_tuple)
- if (result_summary.results[filename] == test_expectations.TIMEOUT or
- result_summary.results[filename] == test_expectations.CRASH):
- is_timeout_crash_or_slow = True
- timeout_or_crash_tests.append(test_tuple)
+ if filename in failures:
+ for failure in failures[filename]:
+ if (failure.__class__ == test_failures.FailureTimeout or
+ failure.__class__ == test_failures.FailureCrash):
+ is_timeout_crash_or_slow = True
+ timeout_or_crash_tests.append(test_tuple)
+ break
if (not is_timeout_crash_or_slow and
num_printed < self._options.num_slow_tests_to_log):
@@ -896,225 +838,181 @@ class TestRunner:
"99th percentile: %s, Standard deviation: %s\n" % (
median, mean, percentile90, percentile99, std_deviation)))
- def _PrintResultSummary(self, result_summary, output):
- """Print a short summary to the output file about how many tests passed.
+ def _GetResultSummary(self, failures):
+ """Returns a ResultSummary object with failure counts.
Args:
- output: a file or stream to write to
+ failures: dictionary mapping the test filename to a list of
+ TestFailure objects if the test failed
"""
- failed = len(result_summary.failures)
- skipped = len(result_summary.tests_by_expectation[test_expectations.SKIP])
- total = result_summary.total
- passed = total - failed - skipped
- pct_passed = 0.0
- if total > 0:
- pct_passed = float(passed) * 100 / total
-
- output.write("\n");
- output.write("=> Total: %d/%d tests passed (%.1f%%)\n" %
- (passed, total, pct_passed))
-
- output.write("\n");
- self._PrintResultSummaryEntry(result_summary, test_expectations.NOW,
- "Tests to be fixed for the current release", output)
-
- output.write("\n");
- self._PrintResultSummaryEntry(result_summary, test_expectations.DEFER,
- "Tests we'll fix in the future if they fail (DEFER)", output)
-
- output.write("\n");
- self._PrintResultSummaryEntry(result_summary, test_expectations.WONTFIX,
- "Tests that won't be fixed if they fail (WONTFIX)", output)
-
- def _PrintResultSummaryEntry(self, result_summary, timeline, heading, output):
- """Print a summary block of results for a particular timeline of test.
+ fixable_counts = {}
+ deferred_counts = {}
+ wontfix_counts = {}
+
+ fixable_failures = set()
+ deferred_failures = set()
+ wontfix_failures = set()
+
+ # Aggregate failures in a dictionary (TestFailure -> frequency),
+ # with known (fixable and wontfix) failures separated out.
+ def AddFailure(dictionary, key):
+ if key in dictionary:
+ dictionary[key] += 1
+ else:
+ dictionary[key] = 1
+
+ for test, failure_types in failures.iteritems():
+ for failure_type in failure_types:
+ # TODO(ojan): Now that we have IMAGE+TEXT, IMAGE and TEXT,
+ # we can avoid adding multiple failures per test since there should
+ # be a unique type of failure for each test. This would make the
+ # statistics printed at the end easier to grok.
+ if self._expectations.IsDeferred(test):
+ count_group = deferred_counts
+ failure_group = deferred_failures
+ elif self._expectations.IsIgnored(test):
+ count_group = wontfix_counts
+ failure_group = wontfix_failures
+ else:
+ count_group = fixable_counts
+ failure_group = fixable_failures
+
+ AddFailure(count_group, failure_type.__class__)
+ failure_group.add(test)
+
+ # Here and below, use the prechuncked expectations object for numbers of
+ # skipped tests. Chunking removes the skipped tests before creating the
+ # expectations file.
+ #
+ # This is a bit inaccurate, since the number of skipped tests will be
+ # duplicated across all shard, but it's the best we can reasonably do.
+
+ deduped_fixable_count = len(fixable_failures |
+ self.prechunk_expectations.GetFixableSkipped())
+ all_fixable_count = len(self._test_files -
+ self._expectations.GetWontFix() - self._expectations.GetDeferred())
+
+ # Breakdown of tests we need to fix and want to pass.
+ # Include skipped fixable tests in the statistics.
+ fixable_result_summary = ResultSummaryEntry(
+ self._expectations.GetFixable() | fixable_failures,
+ fixable_failures,
+ fixable_counts,
+ self.prechunk_expectations.GetFixableSkipped())
+
+ deferred_result_summary = ResultSummaryEntry(
+ self._expectations.GetDeferred(),
+ deferred_failures,
+ deferred_counts,
+ self.prechunk_expectations.GetDeferredSkipped())
+
+ wontfix_result_summary = ResultSummaryEntry(
+ self._expectations.GetWontFix(),
+ wontfix_failures,
+ wontfix_counts,
+ self.prechunk_expectations.GetWontFixSkipped())
+
+ return ResultSummary(deferred_result_summary, wontfix_result_summary,
+ fixable_result_summary, deduped_fixable_count, all_fixable_count)
- Args:
- result_summary: summary to print results for
- timeline: the timeline to print results for (NOT, WONTFIX, etc.)
- heading: a textual description of the timeline
- output: a stream to write to
- """
- total = len(result_summary.tests_by_timeline[timeline])
- not_passing = (total -
- len(result_summary.tests_by_expectation[test_expectations.PASS] &
- result_summary.tests_by_timeline[timeline]))
- output.write("=> %s (%d):\n" % (heading, not_passing))
- for result in TestExpectationsFile.EXPECTATION_ORDER:
- if result == test_expectations.PASS:
- continue
- results = (result_summary.tests_by_expectation[result] &
- result_summary.tests_by_timeline[timeline])
- desc = TestExpectationsFile.EXPECTATION_DESCRIPTIONS[result]
- plural = ["", "s"]
- if not_passing and len(results):
- pct = len(results) * 100.0 / not_passing
- output.write("%d test case%s (%4.1f%%) %s\n" %
- (len(results), plural[len(results) != 1], pct,
- desc[len(results) != 1]))
-
- def _PrintUnexpectedResults(self, original_results_by_test,
- original_results_by_type,
- final_results_by_test,
- final_results_by_type):
- """Print unexpected results (regressions) to stdout and a file.
+ def _PrintResultSummary(self, result_summary, output):
+ """Print a short summary to stdout about how many tests passed.
Args:
- original_results_by_test: dict mapping tests -> results for the first
- (original) test run
- original_results_by_type: dict mapping results -> sets of tests for the
- first test run
- final_results_by_test: dict of tests->results after the retries
- eliminated any flakiness
- final_results_by_type: dict of results->tests after the retries
- eliminated any flakiness
+ result_summary: ResultSummary object with failure counts.
+ output: file descriptor to write the results to. For example, sys.stdout.
"""
- print "-" * 78
+ failed = result_summary.fixable_count
+ total = result_summary.all_fixable_count
+ passed = 0
+ if total > 0:
+ passed = float(total - failed) * 100 / total
+ output.write(
+ "\nTest summary: %.1f%% Passed | %s Failures | %s Tests to pass for "
+ "this release\n" % (
+ passed, failed, total))
+
+ self._PrintResultSummaryEntry(
+ "Tests to be fixed for the current release",
+ result_summary.fixable,
+ output)
+ self._PrintResultSummaryEntry(
+ "Tests to be fixed for a future release (DEFER)",
+ result_summary.deferred,
+ output)
+ self._PrintResultSummaryEntry("Tests never to be fixed (WONTFIX)",
+ result_summary.wontfix,
+ output)
print
- flaky_results_by_type = {}
- non_flaky_results_by_type = {}
-
- for test, result in original_results_by_test.iteritems():
- if (result == test_expectations.PASS or
- (test in final_results_by_test and
- result == final_results_by_test[test])):
- if result in non_flaky_results_by_type:
- non_flaky_results_by_type[result].add(test)
- else:
- non_flaky_results_by_type[result] = set([test])
- else:
- if result in flaky_results_by_type:
- flaky_results_by_type[result].add(test)
- else:
- flaky_results_by_type[result] = set([test])
- self._PrintUnexpectedResultsByType(non_flaky_results_by_type, False,
- sys.stdout)
- self._PrintUnexpectedResultsByType(flaky_results_by_type, True, sys.stdout)
-
- out_filename = os.path.join(self._options.results_directory,
- "regressions.txt")
- output_file = open(out_filename, "w")
- self._PrintUnexpectedResultsByType(non_flaky_results_by_type, False,
- output_file)
- output_file.close()
-
- print "-" * 78
-
- def _PrintUnexpectedResultsByType(self, results_by_type, is_flaky, output):
- """Helper method to print a set of unexpected results to an output stream
- sorted by the result type.
+ def _PrintResultSummaryEntry(self, heading, result_summary, output):
+ """Print a summary block of results for a particular category of test.
Args:
- results_by_type: dict(result_type -> list of files)
- is_flaky: where these results flaky or not (changes the output)
- output: stream to write output to
+ heading: text to print before the block, followed by the total count
+ result_summary: ResultSummaryEntry object with the result counts
+ output: file descriptor to write the results to
"""
- descriptions = TestExpectationsFile.EXPECTATION_DESCRIPTIONS
- keywords = {}
- for k, v in TestExpectationsFile.EXPECTATIONS.iteritems():
- keywords[v] = k.upper()
-
- for result in TestExpectationsFile.EXPECTATION_ORDER:
- if result in results_by_type:
- self._PrintUnexpectedResultSet(output, results_by_type[result],
- is_flaky, descriptions[result][1],
- keywords[result])
-
- def _PrintUnexpectedResultSet(self, output, filenames, is_flaky,
- header_text, keyword):
- """A helper method to print one set of results (all of the same type)
- to a stream.
+ output.write("\n=> %s (%d):\n" % (heading, result_summary.total_count))
+ self._PrintResultLine(result_summary.pass_count, result_summary.total_count,
+ "Passed", output)
+ self._PrintResultLine(result_summary.skip_count, result_summary.total_count,
+ "Skipped", output)
+ sorted_keys = sorted(result_summary.failure_counts.keys(),
+ key=test_failures.FailureSort.SortOrder)
+ for failure in sorted_keys:
+ self._PrintResultLine(result_summary.failure_counts[failure],
+ result_summary.total_count,
+ failure.Message(),
+ output)
+
+ def _PrintResultLine(self, count, total, message, output):
+ if count == 0: return
+ output.write(
+ ("%(count)d test case%(plural)s (%(percent).1f%%) %(message)s\n" %
+ { 'count' : count,
+ 'plural' : ('s', '')[count == 1],
+ 'percent' : float(count) * 100 / total,
+ 'message' : message }))
+
+ def _CompareFailures(self, failures, print_regressions=False, is_flaky=False):
+ """Determine if the failures in this test run are unexpected.
Args:
- output: a stream or file object to write() to
- filenames: a list of absolute filenames
- header_text: a string to display before the list of filenames
- keyword: expectation keyword
- """
- filenames = list(filenames)
- filenames.sort()
- if not is_flaky and keyword == 'PASS':
- self._PrintUnexpectedPasses(output, filenames)
- return
-
- if is_flaky:
- prefix = "Unexpected flakiness:"
- else:
- prefix = "Regressions: Unexpected"
- output.write("%s %s (%d):\n" % (prefix, header_text, len(filenames)))
- for filename in filenames:
- flaky_text = ""
- if is_flaky:
- flaky_text = " " + self._expectations.GetExpectationsString(filename)
-
- filename = path_utils.RelativeTestFilename(filename)
- output.write(" %s = %s%s\n" % (filename, keyword, flaky_text))
- output.write("\n")
-
- def _PrintUnexpectedPasses(self, output, filenames):
- """Prints the list of files that passed unexpectedly.
-
- TODO(dpranke): This routine is a bit of a hack, since it's not clear
- what the best way to output this is. Each unexpected pass might have
- multiple expected results, and printing all the combinations would
- be pretty clunky. For now we sort them into three buckets, crashes,
- timeouts, and everything else.
+ failures: a dictionary mapping the test filename to a list of
+ TestFailure objects if the test failed
+ print_regressions: whether to print the regressions to stdout
+ is_flaky: whether this set of failures represents tests that failed
+ the first time around, but passed on a subsequent run
- Args:
- output: a stream to write to
- filenames: list of files that passed
+ Return:
+ A set of regressions (unexpected failures, hangs, or crashes)
"""
- crashes = []
- timeouts = []
- failures = []
- for filename in filenames:
- expectations = self._expectations.GetExpectations(filename)
- if test_expectations.CRASH in expectations:
- crashes.append(filename)
- elif test_expectations.TIMEOUT in expectations:
- timeouts.append(filename)
- else:
- failures.append(filename)
+ cf = compare_failures.CompareFailures(self._test_files,
+ failures,
+ self._expectations,
+ is_flaky)
+ if print_regressions:
+ cf.PrintRegressions(sys.stdout)
- self._PrintPassSet(output, "crash", crashes)
- self._PrintPassSet(output, "timeout", timeouts)
- self._PrintPassSet(output, "fail", failures)
+ return cf.GetRegressions()
- def _PrintPassSet(self, output, expected_str, filenames):
- """Print a set of unexpected passes.
-
- Args:
- output: stream to write to
- expected_str: worst expectation for the given file
- filenames: list of files in that set
- """
- if len(filenames):
- filenames.sort()
- output.write("Expected to %s, but passed: (%d)\n" %
- (expected_str, len(filenames)))
- for filename in filenames:
- filename = path_utils.RelativeTestFilename(filename)
- output.write(" %s\n" % filename)
- output.write("\n")
-
- def _WriteResultsHtmlFile(self, result_summary):
+ def _WriteResultsHtmlFile(self, failures, regressions):
"""Write results.html which is a summary of tests that failed.
Args:
- result_summary: a summary of the results :)
+ failures: a dictionary mapping the test filename to a list of
+ TestFailure objects if the test failed
+ regressions: a set of test filenames that regressed
Returns:
True if any results were written (since expected failures may be omitted)
"""
# test failures
- failures = result_summary.failures
- failed_results_by_test, failed_results_by_type = self._OnlyFailures(
- result_summary.unexpected_results)
if self._options.full_results_html:
test_files = failures.keys()
else:
- test_files = failed_results_by_test.keys()
+ test_files = list(regressions)
if not len(test_files):
return False
@@ -1264,15 +1162,15 @@ def main(options, args):
if options.lint_test_files:
# Creating the expecations for each platform/target pair does all the
# test list parsing and ensures it's correct syntax (e.g. no dupes).
- for platform in TestExpectationsFile.PLATFORMS:
+ for platform in test_expectations.TestExpectationsFile.PLATFORMS:
test_runner.ParseExpectations(platform, is_debug_mode=True)
test_runner.ParseExpectations(platform, is_debug_mode=False)
print ("If there are no fail messages, errors or exceptions, then the "
"lint succeeded.")
- sys.exit(0)
+ return
else:
test_runner.ParseExpectations(options.platform, options.target == 'Debug')
- result_summary = test_runner.PrepareListsAndPrintOutput()
+ test_runner.PrepareListsAndPrintOutput()
if options.find_baselines:
# Record where we found each baseline, then exit.
@@ -1326,7 +1224,7 @@ def main(options, args):
test_runner.AddTestType(image_diff.ImageDiff)
if options.fuzzy_pixel_tests:
test_runner.AddTestType(fuzzy_image_diff.FuzzyImageDiff)
- has_new_failures = test_runner.Run(result_summary)
+ has_new_failures = test_runner.Run()
logging.info("Exit status: %d" % has_new_failures)
sys.exit(has_new_failures)