1 # Copyright (C) 2013 Google Inc. All rights reserved.
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are
7 # * Redistributions of source code must retain the above copyright
8 # notice, this list of conditions and the following disclaimer.
9 # * Redistributions in binary form must reproduce the above
10 # copyright notice, this list of conditions and the following disclaimer
11 # in the documentation and/or other materials provided with the
13 # * Neither the Google name nor the names of its
14 # contributors may be used to endorse or promote products derived from
15 # this software without specific prior written permission.
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 from webkitpy.layout_tests.layout_package import bot_test_expectations
32 from webkitpy.layout_tests.models import test_expectations
33 from webkitpy.layout_tests.port import builders
36 class BotTestExpectationsFactoryTest(unittest.TestCase):
37 def fake_results_json_for_builder(self, builder):
38 return bot_test_expectations.ResultsJSON(builder, 'Dummy content')
40 def test_expectations_for_builder(self):
41 factory = bot_test_expectations.BotTestExpectationsFactory()
42 factory._results_json_for_builder = self.fake_results_json_for_builder
44 old_builders = builders._exact_matches
45 builders._exact_matches = {
46 "Dummy builder name": {"port_name": "dummy-port", "specifiers": []},
50 self.assertIsNotNone(factory.expectations_for_builder('Dummy builder name'))
52 builders._exact_matches = old_builders
54 def test_expectations_for_port(self):
55 factory = bot_test_expectations.BotTestExpectationsFactory()
56 factory._results_json_for_builder = self.fake_results_json_for_builder
58 old_builders = builders._exact_matches
59 builders._exact_matches = {
60 "Dummy builder name": {"port_name": "dummy-port", "specifiers": []},
64 self.assertIsNotNone(factory.expectations_for_port('dummy-port'))
66 builders._exact_matches = old_builders
69 class BotTestExpectationsTest(unittest.TestCase):
70 # FIXME: Find a way to import this map from Tools/TestResultServer/model/jsonresults.py.
71 FAILURE_MAP = {"A": "AUDIO", "C": "CRASH", "F": "TEXT", "I": "IMAGE", "O": "MISSING",
72 "N": "NO DATA", "P": "PASS", "T": "TIMEOUT", "Y": "NOTRUN", "X": "SKIP", "Z": "IMAGE+TEXT", "K": "LEAK"}
74 # All result_string's in this file expect newest result
75 # on left: "PFF", means it just passed after 2 failures.
77 def _assert_is_flaky(self, results_string, should_be_flaky):
78 results_json = self._results_json_from_test_data({})
79 expectations = bot_test_expectations.BotTestExpectations(results_json, set('test'))
80 length_encoded = self._results_from_string(results_string)['results']
81 num_actual_results = len(expectations._flaky_types_in_results(length_encoded, only_ignore_very_flaky=True))
83 self.assertGreater(num_actual_results, 1)
85 self.assertEqual(num_actual_results, 1)
87 def test_basic_flaky(self):
88 self._assert_is_flaky('PFF', False) # Used to fail, but now passes.
89 self._assert_is_flaky('FFP', False) # Just started failing.
90 self._assert_is_flaky('PFPF', True) # Seen both failures and passes.
91 # self._assert_is_flaky('PPPF', True) # Should be counted as flaky but isn't yet.
92 self._assert_is_flaky('FPPP', False) # Just started failing, not flaky.
93 self._assert_is_flaky('PFFP', True) # Failed twice in a row, still flaky.
94 # Failing 3+ times in a row is unlikely to be flaky, but rather a transient failure on trunk.
95 # self._assert_is_flaky('PFFFP', False)
96 # self._assert_is_flaky('PFFFFP', False)
98 def _results_json_from_test_data(self, test_data):
99 test_data[bot_test_expectations.ResultsJSON.FAILURE_MAP_KEY] = self.FAILURE_MAP
101 'builder': test_data,
103 return bot_test_expectations.ResultsJSON('builder', json_dict)
105 def _results_from_string(self, results_string):
108 for char in results_string:
109 if char != last_char:
110 results_list.insert(0, [1, char])
112 results_list[0][0] += 1
113 return {'results': results_list}
115 def _assert_expectations(self, test_data, expectations_string, only_ignore_very_flaky):
116 results_json = self._results_json_from_test_data(test_data)
117 expectations = bot_test_expectations.BotTestExpectations(results_json, set('test'))
118 self.assertEqual(expectations.flakes_by_path(only_ignore_very_flaky), expectations_string)
120 def _assert_unexpected_results(self, test_data, expectations_string):
121 results_json = self._results_json_from_test_data(test_data)
122 expectations = bot_test_expectations.BotTestExpectations(results_json, set('test'))
123 self.assertEqual(expectations.unexpected_results_by_path(), expectations_string)
125 def test_basic(self):
129 'veryflaky.html': self._results_from_string('FPFP'),
130 'maybeflaky.html': self._results_from_string('PPFP'),
131 'notflakypass.html': self._results_from_string('PPPP'),
132 'notflakyfail.html': self._results_from_string('FFFF'),
136 self._assert_expectations(test_data, {
137 'foo/veryflaky.html': sorted(["TEXT", "PASS"]),
138 }, only_ignore_very_flaky=True)
140 self._assert_expectations(test_data, {
141 'foo/veryflaky.html': sorted(["TEXT", "PASS"]),
142 'foo/maybeflaky.html': sorted(["TEXT", "PASS"]),
143 }, only_ignore_very_flaky=False)
145 def test_all_failure_types(self):
149 'allfailures.html': self._results_from_string('FPFPCNCNTXTXIZIZOCOCYKYK'),
150 'imageplustextflake.html': self._results_from_string('ZPZPPPPPPPPPPPPPPPPP'),
154 self._assert_expectations(test_data, {
155 'foo/imageplustextflake.html': sorted(["IMAGE+TEXT", "PASS"]),
156 'foo/allfailures.html': sorted(["TEXT", "PASS", "IMAGE+TEXT", "TIMEOUT", "CRASH", "IMAGE", "MISSING", "LEAK"]),
157 }, only_ignore_very_flaky=True)
159 def test_unexpected_results_no_unexpected(self):
163 'pass1.html': {'results': [[4, 'P']]},
164 'pass2.html': {'results': [[2, 'Z']], 'expected': 'PASS FAIL'},
165 'fail.html': {'results': [[2, 'P'], [1, 'F']], 'expected': 'PASS FAIL'},
166 'not_run.html': {'results': []},
167 'crash.html': {'results': [[2, 'F'], [1, 'C']], 'expected': 'CRASH FAIL WONTFIX'},
171 self._assert_unexpected_results(test_data, {})
173 def test_unexpected_results_all_unexpected(self):
177 'pass1.html': {'results': [[4, 'P']], 'expected': 'FAIL'},
178 'pass2.html': {'results': [[2, 'P']], 'expected': 'IMAGE'},
179 'fail.html': {'results': [[4, 'F']]},
180 'f_p.html': {'results': [[1, 'F'], [2, 'P']]},
181 'crash.html': {'results': [[2, 'F'], [1, 'C']], 'expected': 'WONTFIX'},
182 'image.html': {'results': [[2, 'F'], [1, 'I']], 'expected': 'CRASH FAIL'},
183 'i_f.html': {'results': [[1, 'F'], [5, 'I']], 'expected': 'PASS'},
184 'all.html': self._results_from_string('FPFPCNCNTXTXIZIZOCOCYKYK'),
189 self._assert_unexpected_results(test_data, {
190 'foo/pass1.html': sorted(["FAIL", "PASS"]),
191 'foo/pass2.html': sorted(["IMAGE", "PASS"]),
192 'foo/fail.html': sorted(["TEXT", "PASS"]),
193 'foo/f_p.html': sorted(["TEXT", "PASS"]),
194 'foo/crash.html': sorted(["WONTFIX", "CRASH", "TEXT"]),
195 'foo/image.html': sorted(["CRASH", "FAIL", "IMAGE"]),
196 'foo/i_f.html': sorted(["PASS", "IMAGE", "TEXT"]),
197 'foo/all.html': sorted(["TEXT", "PASS", "IMAGE+TEXT", "TIMEOUT", "CRASH", "IMAGE", "MISSING", "LEAK"]),