OLD | NEW |
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 Loading... |
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 Loading... |
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() |
OLD | NEW |