xref: /aosp_15_r20/external/autotest/server/hosts/host_info_unittest.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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