1*9c5db199SXin Li# Lint as: python2, python3 2*9c5db199SXin Li# Copyright 2016 The Chromium OS Authors. All rights reserved. 3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 4*9c5db199SXin Li# found in the LICENSE file. 5*9c5db199SXin Li 6*9c5db199SXin Liimport six 7*9c5db199SXin Liimport inspect 8*9c5db199SXin Liimport json 9*9c5db199SXin Liimport unittest 10*9c5db199SXin Li 11*9c5db199SXin Liimport common 12*9c5db199SXin Lifrom autotest_lib.server.hosts import host_info 13*9c5db199SXin Li 14*9c5db199SXin Li 15*9c5db199SXin Liclass HostInfoTest(unittest.TestCase): 16*9c5db199SXin Li """Tests the non-trivial attributes of HostInfo.""" 17*9c5db199SXin Li 18*9c5db199SXin Li def setUp(self): 19*9c5db199SXin Li self.info = host_info.HostInfo() 20*9c5db199SXin Li 21*9c5db199SXin Li def test_info_comparison_to_wrong_type(self): 22*9c5db199SXin Li """Comparing HostInfo to a different type always returns False.""" 23*9c5db199SXin Li self.assertNotEqual(host_info.HostInfo(), 42) 24*9c5db199SXin Li self.assertNotEqual(host_info.HostInfo(), None) 25*9c5db199SXin Li # equality and non-equality are unrelated by the data model. 26*9c5db199SXin Li self.assertFalse(host_info.HostInfo() == 42) 27*9c5db199SXin Li self.assertFalse(host_info.HostInfo() == None) 28*9c5db199SXin Li 29*9c5db199SXin Li 30*9c5db199SXin Li def test_empty_infos_are_equal(self): 31*9c5db199SXin Li """Tests that empty HostInfo objects are considered equal.""" 32*9c5db199SXin Li self.assertEqual(host_info.HostInfo(), host_info.HostInfo()) 33*9c5db199SXin Li # equality and non-equality are unrelated by the data model. 34*9c5db199SXin Li self.assertFalse(host_info.HostInfo() != host_info.HostInfo()) 35*9c5db199SXin Li 36*9c5db199SXin Li 37*9c5db199SXin Li def test_non_trivial_infos_are_equal(self): 38*9c5db199SXin Li """Tests that the most complicated infos are correctly stated equal.""" 39*9c5db199SXin Li info1 = host_info.HostInfo( 40*9c5db199SXin Li labels=['label1', 'label2', 'label1'], 41*9c5db199SXin Li attributes={'attrib1': None, 'attrib2': 'val2'}, 42*9c5db199SXin Li stable_versions={"cros": "xxx-cros", "faft": "xxx-faft", "firmware": "xxx-firmware"},) 43*9c5db199SXin Li info2 = host_info.HostInfo( 44*9c5db199SXin Li labels=['label1', 'label2', 'label1'], 45*9c5db199SXin Li attributes={'attrib1': None, 'attrib2': 'val2'}, 46*9c5db199SXin Li stable_versions={"cros": "xxx-cros", "faft": "xxx-faft", "firmware": "xxx-firmware"},) 47*9c5db199SXin Li self.assertEqual(info1, info2) 48*9c5db199SXin Li # equality and non-equality are unrelated by the data model. 49*9c5db199SXin Li self.assertFalse(info1 != info2) 50*9c5db199SXin Li 51*9c5db199SXin Li 52*9c5db199SXin Li def test_non_equal_infos(self): 53*9c5db199SXin Li """Tests that HostInfo objects with different information are unequal""" 54*9c5db199SXin Li info1 = host_info.HostInfo(labels=['label']) 55*9c5db199SXin Li info2 = host_info.HostInfo(attributes={'attrib': 'value'}) 56*9c5db199SXin Li self.assertNotEqual(info1, info2) 57*9c5db199SXin Li # equality and non-equality are unrelated by the data model. 58*9c5db199SXin Li self.assertFalse(info1 == info2) 59*9c5db199SXin Li 60*9c5db199SXin Li 61*9c5db199SXin Li def test_build_needs_prefix(self): 62*9c5db199SXin Li """The build prefix is of the form '<type>-version:'""" 63*9c5db199SXin Li self.info.labels = ['cros-version', 'fwrw-version', 'fwro-version'] 64*9c5db199SXin Li self.assertIsNone(self.info.build) 65*9c5db199SXin Li 66*9c5db199SXin Li 67*9c5db199SXin Li def test_build_prefix_must_be_anchored(self): 68*9c5db199SXin Li """Ensure that build ignores prefixes occuring mid-string.""" 69*9c5db199SXin Li self.info.labels = ['not-at-start-cros-version:cros1'] 70*9c5db199SXin Li self.assertIsNone(self.info.build) 71*9c5db199SXin Li 72*9c5db199SXin Li 73*9c5db199SXin Li def test_build_ignores_firmware(self): 74*9c5db199SXin Li """build attribute should ignore firmware versions.""" 75*9c5db199SXin Li self.info.labels = ['fwrw-version:fwrw1', 'fwro-version:fwro1'] 76*9c5db199SXin Li self.assertIsNone(self.info.build) 77*9c5db199SXin Li 78*9c5db199SXin Li 79*9c5db199SXin Li def test_build_returns_first_match(self): 80*9c5db199SXin Li """When multiple labels match, first one should be used as build.""" 81*9c5db199SXin Li self.info.labels = ['cros-version:cros1', 'cros-version:cros2'] 82*9c5db199SXin Li self.assertEqual(self.info.build, 'cros1') 83*9c5db199SXin Li 84*9c5db199SXin Li 85*9c5db199SXin Li def test_build_prefer_cros_over_others(self): 86*9c5db199SXin Li """When multiple versions are available, prefer cros.""" 87*9c5db199SXin Li self.info.labels = ['cheets-version:ab1', 'cros-version:cros1'] 88*9c5db199SXin Li self.assertEqual(self.info.build, 'cros1') 89*9c5db199SXin Li self.info.labels = ['cros-version:cros1', 'cheets-version:ab1'] 90*9c5db199SXin Li self.assertEqual(self.info.build, 'cros1') 91*9c5db199SXin Li 92*9c5db199SXin Li 93*9c5db199SXin Li def test_os_no_match(self): 94*9c5db199SXin Li """Use proper prefix to search for os information.""" 95*9c5db199SXin Li self.info.labels = ['something_else', 'cros-version:hana', 96*9c5db199SXin Li 'os_without_colon'] 97*9c5db199SXin Li self.assertEqual(self.info.os, '') 98*9c5db199SXin Li 99*9c5db199SXin Li 100*9c5db199SXin Li def test_os_returns_first_match(self): 101*9c5db199SXin Li """Return the first matching os label.""" 102*9c5db199SXin Li self.info.labels = ['os:linux', 'os:windows', 'os_corrupted_label'] 103*9c5db199SXin Li self.assertEqual(self.info.os, 'linux') 104*9c5db199SXin Li 105*9c5db199SXin Li 106*9c5db199SXin Li def test_board_no_match(self): 107*9c5db199SXin Li """Use proper prefix to search for board information.""" 108*9c5db199SXin Li self.info.labels = ['something_else', 'cros-version:hana', 'os:blah', 109*9c5db199SXin Li 'board_my_board_no_colon'] 110*9c5db199SXin Li self.assertEqual(self.info.board, '') 111*9c5db199SXin Li 112*9c5db199SXin Li 113*9c5db199SXin Li def test_board_returns_first_match(self): 114*9c5db199SXin Li """Return the first matching board label.""" 115*9c5db199SXin Li self.info.labels = ['board_corrupted', 'board:walk', 'board:bored'] 116*9c5db199SXin Li self.assertEqual(self.info.board, 'walk') 117*9c5db199SXin Li 118*9c5db199SXin Li 119*9c5db199SXin Li def test_pools_no_match(self): 120*9c5db199SXin Li """Use proper prefix to search for pool information.""" 121*9c5db199SXin Li self.info.labels = ['something_else', 'cros-version:hana', 'os:blah', 122*9c5db199SXin Li 'board_my_board_no_colon', 'board:my_board'] 123*9c5db199SXin Li self.assertEqual(self.info.pools, set()) 124*9c5db199SXin Li 125*9c5db199SXin Li 126*9c5db199SXin Li def test_pools_returns_all_matches(self): 127*9c5db199SXin Li """Return all matching pool labels.""" 128*9c5db199SXin Li self.info.labels = ['board_corrupted', 'board:walk', 'board:bored', 129*9c5db199SXin Li 'pool:first_pool', 'pool:second_pool'] 130*9c5db199SXin Li self.assertEqual(self.info.pools, {'second_pool', 'first_pool'}) 131*9c5db199SXin Li 132*9c5db199SXin Li 133*9c5db199SXin Li def test_str(self): 134*9c5db199SXin Li """Checks the __str__ implementation.""" 135*9c5db199SXin Li info = host_info.HostInfo(labels=['a'], attributes={'b': 2}) 136*9c5db199SXin Li self.assertEqual(str(info), 137*9c5db199SXin Li "HostInfo[Labels: ['a'], Attributes: {'b': 2}, StableVersions: {}]") 138*9c5db199SXin Li 139*9c5db199SXin Li 140*9c5db199SXin Li def test_clear_version_labels_no_labels(self): 141*9c5db199SXin Li """When no version labels exist, do nothing for clear_version_labels.""" 142*9c5db199SXin Li original_labels = ['board:something', 'os:something_else', 143*9c5db199SXin Li 'pool:mypool', 'cheets-version-corrupted:blah', 144*9c5db199SXin Li 'cros-version'] 145*9c5db199SXin Li self.info.labels = list(original_labels) 146*9c5db199SXin Li self.info.clear_version_labels() 147*9c5db199SXin Li self.assertListEqual(self.info.labels, original_labels) 148*9c5db199SXin Li 149*9c5db199SXin Li 150*9c5db199SXin Li def test_clear_all_version_labels(self): 151*9c5db199SXin Li """Clear each recognized type of version label.""" 152*9c5db199SXin Li original_labels = ['extra_label', 'cros-version:cr1', 153*9c5db199SXin Li 'cheets-version:ab1'] 154*9c5db199SXin Li self.info.labels = list(original_labels) 155*9c5db199SXin Li self.info.clear_version_labels() 156*9c5db199SXin Li self.assertListEqual(self.info.labels, ['extra_label']) 157*9c5db199SXin Li 158*9c5db199SXin Li def test_clear_all_version_label_prefixes(self): 159*9c5db199SXin Li """Clear each recognized type of version label with empty value.""" 160*9c5db199SXin Li original_labels = ['extra_label', 'cros-version:', 'cheets-version:'] 161*9c5db199SXin Li self.info.labels = list(original_labels) 162*9c5db199SXin Li self.info.clear_version_labels() 163*9c5db199SXin Li self.assertListEqual(self.info.labels, ['extra_label']) 164*9c5db199SXin Li 165*9c5db199SXin Li 166*9c5db199SXin Li def test_set_version_labels_updates_in_place(self): 167*9c5db199SXin Li """Update version label in place if prefix already exists.""" 168*9c5db199SXin Li self.info.labels = ['extra', 'cros-version:X', 'cheets-version:Y'] 169*9c5db199SXin Li self.info.set_version_label('cros-version', 'Z') 170*9c5db199SXin Li self.assertListEqual(self.info.labels, ['extra', 'cros-version:Z', 171*9c5db199SXin Li 'cheets-version:Y']) 172*9c5db199SXin Li 173*9c5db199SXin Li def test_set_version_labels_appends(self): 174*9c5db199SXin Li """Append a new version label if the prefix doesn't exist.""" 175*9c5db199SXin Li self.info.labels = ['extra', 'cheets-version:Y'] 176*9c5db199SXin Li self.info.set_version_label('cros-version', 'Z') 177*9c5db199SXin Li self.assertListEqual(self.info.labels, ['extra', 'cheets-version:Y', 178*9c5db199SXin Li 'cros-version:Z']) 179*9c5db199SXin Li 180*9c5db199SXin Li def test_has_level_as_prefix(self): 181*9c5db199SXin Li """Check if label present as prefix with some value.""" 182*9c5db199SXin Li self.info.labels = ['lb1', 'lb2:Y'] 183*9c5db199SXin Li self.assertTrue(self.info.has_label('lb2')) 184*9c5db199SXin Li self.info.labels = ['lb1', 'lb2:'] 185*9c5db199SXin Li self.assertTrue(self.info.has_label('lb2')) 186*9c5db199SXin Li 187*9c5db199SXin Li def test_has_level_as_value(self): 188*9c5db199SXin Li """Check if label present as value.""" 189*9c5db199SXin Li self.info.labels = ['lb1', 'lb2:Y'] 190*9c5db199SXin Li self.assertTrue(self.info.has_label('lb1')) 191*9c5db199SXin Li 192*9c5db199SXin Li def test_has_level_is_not_present(self): 193*9c5db199SXin Li """Check if label present as value.""" 194*9c5db199SXin Li self.info.labels = ['lb1', 'lb2:Y'] 195*9c5db199SXin Li self.assertFalse(self.info.has_label('lb3')) 196*9c5db199SXin Li self.assertFalse(self.info.has_label('LB1')) 197*9c5db199SXin Li 198*9c5db199SXin Li 199*9c5db199SXin Liclass InMemoryHostInfoStoreTest(unittest.TestCase): 200*9c5db199SXin Li """Basic tests for CachingHostInfoStore using InMemoryHostInfoStore.""" 201*9c5db199SXin Li 202*9c5db199SXin Li def setUp(self): 203*9c5db199SXin Li self.store = host_info.InMemoryHostInfoStore() 204*9c5db199SXin Li 205*9c5db199SXin Li 206*9c5db199SXin Li def _verify_host_info_data(self, host_info, labels, attributes): 207*9c5db199SXin Li """Verifies the data in the given host_info.""" 208*9c5db199SXin Li self.assertListEqual(host_info.labels, labels) 209*9c5db199SXin Li self.assertDictEqual(host_info.attributes, attributes) 210*9c5db199SXin Li 211*9c5db199SXin Li 212*9c5db199SXin Li def test_first_get_refreshes_cache(self): 213*9c5db199SXin Li """Test that the first call to get gets the data from store.""" 214*9c5db199SXin Li self.store.info = host_info.HostInfo(['label1'], {'attrib1': 'val1'}) 215*9c5db199SXin Li got = self.store.get() 216*9c5db199SXin Li self._verify_host_info_data(got, ['label1'], {'attrib1': 'val1'}) 217*9c5db199SXin Li 218*9c5db199SXin Li 219*9c5db199SXin Li def test_repeated_get_returns_from_cache(self): 220*9c5db199SXin Li """Tests that repeated calls to get do not refresh cache.""" 221*9c5db199SXin Li self.store.info = host_info.HostInfo(['label1'], {'attrib1': 'val1'}) 222*9c5db199SXin Li got = self.store.get() 223*9c5db199SXin Li self._verify_host_info_data(got, ['label1'], {'attrib1': 'val1'}) 224*9c5db199SXin Li 225*9c5db199SXin Li self.store.info = host_info.HostInfo(['label1', 'label2'], {}) 226*9c5db199SXin Li got = self.store.get() 227*9c5db199SXin Li self._verify_host_info_data(got, ['label1'], {'attrib1': 'val1'}) 228*9c5db199SXin Li 229*9c5db199SXin Li 230*9c5db199SXin Li def test_get_uncached_always_refreshes_cache(self): 231*9c5db199SXin Li """Tests that calling get_uncached always refreshes the cache.""" 232*9c5db199SXin Li self.store.info = host_info.HostInfo(['label1'], {'attrib1': 'val1'}) 233*9c5db199SXin Li got = self.store.get(force_refresh=True) 234*9c5db199SXin Li self._verify_host_info_data(got, ['label1'], {'attrib1': 'val1'}) 235*9c5db199SXin Li 236*9c5db199SXin Li self.store.info = host_info.HostInfo(['label1', 'label2'], {}) 237*9c5db199SXin Li got = self.store.get(force_refresh=True) 238*9c5db199SXin Li self._verify_host_info_data(got, ['label1', 'label2'], {}) 239*9c5db199SXin Li 240*9c5db199SXin Li 241*9c5db199SXin Li def test_commit(self): 242*9c5db199SXin Li """Test that commit sends data to store.""" 243*9c5db199SXin Li info = host_info.HostInfo(['label1'], {'attrib1': 'val1'}) 244*9c5db199SXin Li self._verify_host_info_data(self.store.info, [], {}) 245*9c5db199SXin Li self.store.commit(info) 246*9c5db199SXin Li self._verify_host_info_data(self.store.info, ['label1'], 247*9c5db199SXin Li {'attrib1': 'val1'}) 248*9c5db199SXin Li 249*9c5db199SXin Li 250*9c5db199SXin Li def test_commit_then_get(self): 251*9c5db199SXin Li """Test a commit-get roundtrip.""" 252*9c5db199SXin Li got = self.store.get() 253*9c5db199SXin Li self._verify_host_info_data(got, [], {}) 254*9c5db199SXin Li 255*9c5db199SXin Li info = host_info.HostInfo(['label1'], {'attrib1': 'val1'}) 256*9c5db199SXin Li self.store.commit(info) 257*9c5db199SXin Li got = self.store.get() 258*9c5db199SXin Li self._verify_host_info_data(got, ['label1'], {'attrib1': 'val1'}) 259*9c5db199SXin Li 260*9c5db199SXin Li 261*9c5db199SXin Li def test_commit_then_get_uncached(self): 262*9c5db199SXin Li """Test a commit-get_uncached roundtrip.""" 263*9c5db199SXin Li got = self.store.get() 264*9c5db199SXin Li self._verify_host_info_data(got, [], {}) 265*9c5db199SXin Li 266*9c5db199SXin Li info = host_info.HostInfo(['label1'], {'attrib1': 'val1'}) 267*9c5db199SXin Li self.store.commit(info) 268*9c5db199SXin Li got = self.store.get(force_refresh=True) 269*9c5db199SXin Li self._verify_host_info_data(got, ['label1'], {'attrib1': 'val1'}) 270*9c5db199SXin Li 271*9c5db199SXin Li 272*9c5db199SXin Li def test_commit_deepcopies_data(self): 273*9c5db199SXin Li """Once commited, changes to HostInfo don't corrupt the store.""" 274*9c5db199SXin Li info = host_info.HostInfo(['label1'], {'attrib1': {'key1': 'data1'}}) 275*9c5db199SXin Li self.store.commit(info) 276*9c5db199SXin Li info.labels.append('label2') 277*9c5db199SXin Li info.attributes['attrib1']['key1'] = 'data2' 278*9c5db199SXin Li self._verify_host_info_data(self.store.info, 279*9c5db199SXin Li ['label1'], {'attrib1': {'key1': 'data1'}}) 280*9c5db199SXin Li 281*9c5db199SXin Li 282*9c5db199SXin Li def test_get_returns_deepcopy(self): 283*9c5db199SXin Li """The cached object is protected from |get| caller modifications.""" 284*9c5db199SXin Li self.store.info = host_info.HostInfo(['label1'], 285*9c5db199SXin Li {'attrib1': {'key1': 'data1'}}) 286*9c5db199SXin Li got = self.store.get() 287*9c5db199SXin Li self._verify_host_info_data(got, 288*9c5db199SXin Li ['label1'], {'attrib1': {'key1': 'data1'}}) 289*9c5db199SXin Li got.labels.append('label2') 290*9c5db199SXin Li got.attributes['attrib1']['key1'] = 'data2' 291*9c5db199SXin Li got = self.store.get() 292*9c5db199SXin Li self._verify_host_info_data(got, 293*9c5db199SXin Li ['label1'], {'attrib1': {'key1': 'data1'}}) 294*9c5db199SXin Li 295*9c5db199SXin Li 296*9c5db199SXin Li def test_str(self): 297*9c5db199SXin Li """Tests __str__ implementation.""" 298*9c5db199SXin Li self.store.info = host_info.HostInfo(['label1'], 299*9c5db199SXin Li {'attrib1': {'key1': 'data1'}}) 300*9c5db199SXin Li self.assertEqual(str(self.store), 301*9c5db199SXin Li 'InMemoryHostInfoStore[%s]' % self.store.info) 302*9c5db199SXin Li 303*9c5db199SXin Li 304*9c5db199SXin Liclass ExceptionRaisingStore(host_info.CachingHostInfoStore): 305*9c5db199SXin Li """A test class that always raises on refresh / commit.""" 306*9c5db199SXin Li 307*9c5db199SXin Li def __init__(self): 308*9c5db199SXin Li super(ExceptionRaisingStore, self).__init__() 309*9c5db199SXin Li self.refresh_raises = True 310*9c5db199SXin Li self.commit_raises = True 311*9c5db199SXin Li 312*9c5db199SXin Li 313*9c5db199SXin Li def _refresh_impl(self): 314*9c5db199SXin Li if self.refresh_raises: 315*9c5db199SXin Li raise host_info.StoreError('no can do') 316*9c5db199SXin Li return host_info.HostInfo() 317*9c5db199SXin Li 318*9c5db199SXin Li def _commit_impl(self, _): 319*9c5db199SXin Li if self.commit_raises: 320*9c5db199SXin Li raise host_info.StoreError('wont wont wont') 321*9c5db199SXin Li 322*9c5db199SXin Li 323*9c5db199SXin Liclass CachingHostInfoStoreErrorTest(unittest.TestCase): 324*9c5db199SXin Li """Tests error behaviours of CachingHostInfoStore.""" 325*9c5db199SXin Li 326*9c5db199SXin Li def setUp(self): 327*9c5db199SXin Li self.store = ExceptionRaisingStore() 328*9c5db199SXin Li 329*9c5db199SXin Li 330*9c5db199SXin Li def test_failed_refresh_cleans_cache(self): 331*9c5db199SXin Li """Checks return values when refresh raises.""" 332*9c5db199SXin Li with self.assertRaises(host_info.StoreError): 333*9c5db199SXin Li self.store.get() 334*9c5db199SXin Li # Since |get| hit an error, a subsequent get should again hit the store. 335*9c5db199SXin Li with self.assertRaises(host_info.StoreError): 336*9c5db199SXin Li self.store.get() 337*9c5db199SXin Li 338*9c5db199SXin Li 339*9c5db199SXin Li def test_failed_commit_cleans_cache(self): 340*9c5db199SXin Li """Check that a failed commit cleanes cache.""" 341*9c5db199SXin Li # Let's initialize the store without errors. 342*9c5db199SXin Li self.store.refresh_raises = False 343*9c5db199SXin Li self.store.get(force_refresh=True) 344*9c5db199SXin Li self.store.refresh_raises = True 345*9c5db199SXin Li 346*9c5db199SXin Li with self.assertRaises(host_info.StoreError): 347*9c5db199SXin Li self.store.commit(host_info.HostInfo()) 348*9c5db199SXin Li # Since |commit| hit an error, a subsequent get should again hit the 349*9c5db199SXin Li # store. 350*9c5db199SXin Li with self.assertRaises(host_info.StoreError): 351*9c5db199SXin Li self.store.get() 352*9c5db199SXin Li 353*9c5db199SXin Li 354*9c5db199SXin Liclass GetStoreFromMachineTest(unittest.TestCase): 355*9c5db199SXin Li """Tests the get_store_from_machine function.""" 356*9c5db199SXin Li 357*9c5db199SXin Li def test_machine_is_dict(self): 358*9c5db199SXin Li """We extract the store when machine is a dict.""" 359*9c5db199SXin Li machine = { 360*9c5db199SXin Li 'something': 'else', 361*9c5db199SXin Li 'host_info_store': 5 362*9c5db199SXin Li } 363*9c5db199SXin Li self.assertEqual(host_info.get_store_from_machine(machine), 5) 364*9c5db199SXin Li 365*9c5db199SXin Li 366*9c5db199SXin Li def test_machine_is_string(self): 367*9c5db199SXin Li """We return a trivial store when machine is a string.""" 368*9c5db199SXin Li machine = 'hostname' 369*9c5db199SXin Li self.assertTrue(isinstance(host_info.get_store_from_machine(machine), 370*9c5db199SXin Li host_info.InMemoryHostInfoStore)) 371*9c5db199SXin Li 372*9c5db199SXin Li 373*9c5db199SXin Liclass HostInfoJsonSerializationTestCase(unittest.TestCase): 374*9c5db199SXin Li """Tests the json_serialize and json_deserialize functions.""" 375*9c5db199SXin Li 376*9c5db199SXin Li CURRENT_SERIALIZATION_VERSION = host_info._CURRENT_SERIALIZATION_VERSION 377*9c5db199SXin Li 378*9c5db199SXin Li def test_serialize_empty(self): 379*9c5db199SXin Li """Serializing empty HostInfo results in the expected json.""" 380*9c5db199SXin Li info = host_info.HostInfo() 381*9c5db199SXin Li file_obj = six.StringIO() 382*9c5db199SXin Li host_info.json_serialize(info, file_obj) 383*9c5db199SXin Li file_obj.seek(0) 384*9c5db199SXin Li expected_dict = { 385*9c5db199SXin Li 'serializer_version': self.CURRENT_SERIALIZATION_VERSION, 386*9c5db199SXin Li 'attributes' : {}, 387*9c5db199SXin Li 'labels': [], 388*9c5db199SXin Li 'stable_versions': {}, 389*9c5db199SXin Li } 390*9c5db199SXin Li self.assertEqual(json.load(file_obj), expected_dict) 391*9c5db199SXin Li 392*9c5db199SXin Li 393*9c5db199SXin Li def test_serialize_non_empty(self): 394*9c5db199SXin Li """Serializing a populated HostInfo results in expected json.""" 395*9c5db199SXin Li info = host_info.HostInfo(labels=['label1'], 396*9c5db199SXin Li attributes={'attrib': 'val'}, 397*9c5db199SXin Li stable_versions={'cros': 'xxx-cros'}) 398*9c5db199SXin Li file_obj = six.StringIO() 399*9c5db199SXin Li host_info.json_serialize(info, file_obj) 400*9c5db199SXin Li file_obj.seek(0) 401*9c5db199SXin Li expected_dict = { 402*9c5db199SXin Li 'serializer_version': self.CURRENT_SERIALIZATION_VERSION, 403*9c5db199SXin Li 'attributes' : {'attrib': 'val'}, 404*9c5db199SXin Li 'labels': ['label1'], 405*9c5db199SXin Li 'stable_versions': {'cros': 'xxx-cros'}, 406*9c5db199SXin Li } 407*9c5db199SXin Li self.assertEqual(json.load(file_obj), expected_dict) 408*9c5db199SXin Li 409*9c5db199SXin Li 410*9c5db199SXin Li def test_round_trip_empty(self): 411*9c5db199SXin Li """Serializing - deserializing empty HostInfo keeps it unchanged.""" 412*9c5db199SXin Li info = host_info.HostInfo() 413*9c5db199SXin Li serialized_fp = six.StringIO() 414*9c5db199SXin Li host_info.json_serialize(info, serialized_fp) 415*9c5db199SXin Li serialized_fp.seek(0) 416*9c5db199SXin Li got = host_info.json_deserialize(serialized_fp) 417*9c5db199SXin Li self.assertEqual(got, info) 418*9c5db199SXin Li 419*9c5db199SXin Li 420*9c5db199SXin Li def test_round_trip_non_empty(self): 421*9c5db199SXin Li """Serializing - deserializing non-empty HostInfo keeps it unchanged.""" 422*9c5db199SXin Li info = host_info.HostInfo( 423*9c5db199SXin Li labels=['label1'], 424*9c5db199SXin Li attributes = {'attrib': 'val'}) 425*9c5db199SXin Li serialized_fp = six.StringIO() 426*9c5db199SXin Li host_info.json_serialize(info, serialized_fp) 427*9c5db199SXin Li serialized_fp.seek(0) 428*9c5db199SXin Li got = host_info.json_deserialize(serialized_fp) 429*9c5db199SXin Li self.assertEqual(got, info) 430*9c5db199SXin Li 431*9c5db199SXin Li 432*9c5db199SXin Li def test_deserialize_malformed_json_raises(self): 433*9c5db199SXin Li """Deserializing a malformed string raises.""" 434*9c5db199SXin Li with self.assertRaises(host_info.DeserializationError): 435*9c5db199SXin Li host_info.json_deserialize(six.StringIO('{labels:[')) 436*9c5db199SXin Li 437*9c5db199SXin Li 438*9c5db199SXin Li def test_deserialize_malformed_host_info_raises(self): 439*9c5db199SXin Li """Deserializing a malformed host_info raises.""" 440*9c5db199SXin Li info = host_info.HostInfo() 441*9c5db199SXin Li serialized_fp = six.StringIO() 442*9c5db199SXin Li host_info.json_serialize(info, serialized_fp) 443*9c5db199SXin Li serialized_fp.seek(0) 444*9c5db199SXin Li 445*9c5db199SXin Li serialized_dict = json.load(serialized_fp) 446*9c5db199SXin Li del serialized_dict['labels'] 447*9c5db199SXin Li serialized_no_version_str = json.dumps(serialized_dict) 448*9c5db199SXin Li 449*9c5db199SXin Li with self.assertRaises(host_info.DeserializationError): 450*9c5db199SXin Li host_info.json_deserialize( 451*9c5db199SXin Li six.StringIO(serialized_no_version_str)) 452*9c5db199SXin Li 453*9c5db199SXin Li 454*9c5db199SXin Li def test_enforce_compatibility_version_2(self): 455*9c5db199SXin Li """Tests that required fields are never dropped. 456*9c5db199SXin Li 457*9c5db199SXin Li Never change this test. If you must break compatibility, uprev the 458*9c5db199SXin Li serializer version and add a new test for the newer version. 459*9c5db199SXin Li 460*9c5db199SXin Li Adding a field to compat_info_str means we're making the new field 461*9c5db199SXin Li mandatory. This breaks backwards compatibility. 462*9c5db199SXin Li Removing a field from compat_info_str means we're no longer requiring a 463*9c5db199SXin Li field to be mandatory. This breaks forwards compatibility. 464*9c5db199SXin Li """ 465*9c5db199SXin Li compat_dict = { 466*9c5db199SXin Li 'serializer_version': 2, 467*9c5db199SXin Li 'attributes': {}, 468*9c5db199SXin Li 'labels': [] 469*9c5db199SXin Li } 470*9c5db199SXin Li serialized_str = json.dumps(compat_dict) 471*9c5db199SXin Li serialized_fp = six.StringIO(serialized_str) 472*9c5db199SXin Li host_info.json_deserialize(serialized_fp) 473*9c5db199SXin Li 474*9c5db199SXin Li 475*9c5db199SXin Li def test_serialize_pretty_print(self): 476*9c5db199SXin Li """Serializing a host_info dumps the json in human-friendly format""" 477*9c5db199SXin Li info = host_info.HostInfo(labels=['label1'], 478*9c5db199SXin Li attributes={'attrib': 'val'}) 479*9c5db199SXin Li serialized_fp = six.StringIO() 480*9c5db199SXin Li host_info.json_serialize(info, serialized_fp) 481*9c5db199SXin Li expected = """{ 482*9c5db199SXin Li "attributes": { 483*9c5db199SXin Li "attrib": "val" 484*9c5db199SXin Li }, 485*9c5db199SXin Li "labels": [ 486*9c5db199SXin Li "label1" 487*9c5db199SXin Li ], 488*9c5db199SXin Li "serializer_version": %d, 489*9c5db199SXin Li "stable_versions": {} 490*9c5db199SXin Li }""" % self.CURRENT_SERIALIZATION_VERSION 491*9c5db199SXin Li self.assertEqual(serialized_fp.getvalue(), inspect.cleandoc(expected)) 492*9c5db199SXin Li 493*9c5db199SXin Li 494*9c5db199SXin Liif __name__ == '__main__': 495*9c5db199SXin Li unittest.main() 496