| # Copyright 2015 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| |
| import common |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.server import test |
| |
| |
| def _assert_equal(expected, actual): |
| """Compares objects. |
| |
| @param expected: the expected value. |
| @param actual: the actual value. |
| |
| @raises error.TestFail |
| """ |
| if expected != actual: |
| raise error.TestFail('Expected: %r, actual: %r' % (expected, actual)) |
| |
| |
| class brillo_BootLoader(test.test): |
| """A/B tests for boot loader and boot_control HAL implementation.""" |
| version = 1 |
| |
| |
| def get_slots_and_suffix(self): |
| """Gets number of slots supported and slot suffixes used. |
| |
| Prerequisite: The DUT is in ADB mode. |
| """ |
| self.num_slots = int(self.dut.run_output('bootctl get-number-slots')) |
| logging.info('Number of slots: %d', self.num_slots) |
| self.suffix_a = self.dut.run_output('bootctl get-suffix 0') |
| self.suffix_b = self.dut.run_output('bootctl get-suffix 1') |
| logging.info('Slot 0 suffix: "%s"', self.suffix_a) |
| logging.info('Slot 1 suffix: "%s"', self.suffix_b) |
| _assert_equal(2, self.num_slots) |
| # We're going to need the size of the boot partitions later. |
| self.boot_a_size = int(self.dut.run_output( |
| 'blockdev --getsize64 /dev/block/by-name/boot%s' % self.suffix_a)) |
| self.boot_b_size = int(self.dut.run_output( |
| 'blockdev --getsize64 /dev/block/by-name/boot%s' % self.suffix_b)) |
| if self.boot_a_size != self.boot_b_size: |
| raise error.TestFail('boot partitions are not the same size') |
| logging.info('boot partition size: %d bytes', self.boot_a_size) |
| |
| |
| def fastboot_get_var(self, variable_name): |
| """Gets a fastboot variable. |
| |
| Returns a string with the value or the empty string if the |
| variable does not exist. |
| |
| Prerequisite: The DUT is in bootloader mode. |
| |
| @param variable_name: Name of fastboot variable to get. |
| |
| """ |
| cmd_output = self.dut.fastboot_run('getvar %s' % variable_name) |
| # Gah, 'fastboot getvar' prints requested output on stderr |
| # instead of stdout as you'd expect. |
| lines = cmd_output.stderr.split('\n') |
| if lines[0].startswith(variable_name + ': '): |
| return (lines[0])[len(variable_name + ': '):] |
| return '' |
| |
| |
| def check_fastboot_variables(self): |
| """Checks that fastboot bootloader has necessary variables for A/B. |
| |
| Prerequisite: The DUT is in ADB mode. |
| """ |
| logging.info('Checking fastboot-compliant bootloader has necessary ' |
| 'variables for A/B.') |
| self.dut.ensure_bootloader_mode() |
| # The slot-suffixes looks like '_a,_b' and may have a trailing comma. |
| suffixes = self.fastboot_get_var('slot-suffixes') |
| if suffixes.rstrip(',').split(',') != [self.suffix_a, self.suffix_b]: |
| raise error.TestFail('Variable slot-suffixes has unexpected ' |
| 'value "%s"' % suffixes) |
| # Back to ADB mode. |
| self.dut.fastboot_reboot() |
| |
| |
| def get_current_slot(self): |
| """Gets the current slot the DUT is running from. |
| |
| Prerequisite: The DUT is in ADB mode. |
| """ |
| return int(self.dut.run_output('bootctl get-current-slot')) |
| |
| |
| def assert_current_slot(self, slot_number): |
| """Checks that DUT is running from the given slot. |
| |
| Prerequisite: The DUT is in ADB mode. |
| |
| @param slot_number: Zero-based index of slot to be running from. |
| """ |
| _assert_equal(slot_number, self.get_current_slot()) |
| |
| |
| def set_active_slot(self, slot_number): |
| """Instructs the DUT to attempt booting from given slot. |
| |
| Prerequisite: The DUT is in ADB mode. |
| |
| @param slot_number: Zero-based index of slot to make active. |
| """ |
| logging.info('Setting slot %d active.', slot_number) |
| self.dut.run('bootctl set-active-boot-slot %d' % slot_number) |
| |
| |
| def ensure_running_slot(self, slot_number): |
| """Ensures that DUT is running from the given slot. |
| |
| Prerequisite: The DUT is in ADB mode. |
| |
| @param slot_number: Zero-based index of slot to be running from. |
| """ |
| logging.info('Ensuring device is running from slot %d.', slot_number) |
| if self.get_current_slot() != slot_number: |
| logging.info('Rebooting into slot %d', slot_number) |
| self.set_active_slot(slot_number) |
| self.dut.reboot() |
| self.assert_current_slot(slot_number) |
| |
| |
| def copy_a_to_b(self): |
| """Copies contents of slot A to slot B. |
| |
| Prerequisite: The DUT is in ADB mode and booted from slot A. |
| """ |
| self.assert_current_slot(0) |
| for i in ['boot', 'system']: |
| logging.info('Copying %s%s to %s%s.', |
| i, self.suffix_a, i, self.suffix_b) |
| self.dut.run('dd if=/dev/block/by-name/%s%s ' |
| 'of=/dev/block/by-name/%s%s bs=4096' % |
| (i, self.suffix_a, i, self.suffix_b)) |
| |
| |
| def check_bootctl_set_active(self): |
| """Checks that setActiveBootSlot in the boot_control HAL work. |
| |
| Prerequisite: The DUT is in ADB mode with populated A and B slots. |
| """ |
| logging.info('Check setActiveBootSlot() in boot_control HAL.') |
| self.set_active_slot(0) |
| self.dut.reboot() |
| self.assert_current_slot(0) |
| self.set_active_slot(1) |
| self.dut.reboot() |
| self.assert_current_slot(1) |
| |
| |
| def check_fastboot_set_active(self): |
| """Checks that 'fastboot set_active <SUFFIX>' work. |
| |
| Prerequisite: The DUT is in ADB mode with populated A and B slots. |
| """ |
| logging.info('Check set_active command in fastboot-compliant bootloader.') |
| self.dut.ensure_bootloader_mode() |
| self.dut.fastboot_run('set_active %s' % self.suffix_a) |
| self.dut.fastboot_reboot() |
| self.dut.adb_run('wait-for-device') |
| self.assert_current_slot(0) |
| self.dut.ensure_bootloader_mode() |
| self.dut.fastboot_run('set_active %s' % self.suffix_b) |
| self.dut.fastboot_reboot() |
| self.dut.adb_run('wait-for-device') |
| self.assert_current_slot(1) |
| |
| |
| def check_bootloader_fallback_on_invalid(self): |
| """Checks bootloader fallback if current slot is invalid. |
| |
| Prerequisite: The DUT is in ADB mode with populated A and B slots. |
| """ |
| logging.info('Checking bootloader fallback if current slot ' |
| 'is invalid.') |
| # Make sure we're in slot B, then zero out boot_b (so slot B |
| # is invalid), reboot and check that the bootloader correctly |
| # fell back to A. |
| self.ensure_running_slot(1) |
| self.dut.run('dd if=/dev/zero of=/dev/block/by-name/boot%s ' |
| 'count=%d bs=4096' % (self.suffix_b, |
| self.boot_b_size/4096)) |
| self.dut.reboot() |
| self.assert_current_slot(0) |
| # Restore boot_b for use in future test cases. |
| self.dut.run('dd if=/dev/block/by-name/boot%s ' |
| 'of=/dev/block/by-name/boot%s bs=4096' % |
| (self.suffix_a, self.suffix_b)) |
| |
| |
| def check_bootloader_fallback_on_retries(self): |
| """Checks bootloader fallback if slot made active runs out of tries. |
| |
| Prerequisite: The DUT is in ADB mode with populated A and B slots. |
| |
| @raises error.TestFail |
| """ |
| logging.info('Checking bootloader fallback if slot made active ' |
| 'runs out of tries.') |
| self.ensure_running_slot(0) |
| self.set_active_slot(1) |
| self.dut.reboot() |
| num_retries = 1 |
| while num_retries < 10 and self.get_current_slot() == 1: |
| logging.info('Still with B after %d retries', num_retries) |
| num_retries += 1 |
| self.dut.reboot() |
| if self.get_current_slot() != 0: |
| raise error.TestFail('Bootloader did not fall back after ' |
| '%d retries without the slot being marked ' |
| 'as GOOD' % num_retries) |
| logging.info('Fell back to A after %d retries.', num_retries) |
| |
| |
| def check_bootloader_mark_successful(self): |
| """Checks bootloader stays with slot after it has been marked good. |
| |
| Prerequisite: The DUT is in ADB mode with populated A and B slots. |
| """ |
| logging.info('Checking bootloader is staying with a slot after it has ' |
| 'been marked as GOOD for at least 10 reboots.') |
| self.ensure_running_slot(0) |
| self.dut.run('bootctl mark-boot-successful') |
| num_reboots = 0 |
| while num_reboots < 10: |
| self.dut.reboot() |
| self.assert_current_slot(0) |
| num_reboots += 1 |
| logging.info('Still with A after %d reboots', num_reboots) |
| |
| |
| def run_once(self, dut=None): |
| """A/B tests for boot loader and boot_control HAL implementation. |
| |
| Verifies that boot loader and boot_control HAL implementation |
| implements A/B correctly. |
| |
| Prerequisite: The DUT is in ADB mode. |
| |
| @param dut: host object representing the device under test. |
| """ |
| self.dut = dut |
| self.get_slots_and_suffix() |
| self.check_fastboot_variables() |
| self.ensure_running_slot(0) |
| self.copy_a_to_b() |
| self.check_bootctl_set_active() |
| self.check_fastboot_set_active() |
| self.check_bootloader_fallback_on_invalid() |
| self.check_bootloader_fallback_on_retries() |
| self.check_bootloader_mark_successful() |