| # 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()) |