blob: 5c7bc2f5741926b2e81a19aaa9143af000b59bc4 [file] [log] [blame]
# Copyright (C) 2013 Google Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the Google name nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import unittest
from webkitpy.common.config.builders import BUILDERS
from webkitpy.layout_tests.layout_package import bot_test_expectations
from webkitpy.layout_tests.builder_list import BuilderList
class BotTestExpectationsFactoryTest(unittest.TestCase):
def fake_builder_list(self):
return BuilderList({
'Dummy builder name': {'port_name': 'dummy-port', 'specifiers': []},
})
def fake_results_json_for_builder(self, builder):
return bot_test_expectations.ResultsJSON(builder, 'Dummy content')
def test_expectations_for_builder(self):
factory = bot_test_expectations.BotTestExpectationsFactory(self.fake_builder_list())
factory._results_json_for_builder = self.fake_results_json_for_builder
self.assertIsNotNone(factory.expectations_for_builder('Dummy builder name'))
def test_expectations_for_port(self):
factory = bot_test_expectations.BotTestExpectationsFactory(self.fake_builder_list())
factory._results_json_for_builder = self.fake_results_json_for_builder
self.assertIsNotNone(factory.expectations_for_port('dummy-port'))
class BotTestExpectationsTest(unittest.TestCase):
# FIXME: Find a way to import this map from Tools/TestResultServer/model/jsonresults.py.
FAILURE_MAP = {'A': 'AUDIO', 'C': 'CRASH', 'F': 'TEXT', 'I': 'IMAGE', 'O': 'MISSING',
'N': 'NO DATA', 'P': 'PASS', 'T': 'TIMEOUT', 'Y': 'NOTRUN', 'X': 'SKIP', 'Z': 'IMAGE+TEXT', 'K': 'LEAK'}
# All result_string's in this file represent retries from a single run.
# The left-most entry is the first try, the right-most is the last.
def _assert_is_flaky(self, results_string, should_be_flaky, only_ignore_very_flaky, expected=None):
results_json = self._results_json_from_test_data({})
expectations = bot_test_expectations.BotTestExpectations(results_json, BuilderList(BUILDERS), set('test'))
results_entry = self._results_from_string(results_string)
if expected:
results_entry[bot_test_expectations.ResultsJSON.EXPECTATIONS_KEY] = expected
num_actual_results = len(expectations._flaky_types_in_results(results_entry, only_ignore_very_flaky))
if should_be_flaky:
self.assertGreater(num_actual_results, 1)
else:
self.assertLessEqual(num_actual_results, 1)
def test_basic_flaky(self):
self._assert_is_flaky('P', should_be_flaky=False, only_ignore_very_flaky=False)
self._assert_is_flaky('P', should_be_flaky=False, only_ignore_very_flaky=True)
self._assert_is_flaky('F', should_be_flaky=False, only_ignore_very_flaky=False)
self._assert_is_flaky('F', should_be_flaky=False, only_ignore_very_flaky=True)
self._assert_is_flaky('FP', should_be_flaky=True, only_ignore_very_flaky=False)
self._assert_is_flaky('FP', should_be_flaky=False, only_ignore_very_flaky=True)
self._assert_is_flaky('FFP', should_be_flaky=True, only_ignore_very_flaky=False)
self._assert_is_flaky('FFP', should_be_flaky=True, only_ignore_very_flaky=True)
self._assert_is_flaky('FFT', should_be_flaky=True, only_ignore_very_flaky=False)
self._assert_is_flaky('FFT', should_be_flaky=True, only_ignore_very_flaky=True)
self._assert_is_flaky('FFF', should_be_flaky=False, only_ignore_very_flaky=False)
self._assert_is_flaky('FFF', should_be_flaky=False, only_ignore_very_flaky=True)
self._assert_is_flaky('FT', should_be_flaky=True, only_ignore_very_flaky=False, expected='TIMEOUT')
self._assert_is_flaky('FT', should_be_flaky=False, only_ignore_very_flaky=True, expected='TIMEOUT')
self._assert_is_flaky('FFT', should_be_flaky=True, only_ignore_very_flaky=False, expected='TIMEOUT')
self._assert_is_flaky('FFT', should_be_flaky=True, only_ignore_very_flaky=True, expected='TIMEOUT')
def _results_json_from_test_data(self, test_data):
test_data[bot_test_expectations.ResultsJSON.FAILURE_MAP_KEY] = self.FAILURE_MAP
json_dict = {
'builder': test_data,
}
return bot_test_expectations.ResultsJSON('builder', json_dict)
def _results_from_string(self, results_string):
return {'results': [[1, results_string]]}
def _assert_expectations(self, test_data, expectations_string, only_ignore_very_flaky):
results_json = self._results_json_from_test_data(test_data)
expectations = bot_test_expectations.BotTestExpectations(results_json, BuilderList(BUILDERS), set('test'))
self.assertEqual(expectations.flakes_by_path(only_ignore_very_flaky), expectations_string)
def _assert_unexpected_results(self, test_data, expectations_string):
results_json = self._results_json_from_test_data(test_data)
expectations = bot_test_expectations.BotTestExpectations(results_json, BuilderList(BUILDERS), set('test'))
self.assertEqual(expectations.unexpected_results_by_path(), expectations_string)
def test_all_results_by_path(self):
test_data = {
'tests': {
'foo': {
'multiple_pass.html': {'results': [[4, 'P'], [1, 'P'], [2, 'P']]},
'fail.html': {'results': [[2, 'Z']]},
'all_types.html': {
'results': [[2, 'A'], [1, 'C'], [2, 'F'], [1, 'I'], [1, 'O'], [1, 'N'], [1, 'P'], [1, 'T'],
[1, 'Y'], [10, 'X'], [1, 'Z'], [1, 'K']]
},
'not_run.html': {'results': []},
}
}
}
results_json = self._results_json_from_test_data(test_data)
expectations = bot_test_expectations.BotTestExpectations(results_json, BuilderList(BUILDERS), set('test'))
results_by_path = expectations.all_results_by_path()
expected_output = {
'foo/multiple_pass.html': ['PASS'],
'foo/fail.html': ['IMAGE+TEXT'],
'foo/all_types.html': ['AUDIO', 'CRASH', 'IMAGE', 'IMAGE+TEXT', 'LEAK', 'MISSING', 'PASS', 'TEXT', 'TIMEOUT']
}
self.assertEqual(results_by_path, expected_output)
def test_basic(self):
test_data = {
'tests': {
'foo': {
'veryflaky.html': self._results_from_string('FFP'),
'maybeflaky.html': self._results_from_string('FP'),
'notflakypass.html': self._results_from_string('P'),
'notflakyfail.html': self._results_from_string('F'),
# Even if there are no expected results, it's not very flaky if it didn't do multiple retries.
# This accounts for the latest expectations not necessarily matching the expectations
# at the time of the given run.
'notverflakynoexpected.html': self._results_from_string('FT'),
# If the test is flaky, but marked as such, it shouldn't get printed out.
'notflakyexpected.html': {'results': [[2, 'FFFP']], 'expected': 'PASS FAIL'},
}
}
}
self._assert_expectations(test_data, {
'foo/veryflaky.html': sorted(['TEXT', 'PASS']),
}, only_ignore_very_flaky=True)
self._assert_expectations(test_data, {
'foo/veryflaky.html': sorted(['TEXT', 'PASS']),
'foo/notverflakynoexpected.html': ['TEXT', 'TIMEOUT'],
'foo/maybeflaky.html': sorted(['TEXT', 'PASS']),
}, only_ignore_very_flaky=False)
def test_unexpected_results_no_unexpected(self):
test_data = {
'tests': {
'foo': {
'pass1.html': {'results': [[4, 'P']]},
'pass2.html': {'results': [[2, 'Z']], 'expected': 'PASS FAIL'},
'fail.html': {'results': [[2, 'P'], [1, 'F']], 'expected': 'PASS FAIL'},
'not_run.html': {'results': []},
'crash.html': {'results': [[2, 'F'], [1, 'C']], 'expected': 'CRASH FAIL WONTFIX'},
}
}
}
self._assert_unexpected_results(test_data, {})
def test_unexpected_results_all_unexpected(self):
test_data = {
'tests': {
'foo': {
'pass1.html': {'results': [[4, 'P']], 'expected': 'FAIL'},
'pass2.html': {'results': [[2, 'P']], 'expected': 'IMAGE'},
'fail.html': {'results': [[4, 'F']]},
'f_p.html': {'results': [[1, 'F'], [2, 'P']]},
'crash.html': {'results': [[2, 'F'], [1, 'C']], 'expected': 'WONTFIX'},
'image.html': {'results': [[2, 'F'], [1, 'I']], 'expected': 'CRASH TEXT'},
'i_f.html': {'results': [[1, 'F'], [5, 'I']], 'expected': 'PASS'},
'all.html': self._results_from_string('FPFPCNCNTXTXIZIZOCOCYKYK'),
}
}
}
self.maxDiff = None
self._assert_unexpected_results(test_data, {
'foo/pass1.html': sorted(['FAIL', 'PASS']),
'foo/pass2.html': sorted(['IMAGE', 'PASS']),
'foo/fail.html': sorted(['TEXT', 'PASS']),
'foo/f_p.html': sorted(['TEXT', 'PASS']),
'foo/crash.html': sorted(['WONTFIX', 'CRASH', 'TEXT']),
'foo/image.html': sorted(['CRASH', 'TEXT', 'IMAGE']),
'foo/i_f.html': sorted(['PASS', 'IMAGE', 'TEXT']),
'foo/all.html': sorted(['TEXT', 'PASS', 'IMAGE+TEXT', 'TIMEOUT', 'CRASH', 'IMAGE', 'MISSING', 'LEAK']),
})