| # Copyright 2017 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 chromeos-firmwareupdate to force update Main(AP)/EC/PD firmwares. |
| |
| Description |
| ----------- |
| This test runs firmware updater from local storage or downloaded from remote |
| factory server to update Main(AP)/EC/PD firmware contents. |
| |
| Test Procedure |
| -------------- |
| This is an automatic test that doesn't need any user interaction. |
| |
| 1. If argument ``download_from_server`` is set to True, this test will try to |
| download firmware updater from factory server and ignore argument |
| ``firmware_updater``. If firmware update is not available, this test will |
| just pass and exit. If argument ``download_from_server`` is set to False and |
| the path indicated by argument ``firmware_updater`` doesn't exist, this test |
| will abort. |
| 2. This test will fail if there is another firmware updater running in the same |
| time. Else, start running firmware updater. |
| 3. If firmware updater finished successfully, this test will pass. |
| Otherwise, fail. |
| |
| Dependency |
| ---------- |
| - If argument ``download_from_server`` is set to True, firmware updater needs to |
| be available on factory server. If ``download_from_server`` is set to False, |
| firmware updater must be prepared in the path that argument |
| ``firmware_updater`` indicated. |
| |
| Examples |
| -------- |
| To update all firmwares using local firmware updater, which is located in |
| '/usr/local/factory/board/chromeos-firmwareupdate':: |
| |
| { |
| "pytest_name": "update_firmware" |
| } |
| |
| To update only RW Main(AP) firmware using remote firmware updater:: |
| |
| { |
| "pytest_name": "update_firmware", |
| "args": { |
| "download_from_server": true, |
| "rw_only": true, |
| "host_only": true |
| } |
| } |
| |
| Not to update firmware if the version is the same with current one |
| in the DUT:: |
| |
| { |
| "pytest_name": "update_firmware", |
| "args": { |
| "force_update": false |
| } |
| } |
| """ |
| |
| import contextlib |
| import logging |
| import os |
| import tempfile |
| |
| import factory_common # pylint: disable=unused-import |
| from cros.factory.device import device_utils |
| from cros.factory.test.env import paths |
| from cros.factory.test import event |
| from cros.factory.test import test_case |
| from cros.factory.test import test_ui |
| from cros.factory.test.utils import update_utils |
| from cros.factory.utils.arg_utils import Arg |
| from cros.factory.utils import process_utils |
| from cros.factory.utils import sys_utils |
| |
| _FIRMWARE_UPDATER_NAME = 'chromeos-firmwareupdate' |
| _FIRMWARE_RELATIVE_PATH = 'usr/sbin/chromeos-firmwareupdate' |
| |
| |
| |
| class NoUpdatesException(Exception): |
| pass |
| |
| |
| class UpdateFirmwareTest(test_case.TestCase): |
| ARGS = [ |
| Arg('firmware_updater', str, 'Full path of %s.' % _FIRMWARE_UPDATER_NAME, |
| default=paths.FACTORY_FIRMWARE_UPDATER_PATH), |
| Arg('rw_only', bool, 'Update only RW firmware', default=False), |
| # Updating only EC/PD is not supported. |
| Arg('host_only', bool, 'Update only host (AP, BIOS) firmware.', |
| default=False), |
| Arg('download_from_server', bool, 'Download firmware updater from server', |
| default=False), |
| Arg('from_release', bool, 'Find the firmware from release rootfs.', |
| default=False), |
| Arg('force_update', bool, |
| 'force to update firmware even if the version is the same.', |
| default=True) |
| ] |
| |
| ui_class = test_ui.ScrollableLogUI |
| |
| def setUp(self): |
| self._dut = device_utils.CreateDUTInterface() |
| |
| def DownloadFirmware(self, force_update, target_path): |
| """Downloads firmware updater from server.""" |
| updater = update_utils.Updater(update_utils.COMPONENTS.firmware) |
| if not updater.IsUpdateAvailable(): |
| logging.warn('No firmware updater available on server.') |
| return False |
| |
| rw_version = self._dut.info.firmware_version |
| ro_version = self._dut.info.ro_firmware_version |
| |
| current_version = 'ro:%s;rw:%s' % (ro_version, rw_version) |
| |
| if not updater.IsUpdateAvailable( |
| current_version, match_method=update_utils.MATCH_METHOD.substring): |
| logging.info('Your firmware is already in same version as server (%s)', |
| updater.GetUpdateVersion()) |
| if not force_update: |
| return False |
| |
| updater.PerformUpdate(destination=target_path) |
| os.chmod(target_path, 0o755) |
| return True |
| |
| def UpdateFirmware(self): |
| """Runs firmware updater. |
| |
| While running updater, it shows updater activity on factory UI. |
| """ |
| # Remove /tmp/chromeos-firmwareupdate-running if the process |
| # doesn't seem to be alive anymore. (http://crosbug.com/p/15642) |
| LOCK_FILE = '/tmp/%s-running' % _FIRMWARE_UPDATER_NAME |
| if os.path.exists(LOCK_FILE): |
| process = process_utils.Spawn(['pgrep', '-f', _FIRMWARE_UPDATER_NAME], |
| call=True, log=True, read_stdout=True) |
| if process.returncode == 0: |
| # Found a chromeos-firmwareupdate alive. |
| self.FailTask('Lock file %s is present and firmware update already ' |
| 'running (PID %s)' % |
| (LOCK_FILE, ', '.join(process.stdout_data.split()))) |
| return |
| logging.warn('Removing %s', LOCK_FILE) |
| os.unlink(LOCK_FILE) |
| |
| command = [self.args.firmware_updater, '--force'] |
| if self.args.host_only: |
| command += ['--host_only'] |
| if self.args.rw_only: |
| command += ['--mode=recovery', '--wp=1'] |
| else: |
| command += ['--mode=factory'] |
| |
| returncode = self.ui.PipeProcessOutputToUI(command) |
| |
| # Updates system info so EC and Firmware version in system info box |
| # are correct. |
| self.event_loop.PostEvent(event.Event(event.Event.Type.UPDATE_SYSTEM_INFO)) |
| |
| self.assertEqual(returncode, 0, 'Firmware update failed: %d.' % returncode) |
| |
| def runTest(self): |
| # Either download_from_server or from_release can be True. |
| self.assertFalse(self.args.download_from_server and self.args.from_release) |
| |
| @contextlib.contextmanager |
| def GetUpdater(): |
| if self.args.download_from_server: |
| # The temporary folder will not be removed after this test finished |
| # for the convenient of debugging. |
| temp_path = os.path.join( |
| tempfile.mkdtemp(prefix='test_fw_update_'), _FIRMWARE_UPDATER_NAME) |
| if self.DownloadFirmware( |
| self.args.force_update, temp_path): |
| yield temp_path |
| else: |
| raise NoUpdatesException |
| elif self.args.from_release: |
| with sys_utils.MountPartition( |
| self._dut.partitions.RELEASE_ROOTFS.path, dut=self._dut) as root: |
| yield os.path.join(root, _FIRMWARE_RELATIVE_PATH) |
| else: |
| yield self.args.firmware_updater |
| |
| try: |
| with GetUpdater() as updater_path: |
| self.assertTrue( |
| os.path.isfile(updater_path), msg='%s is missing.' % updater_path) |
| self.args.firmware_updater = updater_path |
| self.UpdateFirmware() |
| except NoUpdatesException: |
| pass |