blob: 19ddf035ed8d4235ed17034fa66018d20d83d63a [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.
"""Unittests for the artifact stages."""
import os
import sys
from unittest import mock
from chromite.cbuildbot import cbuildbot_unittest
from chromite.cbuildbot import commands
from chromite.cbuildbot.stages import artifact_stages
from chromite.cbuildbot.stages import build_stages_unittest
from chromite.cbuildbot.stages import generic_stages_unittest
from chromite.lib import constants
from chromite.lib import cros_build_lib
from chromite.lib import cros_test_lib
from chromite.lib import failures_lib
from chromite.lib import osutils
from chromite.lib import parallel
from chromite.lib import parallel_unittest
from chromite.lib import path_util
from chromite.lib import results_lib
from chromite.lib.buildstore import FakeBuildStore
# pylint: disable=too-many-ancestors
class ArchiveStageTest(
generic_stages_unittest.AbstractStageTestCase,
cbuildbot_unittest.SimpleBuilderTestCase,
):
"""Exercise ArchiveStage functionality."""
# pylint: disable=protected-access
RELEASE_TAG = ""
VERSION = "3333.1.0"
def _PatchDependencies(self) -> None:
"""Patch dependencies of ArchiveStage.PerformStage()."""
to_patch = [
(parallel, "RunParallelSteps"),
(commands, "PushImages"),
(commands, "UploadArchivedFile"),
]
self.AutoPatch(to_patch)
def setUp(self) -> None:
self._PatchDependencies()
self._Prepare()
self.buildstore = FakeBuildStore()
# Our API here is not great when it comes to kwargs passing.
def _Prepare(
self, bot_id=None, **kwargs
) -> None: # pylint: disable=arguments-differ
extra_config = {"upload_symbols": True, "push_image": True}
super()._Prepare(bot_id, extra_config=extra_config, **kwargs)
def ConstructStage(self):
self._run.GetArchive().SetupArchivePath()
return artifact_stages.ArchiveStage(
self._run, self.buildstore, self._current_board
)
def testArchive(self) -> None:
"""Simple did-it-run test."""
# TODO(davidjames): Test the individual archive steps as well.
self.RunStage()
# TODO(build): This test is not actually testing anything real. It confirms
# that PushImages is not called, but the mock for RunParallelSteps already
# prevents PushImages from being called, regardless of whether this is a
# trybot flow.
def testNoPushImagesForRemoteTrybot(self) -> None:
"""Test that remote trybot overrides work to disable push images."""
self._Prepare(
cmd_args=[
"--remote-trybot",
"-r",
self.build_root,
"--buildnumber=1234",
"eve-release",
]
)
self.RunStage()
# pylint: disable=no-member
self.assertEqual(commands.PushImages.call_count, 0)
def ConstructStageForArchiveStep(self):
"""Stage construction for archive steps."""
stage = self.ConstructStage()
self.PatchObject(stage._upload_queue, "put", autospec=True)
self.PatchObject(
path_util, "ToChrootPath", return_value="", autospec=True
)
return stage
class UploadPrebuiltsStageTest(
generic_stages_unittest.RunCommandAbstractStageTestCase,
cbuildbot_unittest.SimpleBuilderTestCase,
):
"""Tests for the UploadPrebuilts stage."""
cmd = "upload_prebuilts"
RELEASE_TAG = ""
# Our API here is not great when it comes to kwargs passing.
def _Prepare(
self, bot_id=None, **kwargs
) -> None: # pylint: disable=arguments-differ
super()._Prepare(bot_id, **kwargs)
self.cmd = os.path.join(
self.build_root, constants.CHROMITE_BIN_SUBDIR, "upload_prebuilts"
)
self._run.options.prebuilts = True
self.buildstore = FakeBuildStore()
def ConstructStage(self):
return artifact_stages.UploadPrebuiltsStage(
self._run, self.buildstore, self._run.config.boards[-1]
)
def _VerifyBoardMap(
self, bot_id, count, board_map, public_args=None, private_args=None
) -> None:
"""Verify that the prebuilts are uploaded for the specified bot.
Args:
bot_id: Bot to upload prebuilts for.
count: Number of assert checks that should be performed.
board_map: Map from slave boards to whether the bot is public.
public_args: List of extra arguments for public boards.
private_args: List of extra arguments for private boards.
"""
self._Prepare(bot_id)
self.RunStage()
public_prefix = [self.cmd] + (public_args or [])
private_prefix = [self.cmd] + (private_args or [])
for board, public in board_map.items():
if public or public_args:
public_cmd = public_prefix + ["--slave-board", board]
self.assertCommandContains(public_cmd, expected=public)
count -= 1
private_cmd = private_prefix + ["--slave-board", board, "--private"]
self.assertCommandContains(private_cmd, expected=not public)
count -= 1
if board_map:
self.assertCommandContains(
[self.cmd, "--set-version", self._run.GetVersion()],
)
count -= 1
self.assertEqual(
count,
0,
"Number of asserts performed does not match (%d remaining)" % count,
)
def testFullPrebuiltsUpload(self) -> None:
"""Test uploading of full builder prebuilts."""
self._VerifyBoardMap("amd64-generic-full", 0, {})
self.assertCommandContains([self.cmd, "--git-sync"])
def testIncorrectCount(self) -> None:
"""Test that _VerifyBoardMap asserts when the count is wrong."""
self.assertRaises(
AssertionError, self._VerifyBoardMap, "amd64-generic-full", 1, {}
)
class DebugSymbolsStageTest(
generic_stages_unittest.AbstractStageTestCase,
cbuildbot_unittest.SimpleBuilderTestCase,
):
"""Test DebugSymbolsStage"""
# pylint: disable=protected-access
def setUp(self) -> None:
self.CreateMockOverlay("amd64-generic")
self.StartPatcher(generic_stages_unittest.ArchivingStageMixinMock())
self.gen_mock = self.PatchObject(commands, "GenerateBreakpadSymbols")
self.gen_android_mock = self.PatchObject(
commands, "GenerateAndroidBreakpadSymbols"
)
self.upload_mock = self.PatchObject(commands, "UploadSymbols")
self.upload_artifact_mock = self.PatchObject(
artifact_stages.DebugSymbolsStage, "UploadArtifact"
)
self.tar_mock = self.PatchObject(commands, "GenerateDebugTarball")
self.rc_mock = self.StartPatcher(cros_test_lib.RunCommandMock())
self.rc_mock.SetDefaultCmdResult(stdout="")
self.stage = None
# Our API here is not great when it comes to kwargs passing.
# pylint: disable=arguments-differ
def _Prepare(self, extra_config=None, **kwargs) -> None:
"""Prepare this stage for testing."""
if extra_config is None:
extra_config = {
"archive_build_debug": True,
"upload_symbols": True,
}
super()._Prepare(extra_config=extra_config, **kwargs)
self._run.attrs.release_tag = self.VERSION
self.buildstore = FakeBuildStore()
# pylint: enable=arguments-differ
def ConstructStage(self):
"""Create a DebugSymbolsStage instance for testing"""
self._run.GetArchive().SetupArchivePath()
return artifact_stages.DebugSymbolsStage(
self._run, self.buildstore, self._current_board
)
def assertBoardAttrEqual(self, attr, expected_value) -> None:
"""Assert the value of a board run |attr| against |expected_value|."""
value = self.stage.board_runattrs.GetParallel(attr)
self.assertEqual(expected_value, value)
def _TestPerformStage(
self, extra_config=None, create_android_symbols_archive=False
):
"""Run PerformStage for the stage with the given extra config."""
self._Prepare(extra_config=extra_config)
self.tar_mock.side_effect = "/my/tar/ball"
self.stage = self.ConstructStage()
if create_android_symbols_archive:
symbols_file = os.path.join(
self.stage.archive_path, constants.ANDROID_SYMBOLS_FILE
)
osutils.Touch(symbols_file)
try:
self.stage.PerformStage()
except Exception:
return self.stage._HandleStageException(sys.exc_info())
def testPerformStageWithSymbols(self) -> None:
"""Smoke test for an PerformStage when debugging is enabled"""
self._TestPerformStage()
self.assertEqual(self.gen_mock.call_count, 1)
self.assertEqual(self.gen_android_mock.call_count, 0)
self.assertEqual(self.upload_mock.call_count, 1)
self.assertEqual(self.tar_mock.call_count, 2)
self.assertEqual(self.upload_artifact_mock.call_count, 2)
self.assertBoardAttrEqual("breakpad_symbols_generated", True)
self.assertBoardAttrEqual("debug_tarball_generated", True)
def testPerformStageWithAndroidSymbols(self) -> None:
"""Smoke test for an PerformStage when Android symbols are available"""
self._TestPerformStage(create_android_symbols_archive=True)
self.assertEqual(self.gen_mock.call_count, 1)
self.assertEqual(self.gen_android_mock.call_count, 1)
self.assertEqual(self.upload_mock.call_count, 1)
self.assertEqual(self.tar_mock.call_count, 2)
self.assertEqual(self.upload_artifact_mock.call_count, 2)
self.assertBoardAttrEqual("breakpad_symbols_generated", True)
self.assertBoardAttrEqual("debug_tarball_generated", True)
def testPerformStageNoSymbols(self) -> None:
"""Smoke test for an PerformStage when debugging is disabled"""
extra_config = {
"archive_build_debug": False,
"upload_symbols": False,
}
result = self._TestPerformStage(extra_config)
self.assertIsNone(result)
self.assertEqual(self.gen_mock.call_count, 1)
self.assertEqual(self.gen_android_mock.call_count, 0)
self.assertEqual(self.upload_mock.call_count, 0)
self.assertEqual(self.tar_mock.call_count, 2)
self.assertEqual(self.upload_artifact_mock.call_count, 2)
self.assertBoardAttrEqual("breakpad_symbols_generated", True)
self.assertBoardAttrEqual("debug_tarball_generated", True)
def testGenerateCrashStillNotifies(self) -> None:
"""Crashes in symbol generation should still notify external events."""
class TestError(Exception):
"""Unique test exception"""
self.gen_mock.side_effect = TestError("mew")
result = self._TestPerformStage()
self.assertIsInstance(result[0], failures_lib.InfrastructureFailure)
self.assertEqual(self.gen_mock.call_count, 1)
self.assertEqual(self.gen_android_mock.call_count, 0)
self.assertEqual(self.upload_mock.call_count, 0)
self.assertEqual(self.tar_mock.call_count, 0)
self.assertEqual(self.upload_artifact_mock.call_count, 0)
self.assertBoardAttrEqual("breakpad_symbols_generated", False)
self.assertBoardAttrEqual("debug_tarball_generated", False)
def testUploadCrashStillNotifies(self) -> None:
"""Crashes in symbol upload should still notify external events."""
self.upload_mock.side_effect = failures_lib.BuildScriptFailure(
cros_build_lib.RunCommandError("mew"), "mew"
)
result = self._TestPerformStage()
self.assertIs(result[0], results_lib.Results.FORGIVEN)
self.assertBoardAttrEqual("breakpad_symbols_generated", True)
self.assertBoardAttrEqual("debug_tarball_generated", True)
def testUploadCrashUploadsList(self) -> None:
"""A crash in symbol upload should still post the failed list file."""
self.upload_mock.side_effect = failures_lib.BuildScriptFailure(
cros_build_lib.RunCommandError("mew"), "mew"
)
self._Prepare()
stage = self.ConstructStage()
with mock.patch.object(
os.path, "exists"
) as mock_exists, mock.patch.object(
artifact_stages.DebugSymbolsStage, "UploadArtifact"
) as mock_upload:
mock_exists.return_value = True
self.assertRaises(
artifact_stages.DebugSymbolsUploadException,
stage.UploadSymbols,
stage._build_root,
stage._current_board,
)
self.assertEqual(mock_exists.call_count, 1)
self.assertEqual(mock_upload.call_count, 1)
class UploadTestArtifactsStageMock(
generic_stages_unittest.ArchivingStageMixinMock
):
"""Partial mock for BuildImageStage."""
TARGET = (
"chromite.cbuildbot.stages.artifact_stages.UploadTestArtifactsStage"
)
ATTRS = generic_stages_unittest.ArchivingStageMixinMock.ATTRS + (
"BuildAutotestTarballs",
"BuildTastTarball",
)
def BuildAutotestTarballs(self, *args, **kwargs) -> None:
with mock.patch.object(
commands, "BuildTarball", autospec=True
), mock.patch.object(
commands,
"FindFilesWithPattern",
autospec=True,
return_value=["foo.txt"],
):
self.backup["BuildAutotestTarballs"](*args, **kwargs)
def BuildTastTarball(self, *args, **kwargs) -> None:
with mock.patch.object(commands, "BuildTarball", autospec=True):
self.backup["BuildTastTarball"](*args, **kwargs)
class UploadTestArtifactsStageTest(
build_stages_unittest.AllConfigsTestCase,
cbuildbot_unittest.SimpleBuilderTestCase,
):
"""Tests UploadTestArtifactsStage."""
def setUp(self) -> None:
self._release_tag = None
osutils.SafeMakedirs(os.path.join(self.build_root, "chroot", "tmp"))
self.StartPatcher(UploadTestArtifactsStageMock())
self.buildstore = FakeBuildStore()
def ConstructStage(self):
return artifact_stages.UploadTestArtifactsStage(
self._run, self.buildstore, self._current_board
)
def RunTestsWithBotId(self, bot_id, options_tests=True) -> None:
"""Test with the config for the specified bot_id."""
self._Prepare(bot_id)
self._run.options.tests = options_tests
self._run.attrs.release_tag = "0.0.1"
# Simulate images being ready.
board_runattrs = self._run.GetBoardRunAttrs(self._current_board)
board_runattrs.SetParallel("images_generated", True)
generate_update_payloads_mock = self.PatchObject(
commands, "GeneratePayloads"
)
with parallel_unittest.ParallelMock():
with self.RunStageWithConfig():
if (
self._run.config.upload_hw_test_artifacts
and self._run.config.images
):
self.assertNotEqual(
generate_update_payloads_mock.call_count, 0
)
else:
self.assertEqual(
generate_update_payloads_mock.call_count, 0
)
def testAllConfigs(self) -> None:
"""Test all major configurations"""
self.RunAllConfigs(self.RunTestsWithBotId)