| 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 |