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 5 from __future__ import print_function 6 from __future__ import absolute_import 7 8 import mock 9 import unittest 10 11 import common 12 from autotest_lib.server.hosts import host_info 13 from autotest_lib.server.hosts import shadowing_store 14 15 16 class ShadowingStoreTestCase(unittest.TestCase): 17 """Tests shadowing capabilities of ShadowingStore""" 18 19 def test_init_commits_to_shadow(self): 20 """Initialization updates the shadow store""" 21 info = host_info.HostInfo(labels='blah', attributes='boo') 22 primary = _FakeRaisingStore(info) 23 shadow = _FakeRaisingStore() 24 store = shadowing_store.ShadowingStore(primary, shadow) 25 self.assertEqual(shadow.get(), info) 26 27 def test_commit_commits_to_both_stores(self): 28 """Successful commit involves committing to both stores""" 29 primary = _FakeRaisingStore() 30 shadow = _FakeRaisingStore() 31 store = shadowing_store.ShadowingStore(primary, shadow) 32 info = host_info.HostInfo(labels='blah', attributes='boo') 33 store.commit(info) 34 self.assertEqual(primary.get(), info) 35 self.assertEqual(shadow.get(), info) 36 37 def test_commit_ignores_failure_to_commit_to_shadow(self): 38 """Failure to commit to shadow store does not affect the result""" 39 init_info = host_info.HostInfo(labels='init') 40 primary = _FakeRaisingStore(init_info) 41 shadow = _FakeRaisingStore(init_info, raise_on_commit=True) 42 store = shadowing_store.ShadowingStore(primary, shadow) 43 info = host_info.HostInfo(labels='blah', attributes='boo') 44 store.commit(info) 45 self.assertEqual(primary.get(), info) 46 self.assertEqual(shadow.get(), init_info) 47 48 def test_refresh_validates_matching_stores(self): 49 """Successful validation on refresh returns the correct info""" 50 init_info = host_info.HostInfo(labels='init') 51 primary = _FakeRaisingStore(init_info) 52 shadow = _FakeRaisingStore(init_info) 53 store = shadowing_store.ShadowingStore(primary, shadow) 54 got = store.get(force_refresh=True) 55 self.assertEqual(got, init_info) 56 57 def test_refresh_ignores_failed_refresh_from_shadow_store(self): 58 """Failure to refresh from shadow store does not affect the result""" 59 init_info = host_info.HostInfo(labels='init') 60 primary = _FakeRaisingStore(init_info) 61 shadow = _FakeRaisingStore(init_info, raise_on_refresh=True) 62 store = shadowing_store.ShadowingStore(primary, shadow) 63 got = store.get(force_refresh=True) 64 self.assertEqual(got, init_info) 65 66 def test_refresh_complains_on_mismatching_stores(self): 67 """Store complains on mismatched responses from the primary / shadow""" 68 callback = mock.MagicMock() 69 p_info = host_info.HostInfo('primary') 70 primary = _FakeRaisingStore(p_info) 71 shadow = _FakeRaisingStore() 72 store = shadowing_store.ShadowingStore(primary, shadow, 73 mismatch_callback=callback) 74 # ShadowingStore will update shadow on initialization, so we modify it 75 # after creating store. 76 s_info = host_info.HostInfo('shadow') 77 shadow.commit(s_info) 78 79 got = store.get(force_refresh=True) 80 self.assertEqual(got, p_info) 81 callback.assert_called_once_with(p_info, s_info) 82 83 def test_refresh_fixes_mismatch_in_stores(self): 84 """On finding a mismatch, the difference is fixed by the store""" 85 callback = mock.MagicMock() 86 p_info = host_info.HostInfo('primary') 87 primary = _FakeRaisingStore(p_info) 88 shadow = _FakeRaisingStore() 89 store = shadowing_store.ShadowingStore(primary, shadow, 90 mismatch_callback=callback) 91 # ShadowingStore will update shadow on initialization, so we modify it 92 # after creating store. 93 s_info = host_info.HostInfo('shadow') 94 shadow.commit(s_info) 95 96 got = store.get(force_refresh=True) 97 self.assertEqual(got, p_info) 98 callback.assert_called_once_with(p_info, s_info) 99 self.assertEqual(got, shadow.get()) 100 101 got = store.get(force_refresh=True) 102 self.assertEqual(got, p_info) 103 # No extra calls, just the one we already saw above. 104 callback.assert_called_once_with(p_info, s_info) 105 106 107 class _FakeRaisingStore(host_info.InMemoryHostInfoStore): 108 """A fake store that raises an error on refresh / commit if requested""" 109 110 def __init__(self, info=None, raise_on_refresh=False, 111 raise_on_commit=False): 112 """ 113 @param info: A HostInfo to initialize the store with. 114 @param raise_on_refresh: If True, _refresh_impl raises a StoreError. 115 @param raise_on_commit: If True, _commit_impl raises a StoreError. 116 """ 117 super(_FakeRaisingStore, self).__init__(info) 118 self._raise_on_refresh = raise_on_refresh 119 self._raise_on_commit = raise_on_commit 120 121 def _refresh_impl(self): 122 print('refresh_impl') 123 if self._raise_on_refresh: 124 raise host_info.StoreError('Test refresh error') 125 return super(_FakeRaisingStore, self)._refresh_impl() 126 127 def _commit_impl(self, info): 128 print('commit_impl: %s' % info) 129 if self._raise_on_commit: 130 raise host_info.StoreError('Test commit error') 131 super(_FakeRaisingStore, self)._commit_impl(info) 132 133 134 if __name__ == '__main__': 135 unittest.main() 136