blob: 94096aa4c36d2b720a46727ee1a63f7b023c9e1a [file] [log] [blame] [edit]
# Copyright 2021 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Test the crosid tool end-to-end."""
import shlex
import struct
import subprocess
import cros_config_host.identity_table # pylint: disable=import-error
import pytest # pylint: disable=wrong-import-order
def getvars(output):
"""Get a dictionary from the output of crosid."""
result = {}
for word in shlex.split(output):
key, _, value = word.partition("=")
result[key] = value
return result
def make_config(
model_name,
frid_match=None,
sku_id=None,
customization_id=None,
custom_label_tag=None,
):
identity = {}
if frid_match is not None:
identity["frid"] = frid_match
if sku_id is not None:
identity["sku-id"] = sku_id
if customization_id is not None:
identity["customization-id"] = customization_id
if custom_label_tag is not None:
identity["custom-label-tag"] = custom_label_tag
return {
"name": model_name,
"identity": identity,
}
def make_fake_sysroot(
path,
smbios_sku=None,
fdt_sku=None,
acpi_frid=None,
fdt_frid=None,
vpd_values=None,
configs=(),
):
smbios_sysfs_path = path / "sys" / "class" / "dmi" / "id"
if smbios_sku is not None:
smbios_sysfs_path.mkdir(exist_ok=True, parents=True)
(smbios_sysfs_path / "product_sku").write_text(f"sku{smbios_sku}\n")
proc_fdt_path = path / "proc" / "device-tree"
proc_fdt_coreboot_path = proc_fdt_path / "firmware" / "coreboot"
if fdt_sku is not None:
proc_fdt_coreboot_path.mkdir(exist_ok=True, parents=True)
contents = fdt_sku.to_bytes(4, byteorder="big")
(proc_fdt_coreboot_path / "sku-id").write_bytes(contents)
proc_fdt_chromeos_path = proc_fdt_path / "firmware" / "chromeos"
if fdt_frid is not None:
proc_fdt_chromeos_path.mkdir(exist_ok=True, parents=True)
(proc_fdt_chromeos_path / "readonly-firmware-version").write_text(
fdt_frid
)
# Leave it use legacy path. Once we completely switched to use current
# chromeos_acpi driver, change the path to "GOOG0016:00".
chromeos_acpi_path = path / "sys" / "devices" / "platform" / "chromeos_acpi"
if acpi_frid is not None:
chromeos_acpi_path.mkdir(exist_ok=True, parents=True)
(chromeos_acpi_path / "FRID").write_text(acpi_frid)
if vpd_values:
vpd_sysfs_path = path / "sys" / "firmware" / "vpd" / "ro"
vpd_sysfs_path.mkdir(exist_ok=True, parents=True)
for name, value in vpd_values.items():
(vpd_sysfs_path / name).write_text(value)
configs_full = {"chromeos": {"configs": configs}}
config_path = path / "usr" / "share" / "chromeos-config"
config_path.mkdir(exist_ok=True, parents=True)
with open(config_path / "identity.bin", "wb") as output_file:
cros_config_host.identity_table.WriteIdentityStruct(
configs_full, output_file
)
REEF_CONFIGS = [
make_config(
"electro",
frid_match="Google_Reef",
sku_id=8,
customization_id="PARMA-ELECTRO",
),
make_config(
"basking",
frid_match="Google_Reef",
sku_id=0,
customization_id="OEM2-BASKING",
),
make_config(
"pyro",
frid_match="Google_Pyro",
customization_id="NEWTON2-PYRO",
),
make_config(
"sand",
frid_match="Google_Sand",
customization_id="ACER-SAND",
),
make_config(
"alan",
frid_match="Google_Snappy",
sku_id=7,
customization_id="DOLPHIN-ALAN",
),
make_config(
"bigdaddy",
frid_match="Google_Snappy",
sku_id=2,
customization_id="BENTLEY-BIGDADDY",
),
make_config(
"bigdaddy",
frid_match="Google_Snappy",
sku_id=5,
customization_id="BENTLEY-BIGDADDY",
),
make_config(
"snappy",
frid_match="Google_Snappy",
sku_id=8,
customization_id="MORGAN-SNAPPY",
),
make_config(
"snappy",
frid_match="Google_Snappy",
),
]
@pytest.mark.parametrize("config_idx", list(range(len(REEF_CONFIGS))))
def test_reef(tmp_path, executable_path, config_idx):
cfg = REEF_CONFIGS[config_idx]
identity = cfg["identity"]
vpd = {}
customization_id = identity.get("customization-id")
if customization_id:
vpd["customization_id"] = customization_id
make_fake_sysroot(
tmp_path,
acpi_frid=f"{identity['frid']}.1234_5678_910.1234.B",
smbios_sku=identity.get("sku-id"),
vpd_values=vpd,
configs=REEF_CONFIGS,
)
result = subprocess.run(
[executable_path, "--sysroot", tmp_path, "-v"],
check=True,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
assert getvars(result.stdout) == {
"SKU": str(identity.get("sku-id", "none")),
"CONFIG_INDEX": str(config_idx),
"FIRMWARE_MANIFEST_KEY": cfg["name"],
}
def test_no_match(tmp_path, executable_path):
# Test the case that no configs match (e.g., running wrong image
# on device)
make_fake_sysroot(
tmp_path,
acpi_frid="Google_Samus.1234_567_890.ohea",
configs=REEF_CONFIGS,
)
# pylint: disable=subprocess-run-check
result = subprocess.run(
[executable_path, "--sysroot", tmp_path, "-v"],
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
assert result.returncode != 0
assert getvars(result.stdout) == {
"SKU": "none",
"CONFIG_INDEX": "unknown",
"FIRMWARE_MANIFEST_KEY": "",
}
def test_both_customization_id_and_whitelabel(tmp_path, executable_path):
# Having both a customization_id and custom_label_tag indicates the
# RO VPD was tampered/corrupted, and should result in errors.
make_fake_sysroot(
tmp_path,
acpi_frid="Google_Sand.1234_5678_90.AAAA.B",
vpd_values={
"customization_id": "ACER-SAND",
"whitelabel_tag": "some_wl",
},
configs=REEF_CONFIGS,
)
# pylint: disable=subprocess-run-check
result = subprocess.run(
[executable_path, "--sysroot", tmp_path, "-v"],
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
assert result.returncode != 0
assert result.stdout == ""
VILBOZ14_CONFIGS = [
make_config("vilboz14", sku_id=0, frid_match="Google_Vilboz"),
make_config("vilboz14", sku_id=1, frid_match="Google_Vilboz"),
make_config(
"vilboz14",
sku_id=1,
frid_match="Google_Vilboz",
custom_label_tag="vilboz14len",
),
]
def test_vilboz14(tmp_path, executable_path):
make_fake_sysroot(
tmp_path,
acpi_frid="Google_Vilboz.123",
smbios_sku=1,
vpd_values={"custom_label_tag": "vilboz14len"},
configs=VILBOZ14_CONFIGS,
)
# pylint: disable=subprocess-run-check
result = subprocess.run(
[executable_path, "--sysroot", tmp_path, "-v"],
check=True,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
assert getvars(result.stdout) == {
"SKU": "1",
"CONFIG_INDEX": "2",
"FIRMWARE_MANIFEST_KEY": "vilboz14",
}
TROGDOR_CONFIGS = [
make_config("trogdor", frid_match="Google_Trogdor"),
make_config("lazor", frid_match="Google_Lazor", sku_id=0),
make_config("lazor", frid_match="Google_Lazor", sku_id=1),
make_config("lazor", frid_match="Google_Lazor", sku_id=2),
make_config("lazor", frid_match="Google_Lazor", sku_id=3),
make_config(
"limozeen", frid_match="Google_Lazor", sku_id=5, custom_label_tag=""
),
make_config(
"limozeen",
frid_match="Google_Lazor",
sku_id=6,
custom_label_tag="lazorwl",
),
make_config(
"limozeen", frid_match="Google_Lazor", sku_id=6, custom_label_tag=""
),
make_config("lazor", frid_match="Google_Lazor"),
]
@pytest.mark.parametrize("config_idx", list(range(len(TROGDOR_CONFIGS))))
def test_trogdor(tmp_path, executable_path, config_idx):
cfg = TROGDOR_CONFIGS[config_idx]
identity = cfg["identity"]
vpd = {}
custom_label_tag = identity.get("custom-label-tag")
if custom_label_tag:
vpd["whitelabel_tag"] = custom_label_tag
make_fake_sysroot(
tmp_path,
fdt_frid=f"{identity['frid']}.123_456",
fdt_sku=identity.get("sku-id"),
vpd_values=vpd,
configs=TROGDOR_CONFIGS,
)
result = subprocess.run(
[executable_path, "--sysroot", tmp_path, "-v"],
check=True,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
assert getvars(result.stdout) == {
"SKU": str(identity.get("sku-id", "none")),
"CONFIG_INDEX": str(config_idx),
"FIRMWARE_MANIFEST_KEY": cfg["name"],
}
def test_frid_missing(tmp_path, executable_path):
# When FRID is not available via ACPI or FDT, but required by all
# configs, this should be an error.
make_fake_sysroot(
tmp_path,
configs=TROGDOR_CONFIGS,
)
# pylint: disable=subprocess-run-check
result = subprocess.run(
[executable_path, "--sysroot", tmp_path, "-v"],
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
assert result.returncode != 0
assert getvars(result.stdout) == {
"SKU": "none",
"CONFIG_INDEX": "unknown",
"FIRMWARE_MANIFEST_KEY": "",
}
def test_missing_identity_table(tmp_path, executable_path):
# When identity.bin is missing, crosid should exit with an error.
# pylint: disable=subprocess-run-check
result = subprocess.run(
[executable_path, "--sysroot", tmp_path],
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
assert result.returncode != 0
assert getvars(result.stdout) == {
"SKU": "none",
"CONFIG_INDEX": "unknown",
"FIRMWARE_MANIFEST_KEY": "",
}
@pytest.mark.parametrize(
"contents",
[
b"", # too small for header
struct.pack("<LLL4x", 42, 0, 0), # bad version
struct.pack("<LLL4x", 0, 0, 1), # too small for entries
],
)
def test_corrupted_identity_table(tmp_path, executable_path, contents):
# When identity.bin is corrupted, crosid should exit with an error.
identity_file = (
tmp_path / "usr" / "share" / "chromeos-config" / "identity.bin"
)
identity_file.parent.mkdir(exist_ok=True, parents=True)
identity_file.write_bytes(contents)
# pylint: disable=subprocess-run-check
result = subprocess.run(
[executable_path, "--sysroot", tmp_path],
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
assert result.returncode != 0
assert getvars(result.stdout) == {
"SKU": "none",
"CONFIG_INDEX": "unknown",
"FIRMWARE_MANIFEST_KEY": "",
}
@pytest.mark.parametrize(
"contents",
[
"",
"\n",
"sku\n",
"sku-\n",
"sku8z\n",
"8\n",
"SKU8\n",
],
)
def test_corrupted_sku_x86(tmp_path, executable_path, contents):
# Test with a corrupted SKU file that we won't match a specific
# SKU.
make_fake_sysroot(
tmp_path,
acpi_frid="Google_Snappy.123",
smbios_sku=0,
vpd_values={
"customization_id": "MORGAN-SNAPPY",
},
configs=REEF_CONFIGS,
)
sku_file = tmp_path / "sys" / "class" / "dmi" / "id" / "product_sku"
sku_file.write_text(contents)
result = subprocess.run(
[executable_path, "--sysroot", tmp_path],
check=True,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
assert getvars(result.stdout) == {
"SKU": "none",
"CONFIG_INDEX": "8",
"FIRMWARE_MANIFEST_KEY": "snappy",
}
@pytest.mark.parametrize(
"contents",
[
b"",
b"\x00",
b"\x00\x00\x00\x00\x00",
],
)
def test_corrupted_sku_arm(tmp_path, executable_path, contents):
# Test with a corrupted SKU file that we won't match a specific
# SKU.
make_fake_sysroot(
tmp_path,
fdt_frid="Google_Lazor.123",
fdt_sku=0,
configs=TROGDOR_CONFIGS,
)
sku_file = (
tmp_path / "proc" / "device-tree" / "firmware" / "coreboot" / "sku-id"
)
sku_file.write_bytes(contents)
result = subprocess.run(
[executable_path, "--sysroot", tmp_path],
check=True,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
assert getvars(result.stdout) == {
"SKU": "none",
"CONFIG_INDEX": "8",
"FIRMWARE_MANIFEST_KEY": "lazor",
}
@pytest.mark.parametrize(
["key", "expected_result"],
[
("CONFIG_INDEX", "4"),
("SKU", "7"),
("FIRMWARE_MANIFEST_KEY", "alan"),
],
)
def test_filter_output(tmp_path, executable_path, key, expected_result):
make_fake_sysroot(
tmp_path,
acpi_frid="Google_Snappy.123",
smbios_sku=7,
vpd_values={"customization_id": "DOLPHIN-ALAN"},
configs=REEF_CONFIGS,
)
result = subprocess.run(
[executable_path, "--sysroot", tmp_path, "-f", key],
check=True,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
encoding="utf-8",
)
assert result.stdout == expected_result
def test_factory_override(tmp_path, executable_path):
"""Test that the --sku-id and --custom-label-tag flags work."""
make_fake_sysroot(
tmp_path,
fdt_frid="Google_Lazor.123_456",
fdt_sku=3,
configs=TROGDOR_CONFIGS,
)
result = subprocess.run(
[
executable_path,
"--sysroot",
tmp_path,
"-v",
"--sku-id",
"6",
"--custom-label-tag",
"lazorwl",
],
check=True,
stdin=subprocess.DEVNULL,
stdout=subprocess.PIPE,
encoding="utf-8",
)
assert getvars(result.stdout) == {
"SKU": "6",
"CONFIG_INDEX": "6",
"FIRMWARE_MANIFEST_KEY": "limozeen",
}