1# Copyright 2016 Google Inc. All rights reserved. 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. 14 15import errno 16import os 17import sys 18import tempfile 19 20import mock 21import unittest2 22 23from oauth2client.contrib import locked_file 24 25 26class TestOpener(unittest2.TestCase): 27 def _make_one(self): 28 _filehandle, filename = tempfile.mkstemp() 29 os.close(_filehandle) 30 return locked_file._Opener(filename, 'r+', 'r'), filename 31 32 def test_ctor(self): 33 instance, filename = self._make_one() 34 self.assertFalse(instance._locked) 35 self.assertEqual(instance._filename, filename) 36 self.assertEqual(instance._mode, 'r+') 37 self.assertEqual(instance._fallback_mode, 'r') 38 self.assertIsNone(instance._fh) 39 self.assertIsNone(instance._lock_fd) 40 41 def test_is_locked(self): 42 instance, _ = self._make_one() 43 self.assertFalse(instance.is_locked()) 44 instance._locked = True 45 self.assertTrue(instance.is_locked()) 46 47 def test_file_handle(self): 48 instance, _ = self._make_one() 49 self.assertIsNone(instance.file_handle()) 50 fh = mock.Mock() 51 instance._fh = fh 52 self.assertEqual(instance.file_handle(), fh) 53 54 def test_filename(self): 55 instance, filename = self._make_one() 56 self.assertEqual(instance.filename(), filename) 57 58 def test_open_and_lock(self): 59 instance, _ = self._make_one() 60 instance.open_and_lock(1, 1) 61 62 def test_unlock_and_close(self): 63 instance, _ = self._make_one() 64 instance.unlock_and_close() 65 66 67class TestPosixOpener(TestOpener): 68 def _make_one(self): 69 _filehandle, filename = tempfile.mkstemp() 70 os.close(_filehandle) 71 return locked_file._PosixOpener(filename, 'r+', 'r'), filename 72 73 def test_relock_fail(self): 74 instance, _ = self._make_one() 75 instance.open_and_lock(1, 1) 76 77 self.assertTrue(instance.is_locked()) 78 self.assertIsNotNone(instance.file_handle()) 79 with self.assertRaises(locked_file.AlreadyLockedException): 80 instance.open_and_lock(1, 1) 81 82 @mock.patch('oauth2client.contrib.locked_file.open', create=True) 83 def test_lock_access_error_fallback_mode(self, mock_open): 84 # NOTE: This is a bad case. The behavior here should be that the 85 # error gets re-raised, but the module lets the if statement fall 86 # through. 87 instance, _ = self._make_one() 88 mock_open.side_effect = [IOError(errno.ENOENT, '')] 89 instance.open_and_lock(1, 1) 90 91 self.assertIsNone(instance.file_handle()) 92 self.assertTrue(instance.is_locked()) 93 94 @mock.patch('oauth2client.contrib.locked_file.open', create=True) 95 def test_lock_non_access_error(self, mock_open): 96 instance, _ = self._make_one() 97 fh_mock = mock.Mock() 98 mock_open.side_effect = [IOError(errno.EACCES, ''), fh_mock] 99 instance.open_and_lock(1, 1) 100 101 self.assertEqual(instance.file_handle(), fh_mock) 102 self.assertFalse(instance.is_locked()) 103 104 @mock.patch('oauth2client.contrib.locked_file.open', create=True) 105 def test_lock_unexpected_error(self, mock_open): 106 instance, _ = self._make_one() 107 108 with mock.patch('os.open') as mock_os_open: 109 mock_os_open.side_effect = [OSError(errno.EPERM, '')] 110 with self.assertRaises(OSError): 111 instance.open_and_lock(1, 1) 112 113 @mock.patch('oauth2client.contrib.locked_file.open', create=True) 114 @mock.patch('oauth2client.contrib.locked_file.logger') 115 @mock.patch('time.time') 116 def test_lock_timeout_error(self, mock_time, mock_logger, mock_open): 117 instance, _ = self._make_one() 118 # Make it seem like 10 seconds have passed between calls. 119 mock_time.side_effect = [0, 10] 120 121 with mock.patch('os.open') as mock_os_open: 122 # Raising EEXIST should cause it to try to retry locking. 123 mock_os_open.side_effect = [OSError(errno.EEXIST, '')] 124 instance.open_and_lock(1, 1) 125 self.assertFalse(instance.is_locked()) 126 self.assertTrue(mock_logger.warn.called) 127 128 @mock.patch('oauth2client.contrib.locked_file.open', create=True) 129 @mock.patch('oauth2client.contrib.locked_file.logger') 130 @mock.patch('time.time') 131 def test_lock_timeout_error_no_fh(self, mock_time, mock_logger, mock_open): 132 instance, _ = self._make_one() 133 # Make it seem like 10 seconds have passed between calls. 134 mock_time.side_effect = [0, 10] 135 # This will cause the retry loop to enter without a file handle. 136 fh_mock = mock.Mock() 137 mock_open.side_effect = [IOError(errno.ENOENT, ''), fh_mock] 138 139 with mock.patch('os.open') as mock_os_open: 140 # Raising EEXIST should cause it to try to retry locking. 141 mock_os_open.side_effect = [OSError(errno.EEXIST, '')] 142 instance.open_and_lock(1, 1) 143 self.assertFalse(instance.is_locked()) 144 self.assertTrue(mock_logger.warn.called) 145 self.assertEqual(instance.file_handle(), fh_mock) 146 147 @mock.patch('oauth2client.contrib.locked_file.open', create=True) 148 @mock.patch('time.time') 149 @mock.patch('time.sleep') 150 def test_lock_retry_success(self, mock_sleep, mock_time, mock_open): 151 instance, _ = self._make_one() 152 # Make it seem like 1 second has passed between calls. Extra values 153 # are needed by the logging module. 154 mock_time.side_effect = [0, 1] 155 156 with mock.patch('os.open') as mock_os_open: 157 # Raising EEXIST should cause it to try to retry locking. 158 mock_os_open.side_effect = [ 159 OSError(errno.EEXIST, ''), mock.Mock()] 160 instance.open_and_lock(10, 1) 161 print(mock_os_open.call_args_list) 162 self.assertTrue(instance.is_locked()) 163 mock_sleep.assert_called_with(1) 164 165 @mock.patch('oauth2client.contrib.locked_file.os') 166 def test_unlock(self, os_mock): 167 instance, _ = self._make_one() 168 instance._locked = True 169 lock_fd_mock = instance._lock_fd = mock.Mock() 170 instance._fh = mock.Mock() 171 172 instance.unlock_and_close() 173 174 self.assertFalse(instance.is_locked()) 175 os_mock.close.assert_called_once_with(lock_fd_mock) 176 self.assertTrue(os_mock.unlink.called) 177 self.assertTrue(instance._fh.close.called) 178 179 180class TestLockedFile(unittest2.TestCase): 181 182 @mock.patch('oauth2client.contrib.locked_file._PosixOpener') 183 def _make_one(self, opener_ctor_mock): 184 opener_mock = mock.Mock() 185 opener_ctor_mock.return_value = opener_mock 186 return locked_file.LockedFile( 187 'a_file', 'r+', 'r', use_native_locking=False), opener_mock 188 189 @mock.patch('oauth2client.contrib.locked_file._PosixOpener') 190 def test_ctor_minimal(self, opener_mock): 191 locked_file.LockedFile( 192 'a_file', 'r+', 'r', use_native_locking=False) 193 opener_mock.assert_called_with('a_file', 'r+', 'r') 194 195 @mock.patch.dict('sys.modules', { 196 'oauth2client.contrib._win32_opener': mock.Mock()}) 197 def test_ctor_native_win32(self): 198 _win32_opener_mock = sys.modules['oauth2client.contrib._win32_opener'] 199 locked_file.LockedFile( 200 'a_file', 'r+', 'r', use_native_locking=True) 201 _win32_opener_mock._Win32Opener.assert_called_with('a_file', 'r+', 'r') 202 203 @mock.patch.dict('sys.modules', { 204 'oauth2client.contrib._win32_opener': None, 205 'oauth2client.contrib._fcntl_opener': mock.Mock()}) 206 def test_ctor_native_fcntl(self): 207 _fnctl_opener_mock = sys.modules['oauth2client.contrib._fcntl_opener'] 208 locked_file.LockedFile( 209 'a_file', 'r+', 'r', use_native_locking=True) 210 _fnctl_opener_mock._FcntlOpener.assert_called_with('a_file', 'r+', 'r') 211 212 @mock.patch('oauth2client.contrib.locked_file._PosixOpener') 213 @mock.patch.dict('sys.modules', { 214 'oauth2client.contrib._win32_opener': None, 215 'oauth2client.contrib._fcntl_opener': None}) 216 def test_ctor_native_posix_fallback(self, opener_mock): 217 locked_file.LockedFile( 218 'a_file', 'r+', 'r', use_native_locking=True) 219 opener_mock.assert_called_with('a_file', 'r+', 'r') 220 221 def test_filename(self): 222 instance, opener = self._make_one() 223 opener._filename = 'some file' 224 self.assertEqual(instance.filename(), 'some file') 225 226 def test_file_handle(self): 227 instance, opener = self._make_one() 228 self.assertEqual(instance.file_handle(), opener.file_handle()) 229 self.assertTrue(opener.file_handle.called) 230 231 def test_is_locked(self): 232 instance, opener = self._make_one() 233 self.assertEqual(instance.is_locked(), opener.is_locked()) 234 self.assertTrue(opener.is_locked.called) 235 236 def test_open_and_lock(self): 237 instance, opener = self._make_one() 238 instance.open_and_lock() 239 opener.open_and_lock.assert_called_with(0, 0.05) 240 241 def test_unlock_and_close(self): 242 instance, opener = self._make_one() 243 instance.unlock_and_close() 244 opener.unlock_and_close.assert_called_with() 245