buildbot_results.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. #!/usr/bin/env python
  2. # Copyright (C) 2012 Google Inc. All rights reserved.
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions are
  6. # met:
  7. #
  8. # * Redistributions of source code must retain the above copyright
  9. # notice, this list of conditions and the following disclaimer.
  10. # * Redistributions in binary form must reproduce the above
  11. # copyright notice, this list of conditions and the following disclaimer
  12. # in the documentation and/or other materials provided with the
  13. # distribution.
  14. # * Neither the name of Google Inc. nor the names of its
  15. # contributors may be used to endorse or promote products derived from
  16. # this software without specific prior written permission.
  17. #
  18. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  19. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  20. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  21. # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  22. # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  23. # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  24. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  25. # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  26. # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  27. # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  28. # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  29. from webkitpy.layout_tests.models import test_expectations
  30. from webkitpy.common.net import resultsjsonparser
  31. TestExpectations = test_expectations.TestExpectations
  32. TestExpectationParser = test_expectations.TestExpectationParser
  33. class BuildBotPrinter(object):
  34. # This output is parsed by buildbots and must only be changed in coordination with buildbot scripts (see webkit.org's
  35. # Tools/BuildSlaveSupport/build.webkit.org-config/master.cfg: RunWebKitTests._parseNewRunWebKitTestsOutput
  36. # and chromium.org's buildbot/master.chromium/scripts/master/log_parser/webkit_test_command.py).
  37. def __init__(self, stream, debug_logging):
  38. self.stream = stream
  39. self.debug_logging = debug_logging
  40. def print_results(self, run_details):
  41. if self.debug_logging:
  42. self.print_run_results(run_details.initial_results)
  43. self.print_unexpected_results(run_details.summarized_results, run_details.enabled_pixel_tests_in_retry)
  44. def _print(self, msg):
  45. self.stream.write(msg + '\n')
  46. def print_run_results(self, run_results):
  47. failed = run_results.total_failures
  48. total = run_results.total
  49. passed = total - failed - run_results.remaining
  50. percent_passed = 0.0
  51. if total > 0:
  52. percent_passed = float(passed) * 100 / total
  53. self._print("=> Results: %d/%d tests passed (%.1f%%)" % (passed, total, percent_passed))
  54. self._print("")
  55. self._print_run_results_entry(run_results, test_expectations.NOW, "Tests to be fixed")
  56. self._print("")
  57. # FIXME: We should be skipping anything marked WONTFIX, so we shouldn't bother logging these stats.
  58. self._print_run_results_entry(run_results, test_expectations.WONTFIX,
  59. "Tests that will only be fixed if they crash (WONTFIX)")
  60. self._print("")
  61. def _print_run_results_entry(self, run_results, timeline, heading):
  62. total = len(run_results.tests_by_timeline[timeline])
  63. not_passing = (total -
  64. len(run_results.tests_by_expectation[test_expectations.PASS] &
  65. run_results.tests_by_timeline[timeline]))
  66. self._print("=> %s (%d):" % (heading, not_passing))
  67. for result in TestExpectations.EXPECTATION_ORDER:
  68. if result in (test_expectations.PASS, test_expectations.SKIP):
  69. continue
  70. results = (run_results.tests_by_expectation[result] & run_results.tests_by_timeline[timeline])
  71. desc = TestExpectations.EXPECTATION_DESCRIPTIONS[result]
  72. if not_passing and len(results):
  73. pct = len(results) * 100.0 / not_passing
  74. self._print(" %5d %-24s (%4.1f%%)" % (len(results), desc, pct))
  75. def print_unexpected_results(self, summarized_results, enabled_pixel_tests_in_retry=False):
  76. passes = {}
  77. flaky = {}
  78. regressions = {}
  79. def add_to_dict_of_lists(dict, key, value):
  80. dict.setdefault(key, []).append(value)
  81. def add_result(test, results, passes=passes, flaky=flaky, regressions=regressions):
  82. actual = results['actual'].split(" ")
  83. expected = results['expected'].split(" ")
  84. def is_expected(result):
  85. return (result in expected) or (result in ('AUDIO', 'TEXT', 'IMAGE+TEXT') and 'FAIL' in expected)
  86. if all(is_expected(actual_result) for actual_result in actual):
  87. # Don't print anything for tests that ran as expected.
  88. return
  89. if actual == ['PASS']:
  90. if 'CRASH' in expected:
  91. add_to_dict_of_lists(passes, 'Expected to crash, but passed', test)
  92. elif 'TIMEOUT' in expected:
  93. add_to_dict_of_lists(passes, 'Expected to timeout, but passed', test)
  94. else:
  95. add_to_dict_of_lists(passes, 'Expected to fail, but passed', test)
  96. elif enabled_pixel_tests_in_retry and actual == ['TEXT', 'IMAGE+TEXT']:
  97. add_to_dict_of_lists(regressions, actual[0], test)
  98. elif len(actual) > 1:
  99. # We group flaky tests by the first actual result we got.
  100. add_to_dict_of_lists(flaky, actual[0], test)
  101. else:
  102. add_to_dict_of_lists(regressions, results['actual'], test)
  103. resultsjsonparser.for_each_test(summarized_results['tests'], add_result)
  104. if len(passes) or len(flaky) or len(regressions):
  105. self._print("")
  106. if len(passes):
  107. for key, tests in passes.iteritems():
  108. self._print("%s: (%d)" % (key, len(tests)))
  109. tests.sort()
  110. for test in tests:
  111. self._print(" %s" % test)
  112. self._print("")
  113. self._print("")
  114. if len(flaky):
  115. descriptions = TestExpectations.EXPECTATION_DESCRIPTIONS
  116. for key, tests in flaky.iteritems():
  117. result = TestExpectations.EXPECTATIONS[key.lower()]
  118. self._print("Unexpected flakiness: %s (%d)" % (descriptions[result], len(tests)))
  119. tests.sort()
  120. for test in tests:
  121. result = resultsjsonparser.result_for_test(summarized_results['tests'], test)
  122. actual = result['actual'].split(" ")
  123. expected = result['expected'].split(" ")
  124. result = TestExpectations.EXPECTATIONS[key.lower()]
  125. # FIXME: clean this up once the old syntax is gone
  126. new_expectations_list = [TestExpectationParser._inverted_expectation_tokens[exp] for exp in list(set(actual) | set(expected))]
  127. self._print(" %s [ %s ]" % (test, " ".join(new_expectations_list)))
  128. self._print("")
  129. self._print("")
  130. if len(regressions):
  131. descriptions = TestExpectations.EXPECTATION_DESCRIPTIONS
  132. for key, tests in regressions.iteritems():
  133. result = TestExpectations.EXPECTATIONS[key.lower()]
  134. self._print("Regressions: Unexpected %s (%d)" % (descriptions[result], len(tests)))
  135. tests.sort()
  136. for test in tests:
  137. self._print(" %s [ %s ]" % (test, TestExpectationParser._inverted_expectation_tokens[key]))
  138. self._print("")
  139. if len(summarized_results['tests']) and self.debug_logging:
  140. self._print("%s" % ("-" * 78))