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 mock 11import unittest 12 13import common 14from autotest_lib.site_utils.stable_images import assign_stable_images 15from autotest_lib.site_utils.stable_images import build_data 16 17 18_DEFAULT_BOARD = assign_stable_images._DEFAULT_BOARD 19 20 21class GetFirmwareUpgradesTests(unittest.TestCase): 22 """Tests for _get_firmware_upgrades.""" 23 24 @mock.patch.object(build_data, 'get_firmware_versions') 25 def test_get_firmware_upgrades(self, mock_get_firmware_versions): 26 """Test _get_firmware_upgrades.""" 27 mock_get_firmware_versions.side_effect = [ 28 {'auron_paine': 'fw_version'}, 29 {'blue': 'fw_version', 30 'robo360': 'fw_version', 31 'porbeagle': 'fw_version'} 32 ] 33 cros_versions = { 34 'coral': 'R64-10176.65.0', 35 'auron_paine': 'R64-10176.65.0' 36 } 37 boards = ['auron_paine', 'coral'] 38 39 firmware_upgrades = assign_stable_images._get_firmware_upgrades( 40 cros_versions) 41 expected_firmware_upgrades = { 42 'auron_paine': 'fw_version', 43 'blue': 'fw_version', 44 'robo360': 'fw_version', 45 'porbeagle': 'fw_version' 46 } 47 self.assertEqual(firmware_upgrades, expected_firmware_upgrades) 48 49 50class GetUpgradeTests(unittest.TestCase): 51 """Tests for the `_get_upgrade_versions()` function.""" 52 53 # _VERSIONS - a list of sample version strings such as may be used 54 # for Chrome OS, sorted from oldest to newest. These are used to 55 # construct test data in multiple test cases, below. 56 _VERSIONS = ['R1-1.0.0', 'R1-1.1.0', 'R2-4.0.0'] 57 58 def test_board_conversions(self): 59 """ 60 Test proper mapping of names from the AFE to Omaha. 61 62 Board names in Omaha don't have '_' characters; when an AFE 63 board contains '_' characters, they must be converted to '-'. 64 65 Assert that for various forms of name in the AFE mapping, the 66 converted name is the one looked up in the Omaha mapping. 67 """ 68 board_equivalents = [ 69 ('a-b', 'a-b'), ('c_d', 'c-d'), 70 ('e_f-g', 'e-f-g'), ('hi', 'hi')] 71 afe_versions = { 72 _DEFAULT_BOARD: self._VERSIONS[0] 73 } 74 omaha_versions = {} 75 expected = {} 76 boards = set() 77 for afe_board, omaha_board in board_equivalents: 78 boards.add(afe_board) 79 afe_versions[afe_board] = self._VERSIONS[1] 80 omaha_versions[omaha_board] = self._VERSIONS[2] 81 expected[afe_board] = self._VERSIONS[2] 82 upgrades, _ = assign_stable_images._get_upgrade_versions( 83 afe_versions, omaha_versions, boards) 84 self.assertEqual(upgrades, expected) 85 86 def test_afe_default(self): 87 """ 88 Test that the AFE default board mapping is honored. 89 90 If a board isn't present in the AFE dictionary, the mapping 91 for `_DEFAULT_BOARD` should be used. 92 93 Primary assertions: 94 * When a board is present in the AFE mapping, its version 95 mapping is used. 96 * When a board is not present in the AFE mapping, the default 97 version mapping is used. 98 99 Secondarily, assert that when a mapping is absent from Omaha, 100 the AFE mapping is left unchanged. 101 """ 102 afe_versions = { 103 _DEFAULT_BOARD: self._VERSIONS[0], 104 'a': self._VERSIONS[1] 105 } 106 boards = set(['a', 'b']) 107 expected = { 108 'a': self._VERSIONS[1], 109 'b': self._VERSIONS[0] 110 } 111 upgrades, _ = assign_stable_images._get_upgrade_versions( 112 afe_versions, {}, boards) 113 self.assertEqual(upgrades, expected) 114 115 def test_omaha_upgrade(self): 116 """ 117 Test that upgrades from Omaha are detected. 118 119 Primary assertion: 120 * If a board is found in Omaha, and the version in Omaha is 121 newer than the AFE version, the Omaha version is the one 122 used. 123 124 Secondarily, asserts that version comparisons between various 125 specific version strings are all correct. 126 """ 127 boards = set(['a']) 128 for i in range(0, len(self._VERSIONS) - 1): 129 afe_versions = {_DEFAULT_BOARD: self._VERSIONS[i]} 130 for j in range(i+1, len(self._VERSIONS)): 131 omaha_versions = {b: self._VERSIONS[j] for b in boards} 132 upgrades, _ = assign_stable_images._get_upgrade_versions( 133 afe_versions, omaha_versions, boards) 134 self.assertEqual(upgrades, omaha_versions) 135 136 def test_no_upgrade(self): 137 """ 138 Test that if Omaha is behind the AFE, it is ignored. 139 140 Primary assertion: 141 * If a board is found in Omaha, and the version in Omaha is 142 older than the AFE version, the AFE version is the one used. 143 144 Secondarily, asserts that version comparisons between various 145 specific version strings are all correct. 146 """ 147 boards = set(['a']) 148 for i in range(1, len(self._VERSIONS)): 149 afe_versions = {_DEFAULT_BOARD: self._VERSIONS[i]} 150 expected = {b: self._VERSIONS[i] for b in boards} 151 for j in range(0, i): 152 omaha_versions = {b: self._VERSIONS[j] for b in boards} 153 upgrades, _ = assign_stable_images._get_upgrade_versions( 154 afe_versions, omaha_versions, boards) 155 self.assertEqual(upgrades, expected) 156 157 def test_ignore_unused_boards(self): 158 """ 159 Test that unlisted boards are ignored. 160 161 Assert that boards present in the AFE or Omaha mappings aren't 162 included in the return mappings when they aren't in the passed 163 in set of boards. 164 """ 165 unused_boards = set(['a', 'b']) 166 used_boards = set(['c', 'd']) 167 afe_versions = {b: self._VERSIONS[0] for b in unused_boards} 168 afe_versions[_DEFAULT_BOARD] = self._VERSIONS[1] 169 expected = {b: self._VERSIONS[1] for b in used_boards} 170 omaha_versions = expected.copy() 171 omaha_versions.update( 172 {b: self._VERSIONS[0] for b in unused_boards}) 173 upgrades, _ = assign_stable_images._get_upgrade_versions( 174 afe_versions, omaha_versions, used_boards) 175 self.assertEqual(upgrades, expected) 176 177 def test_default_unchanged(self): 178 """ 179 Test correct handling when the default build is unchanged. 180 181 Assert that if in Omaha, one board in a set of three upgrades 182 from the AFE default, that the returned default board mapping is 183 the original default in the AFE. 184 """ 185 boards = set(['a', 'b', 'c']) 186 afe_versions = {_DEFAULT_BOARD: self._VERSIONS[0]} 187 omaha_versions = {b: self._VERSIONS[0] for b in boards} 188 omaha_versions['c'] = self._VERSIONS[1] 189 _, new_default = assign_stable_images._get_upgrade_versions( 190 afe_versions, omaha_versions, boards) 191 self.assertEqual(new_default, self._VERSIONS[0]) 192 193 def test_default_upgrade(self): 194 """ 195 Test correct handling when the default build must change. 196 197 Assert that if in Omaha, two boards in a set of three upgrade 198 from the AFE default, that the returned default board mapping is 199 the new build in Omaha. 200 """ 201 boards = set(['a', 'b', 'c']) 202 afe_versions = {_DEFAULT_BOARD: self._VERSIONS[0]} 203 omaha_versions = {b: self._VERSIONS[1] for b in boards} 204 omaha_versions['c'] = self._VERSIONS[0] 205 _, new_default = assign_stable_images._get_upgrade_versions( 206 afe_versions, omaha_versions, boards) 207 self.assertEqual(new_default, self._VERSIONS[1]) 208 209 210# Sample version string values to be used when testing 211# `_apply_upgrades()`. 212# 213# _OLD_DEFAULT - Test value representing the default version mapping 214# in the `old_versions` dictionary in a call to `_apply_upgrades()`. 215# _NEW_DEFAULT - Test value representing the default version mapping 216# in the `new_versions` dictionary when a version update is being 217# tested. 218# _OLD_VERSION - Test value representing an arbitrary version for a 219# board that is mapped in the `old_versions` dictionary in a call to 220# `_apply_upgrades()`. 221# _NEW_VERSION - Test value representing an arbitrary version for a 222# board that is mapped in the `new_versions` dictionary in a call to 223# `_apply_upgrades()`. 224# 225_OLD_DEFAULT = 'old-default-version' 226_NEW_DEFAULT = 'new-default-version' 227_OLD_VERSION = 'old-board-version' 228_NEW_VERSION = 'new-board-version' 229 230 231class _StubAFE(object): 232 """Stubbed out version of `server.frontend.AFE`.""" 233 234 CROS_IMAGE_TYPE = 'cros-image-type' 235 FIRMWARE_IMAGE_TYPE = 'firmware-image-type' 236 237 def get_stable_version_map(self, image_type): 238 return image_type 239 240 241class _TestUpdater(assign_stable_images._VersionUpdater): 242 """ 243 Subclass of `_VersionUpdater` for testing. 244 245 This class extends `_VersionUpdater` to provide support for testing 246 various assertions about the behavior of the base class and its 247 interactions with `_apply_cros_upgrades()` and 248 `_apply_firmware_upgrades()`. 249 250 The class tests assertions along the following lines: 251 * When applied to the original mappings, the calls to 252 `_do_set_mapping()` and `_do_delete_mapping()` create the 253 expected final mapping state. 254 * Calls to report state changes are made with the expected 255 values. 256 * There's a one-to-one match between reported and actually 257 executed changes. 258 259 """ 260 261 def __init__(self, testcase): 262 super(_TestUpdater, self).__init__(_StubAFE(), dry_run=True) 263 self._testcase = testcase 264 self._default_changed = None 265 self._reported_mappings = None 266 self._updated_mappings = None 267 self._reported_deletions = None 268 self._actual_deletions = None 269 self._original_mappings = None 270 self._mappings = None 271 self._expected_mappings = None 272 self._unchanged_boards = None 273 274 def pretest_init(self, initial_versions, expected_versions): 275 """ 276 Initialize for testing. 277 278 @param initial_versions Mappings to be used as the starting 279 point for testing. 280 @param expected_versions The expected final value of the 281 mappings after the test. 282 """ 283 self._default_changed = False 284 self._reported_mappings = {} 285 self._updated_mappings = {} 286 self._reported_deletions = set() 287 self._actual_deletions = set() 288 self._original_mappings = initial_versions.copy() 289 self._mappings = initial_versions.copy() 290 self._expected_mappings = expected_versions 291 self._unchanged_boards = set() 292 293 def check_results(self, change_default): 294 """ 295 Assert that observed changes match expectations. 296 297 Asserts the following: 298 * The `report_default_changed()` method was called (or not) 299 based on whether `change_default` is true (or not). 300 * The changes reported via `_report_board_changed()` match 301 the changes actually applied. 302 * The final mappings after applying requested changes match 303 the actually expected mappings. 304 305 @param old_versions Parameter to be passed to 306 `_apply_cros_upgrades()`. 307 @param new_versions Parameter to be passed to 308 `_apply_cros_upgrades()`. 309 @param change_default Whether the test should include a change 310 to the default version mapping. 311 """ 312 self._testcase.assertEqual(change_default, 313 self._default_changed) 314 self._testcase.assertEqual(self._reported_mappings, 315 self._updated_mappings) 316 self._testcase.assertEqual(self._reported_deletions, 317 self._actual_deletions) 318 self._testcase.assertEqual(self._mappings, 319 self._expected_mappings) 320 321 def report_default_changed(self, old_default, new_default): 322 """ 323 Override of our parent class' method for test purposes. 324 325 Saves a record of the report for testing the final result in 326 `apply_upgrades()`, above. 327 328 Assert the following: 329 * The old and new default values match the values that 330 were passed in the original call's arguments. 331 * This function is not being called for a second time. 332 333 @param old_default The original default version. 334 @param new_default The new default version to be applied. 335 """ 336 self._testcase.assertNotEqual(old_default, new_default) 337 self._testcase.assertEqual(old_default, 338 self._original_mappings[_DEFAULT_BOARD]) 339 self._testcase.assertEqual(new_default, 340 self._expected_mappings[_DEFAULT_BOARD]) 341 self._testcase.assertFalse(self._default_changed) 342 self._default_changed = True 343 self._reported_mappings[_DEFAULT_BOARD] = new_default 344 345 def _report_board_changed(self, board, old_version, new_version): 346 """ 347 Override of our parent class' method for test purposes. 348 349 Saves a record of the report for testing the final result in 350 `apply_upgrades()`, above. 351 352 Assert the following: 353 * The change being reported actually reports two different 354 versions. 355 * If the board isn't mapped to the default version, then the 356 reported old version is the actually mapped old version. 357 * If the board isn't changing to the default version, then the 358 reported new version is the expected new version. 359 * This is not a second report for this board. 360 361 The implementation implicitly requires that the specified board 362 have a valid mapping. 363 364 @param board The board with the changing version. 365 @param old_version The original version mapped to the board. 366 @param new_version The new version to be applied to the board. 367 """ 368 self._testcase.assertNotEqual(old_version, new_version) 369 if board in self._original_mappings: 370 self._testcase.assertEqual(old_version, 371 self._original_mappings[board]) 372 if board in self._expected_mappings: 373 self._testcase.assertEqual(new_version, 374 self._expected_mappings[board]) 375 self._testcase.assertNotIn(board, self._reported_mappings) 376 self._reported_mappings[board] = new_version 377 else: 378 self._testcase.assertNotIn(board, self._reported_deletions) 379 self._reported_deletions.add(board) 380 381 def report_board_unchanged(self, board, old_version): 382 """ 383 Override of our parent class' method for test purposes. 384 385 Assert the following: 386 * The version being reported as unchanged is actually mapped. 387 * The reported old version matches the expected value. 388 * This is not a second report for this board. 389 390 @param board The board that is not changing. 391 @param old_version The board's version mapping. 392 """ 393 self._testcase.assertIn(board, self._original_mappings) 394 self._testcase.assertEqual(old_version, 395 self._original_mappings[board]) 396 self._testcase.assertNotIn(board, self._unchanged_boards) 397 self._unchanged_boards.add(board) 398 399 def _do_set_mapping(self, board, new_version): 400 """ 401 Override of our parent class' method for test purposes. 402 403 Saves a record of the change for testing the final result in 404 `apply_upgrades()`, above. 405 406 Assert the following: 407 * This is not a second change for this board. 408 * If we're changing the default mapping, then every board 409 that will be changing to a non-default mapping has been 410 updated. 411 412 @param board The board with the changing version. 413 @param new_version The new version to be applied to the board. 414 """ 415 self._mappings[board] = new_version 416 self._testcase.assertNotIn(board, self._updated_mappings) 417 self._updated_mappings[board] = new_version 418 if board == _DEFAULT_BOARD: 419 for board in self._expected_mappings: 420 self._testcase.assertIn(board, self._mappings) 421 422 def _do_delete_mapping(self, board): 423 """ 424 Override of our parent class' method for test purposes. 425 426 Saves a record of the change for testing the final result in 427 `apply_upgrades()`, above. 428 429 Assert that the board has a mapping prior to deletion. 430 431 @param board The board with the version to be deleted. 432 """ 433 self._testcase.assertNotEqual(board, _DEFAULT_BOARD) 434 self._testcase.assertIn(board, self._mappings) 435 del self._mappings[board] 436 self._actual_deletions.add(board) 437 438 439class ApplyCrOSUpgradesTests(unittest.TestCase): 440 """Tests for the `_apply_cros_upgrades()` function.""" 441 442 def _apply_upgrades(self, old_versions, new_versions, change_default): 443 """ 444 Test a single call to `_apply_cros_upgrades()`. 445 446 All assertions are handled by an instance of `_TestUpdater`. 447 448 @param old_versions Parameter to be passed to 449 `_apply_cros_upgrades()`. 450 @param new_versions Parameter to be passed to 451 `_apply_cros_upgrades()`. 452 @param change_default Whether the test should include a change 453 to the default version mapping. 454 """ 455 old_versions[_DEFAULT_BOARD] = _OLD_DEFAULT 456 if change_default: 457 new_default = _NEW_DEFAULT 458 else: 459 new_default = _OLD_DEFAULT 460 expected_versions = { 461 b: v for b, v in new_versions.items() if v != new_default 462 } 463 expected_versions[_DEFAULT_BOARD] = new_default 464 updater = _TestUpdater(self) 465 updater.pretest_init(old_versions, expected_versions) 466 assign_stable_images._apply_cros_upgrades( 467 updater, old_versions, new_versions, new_default) 468 updater.check_results(change_default) 469 470 def test_no_changes(self): 471 """ 472 Test an empty upgrade that does nothing. 473 474 Test the boundary case of an upgrade where there are no boards, 475 and the default does not change. 476 """ 477 self._apply_upgrades({}, {}, False) 478 479 def test_change_default(self): 480 """ 481 Test an empty upgrade that merely changes the default. 482 483 Test the boundary case of an upgrade where there are no boards, 484 but the default is upgraded. 485 """ 486 self._apply_upgrades({}, {}, True) 487 488 def test_board_default_no_changes(self): 489 """ 490 Test that a board at default stays with an unchanged default. 491 492 Test the case of a board that is mapped to the default, where 493 neither the board nor the default change. 494 """ 495 self._apply_upgrades({}, {'board': _OLD_DEFAULT}, False) 496 497 def test_board_left_behind(self): 498 """ 499 Test a board left at the old default after a default upgrade. 500 501 Test the case of a board that stays mapped to the old default as 502 the default board is upgraded. 503 """ 504 self._apply_upgrades({}, {'board': _OLD_DEFAULT}, True) 505 506 def test_board_upgrade_from_default(self): 507 """ 508 Test upgrading a board from a default that doesn't change. 509 510 Test the case of upgrading a board from default to non-default, 511 where the default doesn't change. 512 """ 513 self._apply_upgrades({}, {'board': _NEW_VERSION}, False) 514 515 def test_board_and_default_diverge(self): 516 """ 517 Test upgrading a board that diverges from the default. 518 519 Test the case of upgrading a board and default together from the 520 same to different versions. 521 """ 522 self._apply_upgrades({}, {'board': _NEW_VERSION}, True) 523 524 def test_board_tracks_default(self): 525 """ 526 Test upgrading a board to track a default upgrade. 527 528 Test the case of upgrading a board and the default together. 529 """ 530 self._apply_upgrades({}, {'board': _NEW_DEFAULT}, True) 531 532 def test_board_non_default_no_changes(self): 533 """ 534 Test an upgrade with no changes to a board or the default. 535 536 Test the case of an upgrade with a board in it, where neither 537 the board nor the default change. 538 """ 539 self._apply_upgrades({'board': _NEW_VERSION}, 540 {'board': _NEW_VERSION}, 541 False) 542 543 def test_board_upgrade_and_keep_default(self): 544 """ 545 Test a board upgrade with an unchanged default. 546 547 Test the case of upgrading a board while the default stays the 548 same. 549 """ 550 self._apply_upgrades({'board': _OLD_VERSION}, 551 {'board': _NEW_VERSION}, 552 False) 553 554 def test_board_upgrade_and_change_default(self): 555 """ 556 Test upgrading a board and the default separately. 557 558 Test the case of upgrading both a board and the default, each 559 from and to different versions. 560 """ 561 self._apply_upgrades({'board': _OLD_VERSION}, 562 {'board': _NEW_VERSION}, 563 True) 564 565 def test_board_leads_default(self): 566 """ 567 Test a board that upgrades ahead of the new default. 568 569 Test the case of upgrading both a board and the default, where 570 the board's old version is the new default version. 571 """ 572 self._apply_upgrades({'board': _NEW_DEFAULT}, 573 {'board': _NEW_VERSION}, 574 True) 575 576 def test_board_lags_to_old_default(self): 577 """ 578 Test a board that upgrades behind the old default. 579 580 Test the case of upgrading both a board and the default, where 581 the board's new version is the old default version. 582 """ 583 self._apply_upgrades({'board': _OLD_VERSION}, 584 {'board': _OLD_DEFAULT}, 585 True) 586 587 def test_board_joins_old_default(self): 588 """ 589 Test upgrading a board to a default that doesn't change. 590 591 Test the case of upgrading board to the default, where the 592 default mapping stays unchanged. 593 """ 594 self._apply_upgrades({'board': _OLD_VERSION}, 595 {'board': _OLD_DEFAULT}, 596 False) 597 598 def test_board_joins_new_default(self): 599 """ 600 Test upgrading a board to match the new default. 601 602 Test the case of upgrading board and the default to the same 603 version. 604 """ 605 self._apply_upgrades({'board': _OLD_VERSION}, 606 {'board': _NEW_DEFAULT}, 607 True) 608 609 def test_board_becomes_default(self): 610 """ 611 Test a board that becomes default after a default upgrade. 612 613 Test the case of upgrading the default to a version already 614 mapped for an existing board. 615 """ 616 self._apply_upgrades({'board': _NEW_DEFAULT}, 617 {'board': _NEW_DEFAULT}, 618 True) 619 620 621class ApplyFirmwareUpgradesTests(unittest.TestCase): 622 """Tests for the `_apply_firmware_upgrades()` function.""" 623 624 def _apply_upgrades(self, old_versions, new_versions): 625 """ 626 Test a single call to `_apply_firmware_upgrades()`. 627 628 All assertions are handled by an instance of `_TestUpdater`. 629 630 @param old_versions Parameter to be passed to 631 `_apply_firmware_upgrades()`. 632 @param new_versions Parameter to be passed to 633 `_apply_firmware_upgrades()`. 634 """ 635 updater = _TestUpdater(self) 636 updater.pretest_init(old_versions, new_versions) 637 assign_stable_images._apply_firmware_upgrades( 638 updater, old_versions, new_versions) 639 updater.check_results(False) 640 641 def test_no_changes(self): 642 """ 643 Test an empty upgrade that does nothing. 644 645 Test the boundary case of an upgrade where there are no boards. 646 """ 647 self._apply_upgrades({}, {}) 648 649 def test_board_added(self): 650 """ 651 Test an upgrade that adds a new board. 652 653 Test the case of an upgrade where a board that was previously 654 unmapped is added. 655 """ 656 self._apply_upgrades({}, {'board': _NEW_VERSION}) 657 658 def test_board_unchanged(self): 659 """ 660 Test an upgrade with no changes to a board. 661 662 Test the case of an upgrade with a board that stays the same. 663 """ 664 self._apply_upgrades({'board': _NEW_VERSION}, 665 {'board': _NEW_VERSION}) 666 667 def test_board_upgrade_and_change_default(self): 668 """ 669 Test upgrading a board. 670 671 Test the case of upgrading a board to a new version. 672 """ 673 self._apply_upgrades({'board': _OLD_VERSION}, 674 {'board': _NEW_VERSION}) 675 676 677if __name__ == '__main__': 678 unittest.main() 679