blob: a42c554f6c2bb61f41d4876bdd25919c0b79f888 [file] [log] [blame]
# Copyright 2015 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests for the sysroot library."""
import os
from typing import Iterable, List, Optional, Tuple
from chromite.lib import chroot_lib
from chromite.lib import constants
from chromite.lib import cros_build_lib
from chromite.lib import cros_test_lib
from chromite.lib import osutils
from chromite.lib import sysroot_lib
from chromite.lib import toolchain
from chromite.lib import unittest_lib
from chromite.lib.parser import package_info
from chromite.utils import os_util
class SysrootLibTest(cros_test_lib.MockTempDirTestCase):
"""Unittests for sysroot_lib.py"""
def setUp(self):
"""Setup the test environment."""
self.PatchObject(cros_build_lib, "IsInsideChroot", return_value=False)
# Fake being root to avoid running all filesystem commands with
# sudo_run.
self.PatchObject(os_util, "is_root_user", return_value=True)
sysroot_path = os.path.join(self.tempdir, "sysroot")
osutils.SafeMakedirs(sysroot_path)
self.sysroot = sysroot_lib.Sysroot(sysroot_path)
self.relative_sysroot = sysroot_lib.Sysroot("sysroot")
# make.conf needs to exist to correctly read back config.
unittest_lib.create_stub_make_conf(sysroot_path)
def _writeOverlays(
self,
board_overlays: Optional[Iterable[str]] = None,
portdir_overlays: Optional[Iterable[str]] = None,
board: str = None,
) -> Tuple[List[str], List[str]]:
"""Helper function to write board and portdir overlays for the sysroot.
By default, uses one fake board overlay, and the chromiumos and portage
stable overlays. Set the arguments to an empty list to set no values for
that field. When not explicitly set, |portdir_overlays| includes all
values in |board_overlays|.
Returns:
The board overlays, and the portdir overlays.
"""
if board_overlays is None:
board_overlays = ["overlay/board"]
if portdir_overlays is None:
portdir_overlays = [
constants.CHROMIUMOS_OVERLAY_DIR,
constants.PORTAGE_STABLE_OVERLAY_DIR,
] + board_overlays
board_overlays_field = sysroot_lib.STANDARD_FIELD_BOARD_OVERLAY
portdir_field = sysroot_lib.STANDARD_FIELD_PORTDIR_OVERLAY
board_field = sysroot_lib.STANDARD_FIELD_BOARD_USE
board_values = [
f"{constants.CHROOT_SOURCE_ROOT}/{x}" for x in board_overlays
]
board_value = "\n".join(board_values)
portdir_values = [
f"{constants.CHROOT_SOURCE_ROOT}/{x}" for x in portdir_overlays
]
portdir_value = "\n".join(portdir_values)
config_values = {}
if board_values:
config_values[board_overlays_field] = board_value
if portdir_values:
config_values[portdir_field] = portdir_value
if board:
config_values[board_field] = board
config = "\n".join(f'{k}="{v}"' for k, v in config_values.items())
self.sysroot.WriteConfig(config)
return board_values, portdir_values
def testGetStandardField(self):
"""Tests that standard field can be fetched correctly."""
self.sysroot.WriteConfig('FOO="bar"')
self.assertEqual("bar", self.sysroot.GetStandardField("FOO"))
# Works with multiline strings
multiline = """foo
bar
baz
"""
self.sysroot.WriteConfig('TEST="%s"' % multiline)
self.assertEqual(multiline, self.sysroot.GetStandardField("TEST"))
def testReadWriteCache(self):
"""Tests that we can write and read to the cache."""
# If a field is not defined we get None.
self.assertEqual(None, self.sysroot.GetCachedField("foo"))
# If we set a field, we can get it.
self.sysroot.SetCachedField("foo", "bar")
self.assertEqual("bar", self.sysroot.GetCachedField("foo"))
# Setting a field in an existing cache preserve the previous values.
self.sysroot.SetCachedField("hello", "bonjour")
self.assertEqual("bar", self.sysroot.GetCachedField("foo"))
self.assertEqual("bonjour", self.sysroot.GetCachedField("hello"))
# Setting a field to None unsets it.
self.sysroot.SetCachedField("hello", None)
self.assertEqual(None, self.sysroot.GetCachedField("hello"))
def testErrorOnBadCachedValue(self):
"""Tests that we detect bad value for the sysroot cache."""
forbidden = [
'hello"bonjour',
"hello\\bonjour",
"hello\nbonjour",
"hello$bonjour",
"hello`bonjour",
]
for value in forbidden:
with self.assertRaises(ValueError):
self.sysroot.SetCachedField("FOO", value)
def testGenerateConfigNoToolchainRaisesError(self):
"""Tests _GenerateConfig() with no toolchain raises an error."""
self.PatchObject(
toolchain, "FilterToolchains", autospec=True, return_value={}
)
with self.assertRaises(sysroot_lib.ConfigurationError):
# pylint: disable=protected-access
self.sysroot._GenerateConfig(
{}, ["foo_overlay"], ["foo_overlay"], ""
)
def testExists(self):
"""Tests the Exists method."""
self.assertTrue(self.sysroot.Exists())
dne_sysroot = sysroot_lib.Sysroot(os.path.join(self.tempdir, "DNE"))
self.assertFalse(dne_sysroot.Exists())
def testExistsInChroot(self):
"""Test the Exists method with a chroot."""
chroot = chroot_lib.Chroot(self.tempdir, out_path=self.tempdir / "out")
self.assertTrue(self.relative_sysroot.Exists(chroot=chroot))
def testEquals(self):
"""Basic checks for the __eq__ methods."""
sysroot1 = sysroot_lib.Sysroot(self.tempdir)
sysroot2 = sysroot_lib.Sysroot(self.tempdir)
self.assertEqual(sysroot1, sysroot2)
self.assertNotEqual(sysroot1, None)
def testProfileName(self):
"""Test the profile_name property when a value is set."""
profile = "foo"
self.sysroot.SetCachedField(
sysroot_lib.CACHED_FIELD_PROFILE_OVERRIDE, profile
)
self.assertEqual(profile, self.sysroot.profile_name)
def testProfileNameDefault(self):
"""Test the profile_name property when no value is set."""
self.assertEqual(sysroot_lib.DEFAULT_PROFILE, self.sysroot.profile_name)
def testBoardOverlay(self):
"""Test the board_overlay property."""
board_overlays, _portdir_overlays = self._writeOverlays()
self.assertEqual(
sorted(board_overlays), sorted(self.sysroot.board_overlay)
)
def testBuildTargetOverlays(self):
"""Tests for populated _build_target_overlay[s]."""
private = "/path/to/overlay-x-private"
expected = ["/path/to/overlay-x", private]
overlays = expected + ["/path/to/chromeos-overlay"]
self._writeOverlays(overlays, board="x")
# pylint: disable=protected-access
results = [str(x) for x in self.sysroot._build_target_overlays]
self.assertEqual(len(expected), len(results))
for current in expected:
self.assertTrue(any(result.endswith(current) for result in results))
self.assertTrue(
str(self.sysroot.build_target_overlay).endswith(private)
)
def testNoBuildTargetOverlay(self):
"""Test for no standard build target overlay."""
self._writeOverlays(["/path/to/chromeos-overlay", "/path/to/chipset-x"])
# pylint: disable=protected-access
self.assertEqual(0, len(self.sysroot._build_target_overlays))
self.assertIsNone(self.sysroot.build_target_overlay)
def testChipset(self):
"""Test for extracting a valid chipset."""
expected = "foo"
chipsets = [
f"/path/to/chipset-{expected}",
f"/path/to/chipset-{expected}-private",
]
all_overlays = chipsets + ["/path/to/chromeos-overlay"]
self._writeOverlays(all_overlays)
self.assertEqual(expected, self.sysroot.chipset)
def testNoChipset(self):
"""Test for handling no retrievable chipset value."""
self._writeOverlays(
["/path/to/chromeos-overlay", "/path/to/overlay-board"]
)
self.assertIsNone(self.sysroot.chipset)
def testOverlays(self):
"""Test the overlays property."""
_board_overlays, portdir_overlays = self._writeOverlays()
self.assertEqual(portdir_overlays, self.sysroot.portdir_overlay)
def testGetOverlays(self):
"""Test the get_overlays function."""
board_overlays, portdir_overlays = self._writeOverlays()
self.assertEqual(
board_overlays,
[str(x) for x in self.sysroot.get_overlays(build_target_only=True)],
)
self.assertEqual(
portdir_overlays, [str(x) for x in self.sysroot.get_overlays()]
)
def testGetOverlaysRelative(self):
portdir_overlays = [
constants.CHROMIUMOS_OVERLAY_DIR,
constants.PORTAGE_STABLE_OVERLAY_DIR,
]
self._writeOverlays(portdir_overlays=portdir_overlays)
self.assertEqual(
portdir_overlays,
[str(x) for x in self.sysroot.get_overlays(relative=True)],
)
class ProfileTest(cros_test_lib.TestCase):
"""Tests for Profile."""
def testEquality(self):
"""Test that equality functions work."""
profile = sysroot_lib.Profile("profile")
self.assertEqual(profile, sysroot_lib.Profile("profile"))
self.assertNotEqual(profile, sysroot_lib.Profile("other"))
self.assertNotEqual(profile, sysroot_lib.Profile(""))
self.assertNotEqual(profile, None)
class SysrootLibInstallConfigTest(cros_test_lib.MockTempDirTestCase):
"""Unittests for sysroot_lib.py"""
# pylint: disable=protected-access
def setUp(self):
"""Setup the test environment."""
# Fake being root to avoid running all filesystem commands with
# sudo_run.
self.PatchObject(os_util, "is_root_user", return_value=True)
self.sysroot = sysroot_lib.Sysroot(self.tempdir)
self.make_conf_generic_target = os.path.join(
self.tempdir, "make.conf.generic-target"
)
self.make_conf_user = os.path.join(self.tempdir, "make.conf.user")
D = cros_test_lib.Directory
filesystem = (
D("etc", ()),
"make.conf.generic-target",
"make.conf.user",
)
cros_test_lib.CreateOnDiskHierarchy(self.tempdir, filesystem)
def testInstallMakeConf(self):
"""Test make.conf installation."""
self.PatchObject(
sysroot_lib,
"_GetMakeConfGenericPath",
return_value=self.make_conf_generic_target,
)
self.sysroot.InstallMakeConf()
filepath = os.path.join(self.tempdir, sysroot_lib._MAKE_CONF)
self.assertExists(filepath)
def testInstallMakeConfBoard(self):
"""Test make.conf.board installation."""
self.PatchObject(
self.sysroot, "GenerateBoardMakeConf", return_value="#foo"
)
self.PatchObject(
self.sysroot, "GenerateBinhostConf", return_value="#bar"
)
self.sysroot.InstallMakeConfBoard()
filepath = os.path.join(self.tempdir, sysroot_lib._MAKE_CONF_BOARD)
content = "#foo\n#bar\n"
self.assertExists(filepath)
self.assertFileContents(filepath, content)
def testInstallMakeConfBoardSetup(self):
"""Test make.conf.board_setup installation."""
self.PatchObject(
self.sysroot, "GenerateBoardSetupConfig", return_value="#foo"
)
self.sysroot.InstallMakeConfBoardSetup("board")
filepath = os.path.join(
self.tempdir, sysroot_lib._MAKE_CONF_BOARD_SETUP
)
content = "#foo"
self.assertExists(filepath)
self.assertFileContents(filepath, content)
def testInstallMakeConfUser(self):
"""Test make.conf.user installation."""
self.PatchObject(
sysroot_lib,
"_GetChrootMakeConfUserPath",
return_value=self.make_conf_user,
)
self.sysroot.InstallMakeConfUser()
filepath = os.path.join(self.tempdir, sysroot_lib._MAKE_CONF_USER)
self.assertExists(filepath)
class SysrootGenerateBinhostConfTest(cros_test_lib.MockTempDirTestCase):
"""Unittests for GenerateBinhostConf method in sysroot_lib.py"""
def setUp(self):
self.PatchObject(cros_build_lib, "IsInsideChroot", return_value=False)
self.PatchObject(os_util, "is_root_user", return_value=True)
sysroot_path = os.path.join(self.tempdir, "sysroot")
osutils.SafeMakedirs(sysroot_path)
self.sysroot = sysroot_lib.Sysroot(sysroot_path)
self.sysroot.WriteConfig('BOARD_USE="foofoo"')
unittest_lib.create_stub_make_conf(sysroot_path)
self.external_binhost_dir = os.path.join(
self.tempdir,
constants.PUBLIC_BINHOST_CONF_DIR,
"target",
)
self.internal_binhost_file_path = os.path.join(
self.tempdir,
constants.PRIVATE_BINHOST_CONF_DIR,
"target",
)
self.external_cq_binhost_file_path = os.path.join(
self.external_binhost_dir, "foofoo-CQ_BINHOST.conf"
)
self.external_postsubmit_binhost_file_path = os.path.join(
self.external_binhost_dir, "foofoo-POSTSUBMIT_BINHOST.conf"
)
self.internal_cq_binhost_file_path = os.path.join(
self.internal_binhost_file_path, "foofoo-CQ_BINHOST.conf"
)
self.internal_postsubmit_binhost_file_path = os.path.join(
self.internal_binhost_file_path, "foofoo-POSTSUBMIT_BINHOST.conf"
)
def _removeCommentAndEmptyLines(self, lines):
# Remove comment and empty lines.
return [
line for line in lines if line != "" and not line.startswith("#")
]
def testFullBinhost(self):
config = self.sysroot.GenerateBinhostConf(source_root=self.tempdir)
lines = self._removeCommentAndEmptyLines(config.splitlines())
self.assertEqual(len(lines), 1)
self.assertTrue('PORTAGE_BINHOST="$FULL_BINHOST"' in lines)
def testCqBinhost(self):
content = 'CQ_BINHOST="gs://bar/bar"'
osutils.WriteFile(
self.external_cq_binhost_file_path, content, makedirs=True
)
config = self.sysroot.GenerateBinhostConf(
source_root=self.tempdir, use_cq_prebuilts=True
)
lines = self._removeCommentAndEmptyLines(config.splitlines())
self.assertEqual(len(lines), 3)
self.assertEqual(lines[0], 'PORTAGE_BINHOST="$FULL_BINHOST"')
self.assertEqual(
lines[1], f"source {self.external_cq_binhost_file_path}"
)
self.assertEqual(
lines[2], 'PORTAGE_BINHOST="$PORTAGE_BINHOST $CQ_BINHOST"'
)
def testPostsubmitBinhost(self):
content = 'POSTSUBMIT_BINHOST="gs://bar/bar"'
osutils.WriteFile(
self.internal_postsubmit_binhost_file_path, content, makedirs=True
)
config = self.sysroot.GenerateBinhostConf(source_root=self.tempdir)
lines = self._removeCommentAndEmptyLines(config.splitlines())
self.assertEqual(len(lines), 3)
self.assertEqual(lines[0], 'PORTAGE_BINHOST="$FULL_BINHOST"')
self.assertEqual(
lines[1], f"source {self.internal_postsubmit_binhost_file_path}"
)
self.assertEqual(
lines[2], 'PORTAGE_BINHOST="$PORTAGE_BINHOST $POSTSUBMIT_BINHOST"'
)
def testAllBinhost(self):
content = 'CQ_BINHOST="gs://bar/bar"'
osutils.WriteFile(
self.external_cq_binhost_file_path, content, makedirs=True
)
osutils.WriteFile(
self.internal_cq_binhost_file_path, content, makedirs=True
)
content = 'POSTSUBMIT_BINHOST="gs://bar/bar"'
osutils.WriteFile(
self.external_postsubmit_binhost_file_path, content, makedirs=True
)
osutils.WriteFile(
self.internal_postsubmit_binhost_file_path, content, makedirs=True
)
config = self.sysroot.GenerateBinhostConf(source_root=self.tempdir)
lines = self._removeCommentAndEmptyLines(config.splitlines())
self.assertEqual(len(lines), 5)
self.assertEqual(lines[0], 'PORTAGE_BINHOST="$FULL_BINHOST"')
self.assertEqual(
lines[1], f"source {self.external_postsubmit_binhost_file_path}"
)
self.assertEqual(
lines[2], 'PORTAGE_BINHOST="$PORTAGE_BINHOST $POSTSUBMIT_BINHOST"'
)
self.assertEqual(
lines[3], f"source {self.internal_postsubmit_binhost_file_path}"
)
self.assertEqual(
lines[4], 'PORTAGE_BINHOST="$PORTAGE_BINHOST $POSTSUBMIT_BINHOST"'
)
def testAllBinhostWithCqBinhosts(self):
content = 'CQ_BINHOST="gs://bar/bar"'
osutils.WriteFile(
self.external_cq_binhost_file_path, content, makedirs=True
)
osutils.WriteFile(
self.internal_cq_binhost_file_path, content, makedirs=True
)
content = 'POSTSUBMIT_BINHOST="gs://bar/bar"'
osutils.WriteFile(
self.external_postsubmit_binhost_file_path, content, makedirs=True
)
osutils.WriteFile(
self.internal_postsubmit_binhost_file_path, content, makedirs=True
)
config = self.sysroot.GenerateBinhostConf(
source_root=self.tempdir, use_cq_prebuilts=True
)
lines = self._removeCommentAndEmptyLines(config.splitlines())
self.assertEqual(len(lines), 9)
self.assertEqual(lines[0], 'PORTAGE_BINHOST="$FULL_BINHOST"')
self.assertEqual(
lines[1], f"source {self.external_postsubmit_binhost_file_path}"
)
self.assertEqual(
lines[2], 'PORTAGE_BINHOST="$PORTAGE_BINHOST $POSTSUBMIT_BINHOST"'
)
self.assertEqual(
lines[3], f"source {self.internal_postsubmit_binhost_file_path}"
)
self.assertEqual(
lines[4], 'PORTAGE_BINHOST="$PORTAGE_BINHOST $POSTSUBMIT_BINHOST"'
)
self.assertEqual(
lines[5], f"source {self.external_cq_binhost_file_path}"
)
self.assertEqual(
lines[6], 'PORTAGE_BINHOST="$PORTAGE_BINHOST $CQ_BINHOST"'
)
self.assertEqual(
lines[7], f"source {self.internal_cq_binhost_file_path}"
)
self.assertEqual(
lines[8], 'PORTAGE_BINHOST="$PORTAGE_BINHOST $CQ_BINHOST"'
)
class SysrootLibToolchainUpdateTest(cros_test_lib.RunCommandTempDirTestCase):
"""Sysroot.ToolchanUpdate tests."""
def setUp(self):
"""Setup the test environment."""
# Fake being root to avoid running commands with sudo_run.
self.PatchObject(os_util, "is_root_user", return_value=True)
self.sysroot = sysroot_lib.Sysroot(self.tempdir)
self.emerge = constants.CHROMITE_BIN_DIR / "parallel_emerge"
def testDefaultUpdateToolchain(self):
"""Test the default path."""
self.PatchObject(toolchain, "InstallToolchain")
self.sysroot.UpdateToolchain("board")
self.assertCommandContains(
[self.emerge, "--board=board", "--getbinpkg", "--usepkg"]
)
def testNoLocalInitUpdateToolchain(self):
"""Test the nousepkg and not local case."""
self.PatchObject(toolchain, "InstallToolchain")
self.sysroot.UpdateToolchain("board", local_init=False)
self.assertCommandContains(["--getbinpkg", "--usepkg"], expected=False)
self.assertCommandContains([self.emerge, "--board=board"])
def testReUpdateToolchain(self):
"""Test behavior when not running for the first time."""
self.PatchObject(toolchain, "InstallToolchain")
self.PatchObject(
self.sysroot, "IsToolchainInstalled", return_value=True
)
self.sysroot.UpdateToolchain("board")
self.assertCommandContains([self.emerge], expected=False)
def testInstallToolchainError(self):
"""Test error handling from the libc install."""
failed = ["cat/pkg", "cat/pkg2"]
failed_pkgs = [package_info.parse(pkg) for pkg in failed]
result = cros_build_lib.CompletedProcess(returncode=1)
error = toolchain.ToolchainInstallError(
"Error", result=result, tc_info=failed_pkgs
)
self.PatchObject(toolchain, "InstallToolchain", side_effect=error)
try:
self.sysroot.UpdateToolchain("board")
except sysroot_lib.ToolchainInstallError as e:
self.assertTrue(e.failed_toolchain_info)
self.assertEqual(failed_pkgs, e.failed_toolchain_info)
except Exception as e:
self.fail("Unexpected exception raised: %s" % type(e))
else:
self.fail("Expected an exception.")
def testEmergeError(self):
"""Test the emerge error handling."""
self.PatchObject(toolchain, "InstallToolchain")
# pylint: disable=protected-access
command = self.sysroot._UpdateToolchainCommand("board", True)
err = cros_build_lib.RunCommandError(
"Error", cros_build_lib.CompletedProcess(returncode=1)
)
self.rc.AddCmdResult(command, side_effect=err)
with self.assertRaises(sysroot_lib.ToolchainInstallError):
self.sysroot.UpdateToolchain("board", local_init=True)
def test_get_sdk_provided_packages(simple_sysroot):
pkg_provided = simple_sysroot.path / "etc/portage/profile/package.provided"
content = """
foo/bar-2-r3
# Comment line.
cat/pkg-1.0.0 # Comment after package.
"""
osutils.WriteFile(pkg_provided, content, makedirs=True)
pkgs = list(sysroot_lib.get_sdk_provided_packages(simple_sysroot.path))
expected = [
package_info.parse(p) for p in ("foo/bar-2-r3", "cat/pkg-1.0.0")
]
assert pkgs == expected