blob: f92d1f503d788542a13a6e40b08b82084cf501bc [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# 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.
"""Functional test for pack_firmware.py.
This runs a basic scenario and checks the output by running the update script
with a few fake tools.
"""
from __future__ import print_function
import glob
import gzip
import os
import re
import shutil
import tarfile
from chromite.lib import cros_test_lib
from chromite.lib import cros_build_lib
from chromite.lib import osutils
from chromite.lib import path_util
from pack_firmware import FirmwarePacker
import pack_firmware_utils
# We need to poke around in internal members of PackFirmware.
# pylint: disable=W0212
REEF_HWID = 'Reef A12-B3C-D5E-F6G-H7I'
REEF_MODEL = 'reef'
REEF_RO_MAIN_VERSION = 'Google_Reef.9042.43.0'
BIOS_IMAGES = {
'reef': 'bios-reef.ro-9042-50-0.rw-9042-50-0.bin',
'pyro': 'bios-pyro.ro-9042-41-0.rw-9042-41-0.bin',
'sand': 'bios-reef.ro-9000-0-0.rw-9000-0-0.bin'
}
BIOS_IMAGES['electro'] = BIOS_IMAGES['reef']
BIOS_IMAGES['basking'] = BIOS_IMAGES['sand']
BIOS_IMAGES['wlref'] = BIOS_IMAGES['reef']
EC_IMAGES = {
'reef': 'ec-reef.ro-1-1-5857.rw-1-1-5857.bin',
'pyro': 'ec-pyro.ro-1-1-5840.rw-1-1-5840.bin',
}
EC_IMAGES['sand'] = EC_IMAGES['reef']
EC_IMAGES['electro'] = EC_IMAGES['reef']
EC_IMAGES['basking'] = EC_IMAGES['reef']
MODELS_DIR = 'models'
IMG_DIR = 'images'
# Firmware update runs on the device using the dash shell. Try to use this if
# available.
HAVE_DASH = os.path.exists('/bin/dash')
SHELL = '/bin/dash' if HAVE_DASH else '/bin/sh'
class TestFunctional(cros_test_lib.TempDirTestCase):
"""Functional test for firmware packer script.
If a test needs additional writable paths, they can use self.tempdir. Just
make sure to not pick a path already used by one of the other members (see
the code below for actual paths).
Members:
indir: Directory which contains the input firmware files (e.g. image.bin).
basedir: Directory containing this script.
outdir: Directory to place output shellball.
tempdir: Temporary directory to use as our base for other temp dirs.
unpackdir: Directory used to unpack shellball into.
"""
def setUp(self):
# These are the named paths we use under self.tempdir.
self.indir = os.path.join(self.tempdir, 'indir')
self.outdir = os.path.join(self.tempdir, 'outdir')
self.unpackdir = os.path.join(self.tempdir, 'unpackdir')
osutils.SafeMakedirs(self.indir)
osutils.SafeMakedirs(self.outdir)
osutils.SafeMakedirs(self.unpackdir)
pack_firmware_utils.MakeTestFiles()
self.packer = FirmwarePacker('test')
with tarfile.open('functest/Reef.9042.50.0.tbz2') as tar:
tar.extractall(self.indir)
with tarfile.open('functest/Reef_EC.9042.50.0.tbz2') as tar:
tar.extractall(self.indir)
self.basedir = os.path.realpath(os.path.dirname(__file__))
self.chroot = path_util.ToChrootPath('/')
self.packer._force_dash = HAVE_DASH
@staticmethod
def _ExpectedFiles(extra_files, models=()):
"""Get a sorted list of files that we expect to see in the shellball.
Args:
extra_files: A list of extra files to include.
models: A list of models whose files need to be included.
Returns:
A sorted list of files to expect.
"""
expected_files = set(['manifest.json', 'VERSION'] + extra_files)
for model in models:
expected_files.add(os.path.join(IMG_DIR, BIOS_IMAGES[model]))
expected_files.add(os.path.join(IMG_DIR, EC_IMAGES[model]))
expected_files.add(os.path.join(MODELS_DIR, model, 'setvars.sh'))
return sorted(expected_files)
def _RunScript(self, outfile, model, mode='output', whitelabel_tag=''):
"""Run an autoupdate with the shellball and check that it works.
This relies on fake tools, principally crossystem which is controlled by
environment variables set here.
Args:
outfile: Shellball output file to test.
model: Model name to provide when the script asks for it.
mode: Execution mode (can be 'autoupdate' or 'output')
whitelabel_tag: Value to return for whitelabel_tag from the fake vpd
Returns:
List of lines output from the script
"""
# These are used by our fake vpd/mosys programs (see functest/ directory).
os.environ['FAKE_BIOS_BIN'] = os.path.join(self.indir, 'image.bin')
os.environ['FAKE_WHITELABEL_TAG'] = whitelabel_tag
os.environ['FAKE_MODEL'] = model
new_path = ':'.join((os.path.join(self.basedir, 'functest/bin'),
os.environ.get('PATH')))
cmd = [SHELL, outfile]
cmd += ['--mode', mode]
if mode == 'output':
cmd += ['--model', model, '--output_dir', self.outdir]
cmd += ['--verbose', '--debug']
result = cros_build_lib.run(cmd, capture_output=True,
extra_env={'PATH': new_path},
print_cmd=False, encoding='utf-8')
# The stderr may contain debug messages, info, and status (>>).
# Anything else (for instance, 'ERROR:') should be error.
errors = [line for line in result.error.splitlines()
if line.split() and line.split()[0] not in
['(DEBUG)', 'DEBUG:', '>>', 'INFO:']]
self.assertEqual(errors, [])
return result.output.splitlines()
def _RunPackFirmware(self, extra_args):
"""Run the FirmwarePacker process and read the resulting shellball.
Args:
extra_args: Extra arguments to pass to FirmwarePacker.
Returns:
Tuple containing:
Path to output shellball.
Sorted list of files in the shellball.
Dict containing the version information, with each entry being:
key: shell variable (e.g. TARGET_FWID).
value: value of that variable.
"""
outfile = os.path.join(self.outdir, 'output.sh')
argv = extra_args + ['-o', outfile, '-q']
# Create the shellball, extract it, and get a list of files it contains.
os.environ['SYSROOT'] = 'test'
os.environ['FILESDIR'] = 'test'
self.packer.Start(argv)
cros_build_lib.run([outfile, '--unpack', self.unpackdir],
quiet=True, capture_output=True)
files = []
for dirpath, _, fnames in os.walk(self.unpackdir):
for fname in fnames:
rel_path = os.path.join(dirpath, fname)[len(self.unpackdir) + 1:]
files.append(rel_path)
versions = pack_firmware_utils.ReadVersions(outfile)
return outfile, sorted(files), versions
def testFirmwareUpdate(self):
"""Run the firmware packer, unpack the result and check it."""
extra_args = ['-b', os.path.join(self.indir, 'image.bin'), '-L']
_, files, versions = self._RunPackFirmware(extra_args)
# Check that we got the right files.
self.assertEqual(3, len(files))
self.assertEqual(self._ExpectedFiles(['bios.bin']), files)
# Comb through the VERSION file and check that everything is as expected.
lines = osutils.ReadFile(
os.path.join(self.unpackdir, 'VERSION')).splitlines()
self.assertEqual(4, len(lines))
self.assertEqual(
'BIOS image: 99a6fc64e45596aa2c1a9911cddce952 *%s/image.bin' %
self.indir, lines[1])
self.assertEqual('BIOS version: Google_Reef.9042.50.0', lines[2])
self.assertEqual('Google_Reef.9042.50.0', versions['TARGET_RO_FWID'])
self.assertEqual('Google_Reef.9042.50.0', versions['TARGET_FWID'])
self.assertEqual('', versions['TARGET_ECID'])
self.assertEqual('', versions['TARGET_PDID'])
self.assertEqual('Google_Reef', versions['TARGET_PLATFORM'])
def _CheckVersionsReef(self, versions):
"""Check the versions match expectations for reef.
Args:
versions: Dict of version information:
key: Shell variable name..
value: Value of that variable.
"""
self.assertEqual('Google_Reef.9042.50.0', versions['TARGET_FWID'])
self.assertEqual('Google_Reef.9042.50.0', versions['TARGET_RO_FWID'])
self.assertEqual('reef_v1.1.5857-77f6ed7', versions['TARGET_ECID'])
self.assertEqual('', versions['TARGET_PDID'])
self.assertEqual('Google_Reef', versions['TARGET_PLATFORM'])
def _CheckVars(self, model, versions, signature_id):
"""Check the versions match expectations.
Args:
model: The model considered.
versions: Dict of version information:
key: Shell variable name..
value: Value of that variable.
signature_id: Expected signature ID.
"""
base = 'images/%s'
self.assertEqual(base % BIOS_IMAGES[model], versions['IMAGE_MAIN'])
self.assertEqual(base % EC_IMAGES[model], versions['IMAGE_EC'])
self.assertEqual(signature_id, versions['SIGNATURE_ID'])
def _SetupArgs(self, extra_models=None):
"""Set up the arguments to execute a functional test
This is a convenience function to hold common code.
Args:
extra_models: List of extra models to generate firmware for, or None
Returns:
Tuple:
List of extra arguments to pass to the firmware updater
List of files we expect to see in the firmware update
"""
models = ['reef', 'pyro', 'sand']
all_models = models + ['electro', 'basking'] # Share others firmware
if extra_models:
all_models += extra_models
extra_args = []
for model in all_models:
extra_args += ['-m', model]
extra_args += ['-c', 'test/config.yaml', '-i', 'functest']
expected_files = self._ExpectedFiles(
['models/electro/setvars.sh',
'models/basking/setvars.sh',
'signer_config.csv'],
models)
return extra_args, expected_files
def _CreateSignerLine(self, model, fw_target=None, key_id=None):
"""Creates an expected CSV line for signer_instructions.csv.
Args:
model: Expected model name.
fw_target: Expected firmware target.
key_id: Expected key ID.
Returns:
The expected signer instructions CSV line.
"""
fw_target = fw_target or model
key_id = key_id or model.upper()
return '%s,images/%s,%s,images/%s' % (model, BIOS_IMAGES[fw_target],
key_id, EC_IMAGES[fw_target])
def testFirmwareUpdateUnibuild(self):
"""Run the firmware packer, unpack the result and check it."""
extra_args, expected_files = self._SetupArgs()
_, files, versions = self._RunPackFirmware(extra_args)
self.assertEqual(13, len(files))
self.assertEqual(expected_files, files)
version_lines = osutils.ReadFile(
os.path.join(self.unpackdir, 'VERSION')).splitlines()
# Each assertion matches to a line:
assertions = [
None, # Skip testing first line.
lambda x: self.assertIn('reef', x),
lambda x: self.assertIn('reef/image.bin', x),
lambda x: self.assertEqual('BIOS version: Google_Reef.9042.50.0', x),
lambda x: self.assertIn('/reef/ec.bin', x),
lambda x: self.assertEqual('EC version: reef_v1.1.5857-77f6ed7', x),
lambda x: self.assertEqual('', x),
lambda x: self.assertIn('pyro', x),
lambda x: self.assertIn('pyro/image.bin', x),
lambda x: self.assertEqual('BIOS version: Google_Pyro.9042.41.0', x),
lambda x: self.assertIn('/pyro/ec.bin', x),
lambda x: self.assertEqual('EC version: pyro_v1.1.5840-f0d7761', x),
lambda x: self.assertEqual('', x),
lambda x: self.assertIn('sand', x),
lambda x: self.assertIn('sand/image.bin', x),
lambda x: self.assertEqual('BIOS version: Google_Reef.9000.0.0', x),
lambda x: self.assertIn('/sand/ec.bin', x),
lambda x: self.assertEqual('EC version: reef_v1.1.5857-77f6ed7', x),
lambda x: self.assertEqual('', x),
lambda x: self.assertIn('electro', x),
lambda x: self.assertIn('reef/image.bin', x),
lambda x: self.assertEqual('BIOS version: Google_Reef.9042.50.0', x),
lambda x: self.assertIn('/reef/ec.bin', x),
lambda x: self.assertEqual('EC version: reef_v1.1.5857-77f6ed7', x),
lambda x: self.assertEqual('', x),
lambda x: self.assertIn('basking', x),
lambda x: self.assertIn('sand/image.bin', x),
lambda x: self.assertEqual('BIOS version: Google_Reef.9000.0.0', x),
lambda x: self.assertIn('/sand/ec.bin', x),
lambda x: self.assertEqual('EC version: reef_v1.1.5857-77f6ed7', x),
lambda x: self.assertEqual('', x),
]
self.assertEqual(len(assertions), len(version_lines))
for i, assertion in enumerate(assertions):
if assertion:
assertion(version_lines[i])
manifest = pack_firmware_utils.ReadManifest(self.unpackdir)
versions = manifest['reef']
self._CheckVersionsReef(versions)
self._CheckVars('reef', versions, 'reef')
versions = manifest['electro']
self._CheckVersionsReef(versions)
self._CheckVars('electro', versions, 'electro')
versions = manifest['pyro']
self.assertEqual('Google_Pyro.9042.41.0', versions['TARGET_FWID'])
self._CheckVars('pyro', versions, 'pyro')
lines = osutils.ReadFile(
os.path.join(self.unpackdir, 'signer_config.csv')).splitlines()
self.assertEqual('model_name,firmware_image,key_id,ec_image', lines[0])
self.assertEqual(self._CreateSignerLine('reef'), lines[1])
self.assertEqual(self._CreateSignerLine('pyro'), lines[2])
self.assertEqual(self._CreateSignerLine('sand'), lines[3])
self.assertEqual(self._CreateSignerLine('electro', 'reef'), lines[4])
self.assertEqual(self._CreateSignerLine('basking', 'sand'), lines[5])
def testVersionOutput(self):
"""Check that the -V option shows version information as expected"""
extra_args, expected_files = self._SetupArgs()
outfile, files, _ = self._RunPackFirmware(extra_args)
cmd = [SHELL, outfile, '-V']
result = cros_build_lib.run(cmd, capture_output=True,
print_cmd=False, encoding='utf-8')
check_version_lines = result.output.splitlines()
version_lines = osutils.ReadFile(
os.path.join(self.unpackdir, 'VERSION')).splitlines()
self.assertEqual(version_lines, check_version_lines[:len(version_lines)])
self.assertEqual(set(expected_files), set(files))
def _MakeImage(self, outfile, ro_id, rw_id=''):
"""Create a new firmware image with the given IDs."
This uses the existing flashmap defined in functest/base.fmd. It has two
256-byte ID sections followed by an FMAP section. We can easily create a
file that conforms to this map by padding our ID strings to 256 bytes.
Note fmap.bin can be created with:
$ fmaptool functest/base.fmd functtest.fmap.bin
The binary file is checked in since fmaptool is not installed by the
coreboot-utils ebuild.
Args:
outfile: Destination file (within the unpack directory) for output image.
ro_id: Read-only firmware ID to use.
rw_id: Read-write firmware ID to use, empty string if none.
"""
with open(os.path.join(self.unpackdir, outfile), 'wb') as fd:
fd.write(ro_id.encode('utf-8') + b'\x00' * (256 - len(ro_id)))
fd.write(rw_id.encode('utf-8') + b'\x00' * (256 - len(rw_id)))
fd.write(osutils.ReadFile('functest/fmap.bin', mode='rb'))
def testRepack(self):
"""Repacking the shellball with new images should update versions."""
extra_args = ['-b', os.path.join(self.indir, 'image.bin'), '-L']
outfile, _, versions = self._RunPackFirmware(extra_args)
self.assertEqual('Google_Reef.9042.50.0', versions['TARGET_RO_FWID'])
self.assertEqual('Google_Reef.9042.50.0', versions['TARGET_FWID'])
self.assertEqual('', versions['TARGET_ECID'])
self.assertEqual('', versions['TARGET_PDID'])
# Extract the file into a directory, then overwrite various files with new
# images with a different IDs.
cros_build_lib.run([outfile, '--unpack', self.unpackdir],
quiet=True, capture_output=True)
ro_id = 'Google_Veyron_Mickey.6588.197.0'
rw_id = 'Google_Veyron_Mickey.6588.197.1'
ec_id = 'GoogleEC_Veyron_Mickey.6588.197.0'
pd_id = 'GooglePD_Veyron_Mickey.6588.197.0'
self._MakeImage('bios.bin', ro_id, rw_id)
self._MakeImage('ec.bin', ec_id)
self._MakeImage('pd.bin', pd_id)
# Repack the file and make sure that the versions update.
cros_build_lib.run([outfile, '--repack', self.unpackdir],
quiet=True, capture_output=True)
versions = pack_firmware_utils.ReadVersions(outfile)
self.assertEqual(ro_id, versions['TARGET_RO_FWID'])
self.assertEqual(rw_id, versions['TARGET_FWID'])
self.assertEqual(ec_id, versions['TARGET_ECID'])
self.assertEqual(pd_id, versions['TARGET_PDID'])
def testFilesSorted(self):
"""Files in the shellball should be sorted by filename."""
extra_args = ['-b', os.path.join(self.indir, 'image.bin'), '-L']
outfile, files, _ = self._RunPackFirmware(extra_args)
# The shellball shows files in a comment with this format:
# 16777216 -rw-r--r-- bios.bin
re_files = re.compile(br'^# +[0-9]+ [-rwx]+ \(.*\)$')
for line in osutils.ReadFile(outfile, mode='rb').splitlines():
m = re_files.match(line)
if m:
files.append(m.group(1).decode('utf-8'))
self.assertEqual(files, sorted(files))
def _AssertFileEqual(self, expect_fname, fname):
"""Check that two files have the same contents.
This causes a test failure if the file contents do not match.
Args:
expect_fname: File containing expected contents
fname: File containing contents to check
"""
expect = osutils.ReadFile(expect_fname, mode='rb')
data = osutils.ReadFile(fname, mode='rb')
if expect != data:
self.fail("Contents of '%s' does not match '%s'" % (fname, expect_fname))
def _GetUnequalRegions(self, expect_fname, fname, sig_id=None,
expect_root_sum=None):
"""Get a list of firmware regions which are not the same
This is used to compare two firmware images. It checks the files regions
by region. Any regions which do not match are added to the returned list.
For vblock regions the contents are checked against the corresponding
vblock file in functest rather than the contents of expect_fname. For
GBB regions the root key is checked against expect_root_sum.
The goal of this function is to check that the firmware the updater would
write has the correct keys inside it for the model being written. Note that
'SECTION' regions (include 'WP_RO' are ignored since they cover other
regions (all of which we check).
Args:
expect_fname: File containing expected contents
fname: File containing contents to check
sig_id: Signature ID for vblock, or None
expect_root_sum: Expected SHA1 sum of the root key (as a string), or None
Returns:
List of file regions that differ (e.g. ['GBB']) excluding any
'section' regions
"""
try:
expect_dir = os.path.join(self.tempdir, 'expect')
osutils.SafeMakedirs(expect_dir)
cros_build_lib.run(['dump_fmap', '-x', expect_fname], quiet=True,
cwd=expect_dir)
actual_dir = os.path.join(self.tempdir, 'actual')
osutils.SafeMakedirs(actual_dir)
cros_build_lib.run(['dump_fmap', '-x', fname], quiet=True, cwd=actual_dir)
differ = []
for expect_fname in glob.glob('%s/*' % expect_dir):
expect = osutils.ReadFile(expect_fname, mode='rb')
basename = os.path.basename(expect_fname)
data = osutils.ReadFile(os.path.join(actual_dir, basename), mode='rb')
if sig_id and basename in ['VBLOCK_A', 'VBLOCK_B']:
expect = self._GetVblock(sig_id, basename[-1])
elif expect_root_sum and basename == 'GBB':
result = cros_build_lib.run(
['futility', 'show', fname], quiet=True, capture_output=True,
check=False, encoding='utf-8')
# Relevant output is:
# Root Key:
# Vboot API: 1.0
# Algorithm: 11 RSA8192 SHA512
# Key Version: 1
# Key sha1sum: ac7c01b1bea84da486f30a52bba5eb67ff45f50f
lines = result.output.splitlines()
filtered_lines = [line for line in lines if 'sha1sum' in line]
data = re.match(' *Key sha1sum: *(.*)$', filtered_lines[0]).group(1)
expect = expect_root_sum
if 'SECTION' not in basename and basename != 'WP_RO' and expect != data:
differ.append(basename)
finally:
osutils.RmDir(expect_dir)
osutils.RmDir(actual_dir)
return differ
def testFirmwareOutput(self):
"""Check the --output feature."""
extra_args, _ = self._SetupArgs()
outfile, files, _ = self._RunPackFirmware(extra_args)
lines = self._RunScript(outfile, REEF_MODEL)
# TODO(evanhernandez): Consider changing futility so the image
# names below align with those in the shellball. For now, too
# much other code assumes the '{bios,ec,pd}.bin' naming convention.
self.assertIn('Firmware image saved in: %s/bios.bin' % self.outdir, lines)
# Check that the files were written correctly
files = glob.glob('%s/*' % self.outdir)
ec_file = '%s/ec.bin' % self.outdir
bios_file = '%s/bios.bin' % self.outdir
self.assertIn(ec_file, files)
self.assertIn(bios_file, files)
# Now check that the file contents match.
self._AssertFileEqual(bios_file, '%s/image.bin' % self.indir)
self._AssertFileEqual(ec_file, '%s/ec.bin' % self.indir)
# Electro should be the same as reef
self._RunScript(outfile, 'electro')
self._AssertFileEqual(bios_file, '%s/image.bin' % self.indir)
def _GetVblock(self, sig_id, a_or_b):
"""Get the contents of an A or B vblock for a given signature ID
Args:
sig_id: Signature ID for vblock (this is key_id in the update script)
a_or_b: Either 'A' or 'B' to select which vblock to use
Returns:
Contents of the vblock file as a string
"""
self.assertIn(a_or_b, ['A', 'B'])
with gzip.GzipFile('functest/vblock_%s.%s.gz' % (a_or_b, sig_id)) as fd:
return fd.read()
def _CopyVblock(self, keydir, sig_id, a_or_b):
"""Copy a vblock file into the firmware-update key directory
Args:
keydir: Destination directory to add files into
sig_id: Signature ID for vblock
a_or_b: Either 'A' or 'B' to select which vblock to use
"""
path = os.path.join(keydir, 'vblock_%s.%s' % (a_or_b, sig_id))
osutils.WriteFile(path, self._GetVblock(sig_id, a_or_b), mode='wb')
def _AddKeys(self, keydir, sig_id):
"""Add root key and vblock information for testing.
Args:
keydir: Destination directory to add files into
sig_id: Signature ID used to identify files in functest/
Returns:
SHA1 hash of the root key (as a string)
"""
rootkey = 'functest/rootkey.%s' % sig_id
shutil.copy(rootkey, keydir)
self._CopyVblock(keydir, sig_id, 'A')
self._CopyVblock(keydir, sig_id, 'B')
result = cros_build_lib.run(
['futility', 'show', rootkey], quiet=True, encoding='utf-8')
# The last output line has the following format:
# Key sha1sum: ac7c01b1bea84da486f30a52bba5eb67ff45f50f
return result.output.splitlines()[-1].split()[2]
def _CheckOutput(self, outfile, model, rootkey_sum, signature_id='',
whitelabel_tag=''):
"""Check that the firmware updater can generate the correct output.
This runs the firmware in 'output' mode with the given model and checks
that the resulting firmware image is correctly signed for that model.
Args:
outfile: Firmware update shellball filename
model: Name of model to generate firmware for
rootkey_sum: SHA1 sum of the root key (as a string)
signature_id: Value to return for signature_id from the fake mosys
whitelabel_tag: Value to return for whitelabel_tag from the fake vpd
"""
self._RunScript(outfile, model, whitelabel_tag=whitelabel_tag)
expected_sig_id = signature_id or model
bios_file = os.path.join(self.outdir, 'bios.bin')
regions = self._GetUnequalRegions('%s/image.bin' % self.indir, bios_file,
expected_sig_id, rootkey_sum)
self.assertEqual([], regions)
def AddFakeKeys(self, outfile, key_ids):
"""Add fake keys to an existing firmware update to allow testing
Args:
outfile: Firmware update file to modify
key_ids: List of key IDs to add
Returns:
List of sums for each key in key_ids
"""
cros_build_lib.run([outfile, '--unpack', self.unpackdir], quiet=True)
keydir = os.path.join(self.unpackdir, 'keyset')
osutils.SafeMakedirs(keydir)
key_sums = [self._AddKeys(keydir, x) for x in key_ids]
cros_build_lib.run([outfile, '--repack', self.unpackdir], quiet=True)
return key_sums
def testSignedFirmwareOutput(self):
"""Check the --output feature with signed firmware."""
extra_args, _ = self._SetupArgs()
outfile, _, _ = self._RunPackFirmware(extra_args)
# Add some fake root keys and vblocks for some of the models, to simulate
# the action of the signer.
reef_sum, electro_sum = self.AddFakeKeys(outfile, ['reef', 'electro'])
self._CheckOutput(outfile, REEF_MODEL, reef_sum)
self._CheckOutput(outfile, 'electro', electro_sum)
def testWhitelabel(self):
"""Test generation of firmware for a whitelabel model"""
extra_args, _ = self._SetupArgs(['whitelabel-test'])
self._RunPackFirmware(extra_args)
version_lines = osutils.ReadFile(
os.path.join(self.unpackdir, 'VERSION')).splitlines()
# Check that the whitelabel version output matches sand.
found = None
for linenum, line in enumerate(version_lines):
if 'whitelabel-test' in line:
found = linenum
break
self.assertTrue(found)
lines = version_lines[found:]
assertions = [
lambda x: self.assertIn('whitelabel-test', x),
lambda x: self.assertIn('sand/image.bin', x),
lambda x: self.assertEqual('BIOS version: Google_Reef.9000.0.0', x),
lambda x: self.assertIn('/sand/ec.bin', x),
lambda x: self.assertEqual('EC version: reef_v1.1.5857-77f6ed7', x),
lambda x: self.assertEqual('', x),
]
self.assertEqual(len(assertions), len(lines))
for i, assertion in enumerate(assertions):
if assertion:
assertion(lines[i])
# Check that the manifest is correct (points to sand).
versions = pack_firmware_utils.ReadManifest(
self.unpackdir)['whitelabel-test']
self.assertEqual('Google_Reef.9000.0.0', versions['TARGET_FWID'])
self._CheckVars('sand', versions, 'whitelabel-test')
# Check the signer instructions - the last line should be for whitelabel.
lines = osutils.ReadFile(
os.path.join(self.unpackdir, 'signer_config.csv')).splitlines()
self.assertEqual(
self._CreateSignerLine('whitelabel-test', 'sand', 'WHITELABEL'),
lines[-1])
def _CheckZeroTouchWhitelabel(self, model, wl_models, wl_tags):
"""Test generation of firmware for a 'zero-touch' whitelabel model
This is one where the device cannot tell its model name by hardware
detection, but must use the model name stored in the VPD
customization_id.
Args:
model: Main whitelabel model to generate firmware for
wl_models: List of whitelabel models to generate firmware for
wl_tags: List of whitelabel tags to generate firmware for
"""
if wl_tags:
assert not wl_models, 'WL tags cannot be tested with multiple models'
wl_models = ['%s-%s' % (model, wl_tag) for wl_tag in wl_tags]
extra_args, _ = self._SetupArgs([model] + wl_models)
outfile, _, _ = self._RunPackFirmware(extra_args)
version_lines = osutils.ReadFile(
os.path.join(self.unpackdir, 'VERSION')).splitlines()
# Check that the whitelabel version output matches sand.
found = None
for linenum, line in enumerate(version_lines):
if model in line:
found = linenum
break
self.assertTrue(found)
lines = version_lines[found:]
assertions = [
lambda x: self.assertIn(model, x),
lambda x: self.assertIn('reef/image.bin', x),
lambda x: self.assertEqual('BIOS version: Google_Reef.9042.50.0', x),
lambda x: self.assertIn('/reef/ec.bin', x),
lambda x: self.assertEqual('EC version: reef_v1.1.5857-77f6ed7', x),
lambda x: self.assertEqual('', x),
]
self.assertEqual(len(assertions), len(lines))
for i, assertion in enumerate(assertions):
if assertion:
assertion(lines[i])
# We should not create setvars.sh scripts for zero-touch whitelabels.
for wl_model in wl_models:
self.assertNotExists(os.path.join(self.unpackdir, MODELS_DIR, wl_model))
# Check that the manifest is correct (points to sand).
versions = pack_firmware_utils.ReadManifest(self.unpackdir)[model]
self.assertEqual('Google_Reef.9042.50.0', versions['TARGET_FWID'])
self._CheckVars('reef', versions, 'sig-id-in-customization-id')
# Check the signer instructions - we should get keys for the two
# zero-touch whitelabels.
lines = osutils.ReadFile(
os.path.join(self.unpackdir, 'signer_config.csv')).splitlines()
self.assertEqual(self._CreateSignerLine('basking', 'sand'),
lines[-1 - len(wl_models)])
for i, wl_model in enumerate(wl_models):
self.assertEqual(self._CreateSignerLine(wl_model, 'reef'),
lines[i - len(wl_models)])
sums = self.AddFakeKeys(outfile, wl_models)
for i, wl_tag in enumerate(wl_tags):
self._CheckOutput(outfile, model, sums[i], '%s-%s' % (model, wl_tag),
wl_tag)
def testZeroTouchWhitelabel(self):
"""Test generation of firmware for a 'zero-touch' whitelabel model
Whitelabels use a single model with multiple 'whitelabel' tags associated
with it.
"""
self._CheckZeroTouchWhitelabel('wlref', [], ['wltag1', 'wltag2'])
def testEmptyFirmwareOutput(self):
"""Ensure no output is generated if not --local and no URIs."""
args = ['-c', 'test/config_no_uri.yaml', '-i', 'functest']
outfile = os.path.join(self.outdir, 'output.sh')
args += ['-o', outfile]
os.environ['SYSROOT'] = 'test'
os.environ['FILESDIR'] = 'test'
self.packer.Start(args)
# Check that no files were written
files = glob.glob('%s/*' % self.outdir)
self.assertListEqual(files, [], 'Expected outdir to be empty')
if __name__ == '__main__':
cros_test_lib.main(module=__name__)