1# Copyright 2014-2016 Presslabs SRL 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14from _pygit2 import GitError 15 16from unittest.mock import MagicMock, patch, call 17from six.moves.queue import Empty 18import pygit2 19import pytest 20 21from gitfs.worker.sync import SyncWorker 22 23 24class TestSyncWorker(object): 25 def test_work(self): 26 mocked_queue = MagicMock() 27 mocked_idle = MagicMock(side_effect=ValueError) 28 29 mocked_queue.get.side_effect = Empty() 30 31 worker = SyncWorker( 32 "name", 33 "email", 34 "name", 35 "email", 36 strategy="strategy", 37 commit_queue=mocked_queue, 38 ) 39 worker.on_idle = mocked_idle 40 worker.timeout = 1 41 worker.min_idle_times = 1 42 43 with pytest.raises(ValueError): 44 worker.work() 45 46 mocked_queue.get.assert_called_once_with(timeout=1, block=True) 47 assert mocked_idle.call_count == 1 48 49 def test_on_idle_with_commits_and_merges(self): 50 mocked_sync = MagicMock() 51 mocked_syncing = MagicMock() 52 mocked_commit = MagicMock() 53 54 mocked_syncing.is_set.return_value = False 55 56 with patch.multiple( 57 "gitfs.worker.sync", syncing=mocked_syncing, writers=MagicMock(value=0) 58 ): 59 worker = SyncWorker("name", "email", "name", "email", strategy="strategy") 60 worker.commits = "commits" 61 worker.commit = mocked_commit 62 worker.sync = mocked_sync 63 64 commits = worker.on_idle() 65 66 mocked_commit.assert_called_once_with("commits") 67 assert mocked_syncing.set.call_count == 1 68 assert mocked_sync.call_count == 1 69 assert commits is None 70 71 def test_merge(self): 72 mocked_strategy = MagicMock() 73 mocked_repo = MagicMock() 74 upstream = "origin" 75 branch = "master" 76 77 worker = SyncWorker( 78 "name", 79 "email", 80 "name", 81 "email", 82 strategy=mocked_strategy, 83 repository=mocked_repo, 84 upstream=upstream, 85 branch=branch, 86 ) 87 worker.merge() 88 89 mocked_strategy.assert_called_once_with(branch, branch, upstream) 90 assert mocked_repo.commits.update.call_count == 1 91 92 def test_sync(self): 93 upstream = "origin" 94 branch = "master" 95 credentials = "credentials" 96 mocked_repo = MagicMock() 97 mocked_merge = MagicMock() 98 mocked_sync_done = MagicMock() 99 mocked_syncing = MagicMock() 100 mocked_push_successful = MagicMock() 101 mocked_fetch = MagicMock() 102 mocked_strategy = MagicMock() 103 104 mocked_repo.behind = True 105 mocked_push_successful.set.side_effect = ValueError 106 107 with patch.multiple( 108 "gitfs.worker.sync", 109 sync_done=mocked_sync_done, 110 syncing=mocked_syncing, 111 push_successful=mocked_push_successful, 112 fetch=mocked_fetch, 113 ): 114 worker = SyncWorker( 115 "name", 116 "email", 117 "name", 118 "email", 119 repository=mocked_repo, 120 strategy=mocked_strategy, 121 credentials=credentials, 122 upstream=upstream, 123 branch=branch, 124 ) 125 worker.merge = mocked_merge 126 127 worker.sync() 128 129 assert mocked_syncing.clear.call_count == 1 130 assert mocked_push_successful.clear.call_count == 1 131 assert mocked_sync_done.clear.call_count == 1 132 assert mocked_sync_done.set.call_count == 1 133 assert mocked_fetch.set.call_count == 1 134 assert mocked_push_successful.set.call_count == 1 135 assert mocked_repo.behind is False 136 mocked_repo.push.assert_called_once_with(upstream, branch, credentials) 137 138 def test_sync_with_push_conflict(self): 139 upstream = "origin" 140 branch = "master" 141 credentials = "credentials" 142 mocked_repo = MagicMock() 143 mocked_merge = MagicMock() 144 mocked_sync_done = MagicMock() 145 mocked_syncing = MagicMock() 146 mocked_push_successful = MagicMock() 147 mocked_fetch = MagicMock() 148 mocked_strategy = MagicMock() 149 150 mocked_repo.behind = True 151 mocked_repo.ahead = MagicMock(1) 152 mocked_repo.push.side_effect = [GitError("Mocked error"), None] 153 154 with patch.multiple( 155 "gitfs.worker.sync", 156 sync_done=mocked_sync_done, 157 syncing=mocked_syncing, 158 push_successful=mocked_push_successful, 159 fetch=mocked_fetch, 160 ): 161 worker = SyncWorker( 162 "name", 163 "email", 164 "name", 165 "email", 166 repository=mocked_repo, 167 strategy=mocked_strategy, 168 credentials=credentials, 169 upstream=upstream, 170 branch=branch, 171 ) 172 worker.merge = mocked_merge 173 174 while not worker.sync(): 175 pass 176 177 assert mocked_syncing.clear.call_count == 1 178 assert mocked_push_successful.clear.call_count == 1 179 assert mocked_sync_done.clear.call_count == 2 180 assert mocked_sync_done.set.call_count == 1 181 assert mocked_fetch.set.call_count == 1 182 assert mocked_push_successful.set.call_count == 1 183 assert mocked_repo.behind is False 184 assert mocked_repo.ahead.call_count == 2 185 186 mocked_repo.push.assert_has_calls( 187 [ 188 call(upstream, branch, credentials), 189 call(upstream, branch, credentials), 190 ] 191 ) 192 193 def test_commit_with_just_one_job(self): 194 mocked_repo = MagicMock() 195 196 message = "just a simple message" 197 jobs = [{"params": {"message": message}}] 198 author = ("name", "email") 199 200 worker = SyncWorker( 201 author[0], 202 author[1], 203 author[0], 204 author[1], 205 strategy="strategy", 206 repository=mocked_repo, 207 ) 208 worker.commit(jobs) 209 210 mocked_repo.commit.assert_called_once_with(message, author, author) 211 assert mocked_repo.commits.update.call_count == 1 212 213 strategy = pygit2.GIT_CHECKOUT_FORCE 214 mocked_repo.checkout_head.assert_called_once_with(strategy=strategy) 215 216 def test_commit_with_more_than_one_job(self): 217 mocked_repo = MagicMock() 218 219 message = "just a simple message" 220 jobs = [ 221 {"params": {"message": message, "add": ["path1", "path2"], "remove": []}}, 222 {"params": {"message": message, "remove": ["path2"], "add": []}}, 223 ] 224 author = ("name", "email") 225 226 worker = SyncWorker( 227 author[0], 228 author[1], 229 author[0], 230 author[1], 231 strategy="strategy", 232 repository=mocked_repo, 233 ) 234 worker.commit(jobs) 235 236 asserted_message = "Update 2 items. Added 2 items. Removed 1 items." 237 mocked_repo.commit.assert_called_once_with(asserted_message, author, author) 238 assert mocked_repo.commits.update.call_count == 1 239 240 strategy = pygit2.GIT_CHECKOUT_FORCE 241 mocked_repo.checkout_head.assert_called_once_with(strategy=strategy) 242 243 def test_switch_to_idle_mode(self): 244 mocked_queue = MagicMock() 245 mocked_idle = MagicMock(side_effect=ValueError) 246 mocked_idle_event = MagicMock() 247 248 mocked_queue.get.side_effect = Empty() 249 250 with patch.multiple("gitfs.worker.sync", idle=mocked_idle_event): 251 worker = SyncWorker( 252 "name", 253 "email", 254 "name", 255 "email", 256 strategy="strategy", 257 commit_queue=mocked_queue, 258 ) 259 worker.on_idle = mocked_idle 260 worker.timeout = 1 261 worker.min_idle_times = -1 262 263 with pytest.raises(ValueError): 264 worker.work() 265 266 mocked_queue.get.assert_called_once_with(timeout=1, block=True) 267 assert mocked_idle_event.set.call_count == 1 268 assert mocked_idle.call_count == 1 269