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