| # 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. |
| |
| """Unit tests for the deploy_chrome script.""" |
| |
| import errno |
| import os |
| import time |
| from unittest import mock |
| |
| from chromite.cli.cros import cros_chrome_sdk_unittest |
| from chromite.lib import chrome_util |
| from chromite.lib import cros_build_lib |
| from chromite.lib import cros_test_lib |
| from chromite.lib import gs |
| from chromite.lib import osutils |
| from chromite.lib import parallel_unittest |
| from chromite.lib import partial_mock |
| from chromite.lib import remote_access |
| from chromite.lib import remote_access_unittest |
| from chromite.scripts import deploy_chrome |
| |
| |
| # pylint: disable=protected-access |
| |
| |
| _REGULAR_TO = ("--device", "monkey") |
| _TARGET_BOARD = "eve" |
| _GS_PATH = "gs://foon" |
| |
| |
| def _ParseCommandLine(argv): |
| return deploy_chrome._ParseCommandLine(["--log-level", "debug"] + argv) |
| |
| |
| class InterfaceTest(cros_test_lib.OutputTestCase): |
| """Tests the commandline interface of the script.""" |
| |
| def testGsLocalPathUnSpecified(self) -> None: |
| """Test no chrome path specified.""" |
| with self.OutputCapturer(): |
| self.assertRaises2( |
| SystemExit, |
| _ParseCommandLine, |
| list(_REGULAR_TO) + ["--board", _TARGET_BOARD], |
| check_attrs={"code": 2}, |
| ) |
| |
| def testBuildDirSpecified(self) -> None: |
| """Test case of build dir specified.""" |
| argv = list(_REGULAR_TO) + [ |
| "--board", |
| _TARGET_BOARD, |
| "--build-dir", |
| "/path/to/chrome", |
| ] |
| _ParseCommandLine(argv) |
| |
| def testBuildDirSpecifiedWithoutBoard(self) -> None: |
| """Test case of build dir specified without --board.""" |
| argv = list(_REGULAR_TO) + [ |
| "--build-dir", |
| "/path/to/chrome/out_" + _TARGET_BOARD + "/Release", |
| ] |
| options = _ParseCommandLine(argv) |
| self.assertEqual(options.board, _TARGET_BOARD) |
| |
| def testBuildDirSpecifiedWithoutBoardError(self) -> None: |
| """Test case of irregular build dir specified without --board.""" |
| argv = list(_REGULAR_TO) + ["--build-dir", "/path/to/chrome/foo/bar"] |
| self.assertParseError(argv) |
| |
| def testGsPathSpecified(self) -> None: |
| """Test case of GS path specified.""" |
| argv = list(_REGULAR_TO) + [ |
| "--board", |
| _TARGET_BOARD, |
| "--gs-path", |
| _GS_PATH, |
| ] |
| _ParseCommandLine(argv) |
| |
| def testLocalPathSpecified(self) -> None: |
| """Test case of local path specified.""" |
| argv = list(_REGULAR_TO) + [ |
| "--board", |
| _TARGET_BOARD, |
| "--local-pkg-path", |
| "/path/to/chrome", |
| ] |
| _ParseCommandLine(argv) |
| |
| def testNoBoard(self) -> None: |
| """Test no board specified.""" |
| argv = list(_REGULAR_TO) + ["--gs-path", _GS_PATH] |
| self.assertParseError(argv) |
| |
| def testNoTarget(self) -> None: |
| """Test no target specified.""" |
| argv = ["--board", _TARGET_BOARD, "--gs-path", _GS_PATH] |
| self.assertParseError(argv) |
| |
| def testLacros(self) -> None: |
| """Test basic lacros invocation.""" |
| argv = [ |
| "--lacros", |
| "--build-dir", |
| "/path/to/nowhere", |
| "--device", |
| "monkey", |
| "--board", |
| "atlas", |
| ] |
| options = _ParseCommandLine(argv) |
| self.assertTrue(options.lacros) |
| self.assertEqual(options.target_dir, deploy_chrome.LACROS_DIR) |
| |
| def testLacrosNoStrip(self) -> None: |
| """Test lacros invocation with nostrip.""" |
| argv = [ |
| "--lacros", |
| "--nostrip", |
| "--build-dir", |
| "/path/to/nowhere", |
| "--device", |
| "monkey", |
| ] |
| options = _ParseCommandLine(argv) |
| self.assertTrue(options.lacros) |
| self.assertFalse(options.dostrip) |
| self.assertEqual(options.target_dir, deploy_chrome.LACROS_DIR) |
| |
| def testLacrosWithLacrosOnly(self) -> None: |
| """Test lacros invocation with skip restarting ui.""" |
| argv = [ |
| "--lacros", |
| "--build-dir", |
| "/path/to/nowhere", |
| "--device", |
| "monkey", |
| "--board", |
| "atlas", |
| "--skip-restart-ui", |
| ] |
| options = _ParseCommandLine(argv) |
| self.assertTrue(options.lacros) |
| self.assertEqual(options.target_dir, deploy_chrome.LACROS_DIR) |
| self.assertTrue(options.skip_restart_ui) |
| |
| def assertParseError(self, argv) -> None: |
| with self.OutputCapturer(): |
| self.assertRaises2( |
| SystemExit, _ParseCommandLine, argv, check_attrs={"code": 2} |
| ) |
| |
| def testMountOptionSetsTargetDir(self) -> None: |
| argv = list(_REGULAR_TO) + [ |
| "--board", |
| _TARGET_BOARD, |
| "--gs-path", |
| _GS_PATH, |
| "--mount", |
| ] |
| options = _ParseCommandLine(argv) |
| self.assertIsNot(options.target_dir, None) |
| |
| def testMountOptionSetsMountDir(self) -> None: |
| argv = list(_REGULAR_TO) + [ |
| "--board", |
| _TARGET_BOARD, |
| "--gs-path", |
| _GS_PATH, |
| "--mount", |
| ] |
| options = _ParseCommandLine(argv) |
| self.assertIsNot(options.mount_dir, None) |
| |
| def testMountOptionDoesNotOverrideTargetDir(self) -> None: |
| argv = list(_REGULAR_TO) + [ |
| "--board", |
| _TARGET_BOARD, |
| "--gs-path", |
| _GS_PATH, |
| "--mount", |
| "--target-dir", |
| "/foo/bar/cow", |
| ] |
| options = _ParseCommandLine(argv) |
| self.assertEqual(options.target_dir, "/foo/bar/cow") |
| |
| def testMountOptionDoesNotOverrideMountDir(self) -> None: |
| argv = list(_REGULAR_TO) + [ |
| "--board", |
| _TARGET_BOARD, |
| "--gs-path", |
| _GS_PATH, |
| "--mount", |
| "--mount-dir", |
| "/foo/bar/cow", |
| ] |
| options = _ParseCommandLine(argv) |
| self.assertEqual(options.mount_dir, "/foo/bar/cow") |
| |
| def testSshIdentityOptionSetsOption(self) -> None: |
| argv = list(_REGULAR_TO) + [ |
| "--board", |
| _TARGET_BOARD, |
| "--private-key", |
| "/foo/bar/key", |
| "--build-dir", |
| "/path/to/nowhere", |
| ] |
| options = _ParseCommandLine(argv) |
| self.assertEqual(options.private_key, "/foo/bar/key") |
| |
| def testUnlockPassword(self) -> None: |
| argv = list(_REGULAR_TO) + [ |
| "--board", |
| _TARGET_BOARD, |
| "--unlock-password", |
| "letmein", |
| "--build-dir", |
| "/path/to/nowhere", |
| ] |
| options = _ParseCommandLine(argv) |
| self.assertEqual(options.unlock_password, "letmein") |
| |
| |
| class DeployChromeMock(partial_mock.PartialMock): |
| """Deploy Chrome Mock Class.""" |
| |
| TARGET = "chromite.scripts.deploy_chrome.DeployChrome" |
| ATTRS = ( |
| "_ChromeFileInUse", |
| "_DisableRootfsVerification", |
| "_ShouldUseCompressedAsh", |
| ) |
| |
| def __init__(self) -> None: |
| partial_mock.PartialMock.__init__(self) |
| self.remote_device_mock = remote_access_unittest.RemoteDeviceMock() |
| # Target starts off as having rootfs verification enabled. |
| self.rsh_mock = remote_access_unittest.RemoteShMock() |
| self.rsh_mock.SetDefaultCmdResult(0) |
| self.MockMountCmd(1) |
| self.rsh_mock.AddCmdResult( |
| ["lsof", f"{deploy_chrome._CHROME_DIR}/chrome"], 1 |
| ) |
| |
| self.rsh_mock.AddCmdResult( |
| partial_mock.ListRegex("status ui"), |
| stdout="ui start/running, process 123", |
| ) |
| |
| def MockMountCmd(self, returnvalue) -> None: |
| self.rsh_mock.AddCmdResult(deploy_chrome.MOUNT_RW_COMMAND, returnvalue) |
| |
| def _DisableRootfsVerification(self, inst) -> None: |
| with mock.patch.object(time, "sleep"): |
| self.backup["_DisableRootfsVerification"](inst) |
| |
| def PreStart(self) -> None: |
| self.remote_device_mock.start() |
| self.rsh_mock.start() |
| |
| def PreStop(self) -> None: |
| self.rsh_mock.stop() |
| self.remote_device_mock.stop() |
| |
| def _ChromeFileInUse(self, _inst): |
| # Fully stub out for now. Can be replaced if further testing is added. |
| return False |
| |
| def _ShouldUseCompressedAsh(self, inst) -> None: |
| with mock.patch.object( |
| remote_access.RemoteDevice, "IfFileExists" |
| ) as exists_mock: |
| exists_mock.return_value = False |
| self.backup["_ShouldUseCompressedAsh"](inst) |
| |
| |
| class DeployTest(cros_test_lib.MockTempDirTestCase): |
| """Setup a deploy object with a GS-path for use in tests.""" |
| |
| def _GetDeployChrome(self, args): |
| options = _ParseCommandLine(args) |
| return deploy_chrome.DeployChrome( |
| options, self.tempdir, os.path.join(self.tempdir, "staging") |
| ) |
| |
| def setUp(self) -> None: |
| self.deploy_mock = self.StartPatcher(DeployChromeMock()) |
| self.deploy = self._GetDeployChrome( |
| list(_REGULAR_TO) |
| + [ |
| "--board", |
| _TARGET_BOARD, |
| "--gs-path", |
| _GS_PATH, |
| "--force", |
| "--mount", |
| ] |
| ) |
| self.remote_reboot_mock = self.PatchObject( |
| remote_access.RemoteAccess, "RemoteReboot", return_value=True |
| ) |
| |
| def tearDown(self) -> None: |
| self.deploy.Cleanup() |
| |
| |
| class TestCheckIfBoardMatches(DeployTest): |
| """Testing checking whether the DUT board matches the target board.""" |
| |
| def testMatchedBoard(self) -> None: |
| """Test the case where the DUT board matches the target board.""" |
| self.PatchObject(remote_access.ChromiumOSDevice, "board", _TARGET_BOARD) |
| self.assertTrue(self.deploy.options.force) |
| self.deploy._CheckBoard() |
| self.deploy.options.force = False |
| self.deploy._CheckBoard() |
| |
| def testMismatchedBoard(self) -> None: |
| """Test the case where the DUT board does not match the target board.""" |
| self.PatchObject(remote_access.ChromiumOSDevice, "board", "cedar") |
| self.assertTrue(self.deploy.options.force) |
| self.deploy._CheckBoard() |
| self.deploy.options.force = False |
| self.PatchObject(cros_build_lib, "BooleanPrompt", return_value=True) |
| self.deploy._CheckBoard() |
| self.PatchObject(cros_build_lib, "BooleanPrompt", return_value=False) |
| self.assertRaises(deploy_chrome.DeployFailure, self.deploy._CheckBoard) |
| |
| |
| class TestDisableRootfsVerification(DeployTest): |
| """Testing disabling of rootfs verification and RO mode.""" |
| |
| def testDisableRootfsVerificationSuccess(self) -> None: |
| """Test the working case, disabling rootfs verification.""" |
| self.deploy_mock.MockMountCmd(0) |
| self.deploy._DisableRootfsVerification() |
| self.assertFalse(self.deploy._root_dir_is_still_readonly.is_set()) |
| |
| def testDisableRootfsVerificationFailure(self) -> None: |
| """Test failure to disable rootfs verification.""" |
| |
| # pylint: disable=unused-argument |
| def RaiseRunCommandError(timeout_sec=None) -> None: |
| raise cros_build_lib.RunCommandError("Mock RunCommandError") |
| |
| self.remote_reboot_mock.side_effect = RaiseRunCommandError |
| self.assertRaises( |
| cros_build_lib.RunCommandError, |
| self.deploy._DisableRootfsVerification, |
| ) |
| self.remote_reboot_mock.side_effect = None |
| self.assertFalse(self.deploy._root_dir_is_still_readonly.is_set()) |
| |
| |
| class TestDeployCompressedAsh(DeployTest): |
| """Testing deployments with --ash-compressed passed.""" |
| |
| def _GetDeployChrome(self, args): |
| args.append("--compressed-ash") |
| return super(TestDeployCompressedAsh, self)._GetDeployChrome(args) |
| |
| def testUnmountSuccess(self) -> None: |
| """Test case for a successful 'umount' call.""" |
| self.deploy._KillAshChromeIfNeeded() |
| |
| def testUnmountFailure(self) -> None: |
| """Test case for a failed 'umount' call.""" |
| self.deploy_mock.rsh_mock.AddCmdResult( |
| ["umount", deploy_chrome.RAW_ASH_PATH], |
| returncode=32, |
| stderr="umount failure", |
| ) |
| self.assertRaises( |
| deploy_chrome.DeployFailure, self.deploy._KillAshChromeIfNeeded |
| ) |
| |
| |
| class TestMount(DeployTest): |
| """Testing mount success and failure.""" |
| |
| def testSuccess(self) -> None: |
| """Test case where we are able to mount as writable.""" |
| self.assertFalse(self.deploy._root_dir_is_still_readonly.is_set()) |
| self.deploy_mock.MockMountCmd(0) |
| self.deploy._MountRootfsAsWritable() |
| self.assertFalse(self.deploy._root_dir_is_still_readonly.is_set()) |
| |
| def testMountError(self) -> None: |
| """Test that mount failure doesn't raise an exception by default.""" |
| self.assertFalse(self.deploy._root_dir_is_still_readonly.is_set()) |
| self.PatchObject( |
| remote_access.ChromiumOSDevice, |
| "IsDirWritable", |
| return_value=False, |
| autospec=True, |
| ) |
| self.deploy._MountRootfsAsWritable() |
| self.assertTrue(self.deploy._root_dir_is_still_readonly.is_set()) |
| |
| def testMountRwFailure(self) -> None: |
| """Test that mount failure raises an exception if check=True.""" |
| self.assertRaises( |
| cros_build_lib.RunCommandError, |
| self.deploy._MountRootfsAsWritable, |
| check=True, |
| ) |
| self.assertFalse(self.deploy._root_dir_is_still_readonly.is_set()) |
| |
| def testMountTempDir(self) -> None: |
| """Test that mount succeeds if target dir is writable.""" |
| self.assertFalse(self.deploy._root_dir_is_still_readonly.is_set()) |
| self.PatchObject( |
| remote_access.ChromiumOSDevice, |
| "IsDirWritable", |
| return_value=True, |
| autospec=True, |
| ) |
| self.deploy._MountRootfsAsWritable() |
| self.assertFalse(self.deploy._root_dir_is_still_readonly.is_set()) |
| |
| |
| class TestMountTarget(DeployTest): |
| """Testing mount and umount command handling.""" |
| |
| def testMountTargetUmountFailure(self) -> None: |
| """Test error being thrown if umount fails. |
| |
| Test that 'lsof' is run on mount-dir and 'mount -rbind' command is not |
| run if 'umount' cmd fails. |
| """ |
| mount_dir = self.deploy.options.mount_dir |
| target_dir = self.deploy.options.target_dir |
| self.deploy_mock.rsh_mock.AddCmdResult( |
| deploy_chrome._UMOUNT_DIR_IF_MOUNTPOINT_CMD % {"dir": mount_dir}, |
| returncode=errno.EBUSY, |
| stderr="Target is Busy", |
| ) |
| self.deploy_mock.rsh_mock.AddCmdResult( |
| ["lsof", mount_dir], |
| returncode=0, |
| stdout="process " + mount_dir, |
| ) |
| # Check for RunCommandError being thrown. |
| self.assertRaises( |
| cros_build_lib.RunCommandError, self.deploy._MountTarget |
| ) |
| # Check for the 'mount -rbind' command not run. |
| self.deploy_mock.rsh_mock.assertCommandContains( |
| ["mount", "--rbind", target_dir, mount_dir], |
| expected=False, |
| ) |
| # Check for lsof command being called. |
| self.deploy_mock.rsh_mock.assertCommandContains(["lsof", mount_dir]) |
| |
| |
| class TestUiJobStarted(DeployTest): |
| """Test detection of a running 'ui' job.""" |
| |
| def MockStatusUiCmd(self, **kwargs) -> None: |
| self.deploy_mock.rsh_mock.AddCmdResult( |
| partial_mock.ListRegex("status ui"), **kwargs |
| ) |
| |
| def testUiJobStartedFalse(self) -> None: |
| """Correct results with a stopped job.""" |
| self.MockStatusUiCmd(stdout="ui stop/waiting") |
| self.assertFalse(self.deploy._CheckUiJobStarted()) |
| |
| def testNoUiJob(self) -> None: |
| """Correct results when the job doesn't exist.""" |
| self.MockStatusUiCmd(stderr="start: Unknown job: ui", returncode=1) |
| self.assertFalse(self.deploy._CheckUiJobStarted()) |
| |
| def testCheckRootfsWriteableTrue(self) -> None: |
| """Correct results with a running job.""" |
| self.MockStatusUiCmd(stdout="ui start/running, process 297") |
| self.assertTrue(self.deploy._CheckUiJobStarted()) |
| |
| |
| class TestUnlockPassword(DeployTest): |
| """Test that unlock password is sent.""" |
| |
| def _GetDeployChrome(self, args): |
| args.append("--unlock-password=letmein") |
| return super(TestUnlockPassword, self)._GetDeployChrome(args) |
| |
| def testUnlock(self) -> None: |
| """Test that unlock password is sent.""" |
| self.deploy._stopped_ui = True |
| |
| # Update LAST_LOGIN_COMMAND to return a different value. |
| def SideEffect(*args, **kwargs) -> None: |
| # pylint: disable=unused-argument |
| self.deploy_mock.rsh_mock.AddCmdResult( |
| deploy_chrome.LAST_LOGIN_COMMAND, stdout="2.0" |
| ) |
| |
| # LAST_LOGIN_COMMAND returns 1.0 the first time it is called, then 2.0. |
| self.deploy_mock.rsh_mock.AddCmdResult( |
| deploy_chrome.LAST_LOGIN_COMMAND, |
| stdout="1.0", |
| side_effect=SideEffect, |
| ) |
| with mock.patch.object(remote_access.ChromiumOSDevice, "CopyToDevice"): |
| with mock.patch.object(time, "sleep"): |
| self.deploy._Deploy() |
| # Ensure unlock command was called. |
| self.deploy_mock.rsh_mock.assertCommandContains( |
| deploy_chrome.UNLOCK_PASSWORD_COMMAND % "letmein" |
| ) |
| |
| |
| class StagingTest(cros_test_lib.MockTempDirTestCase): |
| """Test user-mode and ebuild-mode staging functionality.""" |
| |
| def setUp(self) -> None: |
| self.staging_dir = os.path.join(self.tempdir, "staging") |
| osutils.SafeMakedirs(self.staging_dir) |
| self.staging_tarball_path = os.path.join( |
| self.tempdir, deploy_chrome._CHROME_DIR_STAGING_TARBALL_ZSTD |
| ) |
| self.build_dir = os.path.join(self.tempdir, "build_dir") |
| self.common_flags = [ |
| "--board", |
| _TARGET_BOARD, |
| "--build-dir", |
| self.build_dir, |
| "--staging-only", |
| "--cache-dir", |
| str(self.tempdir), |
| ] |
| self.sdk_mock = self.StartPatcher( |
| cros_chrome_sdk_unittest.SDKFetcherMock() |
| ) |
| self.PatchObject( |
| osutils, |
| "SourceEnvironment", |
| autospec=True, |
| return_value={"STRIP": "x86_64-cros-linux-gnu-strip"}, |
| ) |
| |
| def testSingleFileDeployFailure(self) -> None: |
| """Default staging enforces that mandatory files are copied""" |
| options = _ParseCommandLine(self.common_flags) |
| osutils.Touch(os.path.join(self.build_dir, "chrome"), makedirs=True) |
| self.assertRaises( |
| chrome_util.MissingPathError, |
| deploy_chrome._PrepareStagingDir, |
| options, |
| self.tempdir, |
| self.staging_dir, |
| chrome_util._COPY_PATHS_CHROME, |
| ) |
| |
| def testSloppyDeployFailure(self) -> None: |
| """Sloppy staging enforces that at least one file is copied.""" |
| options = _ParseCommandLine(self.common_flags + ["--sloppy"]) |
| self.assertRaises( |
| chrome_util.MissingPathError, |
| deploy_chrome._PrepareStagingDir, |
| options, |
| self.tempdir, |
| self.staging_dir, |
| chrome_util._COPY_PATHS_CHROME, |
| ) |
| |
| def testSloppyDeploySuccess(self) -> None: |
| """Sloppy staging - stage one file.""" |
| options = _ParseCommandLine(self.common_flags + ["--sloppy"]) |
| osutils.Touch(os.path.join(self.build_dir, "chrome"), makedirs=True) |
| deploy_chrome._PrepareStagingDir( |
| options, |
| self.tempdir, |
| self.staging_dir, |
| chrome_util._COPY_PATHS_CHROME, |
| ) |
| |
| def testSloppyDeploySuccessLacros(self) -> None: |
| """Ensure the squashfs mechanism with --compressed-ash doesn't throw.""" |
| options = _ParseCommandLine( |
| self.common_flags + ["--sloppy", "--compressed-ash"] |
| ) |
| osutils.Touch(os.path.join(self.build_dir, "chrome"), makedirs=True) |
| deploy_chrome._PrepareStagingDir( |
| options, |
| self.tempdir, |
| self.staging_dir, |
| chrome_util._COPY_PATHS_CHROME, |
| ) |
| |
| @cros_test_lib.pytestmark_network_test |
| def testUploadStagingDir(self) -> None: |
| """Upload staging directory.""" |
| mockGsCopy = self.PatchObject(gs.GSContext, "Copy") |
| staging_upload = "gs://some-path" |
| options = _ParseCommandLine( |
| self.common_flags + ["--staging-upload", staging_upload] |
| ) |
| osutils.Touch(os.path.join(self.build_dir, "chrome"), makedirs=True) |
| deploy_chrome._UploadStagingDir(options, self.tempdir, self.staging_dir) |
| self.assertEqual( |
| mockGsCopy.call_args_list, |
| [ |
| mock.call(self.staging_tarball_path, staging_upload, acl=""), |
| ], |
| ) |
| |
| @cros_test_lib.pytestmark_network_test |
| def testUploadStagingPublicReadACL(self) -> None: |
| """Upload staging directory with public-read ACL.""" |
| mockGsCopy = self.PatchObject(gs.GSContext, "Copy") |
| staging_upload = "gs://some-path" |
| options = _ParseCommandLine( |
| self.common_flags |
| + ["--staging-upload", staging_upload, "--public-read"] |
| ) |
| osutils.Touch(os.path.join(self.build_dir, "chrome"), makedirs=True) |
| deploy_chrome._UploadStagingDir(options, self.tempdir, self.staging_dir) |
| self.assertEqual( |
| mockGsCopy.call_args_list, |
| [ |
| mock.call( |
| self.staging_tarball_path, staging_upload, acl="public-read" |
| ), |
| ], |
| ) |
| |
| |
| class DeployTestBuildDir(cros_test_lib.MockTempDirTestCase): |
| """Set up a deploy object with a build-dir for use in deployment tests""" |
| |
| def _GetDeployChrome(self, args): |
| options = _ParseCommandLine(args) |
| return deploy_chrome.DeployChrome( |
| options, self.tempdir, os.path.join(self.tempdir, "staging") |
| ) |
| |
| def setUp(self) -> None: |
| self.staging_dir = os.path.join(self.tempdir, "staging") |
| self.build_dir = os.path.join(self.tempdir, "build_dir") |
| self.deploy_mock = self.StartPatcher(DeployChromeMock()) |
| self.deploy = self._GetDeployChrome( |
| list(_REGULAR_TO) |
| + [ |
| "--board", |
| _TARGET_BOARD, |
| "--build-dir", |
| self.build_dir, |
| "--staging-only", |
| "--cache-dir", |
| str(self.tempdir), |
| "--sloppy", |
| ] |
| ) |
| |
| def getCopyPath(self, source_path): |
| """Return a chrome_util.Path or None if not present.""" |
| paths = [p for p in self.deploy.copy_paths if p.src == source_path] |
| return paths[0] if paths else None |
| |
| |
| class TestDeploymentType(DeployTestBuildDir): |
| """Test detection of deployment type using build dir.""" |
| |
| def testAppShellDetection(self) -> None: |
| """Check for an app_shell deployment""" |
| osutils.Touch( |
| os.path.join(self.deploy.options.build_dir, "app_shell"), |
| makedirs=True, |
| ) |
| self.deploy._CheckDeployType() |
| self.assertTrue(self.getCopyPath("app_shell")) |
| self.assertFalse(self.getCopyPath("chrome")) |
| |
| def testChromeAndAppShellDetection(self) -> None: |
| """Check for a chrome deployment when app_shell also exists.""" |
| osutils.Touch( |
| os.path.join(self.deploy.options.build_dir, "chrome"), makedirs=True |
| ) |
| osutils.Touch( |
| os.path.join(self.deploy.options.build_dir, "app_shell"), |
| makedirs=True, |
| ) |
| self.deploy._CheckDeployType() |
| self.assertTrue(self.getCopyPath("chrome")) |
| self.assertFalse(self.getCopyPath("app_shell")) |
| |
| def testChromeDetection(self) -> None: |
| """Check for a regular chrome deployment""" |
| osutils.Touch( |
| os.path.join(self.deploy.options.build_dir, "chrome"), makedirs=True |
| ) |
| self.deploy._CheckDeployType() |
| self.assertTrue(self.getCopyPath("chrome")) |
| self.assertFalse(self.getCopyPath("app_shell")) |
| |
| |
| class TestDeployTestBinaries(cros_test_lib.RunCommandTempDirTestCase): |
| """Tests _DeployTestBinaries().""" |
| |
| def setUp(self) -> None: |
| options = _ParseCommandLine( |
| list(_REGULAR_TO) |
| + [ |
| "--board", |
| _TARGET_BOARD, |
| "--force", |
| "--mount", |
| "--build-dir", |
| os.path.join(self.tempdir, "build_dir"), |
| "--nostrip", |
| ] |
| ) |
| self.remote_exists_mock = self.PatchObject( |
| remote_access.RemoteDevice, "IfFileExists", return_value=False |
| ) |
| self.deploy = deploy_chrome.DeployChrome( |
| options, self.tempdir, os.path.join(self.tempdir, "staging") |
| ) |
| |
| def _SimulateBinaries(self): |
| # Ensure the staging dir contains the right binaries to copy over. |
| test_binaries = [ |
| "run_a_tests", |
| "run_b_tests", |
| "run_c_tests", |
| ] |
| # Simulate having the binaries both on the device and in our local build |
| # dir. |
| self.rc.AddCmdResult( |
| partial_mock.ListRegex(" ".join(deploy_chrome._FIND_TEST_BIN_CMD)), |
| stdout="\n".join(test_binaries), |
| ) |
| for binary in test_binaries: |
| osutils.Touch( |
| os.path.join(self.deploy.options.build_dir, binary), |
| makedirs=True, |
| mode=0o700, |
| ) |
| return test_binaries |
| |
| def _AssertBinariesInStagingDir(self, test_binaries) -> None: |
| # Ensure the binaries were placed in the staging dir used to copy them |
| # over. |
| staging_dir = os.path.join( |
| self.tempdir, os.path.basename(deploy_chrome._CHROME_TEST_BIN_DIR) |
| ) |
| for binary in test_binaries: |
| self.assertIn(binary, os.listdir(staging_dir)) |
| |
| def testFindError(self) -> None: |
| """Ensure an error is thrown if we can't inspect the device.""" |
| self.rc.AddCmdResult( |
| partial_mock.ListRegex(" ".join(deploy_chrome._FIND_TEST_BIN_CMD)), |
| returncode=1, |
| ) |
| self.assertRaises( |
| deploy_chrome.DeployFailure, self.deploy._DeployTestBinaries |
| ) |
| |
| def testSuccess(self) -> None: |
| """Ensure that the happy path succeeds as expected.""" |
| test_binaries = self._SimulateBinaries() |
| self.deploy._DeployTestBinaries() |
| self._AssertBinariesInStagingDir(test_binaries) |
| |
| def testRetrySuccess(self) -> None: |
| """Ensure that a transient exception still results in success.""" |
| |
| # Raises a RunCommandError on its first invocation, but passes on |
| # subsequent calls. |
| def SideEffect(*args, **kwargs) -> None: |
| # pylint: disable=unused-argument |
| if not SideEffect.called: |
| SideEffect.called = True |
| raise cros_build_lib.RunCommandError("fail") |
| |
| SideEffect.called = False |
| |
| test_binaries = self._SimulateBinaries() |
| with mock.patch.object( |
| remote_access.ChromiumOSDevice, |
| "CopyToDevice", |
| side_effect=SideEffect, |
| ) as copy_mock: |
| self.deploy._DeployTestBinaries() |
| self.assertEqual(copy_mock.call_count, 2) |
| self._AssertBinariesInStagingDir(test_binaries) |
| |
| def testRetryFailure(self) -> None: |
| """Ensure that consistent exceptions result in failure.""" |
| self._SimulateBinaries() |
| with self.assertRaises(cros_build_lib.RunCommandError): |
| with mock.patch.object( |
| remote_access.ChromiumOSDevice, |
| "CopyToDevice", |
| side_effect=cros_build_lib.RunCommandError("fail"), |
| ): |
| self.deploy._DeployTestBinaries() |
| |
| |
| class LacrosPerformTest(cros_test_lib.RunCommandTempDirTestCase): |
| """Line coverage for Perform() method with --lacros option.""" |
| |
| def setUp(self) -> None: |
| self.deploy = None |
| self._ran_start_command = False |
| self.StartPatcher(parallel_unittest.ParallelMock()) |
| |
| def start_ui_side_effect(*args, **kwargs) -> None: |
| # pylint: disable=unused-argument |
| self._ran_start_command = True |
| |
| self.rc.AddCmdResult( |
| partial_mock.ListRegex("start ui"), side_effect=start_ui_side_effect |
| ) |
| |
| def prepareDeploy(self, options=None) -> None: |
| if not options: |
| options = _ParseCommandLine( |
| [ |
| "--lacros", |
| "--nostrip", |
| "--build-dir", |
| "/path/to/nowhere", |
| "--device", |
| "monkey", |
| ] |
| ) |
| self.deploy = deploy_chrome.DeployChrome( |
| options, self.tempdir, os.path.join(self.tempdir, "staging") |
| ) |
| |
| # These methods being mocked are all side effects expected for a |
| # --lacros deploy. |
| self.deploy._EnsureTargetDir = mock.Mock() |
| self.deploy._GetDeviceInfo = mock.Mock() |
| self.deploy._CheckConnection = mock.Mock() |
| self.deploy._MountRootfsAsWritable = mock.Mock() |
| self.deploy._PrepareStagingDir = mock.Mock() |
| self.deploy._CheckDeviceFreeSpace = mock.Mock() |
| self.deploy._KillAshChromeIfNeeded = mock.Mock() |
| |
| def testLacros(self) -> None: |
| """When no flag is set, Ash should be restarted.""" |
| self.prepareDeploy() |
| |
| self.deploy.Perform() |
| self.deploy._KillAshChromeIfNeeded.assert_called() |
| self.assertTrue(self._ran_start_command) |
| |
| def testSkipRestartUi(self) -> None: |
| """When skip_restart_ui is enabled, Ash should not be restarted.""" |
| self.prepareDeploy( |
| _ParseCommandLine( |
| [ |
| "--lacros", |
| "--nostrip", |
| "--build-dir", |
| "/path/to/nowhere", |
| "--device", |
| "monkey", |
| "--skip-restart-ui", |
| ] |
| ) |
| ) |
| |
| self.deploy.Perform() |
| self.deploy._KillAshChromeIfNeeded.assert_not_called() |
| self.assertFalse(self._ran_start_command) |