| 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 import time |
| 13 | 13 |
| 14 import test_env | 14 import test_env |
| 15 | 15 |
| 16 import mock | 16 import mock |
| 17 import subprocess42 | 17 import subprocess42 |
| 18 | 18 |
| 19 from recipe_engine import fetch | 19 from recipe_engine import fetch |
| 20 from recipe_engine import requests_ssl | 20 from recipe_engine import requests_ssl |
| 21 | 21 |
| 22 | 22 |
| 23 class TestGit(unittest.TestCase): | 23 class TestGit(unittest.TestCase): |
| 24 @mock.patch('recipe_engine.fetch._run_git') | 24 |
| 25 def test_fresh_clone(self, run_git): | 25 def setUp(self): |
| 26 self._patchers = [ |
| 27 mock.patch('logging.warning'), |
| 28 mock.patch('logging.exception'), |
| 29 mock.patch('recipe_engine.fetch.GitBackend.Git._resolve_git', |
| 30 return_value='GIT'), |
| 31 ] |
| 32 for p in self._patchers: |
| 33 p.start() |
| 34 |
| 35 def tearDown(self): |
| 36 for p in reversed(self._patchers): |
| 37 p.stop() |
| 38 |
| 39 @mock.patch('recipe_engine.fetch.GitBackend.Git._execute') |
| 40 def test_fresh_clone(self, git): |
| 26 fetch.GitBackend().checkout( | 41 fetch.GitBackend().checkout( |
| 27 'repo', 'revision', 'dir', allow_fetch=True) | 42 'repo', 'revision', 'dir', allow_fetch=True) |
| 28 run_git.assert_has_calls([ | 43 git.assert_has_calls([ |
| 29 mock.call(None, 'clone', '-q', 'repo', 'dir'), | 44 mock.call('GIT', 'clone', '-q', 'repo', 'dir'), |
| 30 mock.call('dir', 'config', 'remote.origin.url', 'repo'), | 45 mock.call('GIT', '-C', 'dir', 'config', 'remote.origin.url', 'repo'), |
| 31 mock.call('dir', 'rev-parse', '-q', '--verify', 'revision^{commit}'), | 46 mock.call('GIT', '-C', 'dir', 'rev-parse', '-q', '--verify', |
| 32 mock.call('dir', 'reset', '-q', '--hard', 'revision'), | 47 'revision^{commit}'), |
| 48 mock.call('GIT', '-C', 'dir', 'reset', '-q', '--hard', 'revision'), |
| 49 ]) |
| 50 |
| 51 @mock.patch('time.sleep') |
| 52 @mock.patch('os.path.isdir') |
| 53 @mock.patch('recipe_engine.fetch.GitBackend.Git._execute') |
| 54 def test_fresh_clone_retries(self, git, isdir, sleep): |
| 55 isdir.return_value = False |
| 56 |
| 57 clone_fails = [] |
| 58 def fail_four_clones(*args): |
| 59 if 'clone' in args and len(clone_fails) < 4: |
| 60 clone_fails.append(True) |
| 61 raise subprocess42.CalledProcessError(1, args) |
| 62 return None |
| 63 git.side_effect = fail_four_clones |
| 64 |
| 65 fetch.GitBackend().checkout( |
| 66 'repo', 'revision', 'dir', allow_fetch=True) |
| 67 git.assert_has_calls([ |
| 68 mock.call('GIT', 'clone', '-q', 'repo', 'dir')] * 5 + [ |
| 69 mock.call('GIT', '-C', 'dir', 'config', 'remote.origin.url', 'repo'), |
| 70 mock.call('GIT', '-C', 'dir', 'rev-parse', '-q', '--verify', |
| 71 'revision^{commit}'), |
| 72 mock.call('GIT', '-C', 'dir', 'reset', '-q', '--hard', 'revision'), |
| 33 ]) | 73 ]) |
| 34 | 74 |
| 35 @mock.patch('os.path.isdir') | 75 @mock.patch('os.path.isdir') |
| 36 @mock.patch('recipe_engine.fetch._run_git') | 76 @mock.patch('recipe_engine.fetch.GitBackend.Git._execute') |
| 37 def test_existing_checkout(self, run_git, isdir): | 77 def test_existing_checkout(self, git, isdir): |
| 38 isdir.return_value = True | 78 isdir.return_value = True |
| 39 fetch.GitBackend().checkout( | 79 fetch.GitBackend().checkout( |
| 40 'repo', 'revision', 'dir', allow_fetch=True) | 80 'repo', 'revision', 'dir', allow_fetch=True) |
| 41 isdir.assert_has_calls([ | 81 isdir.assert_has_calls([ |
| 42 mock.call('dir'), | 82 mock.call('dir'), |
| 43 mock.call('dir/.git'), | 83 mock.call('dir/.git'), |
| 44 ]) | 84 ]) |
| 45 run_git.assert_has_calls([ | 85 git.assert_has_calls([ |
| 46 mock.call('dir', 'config', 'remote.origin.url', 'repo'), | 86 mock.call('GIT', '-C', 'dir', 'config', 'remote.origin.url', 'repo'), |
| 47 mock.call('dir', 'rev-parse', '-q', '--verify', 'revision^{commit}'), | 87 mock.call('GIT', '-C', 'dir', 'rev-parse', '-q', '--verify', |
| 48 mock.call('dir', 'reset', '-q', '--hard', 'revision'), | 88 'revision^{commit}'), |
| 89 mock.call('GIT', '-C', 'dir', 'reset', '-q', '--hard', 'revision'), |
| 49 ]) | 90 ]) |
| 50 | 91 |
| 51 @mock.patch('recipe_engine.fetch._run_git') | 92 @mock.patch('recipe_engine.fetch.GitBackend.Git._execute') |
| 52 def test_clone_not_allowed(self, run_git): | 93 def test_clone_not_allowed(self, git): |
| 53 with self.assertRaises(fetch.FetchNotAllowedError): | 94 with self.assertRaises(fetch.FetchNotAllowedError): |
| 54 fetch.GitBackend().checkout( | 95 fetch.GitBackend().checkout( |
| 55 'repo', 'revision', 'dir', allow_fetch=False) | 96 'repo', 'revision', 'dir', allow_fetch=False) |
| 56 | 97 |
| 57 @mock.patch('os.path.isdir') | 98 @mock.patch('os.path.isdir') |
| 58 @mock.patch('recipe_engine.fetch._run_git') | 99 @mock.patch('recipe_engine.fetch.GitBackend.Git._execute') |
| 59 def test_unclean_filesystem(self, run_git, isdir): | 100 def test_unclean_filesystem(self, git, isdir): |
| 60 isdir.side_effect = [True, False] | 101 isdir.side_effect = [True, False] |
| 61 with self.assertRaises(fetch.UncleanFilesystemError): | 102 with self.assertRaises(fetch.UncleanFilesystemError): |
| 62 fetch.GitBackend().checkout( | 103 fetch.GitBackend().checkout( |
| 63 'repo', 'revision', 'dir', allow_fetch=False) | 104 'repo', 'revision', 'dir', allow_fetch=False) |
| 64 isdir.assert_has_calls([ | 105 isdir.assert_has_calls([ |
| 65 mock.call('dir'), | 106 mock.call('dir'), |
| 66 mock.call('dir/.git'), | 107 mock.call('dir/.git'), |
| 67 ]) | 108 ]) |
| 68 | 109 |
| 69 @mock.patch('os.path.isdir') | 110 @mock.patch('os.path.isdir') |
| 70 @mock.patch('recipe_engine.fetch._run_git') | 111 @mock.patch('recipe_engine.fetch.GitBackend.Git._execute') |
| 71 def test_origin_mismatch(self, run_git, isdir): | 112 def test_origin_mismatch(self, git, isdir): |
| 72 run_git.return_value = 'not-repo' | 113 git.return_value = 'not-repo' |
| 73 isdir.return_value = True | 114 isdir.return_value = True |
| 74 | 115 |
| 75 # This should not raise UncleanFilesystemError, but instead | 116 # This should not raise UncleanFilesystemError, but instead |
| 76 # set the right origin automatically. | 117 # set the right origin automatically. |
| 77 fetch.GitBackend().checkout( | 118 fetch.GitBackend().checkout( |
| 78 'repo', 'revision', 'dir', allow_fetch=False) | 119 'repo', 'revision', 'dir', allow_fetch=False) |
| 79 | 120 |
| 80 isdir.assert_has_calls([ | 121 isdir.assert_has_calls([ |
| 81 mock.call('dir'), | 122 mock.call('dir'), |
| 82 mock.call('dir/.git'), | 123 mock.call('dir/.git'), |
| 83 ]) | 124 ]) |
| 84 run_git.assert_has_calls([ | 125 git.assert_has_calls([ |
| 85 mock.call('dir', 'config', 'remote.origin.url', 'repo'), | 126 mock.call('GIT', '-C', 'dir', 'config', 'remote.origin.url', 'repo'), |
| 86 ]) | 127 ]) |
| 87 | 128 |
| 88 @mock.patch('os.path.isdir') | 129 @mock.patch('os.path.isdir') |
| 89 @mock.patch('recipe_engine.fetch._run_git') | 130 @mock.patch('recipe_engine.fetch.GitBackend.Git._execute') |
| 90 def test_rev_parse_fail(self, run_git, isdir): | 131 def test_rev_parse_fail(self, git, isdir): |
| 91 run_git.side_effect = [ | 132 git.side_effect = [ |
| 92 None, | 133 None, |
| 93 subprocess42.CalledProcessError(1, ['fakecmd']), | 134 subprocess42.CalledProcessError(1, ['fakecmd']), |
| 94 None, | 135 None, |
| 95 None, | 136 None, |
| 96 ] | 137 ] |
| 97 isdir.return_value = True | 138 isdir.return_value = True |
| 98 fetch.GitBackend().checkout( | 139 fetch.GitBackend().checkout( |
| 99 'repo', 'revision', 'dir', allow_fetch=True) | 140 'repo', 'revision', 'dir', allow_fetch=True) |
| 100 isdir.assert_has_calls([ | 141 isdir.assert_has_calls([ |
| 101 mock.call('dir'), | 142 mock.call('dir'), |
| 102 mock.call('dir/.git'), | 143 mock.call('dir/.git'), |
| 103 ]) | 144 ]) |
| 104 run_git.assert_has_calls([ | 145 git.assert_has_calls([ |
| 105 mock.call('dir', 'config', 'remote.origin.url', 'repo'), | 146 mock.call('GIT', '-C', 'dir', 'config', 'remote.origin.url', 'repo'), |
| 106 mock.call('dir', 'rev-parse', '-q', '--verify', 'revision^{commit}'), | 147 mock.call('GIT', '-C', 'dir', 'rev-parse', '-q', '--verify', |
| 107 mock.call('dir', 'fetch'), | 148 'revision^{commit}'), |
| 108 mock.call('dir', 'reset', '-q', '--hard', 'revision'), | 149 mock.call('GIT', '-C', 'dir', 'fetch'), |
| 150 mock.call('GIT', '-C', 'dir', 'reset', '-q', '--hard', 'revision'), |
| 109 ]) | 151 ]) |
| 110 | 152 |
| 111 @mock.patch('os.path.isdir') | 153 @mock.patch('os.path.isdir') |
| 112 @mock.patch('recipe_engine.fetch._run_git') | 154 @mock.patch('recipe_engine.fetch.GitBackend.Git._execute') |
| 113 def test_rev_parse_fetch_not_allowed(self, run_git, isdir): | 155 def test_rev_parse_fetch_not_allowed(self, git, isdir): |
| 114 run_git.side_effect = [ | 156 git.side_effect = [ |
| 115 None, | 157 None, |
| 116 subprocess42.CalledProcessError(1, ['fakecmd']), | 158 subprocess42.CalledProcessError(1, ['fakecmd']), |
| 117 ] | 159 ] |
| 118 isdir.return_value = True | 160 isdir.return_value = True |
| 119 with self.assertRaises(fetch.FetchNotAllowedError): | 161 with self.assertRaises(fetch.FetchNotAllowedError): |
| 120 fetch.GitBackend().checkout( | 162 fetch.GitBackend().checkout( |
| 121 'repo', 'revision', 'dir', allow_fetch=False) | 163 'repo', 'revision', 'dir', allow_fetch=False) |
| 122 isdir.assert_has_calls([ | 164 isdir.assert_has_calls([ |
| 123 mock.call('dir'), | 165 mock.call('dir'), |
| 124 mock.call('dir/.git'), | 166 mock.call('dir/.git'), |
| 125 ]) | 167 ]) |
| 126 run_git.assert_has_calls([ | 168 git.assert_has_calls([ |
| 127 mock.call('dir', 'config', 'remote.origin.url', 'repo'), | 169 mock.call('GIT', '-C', 'dir', 'config', 'remote.origin.url', 'repo'), |
| 128 mock.call('dir', 'rev-parse', '-q', '--verify', 'revision^{commit}'), | 170 mock.call('GIT', '-C', 'dir', 'rev-parse', '-q', '--verify', |
| 171 'revision^{commit}'), |
| 129 ]) | 172 ]) |
| 130 | 173 |
| 131 @mock.patch('recipe_engine.fetch._run_git') | 174 @mock.patch('recipe_engine.fetch.GitBackend.Git._execute') |
| 132 def test_commit_metadata(self, run_git): | 175 def test_commit_metadata(self, git): |
| 133 run_git.side_effect = ['author', 'message'] | 176 git.side_effect = ['author', 'message'] |
| 134 result = fetch.GitBackend().commit_metadata( | 177 result = fetch.GitBackend().commit_metadata( |
| 135 'repo', 'revision', 'dir', allow_fetch=True) | 178 'repo', 'revision', 'dir', allow_fetch=True) |
| 136 self.assertEqual(result, { | 179 self.assertEqual(result, { |
| 137 'author': 'author', | 180 'author': 'author', |
| 138 'message': 'message', | 181 'message': 'message', |
| 139 }) | 182 }) |
| 140 run_git.assert_has_calls([ | 183 git.assert_has_calls([ |
| 141 mock.call('dir', 'show', '-s', '--pretty=%aE', 'revision'), | 184 mock.call('GIT', '-C', 'dir', 'show', '-s', '--pretty=%aE', 'revision'), |
| 142 mock.call('dir', 'show', '-s', '--pretty=%B', 'revision'), | 185 mock.call('GIT', '-C', 'dir', 'show', '-s', '--pretty=%B', 'revision'), |
| 143 ]) | 186 ]) |
| 144 | 187 |
| 145 | 188 |
| 146 class TestGitiles(unittest.TestCase): | 189 class TestGitiles(unittest.TestCase): |
| 147 def setUp(self): | 190 def setUp(self): |
| 148 requests_ssl.disable_check() | 191 requests_ssl.disable_check() |
| 149 | 192 |
| 150 @mock.patch('__builtin__.open', mock.mock_open()) | 193 @mock.patch('__builtin__.open', mock.mock_open()) |
| 151 @mock.patch('shutil.rmtree') | 194 @mock.patch('shutil.rmtree') |
| 152 @mock.patch('os.makedirs') | 195 @mock.patch('os.makedirs') |
| (...skipping 148 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 301 'author': 'author', | 344 'author': 'author', |
| 302 'message': 'message', | 345 'message': 'message', |
| 303 }) | 346 }) |
| 304 self.assertEqual(counts['sleeps'], 4) | 347 self.assertEqual(counts['sleeps'], 4) |
| 305 requests_get.assert_has_calls([ | 348 requests_get.assert_has_calls([ |
| 306 mock.call('repo/+/revision?format=JSON'), | 349 mock.call('repo/+/revision?format=JSON'), |
| 307 ] * 5) | 350 ] * 5) |
| 308 | 351 |
| 309 if __name__ == '__main__': | 352 if __name__ == '__main__': |
| 310 unittest.main() | 353 unittest.main() |
| OLD | NEW |