blob: ee5ec67955d7b22ddab7caeb331d861b9d578cb8 [file] [log] [blame]
# Copyright 2023 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests the firmware's fastboot capabilities."""
import logging
import re
from typing import Tuple
from fuchsia_base_test import fuchsia_base_test
from honeydew.fuchsia_device import fuchsia_device
from mobly import asserts, test_runner
# Required fastboot variables.
# - key: variable name
# - value: a list of acceptable strings for the value, or None for any value
_REQUIRED_VARS = {
# Required for fastboot protocol communication.
"max-download-size": None,
"serialno": None,
# Required for `ffx target flash` to identify boards.
"hw-revision": None,
# Slot information for debugging and failure analysis.
"current-slot": ["a", "b"],
"slot-count": ["2"],
"slot-retry-count:a": None,
"slot-retry-count:b": None,
"slot-successful:a": None,
"slot-successful:b": None,
"slot-unbootable:a": None,
"slot-unbootable:b": None,
# TODO(b/308030836):: add is-userspace back after all devices support it.
# "is-userspace": ["no"],
# TODO(b/308030836):: add the vx- variables after all devices support it.
# "vx-locked": ["no", "yes"],
# "vx-unlockable": ["ephemeral", "no", "yes"],
}
# Regexp to extract variable name and value from fastboot output.
#
# Example getvar output:
# - "foo:bar"
# - "foo: bar"
# - "foo:a: bar"
# - "foo: bar baz"
# - "(bootloader) foo: bar"
#
# TODO(b/308030836): require a space between the name/arg and value, otherwise
# parsing can be ambiguous if the value itself could contain a colon. Currently
# we assume there is at most one arg per variable, and any other colon belongs
# to the value, e.g. `foo:a:b:c` results in name="foo:a", value="b:c".
_GETVAR_REGEX = re.compile(
r"(\(bootloader\) )?(?P<name>[a-z\-]+(:[a-z0-9]+)?): ?(?P<val>.*)"
)
def parse_getvar(line: str) -> Tuple[str, str]: # type: ignore[return]
"""Parses a `getvar` or `getvar all` output line.
Args:
line: a single line of `getvar` output.
Returns:
A tuple containing (name, value).
Raises:
Mobly assert if the line doesn't look like `getvar` output.
"""
match = _GETVAR_REGEX.match(line)
if match:
return (match.group("name"), match.group("val"))
asserts.fail("Failed to parse getvar output", extras={"line": line})
def ignore_line(line: str) -> bool:
"""Returns True if this fastboot output line should be ignored.
Infra devices seem to occasionally drop off fastboot for short periods of
time, and if we issue a command during this time we get a
"<waiting for _serial_>" line that we should ignore (b/419262916).
TODO(b/466453484): consider addressing this centrally in Lacewing instead.
"""
return "waiting for" in line
class FastbootTest(fuchsia_base_test.FuchsiaBaseTest):
def setup_class(self) -> None:
"""Initializes all DUT(s)"""
super().setup_class()
self.device: fuchsia_device.FuchsiaDevice = self.fuchsia_devices[0]
# TODO(http://b/276740268#comment33): add support for rebooting into
# fastboot here and leaving the device in fastboot mode for the entire
# test class?
#
# For the time being we'll group tests that would ideally be separate
# into single methods to minimize the number of times we have to reboot.
def setup_test(self) -> None:
"""Puts the device into fastboot mode before each test."""
super().setup_test()
self.device.fastboot.boot_to_fastboot_mode()
def teardown_test(self) -> None:
"""Puts the device back into Fuchsia mode after each test."""
if self.device.fastboot.is_in_fastboot_mode():
self.device.fastboot.boot_to_fuchsia_mode()
super().teardown_test()
def test_getvar(self) -> None:
"""Tests fastboot variables."""
# Make sure each variable can also be individually queried and that the
# value is what we expect.
logging.info("Checking `getvar` variables")
for expected_name, expected_values in _REQUIRED_VARS.items():
lines = self.device.fastboot.run(["getvar", expected_name])
if len(lines) == 1:
output = lines[0]
elif len(lines) == 2 and ignore_line(lines[0]):
output = lines[1]
else:
asserts.fail(
"Unexpected getvar output",
extras={"lines": lines},
)
name, value = parse_getvar(output)
asserts.assert_equal(
name, expected_name, "getvar name doesn't match expected"
)
if expected_values:
asserts.assert_in(
value,
expected_values,
"getvar value doesn't match expected",
extras={"name": name},
)
# Make sure all the variables we care about also exist in `getvar all`.
logging.info("Checking `getvar all`")
getvar_all_vars = []
for line in self.device.fastboot.run(["getvar", "all"]):
if ignore_line(line):
continue
name, _ = parse_getvar(line)
if name != "all":
getvar_all_vars.append(name)
for name in _REQUIRED_VARS:
asserts.assert_in(
name, getvar_all_vars, "Missing variable in `getvar all`"
)
if __name__ == "__main__":
test_runner.main()