• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2016 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"""
6Unit tests for functions in `assign_stable_images`.
7"""
8
9
10import json
11import mock
12import os
13import sys
14import unittest
15
16import common
17from autotest_lib.server import frontend
18from autotest_lib.site_utils.stable_images import assign_stable_images
19
20
21# _OMAHA_TEST_DATA - File with JSON data to be used as test input to
22#   `_make_omaha_versions()`.  In the file, the various items in the
23#   `omaha_data` list are selected to capture various specific test
24#   cases:
25#     + Board with no "beta" channel.
26#     + Board with "beta" and another channel.
27#     + Board with only a "beta" channel.
28#     + Board with no "chrome_version" entry.
29#     + Obsolete board with "is_active" set to false.
30# The JSON content of the file is a subset of an actual
31# `omaha_status.json` file copied when the unit test was last
32# updated.
33#
34# _EXPECTED_OMAHA_VERSIONS - The expected output produced by
35#   _STUB_OMAHA_DATA.
36#
37_OMAHA_TEST_DATA = 'test_omaha_status.json'
38
39_EXPECTED_OMAHA_VERSIONS = {'auron-paine': 'R55-8872.54.0',
40                            'gale': 'R55-8872.40.9',
41                            'kevin': 'R55-8872.64.0',
42                            'zako-freon': 'R41-6680.52.0'}
43
44_DEFAULT_BOARD = assign_stable_images._DEFAULT_BOARD
45
46
47class OmahaDataTests(unittest.TestCase):
48    """Tests for the `_make_omaha_versions()` function."""
49
50    def test_make_omaha_versions(self):
51        """
52        Test `_make_omaha_versions()` against one simple input.
53
54        This is a trivial sanity test that confirms that a single
55        hard-coded input returns a correct hard-coded output.
56        """
57        module_dir = os.path.dirname(sys.modules[__name__].__file__)
58        data_file_path = os.path.join(module_dir, _OMAHA_TEST_DATA)
59        omaha_versions = assign_stable_images._make_omaha_versions(
60                json.load(open(data_file_path, 'r')))
61        self.assertEqual(omaha_versions, _EXPECTED_OMAHA_VERSIONS)
62
63
64class KeyPathTests(unittest.TestCase):
65    """Tests for the `_get_by_key_path()` function."""
66
67    DICTDICT = {'level0': 'OK', 'level1_a': {'level1_b': 'OK'}}
68
69    def _get_by_key_path(self, keypath):
70        get_by_key_path = assign_stable_images._get_by_key_path
71        return get_by_key_path(self.DICTDICT, keypath)
72
73    def _check_path_valid(self, keypath):
74        self.assertEqual(self._get_by_key_path(keypath), 'OK')
75
76    def _check_path_invalid(self, keypath):
77        self.assertIsNone(self._get_by_key_path(keypath))
78
79    def test_one_element(self):
80        """Test a single-element key path with a valid key."""
81        self._check_path_valid(['level0'])
82
83    def test_two_element(self):
84        """Test a two-element key path with a valid key."""
85        self._check_path_valid(['level1_a', 'level1_b'])
86
87    def test_one_element_invalid(self):
88        """Test a single-element key path with an invalid key."""
89        self._check_path_invalid(['absent'])
90
91    def test_two_element_invalid(self):
92        """Test a two-element key path with an invalid key."""
93        self._check_path_invalid(['level1_a', 'absent'])
94
95
96class GetFirmwareUpgradesTests(unittest.TestCase):
97    """Tests for _get_firmware_upgrades."""
98
99
100    def setUp(self):
101        self.version_map = frontend._CrosVersionMap(mock.Mock())
102
103
104    @mock.patch.object(assign_stable_images, 'get_firmware_versions')
105    def test_get_firmware_upgrades(self, mock_get_firmware_versions):
106        """Test _get_firmware_upgrades."""
107        mock_get_firmware_versions.side_effect = [
108                {'auron_paine': 'fw_version'},
109                {'blue': 'fw_version',
110                 'robo360': 'fw_version',
111                 'porbeagle': 'fw_version'}
112        ]
113        cros_versions = {
114                'coral': 'R64-10176.65.0',
115                'auron_paine': 'R64-10176.65.0'
116        }
117        boards = ['auron_paine', 'coral']
118
119        firmware_upgrades = assign_stable_images._get_firmware_upgrades(
120            self.version_map, cros_versions)
121        expected_firmware_upgrades = {
122                'auron_paine': 'fw_version',
123                'blue': 'fw_version',
124                'robo360': 'fw_version',
125                'porbeagle': 'fw_version'
126        }
127        self.assertEqual(firmware_upgrades, expected_firmware_upgrades)
128
129
130class GetFirmwareVersionsTests(unittest.TestCase):
131    """Tests for get_firmware_versions."""
132
133
134    def setUp(self):
135        self.version_map = frontend._CrosVersionMap(mock.Mock())
136        self.cros_version = 'R64-10176.65.0'
137
138
139    @mock.patch.object(assign_stable_images, '_read_gs_json_data')
140    def test_get_firmware_versions_on_normal_build(self, mock_read_gs):
141        """Test get_firmware_versions on normal build."""
142        metadata_json = """
143{
144    "unibuild": false,
145    "board-metadata":{
146        "auron_paine":{
147             "main-firmware-version":"Google_Auron_paine.6301.58.98"
148        }
149   }
150}
151        """
152        mock_read_gs.return_value = json.loads(metadata_json)
153        board = 'auron_paine'
154
155        fw_version = assign_stable_images.get_firmware_versions(
156                self.version_map, board, self.cros_version)
157        expected_version = {board: "Google_Auron_paine.6301.58.98"}
158        self.assertEqual(fw_version, expected_version)
159
160
161    @mock.patch.object(assign_stable_images, '_read_gs_json_data',
162                       side_effect = Exception('GS ERROR'))
163    def test_get_firmware_versions_with_exceptions(self, mock_read_gs):
164        """Test get_firmware_versions on normal build with exceptions."""
165        afe_mock = mock.Mock()
166        version_map = frontend._CrosVersionMap(afe_mock)
167
168        fw_version = assign_stable_images.get_firmware_versions(
169                self.version_map, 'auron_paine', self.cros_version)
170        self.assertEqual(fw_version, {'auron_paine': None})
171
172
173    @mock.patch.object(assign_stable_images, '_read_gs_json_data')
174    def test_get_firmware_versions_on_unibuild(self, mock_read_gs):
175        """Test get_firmware_version on uni-build."""
176        metadata_json = """
177{
178    "unibuild": true,
179    "board-metadata":{
180        "coral":{
181            "kernel-version":"4.4.114-r1354",
182            "models":{
183                "blue":{
184                    "main-readonly-firmware-version":"Google_Coral.10068.37.0",
185                    "main-readwrite-firmware-version":"Google_Coral.10068.39.0"
186                },
187                "robo360":{
188                    "main-readonly-firmware-version":"Google_Coral.10068.34.0",
189                    "main-readwrite-firmware-version":null
190                },
191                "porbeagle":{
192                    "main-readonly-firmware-version":null,
193                    "main-readwrite-firmware-version":null
194                }
195            }
196        }
197    }
198}
199"""
200        mock_read_gs.return_value = json.loads(metadata_json)
201
202        fw_version = assign_stable_images.get_firmware_versions(
203                self.version_map, 'coral', self.cros_version)
204        expected_version = {
205                'blue': 'Google_Coral.10068.39.0',
206                'robo360': 'Google_Coral.10068.34.0',
207                'porbeagle': None
208        }
209        self.assertEqual(fw_version, expected_version)
210
211
212    @mock.patch.object(assign_stable_images, '_read_gs_json_data')
213    def test_get_firmware_versions_on_unibuild_no_models(self, mock_read_gs):
214        """Test get_firmware_versions on uni-build without models dict."""
215        metadata_json = """
216{
217    "unibuild": true,
218    "board-metadata":{
219        "coral":{
220            "kernel-version":"4.4.114-r1354"
221        }
222    }
223}
224"""
225        mock_read_gs.return_value = json.loads(metadata_json)
226
227        fw_version = assign_stable_images.get_firmware_versions(
228                self.version_map, 'coral', self.cros_version)
229        self.assertEqual(fw_version, {'coral': None})
230
231
232class GetUpgradeTests(unittest.TestCase):
233    """Tests for the `_get_upgrade_versions()` function."""
234
235    # _VERSIONS - a list of sample version strings such as may be used
236    #   for Chrome OS, sorted from oldest to newest.  These are used to
237    #   construct test data in multiple test cases, below.
238    _VERSIONS = ['R1-1.0.0', 'R1-1.1.0', 'R2-4.0.0']
239
240    def test_board_conversions(self):
241        """
242        Test proper mapping of names from the AFE to Omaha.
243
244        Board names in Omaha don't have '_' characters; when an AFE
245        board contains '_' characters, they must be converted to '-'.
246
247        Assert that for various forms of name in the AFE mapping, the
248        converted name is the one looked up in the Omaha mapping.
249        """
250        board_equivalents = [
251            ('a-b', 'a-b'), ('c_d', 'c-d'),
252            ('e_f-g', 'e-f-g'), ('hi', 'hi')]
253        afe_versions = {
254            _DEFAULT_BOARD: self._VERSIONS[0]
255        }
256        omaha_versions = {}
257        expected = {}
258        boards = set()
259        for afe_board, omaha_board in board_equivalents:
260            boards.add(afe_board)
261            afe_versions[afe_board] = self._VERSIONS[1]
262            omaha_versions[omaha_board] = self._VERSIONS[2]
263            expected[afe_board] = self._VERSIONS[2]
264        upgrades, _ = assign_stable_images._get_upgrade_versions(
265                afe_versions, omaha_versions, boards)
266        self.assertEqual(upgrades, expected)
267
268    def test_afe_default(self):
269        """
270        Test that the AFE default board mapping is honored.
271
272        If a board isn't present in the AFE dictionary, the mapping
273        for `_DEFAULT_BOARD` should be used.
274
275        Primary assertions:
276          * When a board is present in the AFE mapping, its version
277            mapping is used.
278          * When a board is not present in the AFE mapping, the default
279            version mapping is used.
280
281        Secondarily, assert that when a mapping is absent from Omaha,
282        the AFE mapping is left unchanged.
283        """
284        afe_versions = {
285            _DEFAULT_BOARD: self._VERSIONS[0],
286            'a': self._VERSIONS[1]
287        }
288        boards = set(['a', 'b'])
289        expected = {
290            'a': self._VERSIONS[1],
291            'b': self._VERSIONS[0]
292        }
293        upgrades, _ = assign_stable_images._get_upgrade_versions(
294                afe_versions, {}, boards)
295        self.assertEqual(upgrades, expected)
296
297    def test_omaha_upgrade(self):
298        """
299        Test that upgrades from Omaha are detected.
300
301        Primary assertion:
302          * If a board is found in Omaha, and the version in Omaha is
303            newer than the AFE version, the Omaha version is the one
304            used.
305
306        Secondarily, asserts that version comparisons between various
307        specific version strings are all correct.
308        """
309        boards = set(['a'])
310        for i in range(0, len(self._VERSIONS) - 1):
311            afe_versions = {_DEFAULT_BOARD: self._VERSIONS[i]}
312            for j in range(i+1, len(self._VERSIONS)):
313                omaha_versions = {b: self._VERSIONS[j] for b in boards}
314                upgrades, _ = assign_stable_images._get_upgrade_versions(
315                        afe_versions, omaha_versions, boards)
316                self.assertEqual(upgrades, omaha_versions)
317
318    def test_no_upgrade(self):
319        """
320        Test that if Omaha is behind the AFE, it is ignored.
321
322        Primary assertion:
323          * If a board is found in Omaha, and the version in Omaha is
324            older than the AFE version, the AFE version is the one used.
325
326        Secondarily, asserts that version comparisons between various
327        specific version strings are all correct.
328        """
329        boards = set(['a'])
330        for i in range(1, len(self._VERSIONS)):
331            afe_versions = {_DEFAULT_BOARD: self._VERSIONS[i]}
332            expected = {b: self._VERSIONS[i] for b in boards}
333            for j in range(0, i):
334                omaha_versions = {b: self._VERSIONS[j] for b in boards}
335                upgrades, _ = assign_stable_images._get_upgrade_versions(
336                        afe_versions, omaha_versions, boards)
337                self.assertEqual(upgrades, expected)
338
339    def test_ignore_unused_boards(self):
340        """
341        Test that unlisted boards are ignored.
342
343        Assert that boards present in the AFE or Omaha mappings aren't
344        included in the return mappings when they aren't in the passed
345        in set of boards.
346        """
347        unused_boards = set(['a', 'b'])
348        used_boards = set(['c', 'd'])
349        afe_versions = {b: self._VERSIONS[0] for b in unused_boards}
350        afe_versions[_DEFAULT_BOARD] = self._VERSIONS[1]
351        expected = {b: self._VERSIONS[1] for b in used_boards}
352        omaha_versions = expected.copy()
353        omaha_versions.update(
354                {b: self._VERSIONS[0] for b in unused_boards})
355        upgrades, _ = assign_stable_images._get_upgrade_versions(
356                afe_versions, omaha_versions, used_boards)
357        self.assertEqual(upgrades, expected)
358
359    def test_default_unchanged(self):
360        """
361        Test correct handling when the default build is unchanged.
362
363        Assert that if in Omaha, one board in a set of three upgrades
364        from the AFE default, that the returned default board mapping is
365        the original default in the AFE.
366        """
367        boards = set(['a', 'b', 'c'])
368        afe_versions = {_DEFAULT_BOARD: self._VERSIONS[0]}
369        omaha_versions = {b: self._VERSIONS[0] for b in boards}
370        omaha_versions['c'] = self._VERSIONS[1]
371        _, new_default = assign_stable_images._get_upgrade_versions(
372                afe_versions, omaha_versions, boards)
373        self.assertEqual(new_default, self._VERSIONS[0])
374
375    def test_default_upgrade(self):
376        """
377        Test correct handling when the default build must change.
378
379        Assert that if in Omaha, two boards in a set of three upgrade
380        from the AFE default, that the returned default board mapping is
381        the new build in Omaha.
382        """
383        boards = set(['a', 'b', 'c'])
384        afe_versions = {_DEFAULT_BOARD: self._VERSIONS[0]}
385        omaha_versions = {b: self._VERSIONS[1] for b in boards}
386        omaha_versions['c'] = self._VERSIONS[0]
387        _, new_default = assign_stable_images._get_upgrade_versions(
388                afe_versions, omaha_versions, boards)
389        self.assertEqual(new_default, self._VERSIONS[1])
390
391
392# Sample version string values to be used when testing
393# `_apply_upgrades()`.
394#
395# _OLD_DEFAULT - Test value representing the default version mapping
396#   in the `old_versions` dictionary in a call to `_apply_upgrades()`.
397# _NEW_DEFAULT - Test value representing the default version mapping
398#   in the `new_versions` dictionary when a version update is being
399#   tested.
400# _OLD_VERSION - Test value representing an arbitrary version for a
401#   board that is mapped in the `old_versions` dictionary in a call to
402#   `_apply_upgrades()`.
403# _NEW_VERSION - Test value representing an arbitrary version for a
404#   board that is mapped in the `new_versions` dictionary in a call to
405#   `_apply_upgrades()`.
406#
407_OLD_DEFAULT = 'old-default-version'
408_NEW_DEFAULT = 'new-default-version'
409_OLD_VERSION = 'old-board-version'
410_NEW_VERSION = 'new-board-version'
411
412
413class _StubAFE(object):
414    """Stubbed out version of `server.frontend.AFE`."""
415
416    CROS_IMAGE_TYPE = 'cros-image-type'
417    FIRMWARE_IMAGE_TYPE = 'firmware-image-type'
418
419    def get_stable_version_map(self, image_type):
420        return image_type
421
422
423class _TestUpdater(assign_stable_images._VersionUpdater):
424    """
425    Subclass of `_VersionUpdater` for testing.
426
427    This class extends `_VersionUpdater` to provide support for testing
428    various assertions about the behavior of the base class and its
429    interactions with `_apply_cros_upgrades()` and
430    `_apply_firmware_upgrades()`.
431
432    The class tests assertions along the following lines:
433      * When applied to the original mappings, the calls to
434        `_do_set_mapping()` and `_do_delete_mapping()` create the
435        expected final mapping state.
436      * Calls to report state changes are made with the expected
437        values.
438      * There's a one-to-one match between reported and actually
439        executed changes.
440
441    """
442
443    def __init__(self, testcase):
444        super(_TestUpdater, self).__init__(_StubAFE())
445        self._testcase = testcase
446        self._default_changed = None
447        self._reported_mappings = None
448        self._updated_mappings = None
449        self._reported_deletions = None
450        self._actual_deletions = None
451        self._original_mappings = None
452        self._mappings = None
453        self._expected_mappings = None
454        self._unchanged_boards = None
455
456    def pretest_init(self, initial_versions, expected_versions):
457        """
458        Initialize for testing.
459
460        @param initial_versions   Mappings to be used as the starting
461                                  point for testing.
462        @param expected_versions  The expected final value of the
463                                  mappings after the test.
464        """
465        self._default_changed = False
466        self._reported_mappings = {}
467        self._updated_mappings = {}
468        self._reported_deletions = set()
469        self._actual_deletions = set()
470        self._original_mappings = initial_versions.copy()
471        self._mappings = initial_versions.copy()
472        self._expected_mappings = expected_versions
473        self._unchanged_boards = set()
474
475    def check_results(self, change_default):
476        """
477        Assert that observed changes match expectations.
478
479        Asserts the following:
480          * The `report_default_changed()` method was called (or not)
481            based on whether `change_default` is true (or not).
482          * The changes reported via `_report_board_changed()` match
483            the changes actually applied.
484          * The final mappings after applying requested changes match
485            the actually expected mappings.
486
487        @param old_versions   Parameter to be passed to
488                              `_apply_cros_upgrades()`.
489        @param new_versions   Parameter to be passed to
490                              `_apply_cros_upgrades()`.
491        @param change_default   Whether the test should include a change
492                                to the default version mapping.
493        """
494        self._testcase.assertEqual(change_default,
495                                   self._default_changed)
496        self._testcase.assertEqual(self._reported_mappings,
497                                   self._updated_mappings)
498        self._testcase.assertEqual(self._reported_deletions,
499                                   self._actual_deletions)
500        self._testcase.assertEqual(self._mappings,
501                                   self._expected_mappings)
502
503    def report(self, message):
504        """Report message."""
505        pass
506
507    def report_default_changed(self, old_default, new_default):
508        """
509        Override of our parent class' method for test purposes.
510
511        Saves a record of the report for testing the final result in
512        `apply_upgrades()`, above.
513
514        Assert the following:
515          * The old and new default values match the values that
516            were passed in the original call's arguments.
517          * This function is not being called for a second time.
518
519        @param old_default  The original default version.
520        @param new_default  The new default version to be applied.
521        """
522        self._testcase.assertNotEqual(old_default, new_default)
523        self._testcase.assertEqual(old_default,
524                                   self._original_mappings[_DEFAULT_BOARD])
525        self._testcase.assertEqual(new_default,
526                                   self._expected_mappings[_DEFAULT_BOARD])
527        self._testcase.assertFalse(self._default_changed)
528        self._default_changed = True
529        self._reported_mappings[_DEFAULT_BOARD] = new_default
530
531    def _report_board_changed(self, board, old_version, new_version):
532        """
533        Override of our parent class' method for test purposes.
534
535        Saves a record of the report for testing the final result in
536        `apply_upgrades()`, above.
537
538        Assert the following:
539          * The change being reported actually reports two different
540            versions.
541          * If the board isn't mapped to the default version, then the
542            reported old version is the actually mapped old version.
543          * If the board isn't changing to the default version, then the
544            reported new version is the expected new version.
545          * This is not a second report for this board.
546
547        The implementation implicitly requires that the specified board
548        have a valid mapping.
549
550        @param board        The board with the changing version.
551        @param old_version  The original version mapped to the board.
552        @param new_version  The new version to be applied to the board.
553        """
554        self._testcase.assertNotEqual(old_version, new_version)
555        if board in self._original_mappings:
556            self._testcase.assertEqual(old_version,
557                                       self._original_mappings[board])
558        if board in self._expected_mappings:
559            self._testcase.assertEqual(new_version,
560                                       self._expected_mappings[board])
561            self._testcase.assertNotIn(board, self._reported_mappings)
562            self._reported_mappings[board] = new_version
563        else:
564            self._testcase.assertNotIn(board, self._reported_deletions)
565            self._reported_deletions.add(board)
566
567    def report_board_unchanged(self, board, old_version):
568        """
569        Override of our parent class' method for test purposes.
570
571        Assert the following:
572          * The version being reported as unchanged is actually mapped.
573          * The reported old version matches the expected value.
574          * This is not a second report for this board.
575
576        @param board        The board that is not changing.
577        @param old_version  The board's version mapping.
578        """
579        self._testcase.assertIn(board, self._original_mappings)
580        self._testcase.assertEqual(old_version,
581                                   self._original_mappings[board])
582        self._testcase.assertNotIn(board, self._unchanged_boards)
583        self._unchanged_boards.add(board)
584
585    def _do_set_mapping(self, board, new_version):
586        """
587        Override of our parent class' method for test purposes.
588
589        Saves a record of the change for testing the final result in
590        `apply_upgrades()`, above.
591
592        Assert the following:
593          * This is not a second change for this board.
594          * If we're changing the default mapping, then every board
595            that will be changing to a non-default mapping has been
596            updated.
597
598        @param board        The board with the changing version.
599        @param new_version  The new version to be applied to the board.
600        """
601        self._mappings[board] = new_version
602        self._testcase.assertNotIn(board, self._updated_mappings)
603        self._updated_mappings[board] = new_version
604        if board == _DEFAULT_BOARD:
605            for board in self._expected_mappings:
606                self._testcase.assertIn(board, self._mappings)
607
608    def _do_delete_mapping(self, board):
609        """
610        Override of our parent class' method for test purposes.
611
612        Saves a record of the change for testing the final result in
613        `apply_upgrades()`, above.
614
615        Assert that the board has a mapping prior to deletion.
616
617        @param board        The board with the version to be deleted.
618        """
619        self._testcase.assertNotEqual(board, _DEFAULT_BOARD)
620        self._testcase.assertIn(board, self._mappings)
621        del self._mappings[board]
622        self._actual_deletions.add(board)
623
624
625class ApplyCrOSUpgradesTests(unittest.TestCase):
626    """Tests for the `_apply_cros_upgrades()` function."""
627
628    def _apply_upgrades(self, old_versions, new_versions, change_default):
629        """
630        Test a single call to `_apply_cros_upgrades()`.
631
632        All assertions are handled by an instance of `_TestUpdater`.
633
634        @param old_versions   Parameter to be passed to
635                              `_apply_cros_upgrades()`.
636        @param new_versions   Parameter to be passed to
637                              `_apply_cros_upgrades()`.
638        @param change_default   Whether the test should include a change
639                                to the default version mapping.
640        """
641        old_versions[_DEFAULT_BOARD] = _OLD_DEFAULT
642        if change_default:
643            new_default = _NEW_DEFAULT
644        else:
645            new_default = _OLD_DEFAULT
646        expected_versions = {
647            b: v for b, v in new_versions.items() if v != new_default
648        }
649        expected_versions[_DEFAULT_BOARD] = new_default
650        updater = _TestUpdater(self)
651        updater.pretest_init(old_versions, expected_versions)
652        assign_stable_images._apply_cros_upgrades(
653            updater, old_versions, new_versions, new_default)
654        updater.check_results(change_default)
655
656    def test_no_changes(self):
657        """
658        Test an empty upgrade that does nothing.
659
660        Test the boundary case of an upgrade where there are no boards,
661        and the default does not change.
662        """
663        self._apply_upgrades({}, {}, False)
664
665    def test_change_default(self):
666        """
667        Test an empty upgrade that merely changes the default.
668
669        Test the boundary case of an upgrade where there are no boards,
670        but the default is upgraded.
671        """
672        self._apply_upgrades({}, {}, True)
673
674    def test_board_default_no_changes(self):
675        """
676        Test that a board at default stays with an unchanged default.
677
678        Test the case of a board that is mapped to the default, where
679        neither the board nor the default change.
680        """
681        self._apply_upgrades({}, {'board': _OLD_DEFAULT}, False)
682
683    def test_board_left_behind(self):
684        """
685        Test a board left at the old default after a default upgrade.
686
687        Test the case of a board that stays mapped to the old default as
688        the default board is upgraded.
689        """
690        self._apply_upgrades({}, {'board': _OLD_DEFAULT}, True)
691
692    def test_board_upgrade_from_default(self):
693        """
694        Test upgrading a board from a default that doesn't change.
695
696        Test the case of upgrading a board from default to non-default,
697        where the default doesn't change.
698        """
699        self._apply_upgrades({}, {'board': _NEW_VERSION}, False)
700
701    def test_board_and_default_diverge(self):
702        """
703        Test upgrading a board that diverges from the default.
704
705        Test the case of upgrading a board and default together from the
706        same to different versions.
707        """
708        self._apply_upgrades({}, {'board': _NEW_VERSION}, True)
709
710    def test_board_tracks_default(self):
711        """
712        Test upgrading a board to track a default upgrade.
713
714        Test the case of upgrading a board and the default together.
715        """
716        self._apply_upgrades({}, {'board': _NEW_DEFAULT}, True)
717
718    def test_board_non_default_no_changes(self):
719        """
720        Test an upgrade with no changes to a board or the default.
721
722        Test the case of an upgrade with a board in it, where neither
723        the board nor the default change.
724        """
725        self._apply_upgrades({'board': _NEW_VERSION},
726                             {'board': _NEW_VERSION},
727                             False)
728
729    def test_board_upgrade_and_keep_default(self):
730        """
731        Test a board upgrade with an unchanged default.
732
733        Test the case of upgrading a board while the default stays the
734        same.
735        """
736        self._apply_upgrades({'board': _OLD_VERSION},
737                             {'board': _NEW_VERSION},
738                             False)
739
740    def test_board_upgrade_and_change_default(self):
741        """
742        Test upgrading a board and the default separately.
743
744        Test the case of upgrading both a board and the default, each
745        from and to different versions.
746        """
747        self._apply_upgrades({'board': _OLD_VERSION},
748                             {'board': _NEW_VERSION},
749                             True)
750
751    def test_board_leads_default(self):
752        """
753        Test a board that upgrades ahead of the new default.
754
755        Test the case of upgrading both a board and the default, where
756        the board's old version is the new default version.
757        """
758        self._apply_upgrades({'board': _NEW_DEFAULT},
759                             {'board': _NEW_VERSION},
760                             True)
761
762    def test_board_lags_to_old_default(self):
763        """
764        Test a board that upgrades behind the old default.
765
766        Test the case of upgrading both a board and the default, where
767        the board's new version is the old default version.
768        """
769        self._apply_upgrades({'board': _OLD_VERSION},
770                             {'board': _OLD_DEFAULT},
771                             True)
772
773    def test_board_joins_old_default(self):
774        """
775        Test upgrading a board to a default that doesn't change.
776
777        Test the case of upgrading board to the default, where the
778        default mapping stays unchanged.
779        """
780        self._apply_upgrades({'board': _OLD_VERSION},
781                             {'board': _OLD_DEFAULT},
782                             False)
783
784    def test_board_joins_new_default(self):
785        """
786        Test upgrading a board to match the new default.
787
788        Test the case of upgrading board and the default to the same
789        version.
790        """
791        self._apply_upgrades({'board': _OLD_VERSION},
792                             {'board': _NEW_DEFAULT},
793                             True)
794
795    def test_board_becomes_default(self):
796        """
797        Test a board that becomes default after a default upgrade.
798
799        Test the case of upgrading the default to a version already
800        mapped for an existing board.
801        """
802        self._apply_upgrades({'board': _NEW_DEFAULT},
803                             {'board': _NEW_DEFAULT},
804                             True)
805
806
807class ApplyFirmwareUpgradesTests(unittest.TestCase):
808    """Tests for the `_apply_firmware_upgrades()` function."""
809
810    def _apply_upgrades(self, old_versions, new_versions):
811        """
812        Test a single call to `_apply_firmware_upgrades()`.
813
814        All assertions are handled by an instance of `_TestUpdater`.
815
816        @param old_versions   Parameter to be passed to
817                              `_apply_firmware_upgrades()`.
818        @param new_versions   Parameter to be passed to
819                              `_apply_firmware_upgrades()`.
820        """
821        updater = _TestUpdater(self)
822        updater.pretest_init(old_versions, new_versions)
823        assign_stable_images._apply_firmware_upgrades(
824            updater, old_versions, new_versions)
825        updater.check_results(False)
826
827    def test_no_changes(self):
828        """
829        Test an empty upgrade that does nothing.
830
831        Test the boundary case of an upgrade where there are no boards.
832        """
833        self._apply_upgrades({}, {})
834
835    def test_board_added(self):
836        """
837        Test an upgrade that adds a new board.
838
839        Test the case of an upgrade where a board that was previously
840        unmapped is added.
841        """
842        self._apply_upgrades({}, {'board': _NEW_VERSION})
843
844    def test_board_unchanged(self):
845        """
846        Test an upgrade with no changes to a board.
847
848        Test the case of an upgrade with a board that stays the same.
849        """
850        self._apply_upgrades({'board': _NEW_VERSION},
851                             {'board': _NEW_VERSION})
852
853    def test_board_upgrade_and_change_default(self):
854        """
855        Test upgrading a board.
856
857        Test the case of upgrading a board to a new version.
858        """
859        self._apply_upgrades({'board': _OLD_VERSION},
860                             {'board': _NEW_VERSION})
861
862
863if __name__ == '__main__':
864    unittest.main()
865