• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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