blob: 080c7a9f17e48f0f7e0a9433e037ec7e5c858810 [file] [log] [blame]
# 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