2 # Copyright 2014 The Chromium OS Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 """Test suite for tree_status.py"""
8 from __future__ import print_function
14 sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(
15 os.path.abspath(__file__)))))
17 from chromite.cbuildbot import constants
18 from chromite.cbuildbot import tree_status
19 from chromite.lib import cros_test_lib
20 from chromite.lib import timeout_util
25 # pylint: disable=W0212,R0904
27 class TestTreeStatus(cros_test_lib.MoxTestCase):
28 """Tests TreeStatus method in cros_build_lib."""
30 status_url = 'https://chromiumos-status.appspot.com/current?format=json'
35 def _TreeStatusFile(self, message, general_state):
36 """Returns a file-like object with the status message writtin in it."""
37 my_response = self.mox.CreateMockAnything()
38 my_response.json = '{"message": "%s", "general_state": "%s"}' % (
39 message, general_state)
42 def _SetupMockTreeStatusResponses(self, status_url,
43 final_tree_status='Tree is open.',
44 final_general_state=constants.TREE_OPEN,
45 rejected_tree_status='Tree is closed.',
46 rejected_general_state=
47 constants.TREE_CLOSED,
48 rejected_status_count=0,
50 output_final_status=True):
51 """Mocks out urllib.urlopen commands to simulate a given tree status.
54 status_url: The status url that status will be fetched from.
55 final_tree_status: The final value of tree status that will be returned
57 final_general_state: The final value of 'general_state' that will be
59 rejected_tree_status: An intermediate value of tree status that will be
60 returned by urlopen and retried upon.
61 rejected_general_state: An intermediate value of 'general_state' that
62 will be returned by urlopen and retried upon.
63 rejected_status_count: The number of times urlopen will return the
65 retries_500: The number of times urlopen will fail with a 500 code.
66 output_final_status: If True, the status given by final_tree_status and
67 final_general_state will be the last status returned by urlopen. If
68 False, final_tree_status will never be returned, and instead an
69 unlimited number of times rejected_response will be returned.
72 final_response = self._TreeStatusFile(final_tree_status,
74 rejected_response = self._TreeStatusFile(rejected_tree_status,
75 rejected_general_state)
76 error_500_response = self.mox.CreateMockAnything()
77 self.mox.StubOutWithMock(urllib, 'urlopen')
79 for _ in range(retries_500):
80 urllib.urlopen(status_url).AndReturn(error_500_response)
81 error_500_response.getcode().AndReturn(500)
83 if output_final_status:
84 for _ in range(rejected_status_count):
85 urllib.urlopen(status_url).AndReturn(rejected_response)
86 rejected_response.getcode().AndReturn(200)
87 rejected_response.read().AndReturn(rejected_response.json)
89 urllib.urlopen(status_url).AndReturn(final_response)
90 final_response.getcode().AndReturn(200)
91 final_response.read().AndReturn(final_response.json)
93 urllib.urlopen(status_url).MultipleTimes().AndReturn(rejected_response)
94 rejected_response.getcode().MultipleTimes().AndReturn(200)
95 rejected_response.read().MultipleTimes().AndReturn(
96 rejected_response.json)
100 def testTreeIsOpen(self):
101 """Tests that we return True is the tree is open."""
102 self._SetupMockTreeStatusResponses(self.status_url,
103 rejected_status_count=5,
105 self.assertTrue(tree_status.IsTreeOpen(status_url=self.status_url,
108 def testTreeIsClosed(self):
109 """Tests that we return false is the tree is closed."""
110 self._SetupMockTreeStatusResponses(self.status_url,
111 output_final_status=False)
112 self.assertFalse(tree_status.IsTreeOpen(status_url=self.status_url,
115 def testTreeIsThrottled(self):
116 """Tests that we return True if the tree is throttled."""
117 self._SetupMockTreeStatusResponses(self.status_url,
118 'Tree is throttled (flaky bug on flaky builder)',
119 constants.TREE_THROTTLED)
120 self.assertTrue(tree_status.IsTreeOpen(status_url=self.status_url,
123 def testTreeIsThrottledNotOk(self):
124 """Tests that we respect throttled_ok"""
125 self._SetupMockTreeStatusResponses(self.status_url,
126 rejected_tree_status='Tree is throttled (flaky bug on flaky builder)',
127 rejected_general_state=constants.TREE_THROTTLED,
128 output_final_status=False)
129 self.assertFalse(tree_status.IsTreeOpen(status_url=self.status_url,
132 def testWaitForStatusOpen(self):
133 """Tests that we can wait for a tree open response."""
134 self._SetupMockTreeStatusResponses(self.status_url)
135 self.assertEqual(tree_status.WaitForTreeStatus(status_url=self.status_url),
139 def testWaitForStatusThrottled(self):
140 """Tests that we can wait for a tree open response."""
141 self._SetupMockTreeStatusResponses(self.status_url,
142 final_general_state=constants.TREE_THROTTLED)
143 self.assertEqual(tree_status.WaitForTreeStatus(status_url=self.status_url,
145 constants.TREE_THROTTLED)
147 def testWaitForStatusFailure(self):
148 """Tests that we can wait for a tree open response."""
149 self._SetupMockTreeStatusResponses(self.status_url,
150 output_final_status=False)
151 self.assertRaises(timeout_util.TimeoutError,
152 tree_status.WaitForTreeStatus,
153 status_url=self.status_url,
156 def testGetStatusDictParsesMessage(self):
157 """Tests that _GetStatusDict parses message correctly."""
158 self._SetupMockTreeStatusResponses(
160 final_tree_status="Tree is throttled (foo canary: taco investigating)",
161 final_general_state=constants.TREE_OPEN)
162 data = tree_status._GetStatusDict(self.status_url)
163 self.assertEqual(data[tree_status.TREE_STATUS_MESSAGE],
164 'foo canary: taco investigating')
166 def testGetStatusDictEmptyMessage(self):
167 """Tests that _GetStatusDict stores an empty string for unknown format."""
168 self._SetupMockTreeStatusResponses(
170 final_tree_status='Tree is throttled. foo canary -> crbug.com/bar',
171 final_general_state=constants.TREE_OPEN)
172 data = tree_status._GetStatusDict(self.status_url)
173 self.assertEqual(data[tree_status.TREE_STATUS_MESSAGE], '')
175 def testGetStatusDictRawMessage(self):
176 """Tests that _GetStatusDict stores raw message if requested."""
177 self._SetupMockTreeStatusResponses(self.status_url,
178 final_tree_status='Tree is open (taco).',
179 final_general_state=constants.TREE_OPEN)
180 data = tree_status._GetStatusDict(self.status_url, raw_message=True)
181 self.assertEqual(data[tree_status.TREE_STATUS_MESSAGE],
182 'Tree is open (taco).')
184 def testUpdateTreeStatusWithEpilogue(self):
185 """Tests that epilogue is appended to the message."""
186 with mock.patch.object(tree_status,'_UpdateTreeStatus') as m:
187 tree_status.UpdateTreeStatus(
188 constants.TREE_CLOSED, 'failure', announcer='foo',
190 m.assert_called_once_with(mock.ANY, 'Tree is closed (foo: failure | bar)')
192 def testUpdateTreeStatusWithoutEpilogue(self):
193 """Tests that the tree status message is created as expected."""
194 with mock.patch.object(tree_status,'_UpdateTreeStatus') as m:
195 tree_status.UpdateTreeStatus(
196 constants.TREE_CLOSED, 'failure', announcer='foo')
197 m.assert_called_once_with(mock.ANY, 'Tree is closed (foo: failure)')
199 def testUpdateTreeStatusUnknownStatus(self):
200 """Tests that the exception is raised on unknown tree status."""
201 with mock.patch.object(tree_status,'_UpdateTreeStatus'):
202 self.assertRaises(tree_status.InvalidTreeStatus,
203 tree_status.UpdateTreeStatus, 'foostatus', 'failure')
205 def testThrottlesTreeOnWithBuildNumberAndType(self):
206 """Tests that tree is throttled with the build number in the message."""
207 self._SetupMockTreeStatusResponses(self.status_url,
208 final_tree_status='Tree is open (taco)',
209 final_general_state=constants.TREE_OPEN)
210 with mock.patch.object(tree_status, '_UpdateTreeStatus') as m:
211 tree_status.ThrottleOrCloseTheTree('foo', 'failure', buildnumber=1234,
213 m.assert_called_once_with(mock.ANY,
214 'Tree is throttled (foo-i-1234: failure)')
216 def testThrottlesTreeOnWithBuildNumberAndPublicType(self):
217 """Tests that tree is throttled with the build number in the message."""
218 self._SetupMockTreeStatusResponses(self.status_url,
219 final_tree_status='Tree is open (taco)',
220 final_general_state=constants.TREE_OPEN)
221 with mock.patch.object(tree_status, '_UpdateTreeStatus') as m:
222 tree_status.ThrottleOrCloseTheTree('foo', 'failure', buildnumber=1234,
224 m.assert_called_once_with(mock.ANY,
225 'Tree is throttled (foo-p-1234: failure)')
227 def testThrottlesTreeOnOpen(self):
228 """Tests that ThrottleOrCloseTheTree throttles the tree if tree is open."""
229 self._SetupMockTreeStatusResponses(self.status_url,
230 final_tree_status='Tree is open (taco)',
231 final_general_state=constants.TREE_OPEN)
232 with mock.patch.object(tree_status,'_UpdateTreeStatus') as m:
233 tree_status.ThrottleOrCloseTheTree('foo', 'failure')
234 m.assert_called_once_with(mock.ANY, 'Tree is throttled (foo: failure)')
236 def testThrottlesTreeOnThrottled(self):
237 """Tests ThrottleOrCloseTheTree throttles the tree if tree is throttled."""
238 self._SetupMockTreeStatusResponses(
240 final_tree_status='Tree is throttled (taco)',
241 final_general_state=constants.TREE_THROTTLED)
242 with mock.patch.object(tree_status,'_UpdateTreeStatus') as m:
243 tree_status.ThrottleOrCloseTheTree('foo', 'failure')
244 # Also make sure that previous status message is included.
245 m.assert_called_once_with(mock.ANY,
246 'Tree is throttled (foo: failure | taco)')
248 def testClosesTheTreeOnClosed(self):
249 """Tests ThrottleOrCloseTheTree closes the tree if tree is closed."""
250 self._SetupMockTreeStatusResponses(
252 final_tree_status='Tree is closed (taco)',
253 final_general_state=constants.TREE_CLOSED)
254 with mock.patch.object(tree_status,'_UpdateTreeStatus') as m:
255 tree_status.ThrottleOrCloseTheTree('foo', 'failure')
256 m.assert_called_once_with(mock.ANY,
257 'Tree is closed (foo: failure | taco)')
259 def testClosesTheTreeOnMaintenance(self):
260 """Tests ThrottleOrCloseTheTree closes the tree if tree is closed."""
261 self._SetupMockTreeStatusResponses(
263 final_tree_status='Tree is under maintenance (taco)',
264 final_general_state=constants.TREE_MAINTENANCE)
265 with mock.patch.object(tree_status,'_UpdateTreeStatus') as m:
266 tree_status.ThrottleOrCloseTheTree('foo', 'failure')
267 m.assert_called_once_with(
269 'Tree is under maintenance (foo: failure | taco)')
271 def testDiscardUpdateFromTheSameAnnouncer(self):
272 """Tests we don't include messages from the same announcer."""
273 self._SetupMockTreeStatusResponses(
275 final_tree_status='Tree is throttled (foo: failure | bar: taco)',
276 final_general_state=constants.TREE_THROTTLED)
277 with mock.patch.object(tree_status,'_UpdateTreeStatus') as m:
278 tree_status.ThrottleOrCloseTheTree('foo', 'failure')
279 # Also make sure that previous status message is included.
280 m.assert_called_once_with(mock.ANY,
281 'Tree is throttled (foo: failure | bar: taco)')
284 class TestGettingSheriffEmails(cros_test_lib.MockTestCase):
285 """Tests functions related to retrieving the sheriff's email address."""
287 def testParsingSheriffEmails(self):
288 """Tests parsing the raw data to get sheriff emails."""
289 # Test parsing when there is only one sheriff.
290 raw_line = "document.write('taco')"
291 self.PatchObject(tree_status, '_OpenSheriffURL', return_value=raw_line)
292 self.assertEqual(tree_status.GetSheriffEmailAddresses('build'),
295 # Test parsing when there are multiple sheriffs.
296 raw_line = "document.write('taco, burrito')"
297 self.PatchObject(tree_status, '_OpenSheriffURL', return_value=raw_line)
298 self.assertEqual(tree_status.GetSheriffEmailAddresses('build'),
299 ['taco@google.com', 'burrito@google.com'])
301 # Test parsing when sheriff is None.
302 raw_line = "document.write('None (channel is sheriff)')"
303 self.PatchObject(tree_status, '_OpenSheriffURL', return_value=raw_line)
304 self.assertEqual(tree_status.GetSheriffEmailAddresses('lab'), [])
307 if __name__ == '__main__':