blob: b189fab8379014a780fcec717ab58399bcfbbe82 [file] [log] [blame]
# Copyright 2012 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This module tests the cros image command."""
import copy
import os
from pathlib import Path
import shutil
import threading
from unittest import mock
from chromite.third_party.gn_helpers import gn_helpers
from chromite.cli import command_unittest
from chromite.cli.cros import cros_chrome_sdk
from chromite.lib import cache
from chromite.lib import chromite_config
from chromite.lib import constants
from chromite.lib import cros_build_lib
from chromite.lib import cros_test_lib
from chromite.lib import gs
from chromite.lib import gs_unittest
from chromite.lib import osutils
from chromite.lib import parallel_unittest
from chromite.lib import partial_mock
# pylint: disable=protected-access
class MockChromeSDKCommand(command_unittest.MockCommand):
"""Mock out the build command."""
TARGET = "chromite.cli.cros.cros_chrome_sdk.ChromeSDKCommand"
TARGET_CLASS = cros_chrome_sdk.ChromeSDKCommand
COMMAND = "chrome-sdk"
ATTRS = ("_SetupEnvironment",) + command_unittest.MockCommand.ATTRS
def __init__(self, *args, **kwargs) -> None:
command_unittest.MockCommand.__init__(self, *args, **kwargs)
self.env = None
def _SetupEnvironment(self, *args, **kwargs):
env = self.backup["_SetupEnvironment"](*args, **kwargs)
self.env = copy.deepcopy(env)
return env
class ParserTest(cros_test_lib.MockTempDirTestCase):
"""Test the parser."""
def testNormal(self) -> None:
"""Tests that our example parser works normally."""
with MockChromeSDKCommand(
["--board", SDKFetcherMock.BOARD],
base_args=["--cache-dir", str(self.tempdir)],
) as bootstrap:
self.assertEqual(bootstrap.inst.options.board, SDKFetcherMock.BOARD)
self.assertEqual(
bootstrap.inst.options.cache_dir, str(self.tempdir)
)
def testVersion(self) -> None:
"""Tests that a platform version is allowed."""
VERSION = "1234.0.0"
with MockChromeSDKCommand(
["--board", SDKFetcherMock.BOARD, "--version", VERSION]
) as parser:
self.assertEqual(parser.inst.options.version, VERSION)
def testFullVersion(self) -> None:
"""Tests that a full version is allowed."""
FULL_VERSION = "R56-1234.0.0"
with MockChromeSDKCommand(
["--board", SDKFetcherMock.BOARD, "--version", FULL_VERSION]
) as parser:
self.assertEqual(parser.inst.options.version, FULL_VERSION)
def _GSCopyMock(_self, path, dest, **_kwargs) -> None:
"""Used to simulate a GS Copy operation."""
with osutils.TempDir() as tempdir:
local_path = os.path.join(tempdir, os.path.basename(path))
osutils.Touch(local_path)
shutil.move(local_path, dest)
def _DependencyMockCtx(f):
"""Attribute that ensures dependency PartialMocks are started.
Since PartialMock does not support nested mocking, we need to first call
stop() on the outer level PartialMock (which is passed in to us). We then
re-start() the outer level upon exiting the context.
"""
def new_f(self, *args, **kwargs):
if not self.entered:
try:
self.entered = True
# Temporarily disable outer GSContext mock before starting our
# mock.
# TODO(rcui): Generalize this attribute and include in
# partial_mock.py.
for emock in self.external_mocks:
emock.stop()
with self.gs_mock:
return f(self, *args, **kwargs)
finally:
self.entered = False
for emock in self.external_mocks:
emock.start()
else:
return f(self, *args, **kwargs)
return new_f
class SDKFetcherMock(partial_mock.PartialMock):
"""Provides mocking functionality for SDKFetcher."""
TARGET = "chromite.cli.cros.cros_chrome_sdk.SDKFetcher"
ATTRS = (
"__init__",
"GetFullVersion",
"_GetMetadata",
"_UpdateTarball",
"_GetManifest",
"UpdateDefaultVersion",
"_GetTarballCacheKey",
"_GetBuildReport",
)
FAKE_METADATA = """
{
"boards": ["eve"],
"cros-version": "25.3543.2",
"metadata-version": "1",
"bot-hostname": "build82-m2.golo.chromium.org",
"bot-config": "eve-release",
"toolchain-tuple": ["i686-pc-linux-gnu"],
"toolchain-url": "2013/01/%(target)s-2013.01.23.003823.tar.xz",
"sdk-version": "2013.01.23.003823"
}"""
FAKE_BUILD_REPORT = """
{
"sdkVersion": "2013.01.23.003823",
"toolchainUrl": "2013/01/%(target)s-2013.01.23.003823.tar.xz",
"toolchains": ["i686-pc-linux-gnu"]
}
"""
BOARD = "eve"
# These are boards that Lacros is currently supporting.
# Specifically, *-crostoolchain.gni files need to be generated for them.
BOARDS = ["amd64-generic", "arm-generic", "arm64-generic"]
VERSION = "4567.8.9"
def __init__(self, external_mocks=None) -> None:
"""Initializes the mock.
Args:
external_mocks: A list of already started PartialMock/patcher
instances. stop() will be called on each element every time
execution enters one of our the mocked out methods, and start()
called on it once execution leaves the mocked out method.
"""
partial_mock.PartialMock.__init__(self)
self.external_mocks = external_mocks or []
self.entered = False
self.gs_mock = gs_unittest.GSContextMock()
self.gs_mock.SetDefaultCmdResult()
self.env = None
self.tarball_cache_key_map = {}
self.tarball_fetch_lock = threading.Lock()
@_DependencyMockCtx
def _target__init__(self, inst, *args, **kwargs) -> None:
self.backup["__init__"](inst, *args, **kwargs)
if not inst.cache_base.startswith("/tmp"):
raise AssertionError(
"For testing, SDKFetcher cache_dir needs to be a "
"dir under /tmp"
)
@_DependencyMockCtx
def UpdateDefaultVersion(self, inst, *_args, **_kwargs):
inst._SetDefaultVersion(self.VERSION)
return self.VERSION, True
@_DependencyMockCtx
def _UpdateTarball(self, inst, *args, **kwargs):
# The mocks here can fall off in the middle of the test unreliably,
# likely due to a race condition when running _UpdateTarball across
# multiple threads. So lock around the mocks to avoid.
with self.tarball_fetch_lock:
with mock.patch.object(
gs.GSContext, "Copy", autospec=True, side_effect=_GSCopyMock
):
with mock.patch.object(cache, "Untar"):
return self.backup["_UpdateTarball"](inst, *args, **kwargs)
@_DependencyMockCtx
def GetFullVersion(self, _inst, version):
return "R26-%s" % version
@_DependencyMockCtx
def _GetMetadata(self, inst, *args, **kwargs):
self.gs_mock.SetDefaultCmdResult()
self.gs_mock.AddCmdResult(
partial_mock.ListRegex("cat .*/%s" % constants.METADATA_JSON),
stdout=self.FAKE_METADATA,
)
return self.backup["_GetMetadata"](inst, *args, **kwargs)
@_DependencyMockCtx
def _GetBuildReport(self, inst, *args, **kwargs):
self.gs_mock.SetDefaultCmdResult()
self.gs_mock.AddCmdResult(
partial_mock.ListRegex("cat .*/%s" % constants.BUILD_REPORT_JSON),
stdout=self.FAKE_BUILD_REPORT,
)
return self.backup["_GetBuildReport"](inst, *args, **kwargs)
@_DependencyMockCtx
def _GetManifest(self, _inst, _version):
return {
"packages": {
"chromeos-base/tast-cmd": [["1.2.3", {}]],
"chromeos-base/tast-remote-tests-cros": [["7.8.9", {}]],
}
}
@_DependencyMockCtx
def _GetTarballCacheKey(self, _inst, component, _url):
return (
os.path.join(
component,
self.tarball_cache_key_map.get(component, "some-fake-hash"),
),
)
class RunThroughTest(
cros_test_lib.MockTempDirTestCase, cros_test_lib.LoggingTestCase
):
"""Run the script with most things mocked out."""
VERSION_KEY = (
SDKFetcherMock.BOARD,
SDKFetcherMock.VERSION,
constants.CHROME_SYSROOT_TAR,
)
FAKE_ENV = {
"GN_ARGS": 'target_sysroot="/path/to/sysroot" is_clang=false',
"AR": "x86_64-cros-linux-gnu-ar",
"AS": "x86_64-cros-linux-gnu-as",
"CXX": "x86_64-cros-linux-gnu-clang++",
"CC": "x86_64-cros-linux-gnu-clang",
"LD": "x86_64-cros-linux-gnu-clang++",
"NM": "x86_64-cros-linux-gnu-nm",
"RANLIB": "x86_64-cros-linux-gnu-ranlib",
"READELF": "x86_64-cros-linux-gnu-readelf",
"CFLAGS": "-O2",
"CXXFLAGS": "-O2",
}
def SetupCommandMock(
self, many_boards=False, extra_args=None, default_cache_dir=False
) -> None:
cmd_args = ["--chrome-src", self.chrome_src_dir, "true"]
if many_boards:
cmd_args += [
"--boards",
":".join(SDKFetcherMock.BOARDS),
"--no-shell",
]
else:
cmd_args += ["--board", SDKFetcherMock.BOARD]
if extra_args:
cmd_args.extend(extra_args)
# --no-shell drops gni files in //build/args/chromeos/. reclient configs
# are also dropped here regardless of --no-shell or not.
osutils.SafeMakedirs(
os.path.join(self.chrome_root, "src", "build", "args", "chromeos")
)
base_args = (
None if default_cache_dir else ["--cache-dir", str(self.tempdir)]
)
self.cmd_mock = MockChromeSDKCommand(cmd_args, base_args=base_args)
self.StartPatcher(self.cmd_mock)
self.cmd_mock.UnMockAttr("Run")
def SourceEnvironmentMock(self, path, *_args, **_kwargs):
if str(path).endswith("environment"):
return copy.deepcopy(self.FAKE_ENV)
return {}
def setUp(self) -> None:
self.rc_mock = cros_test_lib.RunCommandMock()
self.rc_mock.SetDefaultCmdResult()
self.StartPatcher(self.rc_mock)
self.StartPatcher(parallel_unittest.ParallelMock())
self.sdk_mock = self.StartPatcher(
SDKFetcherMock(external_mocks=[self.rc_mock])
)
# This needs to occur before initializing MockChromeSDKCommand.
self.bashrc = self.tempdir / "bashrc"
self.PatchObject(chromite_config, "CHROME_SDK_BASHRC", new=self.bashrc)
self.PatchObject(
osutils,
"SourceEnvironment",
autospec=True,
side_effect=self.SourceEnvironmentMock,
)
self.rc_mock.AddCmdResult(
cros_chrome_sdk.ChromeSDKCommand.GOMACC_PORT_CMD, stdout="8088"
)
# Initialized by SetupCommandMock.
self.cmd_mock = None
# Set up a fake Chrome src/ directory
self.chrome_root = os.path.join(self.tempdir, "chrome_root")
self.chrome_src_dir = os.path.join(self.chrome_root, "src")
osutils.SafeMakedirs(self.chrome_src_dir)
osutils.Touch(os.path.join(self.chrome_root, ".gclient"))
osutils.SafeMakedirs(os.path.join(self.chrome_src_dir, "chrome"))
osutils.WriteFile(
os.path.join(self.chrome_src_dir, "chrome", "VERSION"), "MAJOR=123"
)
@property
def cache(self):
return self.cmd_mock.inst.sdk.tarball_cache
def testIt(self) -> None:
"""Test a runthrough of the script."""
self.PatchObject(
cros_chrome_sdk.ChromeSDKCommand, "_GomaDir", side_effect=["XXXX"]
)
self.SetupCommandMock()
with cros_test_lib.LoggingCapturer() as logs:
self.cmd_mock.inst.Run()
self.AssertLogsContain(logs, "Goma:", inverted=True)
def testManyBoards(self) -> None:
"""Test a runthrough when multiple boards are specified via --boards."""
self.SetupCommandMock(many_boards=True)
self.cmd_mock.inst.ProcessOptions(
self.cmd_mock.parser, self.cmd_mock.inst.options
)
self.cmd_mock.inst.Run()
for board in SDKFetcherMock.BOARDS:
board_arg_file = os.path.join(
self.chrome_src_dir, "build/args/chromeos/%s.gni" % board
)
self.assertExists(board_arg_file)
board_crostoolchain_arg_file = os.path.join(
self.chrome_src_dir,
"build/args/chromeos/%s-crostoolchain.gni" % board,
)
self.assertNotExists(board_crostoolchain_arg_file)
def testManyBoardsLacros(self) -> None:
"""Test a runthrough when multiple boards are specified via --boards."""
self.SetupCommandMock(
many_boards=True, extra_args=["--is-lacros", "--version=1234.0.0"]
)
lkgm_file = os.path.join(
self.chrome_src_dir, constants.PATH_TO_CHROME_LKGM
)
osutils.Touch(lkgm_file, makedirs=True)
osutils.WriteFile(lkgm_file, "5678.0.0")
self.cmd_mock.inst.ProcessOptions(
self.cmd_mock.parser, self.cmd_mock.inst.options
)
self.cmd_mock.inst.Run()
for board in SDKFetcherMock.BOARDS:
board_arg_file = os.path.join(
self.chrome_src_dir, "build/args/chromeos/%s.gni" % board
)
self.assertNotExists(board_arg_file)
board_crostoolchain_arg_file = os.path.join(
self.chrome_src_dir,
"build/args/chromeos/%s-crostoolchain.gni" % board,
)
self.assertExists(board_crostoolchain_arg_file)
with open(board_crostoolchain_arg_file, encoding="utf-8") as f:
self.assertIn('cros_sdk_version = "5678.0.0"', f.read())
def testManyBoardsBrokenArgs(self) -> None:
"""Tests that malformed args.gn files will be fixed in --boards."""
self.SetupCommandMock(many_boards=True)
for board in SDKFetcherMock.BOARDS:
gn_args_file = os.path.join(
self.chrome_src_dir, "out_%s" % board, "Release", "args.gn"
)
osutils.WriteFile(gn_args_file, "foo\nbar", makedirs=True)
self.cmd_mock.inst.ProcessOptions(
self.cmd_mock.parser, self.cmd_mock.inst.options
)
self.cmd_mock.inst.Run()
for board in SDKFetcherMock.BOARDS:
gn_args_file = os.path.join(
self.chrome_src_dir, "out_%s" % board, "Release", "args.gn"
)
self.assertTrue(osutils.ReadFile(gn_args_file).startswith("import"))
def testErrorCodePassthrough(self) -> None:
"""Test that error codes are passed through."""
self.SetupCommandMock()
with cros_test_lib.LoggingCapturer():
self.rc_mock.AddCmdResult(
partial_mock.ListRegex("-- true"), returncode=5
)
returncode = self.cmd_mock.inst.Run()
self.assertEqual(returncode, 5)
def testEmptyMetadata(self) -> None:
"""Tests the use of build_report.json when metadata.json is empty."""
sdk_dir = os.path.join(self.tempdir, "sdk_dir")
osutils.SafeMakedirs(sdk_dir)
osutils.WriteFile(os.path.join(sdk_dir, constants.METADATA_JSON), "{}")
osutils.WriteFile(
os.path.join(sdk_dir, constants.BUILD_REPORT_JSON),
SDKFetcherMock.FAKE_BUILD_REPORT,
)
self.SetupCommandMock(extra_args=["--sdk-path", sdk_dir])
with cros_test_lib.LoggingCapturer():
self.cmd_mock.inst.Run()
def testLocalSDKPath(self) -> None:
"""Fetch components from a local --sdk-path."""
sdk_dir = os.path.join(self.tempdir, "sdk_dir")
osutils.SafeMakedirs(sdk_dir)
osutils.WriteFile(
os.path.join(sdk_dir, constants.METADATA_JSON),
SDKFetcherMock.FAKE_METADATA,
)
osutils.WriteFile(
os.path.join(sdk_dir, constants.BUILD_REPORT_JSON),
SDKFetcherMock.FAKE_BUILD_REPORT,
)
self.SetupCommandMock(extra_args=["--sdk-path", sdk_dir])
with cros_test_lib.LoggingCapturer():
self.cmd_mock.inst.Run()
def testGomaError(self) -> None:
"""We print an error message when GomaError is raised."""
self.SetupCommandMock(extra_args=["--goma", "--no-use-remoteexec"])
with cros_test_lib.LoggingCapturer() as logs:
self.PatchObject(
cros_chrome_sdk.ChromeSDKCommand,
"_SetupGoma",
side_effect=cros_chrome_sdk.GomaError(),
)
self.cmd_mock.inst.Run()
self.AssertLogsContain(logs, "Goma:")
def testSpecificComponent(self) -> None:
"""Verify SDKFetcher.Prepare() handles |components| param properly."""
sdk = cros_chrome_sdk.SDKFetcher(
os.path.join(self.tempdir), SDKFetcherMock.BOARD
)
components = [constants.BASE_IMAGE_TAR, constants.CHROME_SYSROOT_TAR]
with sdk.Prepare(components=components) as ctx:
for c in components:
self.assertExists(ctx.key_map[c].path)
for c in [constants.IMAGE_SCRIPTS_TAR, constants.CHROME_ENV_TAR]:
self.assertFalse(c in ctx.key_map)
@staticmethod
def FindInPath(paths, endswith):
for path in paths.split(":"):
if path.endswith(endswith):
return True
return False
def testGomaInPath(self) -> None:
"""Verify that we do indeed add Goma to the PATH."""
self.PatchObject(
cros_chrome_sdk.ChromeSDKCommand, "_GomaDir", side_effect=["XXXX"]
)
self.SetupCommandMock(extra_args=["--goma", "--no-use-remoteexec"])
self.cmd_mock.inst.Run()
self.assertIn("use_goma = true", self.cmd_mock.env["GN_ARGS"])
def testRbeIsDefault(self) -> None:
"""Verify that we do not add Goma to the PATH."""
self.SetupCommandMock()
self.cmd_mock.inst.Run()
self.assertIn("use_goma = false", self.cmd_mock.env["GN_ARGS"])
self.assertIn("use_remoteexec = true", self.cmd_mock.env["GN_ARGS"])
wrapper_path = os.path.join(
self.chrome_root,
"src",
"build",
"args",
"chromeos",
"rewrapper_%s" % SDKFetcherMock.BOARD,
)
self.assertIn(
'rbe_cros_cc_wrapper = "%s"' % wrapper_path,
self.cmd_mock.env["GN_ARGS"],
)
def testNoUseRemoteExecDoesNotFallbackToGoma(self) -> None:
"""Verify that we do not add Goma to the PATH."""
self.SetupCommandMock(extra_args=["--no-use-remoteexec"])
self.cmd_mock.inst.Run()
self.assertIn("use_goma = false", self.cmd_mock.env["GN_ARGS"])
self.assertIn("use_remoteexec = false", self.cmd_mock.env["GN_ARGS"])
def testUseRBELacros(self) -> None:
"""Verify that we do not add Goma to the PATH."""
self.SetupCommandMock(extra_args=["--is-lacros", "--version=1234.0.0"])
lkgm_file = os.path.join(
self.chrome_src_dir, constants.PATH_TO_CHROME_LKGM
)
osutils.Touch(lkgm_file, makedirs=True)
osutils.WriteFile(lkgm_file, "5678.0.0")
self.cmd_mock.inst.Run()
self.assertIn("use_goma = false", self.cmd_mock.env["GN_ARGS"])
self.assertIn("use_remoteexec = true", self.cmd_mock.env["GN_ARGS"])
wrapper_path = os.path.join(
self.chrome_root,
"src",
"build",
"args",
"chromeos",
"rewrapper_%s" % SDKFetcherMock.BOARD,
)
self.assertIn(
'rbe_cros_cc_wrapper = "%s"' % wrapper_path,
self.cmd_mock.env["GN_ARGS"],
)
def testGnArgsStalenessCheckNoMatch(self) -> None:
"""Verifies the GN args are checked for staleness with a mismatch."""
with cros_test_lib.LoggingCapturer() as logs:
out_dir = "out_%s" % SDKFetcherMock.BOARD
build_label = "Release"
gn_args_file_dir = os.path.join(
self.chrome_src_dir, out_dir, build_label
)
gn_args_file_path = os.path.join(gn_args_file_dir, "args.gn")
osutils.SafeMakedirs(gn_args_file_dir)
osutils.WriteFile(gn_args_file_path, 'foo = "no match"')
self.SetupCommandMock()
self.cmd_mock.inst.Run()
self.AssertLogsContain(logs, "Stale args.gn file")
def testGnArgsStalenessCheckMatch(self) -> None:
"""Verifies the GN args are checked for staleness with a match."""
with cros_test_lib.LoggingCapturer() as logs:
self.SetupCommandMock()
self.cmd_mock.inst.Run()
out_dir = "out_%s" % SDKFetcherMock.BOARD
build_label = "Release"
gn_args_file_dir = os.path.join(
self.chrome_src_dir, out_dir, build_label
)
gn_args_file_path = os.path.join(gn_args_file_dir, "args.gn")
osutils.SafeMakedirs(gn_args_file_dir)
osutils.WriteFile(gn_args_file_path, self.cmd_mock.env["GN_ARGS"])
self.cmd_mock.inst.Run()
self.AssertLogsContain(logs, "Stale args.gn file", inverted=True)
def testGnArgsStalenessExtraArgs(self) -> None:
"""Verifies the GN extra args regenerate gn."""
with cros_test_lib.LoggingCapturer() as logs:
self.SetupCommandMock(
extra_args=["--gn-extra-args=dcheck_always_on=true"]
)
self.cmd_mock.inst.Run()
out_dir = "out_%s" % SDKFetcherMock.BOARD
build_label = "Release"
gn_args_file_dir = os.path.join(
self.chrome_src_dir, out_dir, build_label
)
gn_args_file_path = os.path.join(gn_args_file_dir, "args.gn")
osutils.SafeMakedirs(gn_args_file_dir)
gn_args_dict = gn_helpers.FromGNArgs(self.cmd_mock.env["GN_ARGS"])
osutils.WriteFile(
gn_args_file_path, gn_helpers.ToGNString(gn_args_dict)
)
self.cmd_mock.inst.Run()
self.AssertLogsContain(logs, "Stale args.gn file", inverted=True)
def testChromiumOutDirSet(self) -> None:
"""Verify that CHROMIUM_OUT_DIR is set."""
self.SetupCommandMock()
self.cmd_mock.inst.Run()
out_dir = os.path.join(
self.chrome_src_dir, "out_%s" % SDKFetcherMock.BOARD
)
self.assertEqual(out_dir, self.cmd_mock.env["CHROMIUM_OUT_DIR"])
@mock.patch("chromite.lib.gclient.LoadGclientFile")
def testInternalGclientSpec(self, mock_gclient_load) -> None:
"""Verify the SDK exits with an error if the gclient spec is wrong."""
self.SetupCommandMock(extra_args=["--internal"])
# Simple Chrome should exit with an error if "--internal" is passed and
# "checkout_src_internal" isn't present in the .gclient file.
mock_gclient_load.return_value = [
{
"url": "https://chromium.googlesource.com/chromium/src.git",
"custom_deps": {},
"custom_vars": {},
}
]
with self.assertRaises(cros_build_lib.DieSystemExit):
self.cmd_mock.inst.Run()
# With "checkout_src_internal" set, Simple Chrome should run without
# error.
mock_gclient_load.return_value = [
{
"url": "https://chromium.googlesource.com/chromium/src.git",
"custom_deps": {},
"custom_vars": {"checkout_src_internal": True},
}
]
self.cmd_mock.inst.Run()
def testClearSDKCache(self) -> None:
"""Verifies cache directories are removed with --clear-sdk-cache."""
# Ensure we have checkout type GCLIENT.
self.PatchObject(constants, "SOURCE_ROOT", new=Path(self.chrome_root))
# Use the default cache location.
self.SetupCommandMock(
extra_args=["--clear-sdk-cache"], default_cache_dir=True
)
chrome_cache = os.path.join(self.chrome_src_dir, "build/cros_cache")
self.assertNotExists(chrome_cache)
self.cmd_mock.inst.Run()
self.assertExists(chrome_cache)
def testSymlinkCache(self) -> None:
"""Verify the symlink cache contains valid tarball cache links."""
self.SetupCommandMock()
self.cmd_mock.inst.Run()
board, version, _ = self.VERSION_KEY
toolchain_dir = os.path.join(
self.tempdir, "chrome-sdk/tarballs/target_toolchain/some-fake-hash"
)
sysroot_dir = os.path.join(
self.tempdir,
"chrome-sdk/tarballs/sysroot_chromeos-base_chromeos-chrome.tar.xz/"
"some-fake-hash",
)
self.assertExists(toolchain_dir)
self.assertExists(sysroot_dir)
toolchain_link = os.path.join(
self.tempdir,
"chrome-sdk/symlinks/%s+%s+target_toolchain" % (board, version),
)
sysroot_link = os.path.join(
self.tempdir,
"chrome-sdk/symlinks/%s+%s+sysroot_chromeos-base_chromeos-"
"chrome.tar.xz" % (board, version),
)
self.assertTrue(os.path.islink(toolchain_link))
self.assertTrue(os.path.islink(sysroot_link))
self.assertEqual(os.path.realpath(toolchain_link), toolchain_dir)
self.assertEqual(os.path.realpath(sysroot_link), sysroot_dir)
def testSymlinkCacheToolchainOverride(self) -> None:
"""Ensures that the SDK picks up an overridden component."""
sdk = cros_chrome_sdk.SDKFetcher(
os.path.join(self.tempdir), SDKFetcherMock.BOARD
)
board, version, _ = self.VERSION_KEY
toolchain_link = os.path.join(
self.tempdir,
"chrome-sdk/symlinks/%s+%s+target_toolchain" % (board, version),
)
components = [sdk.TARGET_TOOLCHAIN_KEY]
toolchain_url_1 = "some-fake-gs-path-1"
toolchain_dir_1 = os.path.join(
self.tempdir,
"chrome-sdk/tarballs/target_toolchain/",
toolchain_url_1,
)
toolchain_url_2 = "some-fake-gs-path-2"
toolchain_dir_2 = os.path.join(
self.tempdir,
"chrome-sdk/tarballs/target_toolchain/",
toolchain_url_2,
)
nacl_toolchain_dir = os.path.join(
self.tempdir, "chrome-sdk/tarballs/arm32_toolchain_for_nacl_helper/"
)
# Prepare the cache using 'toolchain_url_1'.
self.sdk_mock.tarball_cache_key_map = {
sdk.TARGET_TOOLCHAIN_KEY: toolchain_url_1
}
with sdk.Prepare(components, toolchain_url=toolchain_url_1):
self.assertEqual(toolchain_dir_1, os.path.realpath(toolchain_link))
self.assertExists(toolchain_dir_1)
self.assertNotExists(toolchain_dir_2)
# Prepare the cache with 'toolchain_url_2' and make sure the active
# symlink points to it and that 'toolchain_url_1' is still present.
self.sdk_mock.tarball_cache_key_map = {
sdk.TARGET_TOOLCHAIN_KEY: toolchain_url_2
}
with sdk.Prepare(components, toolchain_url=toolchain_url_2):
self.assertEqual(toolchain_dir_2, os.path.realpath(toolchain_link))
self.assertExists(toolchain_dir_2)
self.assertExists(toolchain_dir_1)
# Test that arm32 toolchain is fetched for NaCl for arm64.
with sdk.Prepare(
components, toolchain_url=toolchain_url_2, target_tc=sdk.ARM64_TUPLE
):
self.assertExists(nacl_toolchain_dir)
class GomaTest(
cros_test_lib.MockTempDirTestCase, cros_test_lib.LoggingTestCase
):
"""Test Goma setup functionality."""
def setUp(self) -> None:
self.rc_mock = cros_test_lib.RunCommandMock()
self.rc_mock.SetDefaultCmdResult()
self.StartPatcher(self.rc_mock)
self.cmd_mock = MockChromeSDKCommand(
["--board", SDKFetcherMock.BOARD, "true"],
base_args=["--cache-dir", str(self.tempdir)],
)
self.StartPatcher(self.cmd_mock)
def VerifyGomaError(self) -> None:
self.assertRaises(
cros_chrome_sdk.GomaError, self.cmd_mock.inst._SetupGoma
)
def testNoGomaPort(self) -> None:
"""We print an error when gomacc is not returning a port."""
self.rc_mock.AddCmdResult(
cros_chrome_sdk.ChromeSDKCommand.GOMACC_PORT_CMD
)
self.VerifyGomaError()
def testGomaccError(self) -> None:
"""We print an error when gomacc exits with nonzero returncode."""
self.rc_mock.AddCmdResult(
cros_chrome_sdk.ChromeSDKCommand.GOMACC_PORT_CMD, returncode=1
)
self.VerifyGomaError()
def testSetupError(self) -> None:
"""We print an error when we can't fetch Goma."""
self.rc_mock.AddCmdResult(
cros_chrome_sdk.ChromeSDKCommand.GOMACC_PORT_CMD, returncode=1
)
self.VerifyGomaError()
def testGomaStart(self) -> None:
"""Test that we start Goma if it's not already started."""
# Duplicate return values.
self.PatchObject(
cros_chrome_sdk.ChromeSDKCommand, "_GomaDir", side_effect=["XXXX"]
)
self.PatchObject(
cros_chrome_sdk.ChromeSDKCommand,
"_GomaPort",
side_effect=["XXXX", "XXXX"],
)
goma_dir, goma_port = self.cmd_mock.inst._SetupGoma()
self.assertEqual(goma_port, "XXXX")
self.assertTrue(bool(goma_dir))
class VersionTest(
gs_unittest.AbstractGSContextTest, cros_test_lib.LoggingTestCase
):
"""Tests the determination of which SDK version to use."""
VERSION = "3543.0.0"
FULL_VERSION = "R55-%s" % VERSION
RECENT_VERSION_FOUND = "3541.0.0"
FULL_VERSION_RECENT = "R55-%s" % RECENT_VERSION_FOUND
NON_CANARY_VERSION = "3543.2.1"
FULL_VERSION_NON_CANARY = "R55-%s" % NON_CANARY_VERSION
BOARD = "eve"
VERSION_BASE = "gs://chromeos-image-archive/%s-release/LATEST-%s" % (
BOARD,
VERSION,
)
CAT_ERROR = "CommandException: No URLs matched %s" % VERSION_BASE
LS_ERROR = "CommandException: One or more URLs matched no objects."
def setUp(self) -> None:
self.sdk_mock = self.StartPatcher(
SDKFetcherMock(external_mocks=[self.gs_mock])
)
os.environ.pop(cros_chrome_sdk.SDKFetcher.SDK_VERSION_ENV, None)
self.sdk = cros_chrome_sdk.SDKFetcher(
os.path.join(self.tempdir, "cache"), self.BOARD
)
def testUpdateDefaultChromeVersion(self) -> None:
"""We pick up the right LKGM version from the Chrome tree."""
dir_struct = ["gclient_root/.gclient"]
cros_test_lib.CreateOnDiskHierarchy(self.tempdir, dir_struct)
gclient_root = os.path.join(self.tempdir, "gclient_root")
self.PatchObject(os, "getcwd", return_value=gclient_root)
lkgm_file = os.path.join(
gclient_root, "src", constants.PATH_TO_CHROME_LKGM
)
osutils.Touch(lkgm_file, makedirs=True)
osutils.WriteFile(lkgm_file, self.VERSION)
self.sdk_mock.UnMockAttr("UpdateDefaultVersion")
self.sdk.UpdateDefaultVersion()
self.assertEqual(self.sdk.GetDefaultVersion(), self.VERSION)
def testFullVersionFromFullVersion(self) -> None:
"""Test that a fully specified version is allowed."""
self.sdk_mock.UnMockAttr("GetFullVersion")
self.gs_mock.AddCmdResult(
partial_mock.ListRegex("cat .*/LATEST-%s" % self.VERSION),
stdout=self.FULL_VERSION,
)
self.assertEqual(
self.FULL_VERSION, self.sdk.GetFullVersion(self.FULL_VERSION)
)
def testFullVersionCaching(self) -> None:
"""Test full version calculation and caching."""
def RaiseException(*_args, **_kwargs) -> None:
raise Exception("boom")
self.sdk_mock.UnMockAttr("GetFullVersion")
self.gs_mock.AddCmdResult(
partial_mock.ListRegex("cat .*/LATEST-%s" % self.VERSION),
stdout=self.FULL_VERSION,
)
self.assertEqual(
self.FULL_VERSION, self.sdk.GetFullVersion(self.VERSION)
)
# Test that we access the cache on the next call, rather than checking
# GS.
self.gs_mock.AddCmdResult(
partial_mock.ListRegex("cat .*/LATEST-%s" % self.VERSION),
side_effect=RaiseException,
)
self.assertEqual(
self.FULL_VERSION, self.sdk.GetFullVersion(self.VERSION)
)
# Test that we access GS again if the board is changed.
self.sdk.board += "2"
self.gs_mock.AddCmdResult(
partial_mock.ListRegex("cat .*/LATEST-%s" % self.VERSION),
stdout=self.FULL_VERSION + "2",
)
self.assertEqual(
self.FULL_VERSION + "2", self.sdk.GetFullVersion(self.VERSION)
)
def testNoLatestVersion(self) -> None:
"""We raise an exception when there is no recent latest version."""
self.sdk_mock.UnMockAttr("GetFullVersion")
self.gs_mock.AddCmdResult(
partial_mock.ListRegex("cat .*/LATEST-*"),
stdout="",
stderr=self.CAT_ERROR,
returncode=1,
)
self.gs_mock.AddCmdResult(
partial_mock.ListRegex("ls .*%s" % self.VERSION),
stdout="",
stderr=self.LS_ERROR,
returncode=1,
)
self.assertRaises(
cros_chrome_sdk.MissingSDK, self.sdk.GetFullVersion, self.VERSION
)
def testDefaultEnvBadBoard(self) -> None:
"""Verify skips version in the environment if board doesn't match."""
os.environ[cros_chrome_sdk.SDKFetcher.SDK_VERSION_ENV] = self.VERSION
self.assertNotEqual(self.VERSION, self.sdk_mock.VERSION)
self.assertEqual(self.sdk.GetDefaultVersion(), None)
def testDefaultEnvGoodBoard(self) -> None:
"""We use the version in the environment if board matches."""
sdk_version_env = cros_chrome_sdk.SDKFetcher.SDK_VERSION_ENV
os.environ[sdk_version_env] = self.VERSION
os.environ[cros_chrome_sdk.SDKFetcher.SDK_BOARD_ENV] = self.BOARD
self.assertEqual(self.sdk.GetDefaultVersion(), self.VERSION)
class PathVerifyTest(
cros_test_lib.MockTempDirTestCase, cros_test_lib.LoggingTestCase
):
"""Tests user_rc PATH validation and warnings."""
def testPathVerifyWarnings(self) -> None:
"""Test the user rc PATH verification codepath."""
def SourceEnvironmentMock(*_args, **_kwargs):
return {
"PATH": ":".join([os.path.dirname(p) for p in abs_paths]),
}
self.PatchObject(
osutils, "SourceEnvironment", side_effect=SourceEnvironmentMock
)
file_list = (
"goma/goma_ctl.py",
"clang/clang",
"chromite/parallel_emerge",
)
abs_paths = [
os.path.join(self.tempdir, relpath) for relpath in file_list
]
for p in abs_paths:
osutils.Touch(p, makedirs=True, mode=0o755)
with cros_test_lib.LoggingCapturer() as logs:
cros_chrome_sdk.ChromeSDKCommand._VerifyGoma(None)
cros_chrome_sdk.ChromeSDKCommand._VerifyChromiteBin(None)
for msg in ["managed Goma", "default Chromite"]:
self.AssertLogsMatch(logs, msg)
class ClearOldItemsTest(
cros_test_lib.MockTempDirTestCase, cros_test_lib.LoggingTestCase
):
"""Tests SDKFetcher.ClearOldItems() behavior."""
def setUp(self) -> None:
"""Sets up a temporary symlink & tarball cache."""
self.gs_mock = self.StartPatcher(gs_unittest.GSContextMock())
self.gs_mock.SetDefaultCmdResult()
self.sdk_fetcher = cros_chrome_sdk.SDKFetcher(
self.tempdir, "", use_external_config=True
)
def testBrokenSymlinkCleared(self) -> None:
"""Adds a broken symlink and ensures it gets removed."""
osutils.Touch(os.path.join(self.tempdir, "some-file"))
valid_link_ref = self.sdk_fetcher.symlink_cache.Lookup(
"some-valid-link"
)
with valid_link_ref:
self.sdk_fetcher._UpdateCacheSymlink(
valid_link_ref, os.path.join(self.tempdir, "some-file")
)
broken_link_ref = self.sdk_fetcher.symlink_cache.Lookup(
"some-broken-link"
)
with broken_link_ref:
self.sdk_fetcher._UpdateCacheSymlink(
broken_link_ref, "/some/invalid/file"
)
# Broken symlink should exist before the ClearOldItems() call, and be
# removed after.
self.assertTrue(valid_link_ref.Exists())
self.assertTrue(broken_link_ref.Exists())
cros_chrome_sdk.SDKFetcher.ClearOldItems(self.tempdir)
self.assertTrue(valid_link_ref.Exists())
self.assertFalse(broken_link_ref.Exists())