blob: bc7dccfa6268c0916f58f49ce24ec953e6c5f8ca [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 toolchain."""
import os
from unittest import mock
from chromite.lib import cros_build_lib
from chromite.lib import cros_test_lib
from chromite.lib import osutils
from chromite.lib import partial_mock
from chromite.lib import sysroot_lib
from chromite.lib import toolchain
from chromite.lib.parser import package_info
from chromite.utils import os_util
BASE_TOOLCHAIN_CONF = """# The root of all evil is money, err, this config.
base-target-name # This will become the base target.
# This toolchain is bonus!
bonus-toolchain {"a setting": "bonus value"} # Bonus!
"""
ADDITIONAL_TOOLCHAIN_CONF = """# A helpful toolchain related comment.
extra-toolchain # Unlikely to win any performance tests.
bonus-toolchain {"stable": true}
"""
EXPECTED_TOOLCHAINS = {
"bonus-toolchain": {
"sdk": True,
"crossdev": "",
"default": False,
"a setting": "bonus value",
"stable": True,
"have-binpkg": True,
},
"extra-toolchain": {
"sdk": True,
"crossdev": "",
"default": True,
"have-binpkg": True,
},
"base-target-name": {
"sdk": True,
"crossdev": "",
"default": False,
"have-binpkg": True,
},
}
class ToolchainTest(cros_test_lib.MockTempDirTestCase):
"""Tests for lib.toolchain."""
def testArchForToolchain(self):
"""Tests that we correctly parse crossdev's output."""
rc_mock = cros_test_lib.RunCommandMock()
noarch = """target=foo
category=bla
"""
rc_mock.SetDefaultCmdResult(stdout=noarch)
with rc_mock:
self.assertEqual(None, toolchain.GetArchForTarget("fake_target"))
amd64arch = """arch=amd64
target=foo
"""
rc_mock.SetDefaultCmdResult(stdout=amd64arch)
with rc_mock:
self.assertEqual("amd64", toolchain.GetArchForTarget("fake_target"))
@mock.patch("chromite.lib.toolchain.portage_util.FindOverlays")
def testReadsBoardToolchains(self, find_overlays_mock):
"""Verify we correctly parse toolchain configs for an overlay stack."""
# Create some fake overlays and put toolchain confs in a subset of them.
overlays = [
os.path.join(self.tempdir, "overlay%d" % i) for i in range(3)
]
for overlay in overlays:
osutils.SafeMakedirs(overlay)
for overlay, contents in [
(overlays[0], BASE_TOOLCHAIN_CONF),
(overlays[2], ADDITIONAL_TOOLCHAIN_CONF),
]:
osutils.WriteFile(os.path.join(overlay, "toolchain.conf"), contents)
find_overlays_mock.return_value = overlays
actual_targets = toolchain.GetToolchainsForBoard("board_value")
self.assertEqual(EXPECTED_TOOLCHAINS, actual_targets)
class ToolchainInfoTest(cros_test_lib.MockTestCase):
"""Tests for the ToolchainInfo class."""
def setUp(self):
self.gcc_cpv = package_info.parse("sys-devel/gcc-1.2")
self.libc_cpv = package_info.parse("sys-libs/glibc-3.4.5")
self.go_cpv = package_info.parse("dev-lang/go-6.7-r8")
self.libcxx_cpv = package_info.parse("sys-libs/libcxx-1.2-r3")
self.libgcc_cpv = package_info.parse("sys-libs/llvm-libunwind-7.8-r9")
self.matching_toolchain = toolchain.ToolchainInfo("tc", "tc")
self.not_matching_toolchain = toolchain.ToolchainInfo("tc", "dtc")
def testVersion(self):
"""Test the version fetching functionality."""
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.gcc_cpv
)
self.assertEqual("1.2", self.matching_toolchain.gcc_version)
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.libc_cpv
)
self.assertEqual("3.4.5", self.matching_toolchain.libc_version)
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.go_cpv
)
self.assertEqual("6.7-r8", self.matching_toolchain.go_version)
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.libcxx_cpv
)
self.assertEqual("1.2-r3", self.matching_toolchain.libcxx_version)
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.libgcc_cpv
)
self.assertEqual("7.8-r9", self.matching_toolchain.libgcc_version)
def testCpv(self):
"""Test the CPV version functionality."""
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.gcc_cpv
)
self.assertEqual(self.gcc_cpv.cpvr, self.matching_toolchain.gcc_cpf)
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.libc_cpv
)
self.assertEqual(self.libc_cpv.cpvr, self.matching_toolchain.libc_cpf)
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.go_cpv
)
self.assertEqual(self.go_cpv.cpvr, self.matching_toolchain.go_cpf)
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.libcxx_cpv
)
self.assertEqual(
self.libcxx_cpv.cpvr, self.matching_toolchain.libcxx_cpf
)
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.libgcc_cpv
)
self.assertEqual(
self.libgcc_cpv.cpvr, self.matching_toolchain.libgcc_cpf
)
def testCP(self):
"""Test the GetCP method."""
# pylint: disable=protected-access
# Use wrong CPV instances to make sure it's not using them since _GetCP
# is the "base case" for fetching the CPV objects.
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.go_cpv
)
self.PatchObject(
self.not_matching_toolchain, "_get_pkg", return_value=self.go_cpv
)
self.assertEqual("sys-devel/gcc", self.matching_toolchain._GetCP("gcc"))
self.assertEqual(
"cross-tc/gcc", self.not_matching_toolchain._GetCP("gcc")
)
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.go_cpv
)
self.PatchObject(
self.not_matching_toolchain, "_get_pkg", return_value=self.go_cpv
)
self.assertEqual(
"sys-libs/glibc", self.matching_toolchain._GetCP("glibc")
)
self.assertEqual(
"cross-tc/glibc", self.not_matching_toolchain._GetCP("glibc")
)
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.go_cpv
)
self.PatchObject(
self.not_matching_toolchain, "_get_pkg", return_value=self.go_cpv
)
self.assertEqual(
"sys-libs/libcxx", self.matching_toolchain._GetCP("libcxx")
)
self.assertEqual(
"cross-tc/libcxx", self.not_matching_toolchain._GetCP("libcxx")
)
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.go_cpv
)
self.PatchObject(
self.not_matching_toolchain, "_get_pkg", return_value=self.go_cpv
)
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.go_cpv
)
self.PatchObject(
self.not_matching_toolchain, "_get_pkg", return_value=self.go_cpv
)
self.assertEqual(
"sys-libs/llvm-libunwind",
self.matching_toolchain._GetCP("llvm-libunwind"),
)
self.assertEqual(
"cross-tc/llvm-libunwind",
self.not_matching_toolchain._GetCP("llvm-libunwind"),
)
self.PatchObject(
self.matching_toolchain, "_get_pkg", return_value=self.gcc_cpv
)
self.PatchObject(
self.not_matching_toolchain, "_get_pkg", return_value=self.gcc_cpv
)
self.assertEqual("dev-lang/go", self.matching_toolchain._GetCP("go"))
self.assertEqual(
"cross-tc/go", self.not_matching_toolchain._GetCP("go")
)
class ToolchainInstallerTest(cros_test_lib.RunCommandTempDirTestCase):
"""Tests for the toolchain installer class."""
def setUp(self):
# Setup the temp filesystem matching the expected layout.
D = cros_test_lib.Directory
filesystem = (
D(
"build",
(
D(
"board",
(
D(
"etc",
(
D(
"portage",
(D("profile", ("package.provided",)),),
),
# 'make.conf.board_setup',
),
),
D(
"var",
(
D(
"cache",
(
D(
"edb",
(
"chromeos",
"chromeos.lock",
),
),
),
),
D("lib", (D("portage", (D("pkgs", ()),)),)),
),
),
),
),
),
),
)
cros_test_lib.CreateOnDiskHierarchy(self.tempdir, filesystem)
self.sysroot = sysroot_lib.Sysroot(
os.path.join(self.tempdir, "build/board")
)
# Build out the testing CPV objects.
self.gcc_cpv = package_info.parse("sys-devel/gcc-1.2")
self.libc_cpv = package_info.parse("sys-libs/glibc-3.4.5")
self.go_cpv = package_info.parse("dev-lang/go-6.7-r8")
self.rpcsvc_cpv = package_info.parse("net-libs/rpcsvc-proto-9.10")
self.libcxx_cpv = package_info.parse("sys-libs/libcxx-1.2.3")
self.libgcc_cpv = package_info.parse("sys-libs/llvm-libunwind-7.8.9")
# pylint: disable=protected-access
self.go_toolchain = toolchain.ToolchainInfo("tc", "tc")
self.go_toolchain._pkgs = {
"gcc": self.gcc_cpv,
"glibc": self.libc_cpv,
"go": self.go_cpv,
"rpcsvc": self.rpcsvc_cpv,
"libcxx": self.libcxx_cpv,
"llvm-libunwind": self.libgcc_cpv,
}
self.no_go_toolchain = toolchain.ToolchainInfo("tc", "tc")
self.no_go_toolchain._pkgs = {
"gcc": self.gcc_cpv,
"glibc": self.libc_cpv,
"go": None,
"rpcsvc": self.rpcsvc_cpv,
"libcxx": self.libcxx_cpv,
"llvm-libunwind": self.libgcc_cpv,
}
self.different_toolchain = toolchain.ToolchainInfo("nottc", "tc")
self.different_toolchain._pkgs = {
"gcc": self.gcc_cpv,
"glibc": self.libc_cpv,
"go": self.go_cpv,
"rpcsvc": None,
"libcxx": self.libcxx_cpv,
"llvm-libunwind": self.libgcc_cpv,
}
pkgdir = os.path.join(self.tempdir, "var/lib/portage/pkgs")
self.updater = toolchain.ToolchainInstaller(False, True, "tc", pkgdir)
# Avoid sudo password prompt for _WriteConfigs.
self.PatchObject(os_util, "is_root_user", return_value=True)
def testUpdateProvided(self):
"""Test the updates to the package.provided file."""
path = os.path.join(
self.sysroot.path, "etc/portage/profile/package.provided"
)
# pylint: disable=protected-access
# All 3 packages.
self.updater._UpdateProvided(self.sysroot, self.go_toolchain)
expected = [
"sys-devel/gcc-1.2",
"sys-libs/glibc-3.4.5",
"dev-lang/go-6.7-r8",
"net-libs/rpcsvc-proto-9.10",
]
for line in osutils.ReadFile(path).splitlines():
self.assertIn(line, expected)
expected.remove(line)
self.assertEqual([], expected)
# No go package.
self.updater._UpdateProvided(self.sysroot, self.no_go_toolchain)
expected = [
"sys-devel/gcc-1.2",
"sys-libs/glibc-3.4.5",
"net-libs/rpcsvc-proto-9.10",
]
for line in osutils.ReadFile(path).splitlines():
self.assertIn(line, expected)
expected.remove(line)
self.assertEqual([], expected)
# Different toolchain.
self.updater._UpdateProvided(self.sysroot, self.different_toolchain)
expected = [
"sys-devel/gcc-1.2",
"sys-libs/glibc-3.4.5",
"dev-lang/go-6.7-r8",
]
for line in osutils.ReadFile(path).splitlines():
self.assertIn(line, expected)
expected.remove(line)
self.assertEqual([], expected)
def testWriteConfig(self):
"""Test the sysroot configs are updated correctly."""
# This test is safe to run all the real commands.
self.rc.stop()
# pylint: disable=protected-access
self.updater._WriteConfigs(self.sysroot, self.go_toolchain)
self.assertEqual("3.4.5", self.sysroot.GetCachedField("LIBC_VERSION"))
def testInstallLibcFailures(self):
"""Test the installer error handling."""
# Test error thrown during toolchain installation.
# We want a ToolchainInstallError with the glibc info set.
self.rc.AddCmdResult(
partial_mock.ListRegex(f"emerge.*{self.libc_cpv}"), returncode=1
)
try:
# pylint: disable=protected-access
self.updater._InstallLibc(self.sysroot, self.go_toolchain)
except toolchain.ToolchainInstallError as e:
self.assertTrue(e.failed_toolchain_info)
self.assertEqual(
self.different_toolchain.libc_cpf,
e.failed_toolchain_info[0].cpf,
)
except Exception as e:
self.fail("Unexpected exception type thrown: %s" % type(e))
else:
self.fail("_InstallLibc should have thrown an error.")
def testExtractLibcFailures(self):
"""Test the installer error handling."""
# Test error thrown during cross toolchain installation.
# This is the error we're testing for, but _InstallLibc catches and
# modifies the error before re-raising it.
error_result = cros_build_lib.CompletedProcess(returncode=1)
self.PatchObject(
self.updater,
"_ExtractLibc",
side_effect=toolchain.ToolchainInstallError("Error", error_result),
)
try:
# pylint: disable=protected-access
self.updater._InstallLibc(self.sysroot, self.different_toolchain)
except toolchain.ToolchainInstallError as e:
# Make sure it did in fact modify the error to include the glibc
# CPV.
self.assertTrue(e.failed_toolchain_info)
self.assertEqual(
self.go_toolchain.libc_cpf, e.failed_toolchain_info[0].cpf
)
except Exception as e:
self.fail("Unexpected exception type thrown: %s" % type(e))
else:
self.fail("_InstallLibc should have thrown an error.")