# Copyright 2017 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. from __future__ import print_function from __future__ import absolute_import import mock import unittest import common from autotest_lib.server.hosts import host_info from autotest_lib.server.hosts import shadowing_store class ShadowingStoreTestCase(unittest.TestCase): """Tests shadowing capabilities of ShadowingStore""" def test_init_commits_to_shadow(self): """Initialization updates the shadow store""" info = host_info.HostInfo(labels='blah', attributes='boo') primary = _FakeRaisingStore(info) shadow = _FakeRaisingStore() store = shadowing_store.ShadowingStore(primary, shadow) self.assertEqual(shadow.get(), info) def test_commit_commits_to_both_stores(self): """Successful commit involves committing to both stores""" primary = _FakeRaisingStore() shadow = _FakeRaisingStore() store = shadowing_store.ShadowingStore(primary, shadow) info = host_info.HostInfo(labels='blah', attributes='boo') store.commit(info) self.assertEqual(primary.get(), info) self.assertEqual(shadow.get(), info) def test_commit_ignores_failure_to_commit_to_shadow(self): """Failure to commit to shadow store does not affect the result""" init_info = host_info.HostInfo(labels='init') primary = _FakeRaisingStore(init_info) shadow = _FakeRaisingStore(init_info, raise_on_commit=True) store = shadowing_store.ShadowingStore(primary, shadow) info = host_info.HostInfo(labels='blah', attributes='boo') store.commit(info) self.assertEqual(primary.get(), info) self.assertEqual(shadow.get(), init_info) def test_refresh_validates_matching_stores(self): """Successful validation on refresh returns the correct info""" init_info = host_info.HostInfo(labels='init') primary = _FakeRaisingStore(init_info) shadow = _FakeRaisingStore(init_info) store = shadowing_store.ShadowingStore(primary, shadow) got = store.get(force_refresh=True) self.assertEqual(got, init_info) def test_refresh_ignores_failed_refresh_from_shadow_store(self): """Failure to refresh from shadow store does not affect the result""" init_info = host_info.HostInfo(labels='init') primary = _FakeRaisingStore(init_info) shadow = _FakeRaisingStore(init_info, raise_on_refresh=True) store = shadowing_store.ShadowingStore(primary, shadow) got = store.get(force_refresh=True) self.assertEqual(got, init_info) def test_refresh_complains_on_mismatching_stores(self): """Store complains on mismatched responses from the primary / shadow""" callback = mock.MagicMock() p_info = host_info.HostInfo('primary') primary = _FakeRaisingStore(p_info) shadow = _FakeRaisingStore() store = shadowing_store.ShadowingStore(primary, shadow, mismatch_callback=callback) # ShadowingStore will update shadow on initialization, so we modify it # after creating store. s_info = host_info.HostInfo('shadow') shadow.commit(s_info) got = store.get(force_refresh=True) self.assertEqual(got, p_info) callback.assert_called_once_with(p_info, s_info) def test_refresh_fixes_mismatch_in_stores(self): """On finding a mismatch, the difference is fixed by the store""" callback = mock.MagicMock() p_info = host_info.HostInfo('primary') primary = _FakeRaisingStore(p_info) shadow = _FakeRaisingStore() store = shadowing_store.ShadowingStore(primary, shadow, mismatch_callback=callback) # ShadowingStore will update shadow on initialization, so we modify it # after creating store. s_info = host_info.HostInfo('shadow') shadow.commit(s_info) got = store.get(force_refresh=True) self.assertEqual(got, p_info) callback.assert_called_once_with(p_info, s_info) self.assertEqual(got, shadow.get()) got = store.get(force_refresh=True) self.assertEqual(got, p_info) # No extra calls, just the one we already saw above. callback.assert_called_once_with(p_info, s_info) class _FakeRaisingStore(host_info.InMemoryHostInfoStore): """A fake store that raises an error on refresh / commit if requested""" def __init__(self, info=None, raise_on_refresh=False, raise_on_commit=False): """ @param info: A HostInfo to initialize the store with. @param raise_on_refresh: If True, _refresh_impl raises a StoreError. @param raise_on_commit: If True, _commit_impl raises a StoreError. """ super(_FakeRaisingStore, self).__init__(info) self._raise_on_refresh = raise_on_refresh self._raise_on_commit = raise_on_commit def _refresh_impl(self): print('refresh_impl') if self._raise_on_refresh: raise host_info.StoreError('Test refresh error') return super(_FakeRaisingStore, self)._refresh_impl() def _commit_impl(self, info): print('commit_impl: %s' % info) if self._raise_on_commit: raise host_info.StoreError('Test commit error') super(_FakeRaisingStore, self)._commit_impl(info) if __name__ == '__main__': unittest.main()