blob: d1685604a3d8ba3d3f43118af448b1dd79efcb81 [file] [log] [blame]
# Copyright 2019 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Sysroot service unittest."""
from operator import attrgetter
import os
from pathlib import Path
import shutil
from typing import Optional, Union
from unittest import mock
from chromite.lib import binpkg
from chromite.lib import build_target_lib
from chromite.lib import chroot_lib
from chromite.lib import constants
from chromite.lib import cpupower_helper
from chromite.lib import cros_build_lib
from chromite.lib import cros_test_lib
from chromite.lib import goma_lib
from chromite.lib import osutils
from chromite.lib import partial_mock
from chromite.lib import portage_util
from chromite.lib import remoteexec_util
from chromite.lib import sysroot_lib
from chromite.lib.parser import package_info
from chromite.service import sysroot
class SetupBoardRunConfigTest(cros_test_lib.TestCase):
"""Tests for the SetupBoardRunConfig class."""
def testGetUpdateChrootArgs(self):
"""Test the update chroot args conversion method."""
# False/0/None tests.
instance = sysroot.SetupBoardRunConfig(
usepkg=False, jobs=None, update_toolchain=False, backtrack=1
)
args = instance.GetUpdateChrootArgs()
self.assertIn("--backtrack=1", args)
self.assertIn("--nousepkg", args)
self.assertIn("--skip_toolchain_update", args)
self.assertNotIn("--usepkg", args)
self.assertNotIn("--jobs", args)
instance.jobs = 0
args = instance.GetUpdateChrootArgs()
self.assertNotIn("--jobs", args)
# True/set values tests.
instance = sysroot.SetupBoardRunConfig(
usepkg=True, jobs=1, update_toolchain=True
)
args = instance.GetUpdateChrootArgs()
self.assertIn("--usepkg", args)
self.assertIn("--jobs", args)
self.assertNotIn("--nousepkg", args)
self.assertNotIn("--skip_toolchain_update", args)
class SetupBoardTest(cros_test_lib.MockTestCase):
"""Tests for SetupBoard."""
def setUp(self):
self.PatchObject(cros_build_lib, "IsInsideChroot", return_value=True)
def testFullRun(self):
"""Test a regular full run.
This method just checks that it's trying to create the sysroot and
install the toolchain by default.
"""
target_sysroot = sysroot_lib.Sysroot("/build/board")
create_mock = self.PatchObject(
sysroot, "Create", return_value=target_sysroot
)
install_toolchain_mock = self.PatchObject(sysroot, "InstallToolchain")
sysroot.SetupBoard(build_target_lib.BuildTarget("board"))
create_mock.assert_called_once()
install_toolchain_mock.assert_called_once()
def testRegenConfigs(self):
"""Test the regen configs install prevention."""
target_sysroot = sysroot_lib.Sysroot("/build/board")
create_mock = self.PatchObject(
sysroot, "Create", return_value=target_sysroot
)
install_toolchain_mock = self.PatchObject(sysroot, "InstallToolchain")
target = build_target_lib.BuildTarget("board")
configs = sysroot.SetupBoardRunConfig(regen_configs=True)
sysroot.SetupBoard(target, run_configs=configs)
# Should still try to create the sysroot, but should not try to install
# the toolchain.
create_mock.assert_called_once()
install_toolchain_mock.assert_not_called()
class CreateTest(cros_test_lib.RunCommandTempDirTestCase):
"""Create function tests."""
def setUp(self):
# Avoid sudo password prompt for config writing.
self.PatchObject(osutils, "IsRootUser", return_value=True)
# It has to be run inside the chroot.
self.PatchObject(cros_build_lib, "IsInsideChroot", return_value=True)
# A board we have a sysroot for already.
self.board = "board"
self.sysroot_path = os.path.join(self.tempdir, "build", self.board)
self.build_target = build_target_lib.BuildTarget(
self.board, build_root=self.sysroot_path
)
# A board we don't have a sysroot for yet.
self.unbuilt_board = "board2"
self.unbuilt_path = os.path.join(
self.tempdir, "build", self.unbuilt_board
)
self.unbuilt_target = build_target_lib.BuildTarget(
self.unbuilt_board, build_root=self.unbuilt_path
)
# Create the sysroot.
osutils.SafeMakedirs(self.sysroot_path)
def testUpdateChroot(self):
"""Test the update_chroot related handling."""
# Prevent it from doing anything else for this test.
self.PatchObject(sysroot, "_CreateSysrootSkeleton")
self.PatchObject(sysroot, "_InstallConfigs")
self.PatchObject(sysroot, "_InstallPortageConfigs")
# Make sure we have a board we haven't setup to avoid triggering the
# existing sysroot logic. That is entirely unrelated to the chroot
# update.
target = self.unbuilt_target
# Test no update case.
config = sysroot.SetupBoardRunConfig(upgrade_chroot=False)
get_args_patch = self.PatchObject(config, "GetUpdateChrootArgs")
sysroot.Create(target, config, None)
# The update chroot args not being fetched is a
# strong enough signal that the update wasn't run.
get_args_patch.assert_not_called()
# Test update case.
script_loc = constants.CROSUTILS_DIR / "update_chroot"
config = sysroot.SetupBoardRunConfig(upgrade_chroot=True)
sysroot.Create(target, config, None)
self.assertCommandContains([script_loc])
def testForce(self):
"""Test the force flag."""
# Prevent it from doing anything else for this test.
self.PatchObject(sysroot, "_CreateSysrootSkeleton")
self.PatchObject(sysroot, "_InstallConfigs")
self.PatchObject(sysroot, "_InstallPortageConfigs")
delete_patch = self.PatchObject(sysroot_lib.Sysroot, "Delete")
config = sysroot.SetupBoardRunConfig(force=False)
sysroot.Create(self.build_target, config, None)
delete_patch.assert_not_called()
config = sysroot.SetupBoardRunConfig(force=True)
sysroot.Create(self.build_target, config, None)
delete_patch.assert_called_once()
class CreateSimpleChromeSysrootTest(cros_test_lib.MockTempDirTestCase):
"""Tests for CreateSimpleChromeSysroot."""
def setUp(self):
self.run_mock = self.PatchObject(
cros_build_lib, "run", return_value=True
)
self.source_root = os.path.join(self.tempdir, "source_root")
osutils.SafeMakedirs(self.source_root)
self.PatchObject(constants, "SOURCE_ROOT", new=self.source_root)
# Configure {chroot,out}_path.
self.chroot_path = os.path.join(self.tempdir, "chroot_dir")
self.out_path = self.tempdir / "out_dir"
# Create output dir.
self.output_dir = os.path.join(self.tempdir, "output_dir")
osutils.SafeMakedirs(self.output_dir)
# Create chroot and build_target objs.
self.chroot = chroot_lib.Chroot(
path=self.chroot_path, out_path=self.out_path
)
self.build_target = build_target_lib.BuildTarget("target")
# Create the tmp dir.
osutils.SafeMakedirs(self.chroot.tmp)
def testCreateSimpleChromeSysroot(self):
# Mock the artifact copy.
tar_dest = os.path.join(self.output_dir, constants.CHROME_SYSROOT_TAR)
self.PatchObject(shutil, "copy", return_value=tar_dest)
# Call service, verify arguments passed to run.
sysroot.CreateSimpleChromeSysroot(
self.chroot, None, self.build_target, self.output_dir
)
self.run_mock.assert_called_with(
[
"cros_generate_sysroot",
"--out-dir",
mock.ANY,
"--board",
self.build_target.name,
"--package",
"chromeos-base/chromeos-chrome",
"--out-file",
"sysroot_chromeos-base_chromeos-chrome.tar.xz",
"--deps-only",
],
enter_chroot=True,
cwd=self.source_root,
chroot_args=mock.ANY,
extra_env=mock.ANY,
)
class CreateFuzzerSysrootTest(cros_test_lib.MockTempDirTestCase):
"""Tests for CreateFuzzerSysroot."""
def setUp(self):
self.run_mock = self.PatchObject(
cros_build_lib, "run", return_value=True
)
self.source_root = os.path.join(self.tempdir, "source_root")
osutils.SafeMakedirs(self.source_root)
self.PatchObject(constants, "SOURCE_ROOT", new=self.source_root)
# Configure {chroot,out}_path.
self.chroot_path = os.path.join(self.tempdir, "chroot_dir")
self.out_path = self.tempdir / "out_dir"
# Create output dir.
self.output_dir = os.path.join(self.tempdir, "output_dir")
osutils.SafeMakedirs(self.output_dir)
# Create chroot and build_target objs.
self.chroot = chroot_lib.Chroot(
path=self.chroot_path, out_path=self.out_path
)
self.build_target = build_target_lib.BuildTarget("target")
# Create the tmp dir.
osutils.SafeMakedirs(self.chroot.tmp)
def testCreateFuzzerSysroot(self):
"""Test the CreateFuzzerSysroot function under normal operation."""
# Mock the artifact copy.
tar_dest = os.path.join(self.output_dir, constants.CHROME_SYSROOT_TAR)
self.PatchObject(shutil, "copy", return_value=tar_dest)
# Call service, verify arguments passed to run.
sysroot.CreateFuzzerSysroot(
self.chroot, None, self.build_target, self.output_dir
)
self.run_mock.assert_called_with(
[
"cros_generate_sysroot",
"--out-dir",
mock.ANY,
"--board",
self.build_target.name,
"--package",
"virtual/target-fuzzers",
"--out-file",
"sysroot_virtual_target-os.tar.xz",
],
enter_chroot=True,
cwd=self.source_root,
chroot_args=mock.ANY,
extra_env=mock.ANY,
)
class ArchiveChromeEbuildEnvTest(cros_test_lib.MockTempDirTestCase):
"""ArchiveChromeEbuildEnv tests."""
def setUp(self):
# Create the chroot and sysroot instances.
self.chroot_path = os.path.join(self.tempdir, "chroot_dir")
self.out_path = self.tempdir / "out_dir"
self.chroot = chroot_lib.Chroot(
path=self.chroot_path, out_path=self.out_path
)
self.sysroot_path = os.path.join(self.chroot_path, "sysroot_dir")
self.sysroot = sysroot_lib.Sysroot(self.sysroot_path)
# Create the output directory.
self.output_dir = os.path.join(self.tempdir, "output_dir")
osutils.SafeMakedirs(self.output_dir)
# The sysroot's /var/db/pkg prefix for the chrome package directories.
var_db_pkg = self.chroot.full_path(
self.sysroot_path, portage_util.VDB_PATH
)
# Create the var/db/pkg dir so we have that much for no-chrome tests.
osutils.SafeMakedirs(var_db_pkg)
# Two versions of chrome to test the multiple version checks/handling.
chrome_v1 = "%s-1.0.0-r1" % constants.CHROME_PN
chrome_v2 = "%s-2.0.0-r1" % constants.CHROME_PN
# Build the two chrome version paths.
chrome_cat_dir = os.path.join(var_db_pkg, constants.CHROME_CN)
self.chrome_v1_dir = os.path.join(chrome_cat_dir, chrome_v1)
self.chrome_v2_dir = os.path.join(chrome_cat_dir, chrome_v2)
# Directory tuple for verifying the result archive contents.
self.expected_archive_contents = cros_test_lib.Directory(
"./", "environment"
)
# Create a environment.bz2 file to put into folders.
env_file = os.path.join(self.tempdir, "environment")
osutils.Touch(env_file)
cros_build_lib.run(["bzip2", env_file])
self.env_bz2 = "%s.bz2" % env_file
def _CreateChromeDir(self, path: str, populate: bool = True):
"""Setup a chrome package directory.
Args:
path: The full chrome package path.
populate: Whether to include the environment bz2.
"""
osutils.SafeMakedirs(path)
if populate:
shutil.copy(self.env_bz2, path)
def testSingleChromeVersion(self):
"""Test a successful single-version run."""
self._CreateChromeDir(self.chrome_v1_dir)
created = sysroot.CreateChromeEbuildEnv(
self.chroot, self.sysroot, None, self.output_dir
)
self.assertStartsWith(created, self.output_dir)
cros_test_lib.VerifyTarball(created, self.expected_archive_contents)
def testMultipleChromeVersions(self):
"""Test a successful multiple version run."""
# Create both directories, but don't populate the v1 dir so it'll hit an
# error if the wrong one is used.
self._CreateChromeDir(self.chrome_v1_dir, populate=False)
self._CreateChromeDir(self.chrome_v2_dir)
created = sysroot.CreateChromeEbuildEnv(
self.chroot, self.sysroot, None, self.output_dir
)
self.assertStartsWith(created, self.output_dir)
cros_test_lib.VerifyTarball(created, self.expected_archive_contents)
def testNoChrome(self):
"""Test no version of chrome present."""
self.assertIsNone(
sysroot.CreateChromeEbuildEnv(
self.chroot, self.sysroot, None, self.output_dir
)
)
class GenerateArchiveTest(cros_test_lib.MockTempDirTestCase):
"""Tests for GenerateArchive."""
def setUp(self):
self.run_mock = self.PatchObject(
cros_build_lib, "run", return_value=True
)
self.chroot_path = os.path.join(self.tempdir, "chroot_dir")
def testCreateSimpleChromeSysroot(self):
# A board for which we will create a simple chrome sysroot.
target = "board"
pkg_list = ["virtual/target-fuzzers"]
# Call service, verify arguments passed to run.
sysroot.GenerateArchive(self.chroot_path, target, pkg_list)
self.run_mock.assert_called_with(
[
"cros_generate_sysroot",
"--out-file",
constants.TARGET_SYSROOT_TAR,
"--out-dir",
mock.ANY,
"--board",
target,
"--package",
"virtual/target-fuzzers",
],
cwd=constants.SOURCE_ROOT,
)
class InstallToolchainTest(cros_test_lib.MockTempDirTestCase):
"""Tests for InstallToolchain."""
def setUp(self):
self.PatchObject(cros_build_lib, "IsInsideChroot", return_value=True)
# A board we have a sysroot for already.
self.board = "board"
self.sysroot_path = os.path.join(self.tempdir, "build", self.board)
self.build_target = build_target_lib.BuildTarget(
self.board, build_root=self.sysroot_path
)
self.sysroot = sysroot_lib.Sysroot(self.sysroot_path)
# A board we don't have a sysroot for yet.
self.unbuilt_board = "board2"
self.unbuilt_path = os.path.join(
self.tempdir, "build", self.unbuilt_board
)
self.unbuilt_target = build_target_lib.BuildTarget(
self.unbuilt_board, build_root=self.unbuilt_path
)
self.unbuilt_sysroot = sysroot_lib.Sysroot(self.unbuilt_path)
osutils.SafeMakedirs(self.sysroot_path)
def testNoSysroot(self):
"""Test handling of no sysroot."""
with self.assertRaises(ValueError):
sysroot.InstallToolchain(
self.unbuilt_target,
self.unbuilt_sysroot,
sysroot.SetupBoardRunConfig(),
)
def testLocalBuild(self):
"""Test the local build logic."""
update_patch = self.PatchObject(self.sysroot, "UpdateToolchain")
# Test False.
config = sysroot.SetupBoardRunConfig(usepkg=False, local_build=False)
sysroot.InstallToolchain(self.build_target, self.sysroot, config)
update_patch.assert_called_with(self.board, local_init=False)
# Test usepkg True.
update_patch.reset_mock()
config = sysroot.SetupBoardRunConfig(usepkg=True, local_build=False)
sysroot.InstallToolchain(self.build_target, self.sysroot, config)
update_patch.assert_called_with(self.board, local_init=True)
# Test local_build True.
update_patch.reset_mock()
config = sysroot.SetupBoardRunConfig(usepkg=False, local_build=True)
sysroot.InstallToolchain(self.build_target, self.sysroot, config)
update_patch.assert_called_with(self.board, local_init=True)
class BuildPackagesRunConfigTest(
cros_test_lib.RunCommandTestCase, cros_test_lib.LoggingTestCase
):
"""Tests for the BuildPackagesRunConfig."""
def testGetBuildPackagesExtraEnv(self):
"""Test the build_packages extra env."""
# Test the default config.
instance = sysroot.BuildPackagesRunConfig()
extra_env = instance.GetExtraEnv()
self.assertNotIn("USE_GOMA", extra_env)
self.assertNotIn("USE_REMOTEEXEC", extra_env)
self.assertNotIn("PORTAGE_BINHOST", extra_env)
# Test when package_indexes are specified.
pkg_indexes = [
binpkg.PackageIndexInfo(
build_target=build_target_lib.BuildTarget("board"),
snapshot_sha="A",
location="AAAA",
),
binpkg.PackageIndexInfo(
build_target=build_target_lib.BuildTarget("board"),
snapshot_sha="B",
location="BBBB",
),
]
instance = sysroot.BuildPackagesRunConfig(package_indexes=pkg_indexes)
extra_env = instance.GetExtraEnv()
self.assertEqual(
extra_env.get("PORTAGE_BINHOST"),
" ".join([x.location for x in reversed(pkg_indexes)]),
)
# Test when use_flags are specified.
use_flags = ["flag1", "flag2"]
instance = sysroot.BuildPackagesRunConfig(use_flags=use_flags)
extra_env = instance.GetExtraEnv()
self.assertEqual(extra_env.get("USE"), instance.GetUseFlags())
def testGetPackages(self):
"""Test getting packages for the config."""
# Test the default config.
instance = sysroot.BuildPackagesRunConfig()
packages = instance.GetPackages()
self.assertIn("virtual/target-os", packages)
self.assertIn("virtual/target-os-dev", packages)
self.assertIn("virtual/target-os-factory", packages)
self.assertIn("virtual/target-os-test", packages)
self.assertIn("chromeos-base/autotest-all", packages)
# Test when packages are specified.
test_packages = ["test/package"]
instance = sysroot.BuildPackagesRunConfig(packages=test_packages)
packages = instance.GetPackages()
self.assertEqual(packages, test_packages)
def testGetForceLocalBuildPackages(self):
"""Test getting force local build packages for the config."""
test_sysroot_path = "/sysroot/path"
test_sysroot = sysroot_lib.Sysroot(test_sysroot_path)
get_reverse_dependencies_mock = self.PatchObject(
portage_util, "GetReverseDependencies"
)
# Test the default config.
instance = sysroot.BuildPackagesRunConfig()
self.rc.AddCmdResult(
["cros_list_modified_packages", "--sysroot", test_sysroot.path],
stdout="",
)
packages = instance.GetForceLocalBuildPackages(test_sysroot)
self.assertIn("chromeos-base/chromeos-ssh-testkeys", packages)
# Test when there are cros_workon packages and reverse dependencies
# but skipping base install packages and their reverse dependencies.
instance = sysroot.BuildPackagesRunConfig(incremental_build=False)
test_cros_list_modified_packages_output = (
"test/package1 test/package2\n"
)
self.rc.AddCmdResult(
["cros_list_modified_packages", "--sysroot", test_sysroot.path],
stdout=test_cros_list_modified_packages_output,
)
test_reverse_dependencies = [
package_info.parse("test/package3-1.0"),
package_info.parse("test/package4-2.0-r1"),
]
get_reverse_dependencies_mock.return_value = test_reverse_dependencies
packages = instance.GetForceLocalBuildPackages(test_sysroot)
self.assertIn("test/package1", packages)
self.assertIn("test/package2", packages)
self.assertIn("test/package3", packages)
self.assertIn("test/package4", packages)
# Test base install packages and their reverse dependency packages
# but skipping cros workon packages and their reverse dependencies.
instance = sysroot.BuildPackagesRunConfig(workon=False)
test_emerge_output = """\
[binary N] test/package1 ... to /build/sysroot/
[ebuild r U] chromeos-base/tast-build-deps ... to /build/sysroot/
[binary U] chromeos-base/chromeos-chrome ... /build/sysroot/"""
self.rc.AddCmdResult(
partial_mock.ListRegex(
f"parallel_emerge --sysroot={test_sysroot.path}"
),
stdout=test_emerge_output,
)
test_reverse_dependencies = [
package_info.parse("virtual/package3-1.0"),
package_info.parse("test/package4-2.0-r1"),
]
get_reverse_dependencies_mock.return_value = test_reverse_dependencies
packages = instance.GetForceLocalBuildPackages(test_sysroot)
self.assertIn("chromeos-base/tast-build-deps", packages)
self.assertIn("test/package4", packages)
# Test base install packages and their reverse dependencies are skipped
# when --no-withrevdeps is specified.
instance = sysroot.BuildPackagesRunConfig(incremental_build=False)
with cros_test_lib.LoggingCapturer() as logs:
instance.GetForceLocalBuildPackages(test_sysroot)
self.AssertLogsContain(
logs,
"Starting reverse dependency calculations...",
inverted=True,
)
# Test base install packages and their reverse dependencies are skipped
# when --cleanbuild is specified and the sysroot does not exist.
instance = sysroot.BuildPackagesRunConfig(clean_build=True)
with cros_test_lib.LoggingCapturer() as logs:
instance.GetForceLocalBuildPackages(test_sysroot)
self.AssertLogsContain(
logs,
"Starting reverse dependency calculations...",
inverted=True,
)
def testGetEmergeFlags(self):
"""Test building the emerge flags."""
# Test the default config.
instance = sysroot.BuildPackagesRunConfig()
flags = instance.GetEmergeFlags()
self.assertIn("--with-test-deps", flags)
self.assertIn("--getbinpkg", flags)
self.assertIn("--with-bdeps", flags)
self.assertIn("--usepkg", flags)
self.assertIn("--rebuild-if-new-rev", flags)
# Test when use_any_chrome is specified.
instance = sysroot.BuildPackagesRunConfig(use_any_chrome=True)
flags = instance.GetEmergeFlags()
self.assertIn(
"--force-remote-binary=chromeos-base/chromeos-chrome", flags
)
self.assertIn("--force-remote-binary=chromeos-base/chrome-icu", flags)
# Test when usepkgonly is specified.
instance = sysroot.BuildPackagesRunConfig(usepkgonly=True)
flags = instance.GetEmergeFlags()
self.assertIn("--usepkgonly", flags)
# Test when jobs is specified.
instance = sysroot.BuildPackagesRunConfig(jobs=10)
flags = instance.GetEmergeFlags()
self.assertIn("--jobs=10", flags)
class BuildPackagesTest(
cros_test_lib.RunCommandTestCase, cros_test_lib.LoggingTestCase
):
"""Test BuildPackages function."""
def setUp(self):
# Currently just used to keep the parallel emerge status file from being
# created in the chroot. This probably isn't strictly necessary, but
# since we can otherwise run this test without a chroot existing at all
# and without touching the chroot folder, it's better to keep it out of
# there all together.
self.PatchObject(cros_build_lib, "IsInsideChroot", return_value=True)
self.board = "board"
self.target = build_target_lib.BuildTarget(self.board)
self.sysroot = sysroot_lib.Sysroot(self.target.root)
self.build_target_name_mock = self.PatchObject(
sysroot_lib.Sysroot, "build_target_name", return_value=self.board
)
self.base_command = [
constants.CHROMITE_BIN_DIR / "parallel_emerge",
f"--sysroot={self.sysroot.path}",
f"--root={self.sysroot.path}",
]
# Prevent the test from switching the cpu governor.
self.PatchObject(cpupower_helper, "ModifyCpuGovernor")
# Prevent the test from remove files in the system.
self.PatchObject(cros_build_lib, "ClearShadowLocks")
self.PatchObject(
portage_util, "PortageqEnvvar", return_value="gs://fake/binhost"
)
self.PatchObject(portage_util, "RegenDependencyCache")
self.installed_packages_mock = self.PatchObject(
portage_util.PortageDB, "InstalledPackages"
)
self.clean_outdated_binpkgs_mock = self.PatchObject(
portage_util, "CleanOutdatedBinaryPackages"
)
def testSuccess(self):
"""Test successful run."""
config = sysroot.BuildPackagesRunConfig()
self.PatchObject(
config, "GetForceLocalBuildPackages", return_value=["test/package1"]
)
sdk_pkgs = " ".join(
sysroot._CRITICAL_SDK_PACKAGES # pylint: disable=protected-access
)
with cros_test_lib.LoggingCapturer() as logs:
sysroot.BuildPackages(self.target, self.sysroot, config)
# The rest of the command's args we test in
# BuildPackagesRunConfigTest, so just make sure we're calling the
# right command and pass the args not handled by the run config.
self.assertCommandContains(self.base_command)
self.assertCommandContains(
[
"--reinstall-atoms=test/package1",
"--usepkg-exclude=test/package1",
]
)
self.assertCommandContains(
[
f"--useoldpkg-atoms={sdk_pkgs}",
f"--rebuild-exclude={sdk_pkgs}",
]
)
# Verify the extra environment variables are passed correctly.
self.assertCommandContains([f"PKGDIR={self.sysroot.path}/packages"])
# Check and the logs contain the expected output.
self.AssertLogsContain(logs, "PORTAGE_BINHOST")
self.AssertLogsContain(logs, "Rebuilding Portage cache.")
self.AssertLogsContain(logs, "Cleaning stale binpkgs.")
self.AssertLogsContain(logs, "Merging board packages now.")
def testEcleanBinpkgs(self):
"""Test that eclean is called with the expected packages."""
def assert_file_contents(
sysroot_path: Union[str, os.PathLike],
deep: bool,
exclusion_file: Optional[Union[str, os.PathLike]] = None,
):
if exclusion_file:
contents = osutils.ReadFile(exclusion_file)
self.assertEqual("cross-dev/package", contents)
self.assertEqual(self.sysroot.path, sysroot_path)
self.assertTrue(deep)
self.PatchObject(os.path, "exists", return_value=True)
self.installed_packages_mock.return_value = [
portage_util.InstalledPackage(None, "", "test", "package-1.0"),
portage_util.InstalledPackage(None, "", "cross-dev", "package-1.0"),
]
self.clean_outdated_binpkgs_mock.side_effect = assert_file_contents
config = sysroot.BuildPackagesRunConfig()
sysroot.BuildPackages(self.target, self.sysroot, config)
def testInstallDebugSymbols(self):
"""Test cros_install_debug_syms is called with the expected args."""
config = sysroot.BuildPackagesRunConfig(install_debug_symbols=True)
with cros_test_lib.LoggingCapturer() as logs:
sysroot.BuildPackages(self.target, self.sysroot, config)
self.assertCommandContains(
[
constants.CHROMITE_BIN_DIR / "cros_install_debug_syms",
f"--board={self.build_target_name_mock}",
"--all",
]
)
self.AssertLogsContain(logs, "Fetching the debug symbols.")
def testPackageFailure(self):
"""Test package failure handling."""
failed = ["cat/pkg", "foo/bar"]
cpvs = [package_info.SplitCPV(p, strict=False) for p in failed]
self.PatchObject(
portage_util, "ParseDieHookStatusFile", return_value=cpvs
)
config = sysroot.BuildPackagesRunConfig()
result = cros_build_lib.CompletedProcess(
self.base_command, returncode=1
)
error = cros_build_lib.RunCommandError("Error", result)
self.PatchObject(
cros_build_lib,
"run",
side_effect=(
cros_build_lib.CompletedProcess(stdout=""),
cros_build_lib.CompletedProcess(stdout=""),
error,
),
)
with self.assertRaises(sysroot_lib.PackageInstallError) as e:
sysroot.BuildPackages(self.target, self.sysroot, config)
self.assertEqual(cpvs, e.failed_packages)
self.assertEqual(result, e.result)
class GatherSymbolFilesTest(cros_test_lib.MockTempDirTestCase):
"""Base class for testing GatherSymbolFiles."""
SLIM_CONTENT = """
some junk
"""
FAT_CONTENT = """
STACK CFI 1234
some junk
STACK CFI 1234
"""
def createSymbolFile(self, filename, content=FAT_CONTENT, size=0):
"""Create a symbol file using content with minimum size."""
osutils.SafeMakedirs(os.path.dirname(filename))
# If a file size is given, force that to be the minimum file size.
# Create a sparse file so large files are practical.
with open(filename, "w+b") as f:
f.truncate(size)
f.seek(0)
f.write(content.encode("utf-8"))
def test_ListOutputOfGatherSymbolFiles(self):
"""Mimic how the controller materializes output of GatherSymbolFiles."""
# Create directory with some symbol files.
tar_tmp_dir = os.path.join(self.tempdir, "tar_tmp")
output_dir = os.path.join(self.tempdir, "output")
input_dir = os.path.join(self.tempdir, "input")
osutils.SafeMakedirs(output_dir)
self.createSymbolFile(os.path.join(input_dir, "a/b/c/file1.sym"))
self.createSymbolFile(os.path.join(input_dir, "a/b/c/d/file2.sym"))
self.createSymbolFile(os.path.join(input_dir, "a/file3.sym"))
self.createSymbolFile(os.path.join(input_dir, "a/b/c/d/e/file1.sym"))
# Call sysroot.GatherSymbolFiles to find symbol files under self.tempdir
# and copy them to output_dir.
symbol_files = list(
sysroot.GatherSymbolFiles(tar_tmp_dir, output_dir, [input_dir])
)
self.assertEqual(len(symbol_files), 4)
def test_GatherSymbolFiles(self):
"""Test that files are found and copied."""
# Create directory with some symbol files.
tar_tmp_dir = os.path.join(self.tempdir, "tar_tmp")
output_dir = os.path.join(self.tempdir, "output")
input_dir = os.path.join(self.tempdir, "input")
osutils.SafeMakedirs(output_dir)
self.createSymbolFile(os.path.join(input_dir, "a/b/c/file1.sym"))
self.createSymbolFile(os.path.join(input_dir, "a/b/c/d/file2.sym"))
self.createSymbolFile(os.path.join(input_dir, "a/file3.sym"))
self.createSymbolFile(os.path.join(input_dir, "a/b/c/d/e/file1.sym"))
# Call sysroot.GatherSymbolFiles to find symbol files under self.tempdir
# and copy them to output_dir.
symbol_files = list(
sysroot.GatherSymbolFiles(tar_tmp_dir, output_dir, [input_dir])
)
# Construct the expected symbol files. Note that the SymbolFileTuple
# field source_file_name is the full path to where a symbol file was
# found, while relative_path is the relative path (from the search)
# where it is created in the output directory.
expected_symbol_files = [
sysroot.SymbolFileTuple(
source_file_name=os.path.join(input_dir, "a/b/c/file1.sym"),
relative_path="a/b/c/file1.sym",
),
sysroot.SymbolFileTuple(
source_file_name=os.path.join(input_dir, "a/b/c/d/file2.sym"),
relative_path="a/b/c/d/file2.sym",
),
sysroot.SymbolFileTuple(
source_file_name=os.path.join(input_dir, "a/file3.sym"),
relative_path="a/file3.sym",
),
sysroot.SymbolFileTuple(
source_file_name=os.path.join(input_dir, "a/b/c/d/e/file1.sym"),
relative_path="a/b/c/d/e/file1.sym",
),
]
# Sort symbol_files and expected_output_files by the relative_path
# attribute.
symbol_files = sorted(symbol_files, key=attrgetter("relative_path"))
expected_symbol_files = sorted(
expected_symbol_files, key=attrgetter("relative_path")
)
# Compare the files to the expected files. This verifies the size and
# contents, and on failure shows the full contents.
self.assertEqual(symbol_files, expected_symbol_files)
# Verify that the files in output_dir match the SymbolFile
# relative_paths.
files_in_output_dir = self.getFilesWithRelativeDir(output_dir)
files_in_output_dir.sort()
symbol_file_relative_paths = [obj.relative_path for obj in symbol_files]
symbol_file_relative_paths.sort()
self.assertEqual(files_in_output_dir, symbol_file_relative_paths)
# Verify that the display_name of each symbol does not contain pathsep.
symbol_file_relative_paths = [
os.path.basename(obj.relative_path) for obj in symbol_files
]
for display_name in symbol_file_relative_paths:
self.assertEqual(-1, display_name.find(os.path.sep))
def test_GatherSymbolTarFiles(self):
"""Test that symbol files in tar files are extracted."""
output_dir = os.path.join(self.tempdir, "output")
osutils.SafeMakedirs(output_dir)
# Set up test input directory.
tarball_dir = os.path.join(self.tempdir, "some/path")
files_in_tarball = [
"dir1/fileZ.sym",
"dir2/fileY.sym",
"dir2/fileX.sym",
"fileA.sym",
"fileB.sym",
"fileC.sym",
]
for filename in files_in_tarball:
self.createSymbolFile(os.path.join(tarball_dir, filename))
temp_tarball_file_path = os.path.join(self.tempdir, "symfiles.tar")
cros_build_lib.CreateTarball(temp_tarball_file_path, tarball_dir)
# Now that we've created the tarball, remove the .sym files in
# the tarball dir and move the tarball to that dir.
for filename in files_in_tarball:
os.remove(os.path.join(tarball_dir, filename))
tarball_path = os.path.join(tarball_dir, "symfiles.tar")
shutil.move(temp_tarball_file_path, tarball_path)
# Execute sysroot.GatherSymbolFiles where the path contains the tarball.
symbol_files = list(
sysroot.GatherSymbolFiles(tarball_dir, output_dir, [tarball_path])
)
self.assertEqual(len(symbol_files), 6)
# Verify the symbol_file relative_paths.
symbol_file_relative_paths = [obj.relative_path for obj in symbol_files]
symbol_file_relative_paths.sort()
self.assertEqual(
symbol_file_relative_paths,
[
"dir1/fileZ.sym",
"dir2/fileX.sym",
"dir2/fileY.sym",
"fileA.sym",
"fileB.sym",
"fileC.sym",
],
)
# Verify the symbol_file source_file_names.
symbol_file_source_file_names = [
obj.source_file_name for obj in symbol_files
]
symbol_file_source_file_names.sort()
# Note that the expected symbol_file_source_names are the full path to
# the tarfile followed by the relative path, such as
# /tmp/chromite.test2ng92vzo/some/path/symfiles.tar/dir1/fileZ.sym
expected_symbol_file_source_names = [
os.path.join(tarball_path, "dir1/fileZ.sym"),
os.path.join(tarball_path, "dir2/fileX.sym"),
os.path.join(tarball_path, "dir2/fileY.sym"),
os.path.join(tarball_path, "fileA.sym"),
os.path.join(tarball_path, "fileB.sym"),
os.path.join(tarball_path, "fileC.sym"),
]
self.assertEqual(
symbol_file_source_file_names, expected_symbol_file_source_names
)
# Verify that the files in output_dir match the SymbolFile
# relative_paths.
files_in_output_dir = self.getFilesWithRelativeDir(output_dir)
files_in_output_dir.sort()
symbol_file_relative_paths = [obj.relative_path for obj in symbol_files]
symbol_file_relative_paths.sort()
self.assertEqual(files_in_output_dir, symbol_file_relative_paths)
# Verify that the display_name of each symbol does not contain pathsep.
symbol_file_relative_paths = [
os.path.basename(obj.relative_path) for obj in symbol_files
]
for display_name in symbol_file_relative_paths:
self.assertEqual(-1, display_name.find(os.path.sep))
def test_GatherSymbolTarFilesWithNonSymFiles(self):
"""Test that non-symbol files in tar files are not extracted."""
output_dir = os.path.join(self.tempdir, "output")
osutils.SafeMakedirs(output_dir)
# Set up test input directory.
tarball_dir = os.path.join(self.tempdir, "some/path")
files_in_tarball = [
"dir1/fileU.sym",
"dir1/fileU.txt",
"fileD.sym",
"fileD.txt",
]
for filename in files_in_tarball:
# we don't care about file contents, so we are using
# createSymbolFile for files whether they end with .sym or not.
self.createSymbolFile(os.path.join(tarball_dir, filename))
temp_tarball_file_path = os.path.join(self.tempdir, "symfiles.tar")
cros_build_lib.CreateTarball(temp_tarball_file_path, tarball_dir)
# Now that we've created the tarball, remove the .sym files in
# the tarball dir and move the tarball to that dir.
for filename in files_in_tarball:
os.remove(os.path.join(tarball_dir, filename))
tarball_path = os.path.join(tarball_dir, "symfiles.tar")
shutil.move(temp_tarball_file_path, tarball_path)
# Execute sysroot.GatherSymbolFiles where the path contains the tarball.
symbol_files = list(
sysroot.GatherSymbolFiles(tarball_dir, output_dir, [tarball_path])
)
# Verify the symbol_file relative_paths only has .sym files.
symbol_file_relative_paths = [obj.relative_path for obj in symbol_files]
symbol_file_relative_paths.sort()
self.assertEqual(
symbol_file_relative_paths, ["dir1/fileU.sym", "fileD.sym"]
)
for symfile in symbol_file_relative_paths:
extension = symfile.split(".")[1]
self.assertEqual(extension, "sym")
def test_GatherSymbolFileFullFilePaths(self):
"""Test full filepaths (.sym and .txt) only gather .sym files."""
tar_tmp_dir = os.path.join(self.tempdir, "tar_tmp")
output_dir = os.path.join(self.tempdir, "output")
input_dir = os.path.join(self.tempdir, "input")
osutils.SafeMakedirs(output_dir)
# We don't care about contents, so use createSymbolFiles for all files.
self.createSymbolFile(os.path.join(input_dir, "a_file.sym"))
self.createSymbolFile(os.path.join(input_dir, "a_file.txt"))
# Call sysroot.GatherSymbolFiles with full paths to files, some of which
# don't end in .sym, verify that only .sym files get copied to
# output_dir.
symbol_files = list(
sysroot.GatherSymbolFiles(
tar_tmp_dir,
output_dir,
[
os.path.join(input_dir, "a_file.sym"),
os.path.join(input_dir, "a_file.txt"),
],
)
)
# Construct the expected symbol files. Note that the SymbolFileTuple
# field source_file_name is the full path to where a symbol file was
# found, while relative_path is the relative path (from the search)
# where it is created in the output directory.
expected_symbol_files = [
sysroot.SymbolFileTuple(
source_file_name=os.path.join(input_dir, "a_file.sym"),
relative_path="a_file.sym",
)
]
# Compare the files to the expected files. This verifies the size and
# contents, and on failure shows the full contents.
self.assertEqual(symbol_files, expected_symbol_files)
# Verify that only a_file.sym is in the output_dir.
files_in_output_dir = self.getFilesWithRelativeDir(output_dir)
self.assertEqual(files_in_output_dir, ["a_file.sym"])
def getFilesWithRelativeDir(self, dest_dir):
"""Find all files below dest_dir using dir relative to dest_dir."""
relative_files = []
for path, __, files in os.walk(dest_dir):
for filename in files:
fullpath = os.path.join(path, filename)
relpath = os.path.relpath(fullpath, dest_dir)
relative_files.append(relpath)
return relative_files
class GenerateBreakpadSymbolsTest(cros_test_lib.MockTempDirTestCase):
"""Base class for testing GenerateBreakpadSymbols."""
def setUp(self):
self.chroot_dir = os.path.join(self.tempdir, "chroot_dir")
osutils.SafeMakedirs(self.chroot_dir)
def test_generateBreakpadSymbols(self):
"""Verify calling the service layer invokes the script as expected."""
chroot = chroot_lib.Chroot(self.chroot_dir)
build_target = build_target_lib.BuildTarget("board")
self.PatchObject(cros_build_lib, "run")
# Call the method being tested.
sysroot.GenerateBreakpadSymbols(chroot, build_target, False)
cros_build_lib.run.assert_called_with(
[
"cros_generate_breakpad_symbols",
"--board=board",
"--jobs",
mock.ANY,
"--exclude-dir=firmware",
],
enter_chroot=True,
chroot_args=["--chroot", mock.ANY],
)
def test_generateBreakpadSymbolsWithDebug(self):
"""Verify that calling with debug invokes the script as expected."""
chroot = chroot_lib.Chroot(self.chroot_dir)
build_target = build_target_lib.BuildTarget("board")
self.PatchObject(cros_build_lib, "run")
# Call the method being tested.
sysroot.GenerateBreakpadSymbols(chroot, build_target, True)
cros_build_lib.run.assert_called_with(
[
"cros_generate_breakpad_symbols",
"--debug",
"--board=board",
"--jobs",
mock.ANY,
"--exclude-dir=firmware",
],
enter_chroot=True,
chroot_args=["--chroot", mock.ANY],
)
class BundleDebugSymbolsTest(cros_test_lib.MockTempDirTestCase):
"""Unittests for BundleDebugSymbols."""
def setUp(self):
# Configure {chroot,out,syroot}_path.
self.chroot_path = os.path.join(self.tempdir, "chroot_dir")
self.out_path = self.tempdir / "out_dir"
self.sysroot_path = os.path.join(self.chroot_path, "build/target")
# Has to be run outside the chroot.
self.PatchObject(cros_build_lib, "IsInsideChroot", return_value=False)
osutils.SafeMakedirs(self.sysroot_path)
# Create output dir.
self.output_dir = os.path.join(self.tempdir, "output_dir")
osutils.SafeMakedirs(self.output_dir)
# Create chroot, sysroot, and build_target objs.
self.chroot = chroot_lib.Chroot(
path=self.chroot_path, out_path=self.out_path
)
self.sysroot = sysroot_lib.Sysroot(path=self.sysroot_path)
self.build_target = build_target_lib.BuildTarget("target")
# Create the tmp dir.
osutils.SafeMakedirs(self.chroot.tmp)
def testBundleBreakpadDebugSymbols(self):
"""BundleBreakpadSymbols calls cbuildbot/commands with correct args."""
# Patch service layer functions.
generate_breakpad_symbols_patch = self.PatchObject(
sysroot,
"GenerateBreakpadSymbols",
return_value=cros_build_lib.CompletedProcess(
returncode=0, stdout=""
),
)
gather_symbol_files_patch = self.PatchObject(
sysroot,
"GatherSymbolFiles",
return_value=[
sysroot.SymbolFileTuple(
source_file_name="path/to/source/file1.sym",
relative_path="file1.sym",
)
],
)
tar_file = sysroot.BundleBreakpadSymbols(
self.chroot, self.sysroot, self.build_target, self.output_dir
)
# Verify mock objects were called.
generate_breakpad_symbols_patch.assert_called_with(
self.chroot, self.build_target, debug=True
)
gather_symbol_files_patch.assert_called()
# Verify response proto contents and output directory contents.
self.assertIsNotNone(tar_file)
self.assertTrue(tar_file.endswith("/output_dir/debug_breakpad.tar.xz"))
def testBundleDebugSymbols(self):
"""BundleDebugSymbols calls cbuildbot/commands with correct args."""
# Patch service layer functions.
self.PatchObject(os.path, "exists", return_value=True)
self.PatchObject(os.path, "isdir", return_value=True)
create_tarball_patch = self.PatchObject(
cros_build_lib,
"CreateTarball",
return_value=cros_build_lib.CompletedProcess(
returncode=0, stdout=""
),
)
tar_file = sysroot.BundleDebugSymbols(
self.chroot,
self.sysroot,
build_target_lib.BuildTarget(None),
self.output_dir,
)
create_tarball_patch.assert_called()
# Verify response contents.
self.assertIsNotNone(tar_file)
self.assertTrue(tar_file.endswith("/output_dir/debug.tgz"))
class RemoteExecutionTest(cros_test_lib.MockLoggingTestCase):
"""Unittests for remote execution context manager."""
def setUp(self):
self.goma_mock = self.PatchObject(goma_lib, "Goma", autospec=True)
self.goma_instance = self.goma_mock.return_value
self.remoteexec_mock = self.PatchObject(remoteexec_util, "Remoteexec")
self.remoteexec_instance = self.remoteexec_mock.return_value
def testGomaDir(self):
"""Test the case where GOMA env variable is defined."""
os.environ.update(
{
"GOMA_DIR": "goma/path",
"GOMA_TMP_DIR": "goma/tmp/dir",
"GLOG_log_dir": "glog/log/dir",
}
)
with sysroot.RemoteExecution(use_goma=True, use_remoteexec=False):
self.goma_mock.assert_called_once_with(
Path("goma/path"),
"goma/tmp/dir",
stage_name="BuildPackages",
log_dir="glog/log/dir",
)
self.goma_instance.Restart.assert_called_once()
self.goma_instance.Stop.assert_called_once()
self.remoteexec_mock.assert_not_called()
def testGomaHomeDir(self):
"""Test the case where Home Path is used."""
self.PatchObject(Path, "home", return_value=Path("home"))
with sysroot.RemoteExecution(use_goma=True, use_remoteexec=False):
self.goma_mock.assert_called_once_with(
Path("home/goma"),
None,
stage_name="BuildPackages",
log_dir=None,
)
self.goma_instance.Restart.assert_called_once()
self.goma_instance.Stop.assert_called_once()
self.remoteexec_mock.assert_not_called()
def testGomaException(self):
"""Test the case where GOMA interface raises exception."""
self.goma_mock.side_effect = ValueError()
with cros_test_lib.LoggingCapturer() as log:
with sysroot.RemoteExecution(use_goma=True, use_remoteexec=False):
self.AssertLogsMatch(log, ".*initialization error.*")
self.goma_instance.Restart.assert_not_called()
self.goma_instance.Stop.assert_not_called()
self.remoteexec_mock.assert_not_called()
def testRemoteExec(self):
"""Test the case where remoteexec env variables are defined."""
os.environ.update(
{
"RECLIENT_DIR": "reclient/path",
"REPROXY_CFG": "reclient_cfg",
}
)
with sysroot.RemoteExecution(use_goma=False, use_remoteexec=True):
self.remoteexec_mock.assert_called_once_with(
"reclient/path", "reclient_cfg"
)
self.remoteexec_instance.Start.assert_called_once()
self.remoteexec_instance.Stop.assert_called_once()
self.goma_mock.assert_not_called()
def testRemoteExecNoEnv(self):
"""Test the case where remoteexec env variables are not defined."""
with sysroot.RemoteExecution(use_goma=False, use_remoteexec=True):
pass
self.remoteexec_mock.assert_not_called()
self.goma_mock.assert_not_called()
def testRemoteExecException(self):
"""Test the case where remoteexec raises exception."""
os.environ.update(
{
"RECLIENT_DIR": "reclient/path",
"REPROXY_CFG": "reclient_cfg",
}
)
self.remoteexec_mock.side_effect = ValueError()
with cros_test_lib.LoggingCapturer() as log:
with sysroot.RemoteExecution(use_goma=False, use_remoteexec=True):
self.AssertLogsMatch(log, ".*initialization error.*")
self.remoteexec_instance.Start.assert_not_called()
self.remoteexec_instance.Stop.assert_not_called()
self.goma_mock.assert_not_called()
def testNoRemoteExec(self):
"""Test the case where no remoteexec is requested with env variable."""
os.environ.update(
{
"RECLIENT_DIR": "reclient/path",
"REPROXY_CFG": "reclient_cfg",
"GOMA_DIR": "goma/path",
}
)
with sysroot.RemoteExecution(use_goma=False, use_remoteexec=False):
pass
self.remoteexec_mock.assert_not_called()
self.goma_mock.assert_not_called()
def testNoRemoteExecNoEnv(self):
"""Test case where no remoteexec is requested without env variable."""
with sysroot.RemoteExecution(use_goma=False, use_remoteexec=False):
pass
self.remoteexec_mock.assert_not_called()
self.goma_mock.assert_not_called()
class ArchiveSysrootTest(cros_test_lib.TempDirTestCase):
"""ArchiveSysroot tests."""
def setUp(self):
chroot_path = self.tempdir / "chroot"
self.chroot = chroot_lib.Chroot(path=chroot_path)
sysroot_path = chroot_path / "build" / "testBoard"
self.sysroot = sysroot_lib.Sysroot(sysroot_path)
self.dir_structure = [
cros_test_lib.Directory(".", []),
cros_test_lib.Directory("test", ["foo.bar"]),
]
def testArchiveSysroot(self):
"""Archive a sample folder and verify its contents."""
cros_test_lib.CreateOnDiskHierarchy(
self.chroot.full_path(self.sysroot.path), self.dir_structure
)
archive_file = sysroot.ArchiveSysroot(
self.chroot,
self.sysroot,
build_target_lib.BuildTarget("testBoard"),
self.tempdir,
)
self.assertTrue(Path(archive_file).exists())
self.assertEqual(
archive_file, self.tempdir / sysroot.SYSROOT_ARCHIVE_FILE
)
cros_test_lib.VerifyTarball(archive_file, self.dir_structure)
def testArchiveSysrootFailure(self):
"""Archive a sample folder that doesnt exists."""
archive_file = sysroot.ArchiveSysroot(
self.chroot,
self.sysroot,
build_target_lib.BuildTarget("testBoard"),
self.tempdir,
)
self.assertIsNone(archive_file)