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