1# Copyright 2017 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import mock 6import os 7import stat 8import unittest 9 10import common 11from autotest_lib.client.common_lib import autotemp 12from autotest_lib.server.hosts import file_store 13from autotest_lib.server.hosts import host_info 14from chromite.lib import locking 15 16class FileStoreTestCase(unittest.TestCase): 17 """Test file_store.FileStore functionality.""" 18 19 def setUp(self): 20 self._tempdir = autotemp.tempdir(unique_id='file_store_test') 21 self.addCleanup(self._tempdir.clean) 22 self._store_file = os.path.join(self._tempdir.name, 'store_42') 23 24 25 def test_commit_refresh_round_trip(self): 26 """Refresh-commit cycle from a single store restores HostInfo.""" 27 info = host_info.HostInfo(labels=['labels'], 28 attributes={'attrib': 'value'}) 29 store = file_store.FileStore(self._store_file) 30 store.commit(info) 31 got = store.get(force_refresh=True) 32 self.assertEqual(info, got) 33 34 35 def test_commit_refresh_separate_stores(self): 36 """Refresh-commit cycle from separate stores restores HostInfo.""" 37 info = host_info.HostInfo(labels=['labels'], 38 attributes={'attrib': 'value'}) 39 store = file_store.FileStore(self._store_file) 40 store.commit(info) 41 42 read_store = file_store.FileStore(self._store_file) 43 got = read_store.get() 44 self.assertEqual(info, got) 45 46 47 def test_empty_store_raises_on_get(self): 48 """Refresh from store before commit raises StoreError""" 49 store = file_store.FileStore(self._store_file) 50 with self.assertRaises(host_info.StoreError): 51 store.get() 52 53 54 def test_commit_blocks_for_locked_file(self): 55 """Commit blocks when the backing file is locked. 56 57 This is a greybox test. We artificially lock the backing file. 58 This test intentionally uses a real locking.FileLock to ensure that 59 locking API is used correctly. 60 """ 61 # file_lock_timeout of 0 forces no retries (speeds up the test) 62 store = file_store.FileStore(self._store_file, 63 file_lock_timeout_seconds=0) 64 file_lock = locking.FileLock(store._lock_path, 65 locktype=locking.FLOCK) 66 with file_lock.lock(), self.assertRaises(host_info.StoreError): 67 store.commit(host_info.HostInfo()) 68 69 70 def test_refresh_blocks_for_locked_file(self): 71 """Refresh blocks when the backing file is locked. 72 73 This is a greybox test. We artificially lock the backing file. 74 This test intentionally uses a real locking.FileLock to ensure that 75 locking API is used correctly. 76 """ 77 # file_lock_timeout of 0 forces no retries (speeds up the test) 78 store = file_store.FileStore(self._store_file, 79 file_lock_timeout_seconds=0) 80 store.commit(host_info.HostInfo()) 81 store.get(force_refresh=True) 82 file_lock = locking.FileLock(store._lock_path, 83 locktype=locking.FLOCK) 84 with file_lock.lock(), self.assertRaises(host_info.StoreError): 85 store.get(force_refresh=True) 86 87 88 def test_commit_to_bad_path_raises(self): 89 """Commit to a non-writable path raises StoreError.""" 90 # file_lock_timeout of 0 forces no retries (speeds up the test) 91 store = file_store.FileStore('/rooty/non-writable/path/mostly', 92 file_lock_timeout_seconds=0) 93 with self.assertRaises(host_info.StoreError): 94 store.commit(host_info.HostInfo()) 95 96 97 def test_refresh_from_non_existent_path_raises(self): 98 """Refresh from a non-existent backing file raises StoreError.""" 99 # file_lock_timeout of 0 forces no retries (speeds up the test) 100 store = file_store.FileStore(self._store_file, 101 file_lock_timeout_seconds=0) 102 store.commit(host_info.HostInfo()) 103 os.unlink(self._store_file) 104 with self.assertRaises(host_info.StoreError): 105 store.get(force_refresh=True) 106 107 108 def test_refresh_from_unreadable_path_raises(self): 109 """Refresh from an unreadable backing file raises StoreError.""" 110 # file_lock_timeout of 0 forces no retries (speeds up the test) 111 store = file_store.FileStore(self._store_file, 112 file_lock_timeout_seconds=0) 113 store.commit(host_info.HostInfo()) 114 old_mode = os.stat(self._store_file).st_mode 115 os.chmod(self._store_file, old_mode & ~stat.S_IRUSR) 116 self.addCleanup(os.chmod, self._store_file, old_mode) 117 118 with self.assertRaises(host_info.StoreError): 119 store.get(force_refresh=True) 120 121 122 @mock.patch('chromite.lib.locking.FileLock', autospec=True) 123 def test_commit_succeeds_after_lock_retry(self, mock_file_lock_class): 124 """Tests that commit succeeds when locking requires retries. 125 126 @param mock_file_lock_class: A patched version of the locking.FileLock 127 class. 128 """ 129 mock_file_lock = mock_file_lock_class.return_value 130 mock_file_lock.__enter__.return_value = mock_file_lock 131 mock_file_lock.write_lock.side_effect = [ 132 locking.LockNotAcquiredError('Testing error'), 133 True, 134 ] 135 136 store = file_store.FileStore(self._store_file, 137 file_lock_timeout_seconds=0.1) 138 store.commit(host_info.HostInfo()) 139 self.assertEqual(2, mock_file_lock.write_lock.call_count) 140 141 142 @mock.patch('chromite.lib.locking.FileLock', autospec=True) 143 def test_refresh_succeeds_after_lock_retry(self, mock_file_lock_class): 144 """Tests that refresh succeeds when locking requires retries. 145 146 @param mock_file_lock_class: A patched version of the locking.FileLock 147 class. 148 """ 149 mock_file_lock = mock_file_lock_class.return_value 150 mock_file_lock.__enter__.return_value = mock_file_lock 151 mock_file_lock.write_lock.side_effect = [ 152 # For first commit 153 True, 154 # For refresh 155 locking.LockNotAcquiredError('Testing error'), 156 locking.LockNotAcquiredError('Testing error'), 157 True, 158 ] 159 160 store = file_store.FileStore(self._store_file, 161 file_lock_timeout_seconds=0.1) 162 store.commit(host_info.HostInfo()) 163 store.get(force_refresh=True) 164 self.assertEqual(4, mock_file_lock.write_lock.call_count) 165 166 167 @mock.patch('chromite.lib.locking.FileLock', autospec=True) 168 def test_commit_with_negative_timeout_clips(self, mock_file_lock_class): 169 """Commit request with negative timeout is same as 0 timeout. 170 171 @param mock_file_lock_class: A patched version of the locking.FileLock 172 class. 173 """ 174 mock_file_lock = mock_file_lock_class.return_value 175 mock_file_lock.__enter__.return_value = mock_file_lock 176 mock_file_lock.write_lock.side_effect = ( 177 locking.LockNotAcquiredError('Testing error')) 178 179 store = file_store.FileStore(self._store_file, 180 file_lock_timeout_seconds=-1) 181 with self.assertRaises(host_info.StoreError): 182 store.commit(host_info.HostInfo()) 183 self.assertEqual(1, mock_file_lock.write_lock.call_count) 184 185 186 def test_str(self): 187 """Sanity tests the __str__ implementaiton""" 188 store = file_store.FileStore('/foo/path') 189 self.assertEqual(str(store), 'FileStore[/foo/path]') 190 191 192if __name__ == '__main__': 193 unittest.main() 194