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 os 6import stat 7import unittest 8from unittest import mock 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 autotest_lib.utils.frozen_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('autotest_lib.utils.frozen_chromite.lib.locking.FileLock', 123 autospec=True) 124 def test_commit_succeeds_after_lock_retry(self, mock_file_lock_class): 125 """Tests that commit succeeds when locking requires retries. 126 127 @param mock_file_lock_class: A patched version of the locking.FileLock 128 class. 129 """ 130 mock_file_lock = mock_file_lock_class.return_value 131 mock_file_lock.__enter__.return_value = mock_file_lock 132 mock_file_lock.write_lock.side_effect = [ 133 locking.LockNotAcquiredError('Testing error'), 134 True, 135 ] 136 137 store = file_store.FileStore(self._store_file, 138 file_lock_timeout_seconds=0.1) 139 store.commit(host_info.HostInfo()) 140 self.assertEqual(2, mock_file_lock.write_lock.call_count) 141 142 143 @mock.patch('autotest_lib.utils.frozen_chromite.lib.locking.FileLock', 144 autospec=True) 145 def test_refresh_succeeds_after_lock_retry(self, mock_file_lock_class): 146 """Tests that refresh succeeds when locking requires retries. 147 148 @param mock_file_lock_class: A patched version of the locking.FileLock 149 class. 150 """ 151 mock_file_lock = mock_file_lock_class.return_value 152 mock_file_lock.__enter__.return_value = mock_file_lock 153 mock_file_lock.write_lock.side_effect = [ 154 # For first commit 155 True, 156 # For refresh 157 locking.LockNotAcquiredError('Testing error'), 158 locking.LockNotAcquiredError('Testing error'), 159 True, 160 ] 161 162 store = file_store.FileStore(self._store_file, 163 file_lock_timeout_seconds=0.1) 164 store.commit(host_info.HostInfo()) 165 store.get(force_refresh=True) 166 self.assertEqual(4, mock_file_lock.write_lock.call_count) 167 168 169 @mock.patch('autotest_lib.utils.frozen_chromite.lib.locking.FileLock', 170 autospec=True) 171 def test_commit_with_negative_timeout_clips(self, mock_file_lock_class): 172 """Commit request with negative timeout is same as 0 timeout. 173 174 @param mock_file_lock_class: A patched version of the locking.FileLock 175 class. 176 """ 177 mock_file_lock = mock_file_lock_class.return_value 178 mock_file_lock.__enter__.return_value = mock_file_lock 179 mock_file_lock.write_lock.side_effect = ( 180 locking.LockNotAcquiredError('Testing error')) 181 182 store = file_store.FileStore(self._store_file, 183 file_lock_timeout_seconds=-1) 184 with self.assertRaises(host_info.StoreError): 185 store.commit(host_info.HostInfo()) 186 self.assertEqual(1, mock_file_lock.write_lock.call_count) 187 188 189 def test_str(self): 190 """Tests the __str__ implementaiton""" 191 store = file_store.FileStore('/foo/path') 192 self.assertEqual(str(store), 'FileStore[/foo/path]') 193 194 195if __name__ == '__main__': 196 unittest.main() 197