| # Copyright 2016 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. |
| |
| """Runs fastboot flash to update images on the device.""" |
| |
| import os |
| import re |
| import subprocess |
| import unittest |
| |
| import factory_common # pylint: disable=unused-import |
| from cros.factory.device import device_utils |
| from cros.factory.test import factory |
| from cros.factory.test.i18n import test_ui as i18n_test_ui |
| from cros.factory.test import test_ui |
| from cros.factory.test import ui_templates |
| from cros.factory.utils.arg_utils import Arg |
| from cros.factory.utils import process_utils |
| from cros.factory.utils import sync_utils |
| |
| |
| _MSG_SWITCH_TO_FASTBOOT = i18n_test_ui.MakeI18nLabel( |
| 'Switching device into fastboot.') |
| |
| _MSG_SWITCH_TO_NORMAL = i18n_test_ui.MakeI18nLabel( |
| 'Switching device back to normal mode.') |
| |
| def GetFlashingMessage(partition, file_path): |
| return i18n_test_ui.MakeI18nLabel( |
| 'Flashing {file} to {partition}.', |
| file=file_path, |
| partition=partition) |
| |
| _RE_SENDING_TIME = re.compile( |
| r'^sending.*\(([0-9]+) KB\).*\nOKAY\s*\[\s*([0-9]+\.[0-9]+)s\]', |
| re.MULTILINE) |
| |
| |
| class FastbootFlash(unittest.TestCase): |
| """Flash images using fastboot with the give image files. |
| |
| The device will be rebooted into fastboot, and 'fastboot flash' will be |
| applied to flash the given image files. |
| |
| This test can also be used to verify the throughput of USB in device mode |
| by checking the flash speed. |
| """ |
| ARGS = [ |
| Arg('image_dir', str, 'Full path of the dir that contains images.', |
| default='/usr/local/factory/images/android'), |
| Arg('images', list, 'list of images to be updated. Each item should a ' |
| 'tuple of (partition, image_name). ' |
| "E.g., [('boot', 'boot.img'), ('system', 'system.img')]. " |
| 'The images will be flashed in order of the list.'), |
| Arg('expected_throughput', int, 'Expected throughput minimum when ' |
| 'sending the image files, in unit of byte/sec. This can be used to ' |
| 'cover USB device mode throughput testing.', default=0), |
| Arg('command_to_fastboot', str, 'Command used to switch the device ' |
| 'into fastboot mode.', default='adb reboot-bootloader'), |
| Arg('command_to_check_device', str, 'Command to check device boot ' |
| 'normally.', default='timeout 60 adb wait-for-device') |
| ] |
| |
| def setUp(self): |
| self._dut = device_utils.CreateDUTInterface() |
| self._ui = test_ui.UI() |
| self._template = ui_templates.OneSection(self._ui) |
| |
| def IsDeviceInFastboot(self): |
| """Check if the device is already in fastboot mode.""" |
| |
| return bool(process_utils.SpawnOutput(['fastboot', 'devices'], |
| check_call=True)) |
| |
| def BootToFastboot(self): |
| """Reboot device into fastboot mode if needed.""" |
| |
| # Check if the device is in fastboot mode now. |
| if self.IsDeviceInFastboot(): |
| return |
| |
| self._template.SetState(_MSG_SWITCH_TO_FASTBOOT) |
| process_utils.Spawn(self.args.command_to_fastboot, |
| shell=True, check_call=True, log=True) |
| sync_utils.PollForCondition( |
| poll_method=self.IsDeviceInFastboot, |
| timeout_secs=60, |
| condition_name='WaitForFastboot') |
| |
| def BootToNormal(self): |
| """Reboot the device back to normal mode from fastboot mode.""" |
| self._template.SetState(_MSG_SWITCH_TO_NORMAL) |
| process_utils.Spawn(['fastboot', 'reboot'], check_call=True, log=True) |
| process_utils.Spawn(self.args.command_to_check_device, shell=True, |
| check_call=True, log=True) |
| |
| def FlashAll(self): |
| def _CheckThroughput(fastboot_output): |
| """Check throughput using the output of fastboot flash. |
| |
| An example of the typical output from fastboot flash: |
| erasing 'system'... |
| OKAY [ 0.643s] |
| sending sparse 'system' (524232 KB)... |
| OKAY [ 15.303s] |
| writing 'system'... |
| OKAY [ 14.853s] |
| sending sparse 'system' (418948 KB)... |
| OKAY [ 12.181s] |
| writing 'system'... |
| OKAY [ 11.248s] |
| finished. total time: 54.229s |
| """ |
| data = _RE_SENDING_TIME.findall(fastboot_output) |
| if not data: |
| self.fail('Incorrect fastboot output. %s' % fastboot_output) |
| |
| for size_time in data: |
| # Get the size in byte. |
| size = int(size_time[0]) * 1024 |
| time = float(size_time[1]) |
| throughput = size / time |
| |
| # If the size is too small, ignore the checking. |
| if size < 1024 * 1024: |
| continue |
| factory.console.info( |
| 'Flash %d bytes in %.1f sec (%.1f bytes/s).', |
| size, time, throughput) |
| self.assertGreater(throughput, self.args.expected_throughput) |
| |
| def _FlashImage(partition, file_path): |
| """Flash image to the given partition.""" |
| if not os.path.exists(file_path): |
| self.fail('Not able to find required image file %s' % file_path) |
| self._template.SetState(GetFlashingMessage(partition, file_path)) |
| msg = process_utils.SpawnOutput( |
| ['fastboot', 'flash', partition, file_path], |
| stderr=subprocess.STDOUT, check_call=True, log=True) |
| _CheckThroughput(msg) |
| |
| for image in self.args.images: |
| _FlashImage(image[0], os.path.join(self.args.image_dir, image[1])) |
| |
| def runTest(self): |
| self.BootToFastboot() |
| self.FlashAll() |
| self.BootToNormal() |