Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(70)

Side by Side Diff: recipe_engine/unittests/fetch_test.py

Issue 2362993002: More strategic retries in fetch. (Closed)
Patch Set: Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2016 The LUCI Authors. All rights reserved. 2 # Copyright 2016 The LUCI Authors. All rights reserved.
3 # Use of this source code is governed under the Apache License, Version 2.0 3 # Use of this source code is governed under the Apache License, Version 2.0
4 # that can be found in the LICENSE file. 4 # that can be found in the LICENSE file.
5 5
6 import base64 6 import base64
7 import io 7 import io
8 import json 8 import json
9 import os 9 import os
10 import sys 10 import sys
11 import unittest 11 import unittest
12 import time
12 13
13 import test_env 14 import test_env
14 15
15 import mock 16 import mock
16 import subprocess42 17 import subprocess42
17 18
18 from recipe_engine import fetch 19 from recipe_engine import fetch
19 from recipe_engine import requests_ssl 20 from recipe_engine import requests_ssl
20 21
21 22
(...skipping 129 matching lines...) Expand 10 before | Expand all | Expand 10 after
151 @mock.patch('os.makedirs') 152 @mock.patch('os.makedirs')
152 @mock.patch('tarfile.open') 153 @mock.patch('tarfile.open')
153 @mock.patch('requests.get') 154 @mock.patch('requests.get')
154 def test_checkout(self, requests_get, tarfile_open, makedirs, rmtree): 155 def test_checkout(self, requests_get, tarfile_open, makedirs, rmtree):
155 proto_text = u""" 156 proto_text = u"""
156 api_version: 1 157 api_version: 1
157 project_id: "foo" 158 project_id: "foo"
158 recipes_path: "path/to/recipes" 159 recipes_path: "path/to/recipes"
159 """.lstrip() 160 """.lstrip()
160 requests_get.side_effect = [ 161 requests_get.side_effect = [
161 mock.Mock(text=u')]}\'\n{ "commit": "abc123" }'), 162 mock.Mock(text=u')]}\'\n{ "commit": "abc123" }', status_code=200),
162 mock.Mock(text=base64.b64encode(proto_text)), 163 mock.Mock(text=base64.b64encode(proto_text), status_code=200),
163 mock.Mock(content=''), 164 mock.Mock(content='', status_code=200),
164 ] 165 ]
165 166
166 fetch.GitilesBackend().checkout( 167 fetch.GitilesBackend().checkout(
167 'repo', 'revision', 'dir', allow_fetch=True) 168 'repo', 'revision', 'dir', allow_fetch=True)
168 169
169 requests_get.assert_has_calls([ 170 requests_get.assert_has_calls([
170 mock.call('repo/+/revision?format=JSON'), 171 mock.call('repo/+/revision?format=JSON'),
171 mock.call('repo/+/abc123/infra/config/recipes.cfg?format=TEXT'), 172 mock.call('repo/+/abc123/infra/config/recipes.cfg?format=TEXT'),
172 mock.call('repo/+archive/abc123/path/to/recipes.tar.gz'), 173 mock.call('repo/+archive/abc123/path/to/recipes.tar.gz'),
173 ]) 174 ])
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
210 }, 211 },
211 { 212 {
212 'old_path': 'path2/foo', 213 'old_path': 'path2/foo',
213 'new_path': '/dev/null', 214 'new_path': '/dev/null',
214 }, 215 },
215 ], 216 ],
216 }, 217 },
217 ], 218 ],
218 } 219 }
219 requests_get.side_effect = [ 220 requests_get.side_effect = [
220 mock.Mock(text=u')]}\'\n{ "commit": "sha_a" }'), 221 mock.Mock(text=u')]}\'\n{ "commit": "sha_a" }', status_code=200),
221 mock.Mock(text=u')]}\'\n{ "commit": "sha_b" }'), 222 mock.Mock(text=u')]}\'\n{ "commit": "sha_b" }', status_code=200),
222 mock.Mock(text=u')]}\'\n%s' % json.dumps(log_json)), 223 mock.Mock(text=u')]}\'\n%s' % json.dumps(log_json), status_code=200),
223 ] 224 ]
224 225
225 self.assertEqual( 226 self.assertEqual(
226 ['ghi789', 'abc123'], 227 ['ghi789', 'abc123'],
227 fetch.GitilesBackend().updates( 228 fetch.GitilesBackend().updates(
228 'repo', 'reva', 'dir', True, 'revb', 229 'repo', 'reva', 'dir', True, 'revb',
229 ['path1', 'path2'])) 230 ['path1', 'path2']))
230 231
231 requests_get.assert_has_calls([ 232 requests_get.assert_has_calls([
232 mock.call('repo/+/reva?format=JSON'), 233 mock.call('repo/+/reva?format=JSON'),
233 mock.call('repo/+/revb?format=JSON'), 234 mock.call('repo/+/revb?format=JSON'),
234 mock.call('repo/+log/sha_a..sha_b?name-status=1&format=JSON'), 235 mock.call('repo/+log/sha_a..sha_b?name-status=1&format=JSON'),
235 ]) 236 ])
236 237
237 @mock.patch('requests.get') 238 @mock.patch('requests.get')
238 def test_commit_metadata(self, requests_get): 239 def test_commit_metadata(self, requests_get):
239 revision_json = { 240 revision_json = {
240 'author': {'email': 'author'}, 241 'author': {'email': 'author'},
241 'message': 'message', 242 'message': 'message',
242 } 243 }
243 requests_get.side_effect = [ 244 requests_get.side_effect = [
244 mock.Mock(text=u')]}\'\n%s' % json.dumps(revision_json)), 245 mock.Mock(text=u')]}\'\n%s' % json.dumps(revision_json),
246 status_code=200),
245 ] 247 ]
246 result = fetch.GitilesBackend().commit_metadata( 248 result = fetch.GitilesBackend().commit_metadata(
247 'repo', 'revision', 'dir', allow_fetch=True) 249 'repo', 'revision', 'dir', allow_fetch=True)
248 self.assertEqual(result, { 250 self.assertEqual(result, {
249 'author': 'author', 251 'author': 'author',
250 'message': 'message', 252 'message': 'message',
251 }) 253 })
252 requests_get.assert_has_calls([ 254 requests_get.assert_has_calls([
253 mock.call('repo/+/revision?format=JSON'), 255 mock.call('repo/+/revision?format=JSON'),
254 ]) 256 ])
255 257
258 @mock.patch('requests.get')
259 def test_non_transient_error(self, requests_get):
260 requests_get.side_effect = [
261 mock.Mock(text='Not permitted.', status_code=403),
262 ]
263 with self.assertRaises(fetch.GitilesFetchError):
264 fetch.GitilesBackend().commit_metadata(
265 'repo', 'revision', 'dir', allow_fetch=True)
266 requests_get.assert_has_calls([
267 mock.call('repo/+/revision?format=JSON'),
268 ])
269
270 @mock.patch('requests.get')
271 @mock.patch('time.sleep')
272 @mock.patch('logging.exception')
273 def test_transient_retry(self, logging_exception, time_sleep, requests_get):
274 counts = {
275 'sleeps': 0,
276 'fails': 0,
277 }
278
279 def count_sleep(delay):
280 counts['sleeps'] += 1
281 time_sleep.side_effect = count_sleep
282
283 revision_json = {
284 'author': {'email': 'author'},
285 'message': 'message',
286 }
287
288 # Fail transiently 4 times, but succeed on the 5th.
289 def transient_side_effect(*args, **kwargs):
290 if counts['fails'] < 4:
291 counts['fails'] += 1
292 return mock.Mock(text=u'Not permitted (%(fails)d).' % counts,
293 status_code=500)
294 return mock.Mock(text=u')]}\'\n%s' % json.dumps(revision_json),
295 status_code=200)
296 requests_get.side_effect = transient_side_effect
297
298 result = fetch.GitilesBackend().commit_metadata(
299 'repo', 'revision', 'dir', allow_fetch=True)
300 self.assertEqual(result, {
301 'author': 'author',
302 'message': 'message',
303 })
304 self.assertEqual(counts['sleeps'], 4)
305 requests_get.assert_has_calls([
306 mock.call('repo/+/revision?format=JSON'),
307 ] * 5)
256 308
257 if __name__ == '__main__': 309 if __name__ == '__main__':
258 unittest.main() 310 unittest.main()
OLDNEW
« recipe_engine/fetch.py ('K') | « recipe_engine/fetch.py ('k') | recipe_engine/util.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698