blob: a4365ffb2460c4a1956862929f521a5696cdcfa1 [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Module that contains unittests for triage_lib module."""
from __future__ import print_function
import json
import mock
from chromite.lib import build_failure_message
from chromite.lib import config_lib
from chromite.lib import constants
from chromite.lib import cq_config
from chromite.lib import cros_test_lib
from chromite.lib import failure_message_lib_unittest
from chromite.lib import gerrit
from chromite.lib import patch as cros_patch
from chromite.lib import patch_unittest
from chromite.lib import portage_util
from chromite.lib import triage_lib
failure_msg_helper = failure_message_lib_unittest.FailureMessageHelper()
class MessageHelper(object):
"""Helper class to create failure messages for tests."""
@staticmethod
def GetFailedMessage(failure_messages, stage='Build', internal=False,
bot='daisy_spring-paladin'):
"""Returns a build_failure_message.BuildFailureMessage object."""
return build_failure_message.BuildFailureMessage(
'Stage %s failed' % stage, failure_messages, internal,
'failure reason string', bot)
@staticmethod
def GetGeneralFailure(stage='Build'):
return failure_msg_helper.GetStageFailureMessage(stage_name=stage)
@staticmethod
def GetTestLabFailure(stage='Build'):
return failure_msg_helper.GetStageFailureMessage(
exception_type='TestLabFailure',
exception_category=constants.EXCEPTION_CATEGORY_LAB,
stage_name=stage)
@staticmethod
def GetInfraFailure(stage='Build'):
return failure_msg_helper.GetStageFailureMessage(
exception_type='InfrastructureFailure',
exception_category=constants.EXCEPTION_CATEGORY_INFRA,
stage_name=stage)
@staticmethod
def GetPackageStageBuildFailure(extra_info=None, stage='Build'):
return failure_msg_helper.GetPackageBuildFailureMessage(
extra_info=extra_info,
stage_name=stage)
# pylint: disable=protected-access
class TestFindSuspects(cros_test_lib.MockTestCase):
"""Tests CalculateSuspects."""
def setUp(self):
site_params = config_lib.GetSiteParams()
overlay = 'chromiumos/overlays/chromiumos-overlay'
self._patch_factory = patch_unittest.MockPatchFactory()
self.overlay_patch = self._patch_factory.GetPatches(project=overlay)
chromite = 'chromiumos/chromite'
self.chromite_patch = self._patch_factory.GetPatches(project=chromite)
self.power_manager = 'chromiumos/platform2/power_manager'
self.power_manager_pkg = 'chromeos-base/power_manager'
self.power_manager_patch = self._patch_factory.GetPatches(
project=self.power_manager)
self.kernel = 'chromiumos/third_party/kernel/foo'
self.kernel_pkg = 'sys-kernel/chromeos-kernel-foo'
self.kernel_patch = self._patch_factory.GetPatches(project=self.kernel)
self.secret = 'chromeos/secret'
self.secret_patch = self._patch_factory.GetPatches(
project=self.secret, remote=site_params.INTERNAL_REMOTE)
self.PatchObject(cros_patch.GitRepoPatch, 'GetCheckout')
self.PatchObject(cros_patch.GitRepoPatch, 'GetDiffStatus')
self.PatchObject(gerrit, 'GetGerritPatchInfoWithPatchQueries',
side_effect=lambda x: x)
self.changes = [self.overlay_patch, self.chromite_patch,
self.power_manager_patch, self.kernel_patch,
self.secret_patch]
@staticmethod
def _GetBuildFailure(pkg):
"""Create a PackageBuildFailure for the specified |pkg|.
Args:
pkg: Package that failed to build.
"""
extra_info_dict = {'shortname': './build_image',
'failed_packages': [pkg]}
extra_info = json.dumps(extra_info_dict)
return MessageHelper.GetPackageStageBuildFailure(extra_info=extra_info)
def _AssertSuspects(self, patches, suspects, pkgs=(), exceptions=(),
internal=False, infra_fail=False, lab_fail=False,
sanity=True):
"""Run _FindSuspects and verify its output.
Args:
patches: List of patches to look at.
suspects: Expected list of suspects returned by _FindSuspects.
pkgs: List of packages that failed with exceptions in the build.
exceptions: List of other failure messages (instances of
failure_message_lib.StageFailureMessage) that occurred during the build.
internal: Whether the failures occurred on an internal bot.
infra_fail: Whether the build failed due to infrastructure issues.
lab_fail: Whether the build failed due to lab infrastructure issues.
sanity: The sanity checker builder passed and the tree was open when
the build started.
"""
all_exceptions = list(exceptions) + [self._GetBuildFailure(x) for x in pkgs]
message = MessageHelper.GetFailedMessage(all_exceptions, internal=internal)
results = triage_lib.CalculateSuspects.FindSuspects(
patches, [message], lab_fail=lab_fail, infra_fail=infra_fail,
sanity=sanity)
self.assertItemsEqual(suspects, results.keys())
def testFailSameProject(self):
"""Patches to the package that failed should be marked as failing."""
suspects = [self.kernel_patch]
patches = suspects + [self.power_manager_patch, self.secret_patch]
with self.PatchObject(portage_util, 'FindWorkonProjects',
return_value=self.kernel):
self._AssertSuspects(patches, suspects, [self.kernel_pkg])
self._AssertSuspects(patches, suspects, [self.kernel_pkg], sanity=False)
def testFailSameProjectPlusOverlay(self):
"""Patches to the overlay should be marked as failing."""
suspects = [self.overlay_patch, self.kernel_patch]
patches = suspects + [self.power_manager_patch, self.secret_patch]
with self.PatchObject(portage_util, 'FindWorkonProjects',
return_value=self.kernel):
self._AssertSuspects(patches, suspects, [self.kernel_pkg])
self._AssertSuspects(patches, [self.kernel_patch], [self.kernel_pkg],
sanity=False)
def testFailUnknownPackage(self):
"""If no patches changed the package, all patches should fail."""
changes = [self.overlay_patch, self.power_manager_patch, self.secret_patch]
self._AssertSuspects(changes, changes, [self.kernel_pkg])
self._AssertSuspects(changes, [], [self.kernel_pkg], sanity=False)
def testFailUnknownException(self):
"""An unknown exception should cause all patches to fail."""
changes = [self.kernel_patch, self.power_manager_patch, self.secret_patch]
self._AssertSuspects(changes, changes,
exceptions=[MessageHelper.GetGeneralFailure()])
self._AssertSuspects(changes, [],
exceptions=[MessageHelper.GetGeneralFailure()],
sanity=False)
def testFailUnknownInternalException(self):
"""An unknown exception should cause all patches to fail."""
suspects = [self.kernel_patch, self.power_manager_patch, self.secret_patch]
self._AssertSuspects(
suspects, suspects, exceptions=[MessageHelper.GetGeneralFailure()],
internal=True)
self._AssertSuspects(
suspects, [], exceptions=[MessageHelper.GetGeneralFailure()],
internal=True, sanity=False)
def testFailUnknownCombo(self):
"""Unknown exceptions should cause all patches to fail.
Even if there are also build failures that we can explain.
"""
suspects = [self.kernel_patch, self.power_manager_patch, self.secret_patch]
with self.PatchObject(portage_util, 'FindWorkonProjects',
return_value=self.kernel):
self._AssertSuspects(suspects, suspects, [self.kernel_pkg],
[MessageHelper.GetGeneralFailure()])
self._AssertSuspects(suspects, [self.kernel_patch], [self.kernel_pkg],
[MessageHelper.GetGeneralFailure()], sanity=False)
def testFailNone(self):
"""If a message is just 'None', it should cause all patches to fail."""
patches = [self.kernel_patch, self.power_manager_patch, self.secret_patch]
results = triage_lib.CalculateSuspects.FindSuspects(patches, [None])
self.assertItemsEqual(results.keys(), patches)
results = triage_lib.CalculateSuspects.FindSuspects(
patches, [None], sanity=False)
self.assertItemsEqual(results.keys(), [])
def testFailNoExceptions(self):
"""If there are no exceptions, all patches should be failed."""
suspects = [self.kernel_patch, self.power_manager_patch, self.secret_patch]
self._AssertSuspects(suspects, suspects)
self._AssertSuspects(suspects, [], sanity=False)
def testLabFail(self):
"""If there are only lab failures, no suspect is chosen."""
suspects = []
changes = [self.kernel_patch, self.power_manager_patch]
self._AssertSuspects(changes, suspects, lab_fail=True, infra_fail=True)
self._AssertSuspects(changes, suspects, lab_fail=True, infra_fail=True,
sanity=False)
def testInfraFail(self):
"""If there are only non-lab infra failures, pick chromite changes."""
suspects = [self.chromite_patch]
changes = [self.kernel_patch, self.power_manager_patch] + suspects
self._AssertSuspects(changes, suspects, lab_fail=False, infra_fail=True)
self._AssertSuspects(changes, suspects, lab_fail=False, infra_fail=True,
sanity=False)
def testManualBlame(self):
"""If there are changes that were manually blamed, pick those changes."""
approvals1 = [{'type': 'VRIF', 'value': '-1', 'grantedOn': 1391733002},
{'type': 'CRVW', 'value': '2', 'grantedOn': 1391733002},
{'type': 'COMR', 'value': '1', 'grantedOn': 1391733002},]
approvals2 = [{'type': 'VRIF', 'value': '1', 'grantedOn': 1391733002},
{'type': 'CRVW', 'value': '-2', 'grantedOn': 1391733002},
{'type': 'COMR', 'value': '1', 'grantedOn': 1391733002},]
suspects = [self._patch_factory.MockPatch(approvals=approvals1),
self._patch_factory.MockPatch(approvals=approvals2)]
changes = [self.kernel_patch, self.chromite_patch] + suspects
self._AssertSuspects(changes, suspects, lab_fail=False, infra_fail=False)
self._AssertSuspects(changes, suspects, lab_fail=True, infra_fail=False)
self._AssertSuspects(changes, suspects, lab_fail=True, infra_fail=True)
self._AssertSuspects(changes, suspects, lab_fail=False, infra_fail=True)
self._AssertSuspects(changes, suspects, lab_fail=False, infra_fail=False,
sanity=False)
self._AssertSuspects(changes, suspects, lab_fail=True, infra_fail=False,
sanity=False)
self._AssertSuspects(changes, suspects, lab_fail=True, infra_fail=True,
sanity=False)
self._AssertSuspects(changes, suspects, lab_fail=False, infra_fail=True,
sanity=False)
def _GetMessages(self, lab_fail=0, infra_fail=0, other_fail=0):
"""Returns a list ofbuild_failure_message.BuildFailureMessage objects."""
messages = []
messages.extend(
[MessageHelper.GetFailedMessage([MessageHelper.GetTestLabFailure()])
for _ in range(lab_fail)])
messages.extend(
[MessageHelper.GetFailedMessage([MessageHelper.GetInfraFailure()])
for _ in range(infra_fail)])
messages.extend(
[MessageHelper.GetFailedMessage([MessageHelper.GetGeneralFailure()])
for _ in range(other_fail)])
return messages
def testMatchesExceptionCategories(self):
"""Test MatchesExceptionCategories."""
messages = self._GetMessages(lab_fail=1, infra_fail=1)
messages.append(None)
self.assertFalse(
triage_lib.CalculateSuspects._MatchesExceptionCategories(
messages, [constants.EXCEPTION_CATEGORY_LAB]))
self.assertFalse(
triage_lib.CalculateSuspects._MatchesExceptionCategories(
messages, [constants.EXCEPTION_CATEGORY_LAB], strict=False))
self.assertTrue(
triage_lib.CalculateSuspects._MatchesExceptionCategories(
messages,
{constants.EXCEPTION_CATEGORY_INFRA,
constants.EXCEPTION_CATEGORY_LAB},
strict=False))
def testMatchesExceptionCategoriesWithEmptyMessages(self):
"""Test MatchesExceptionCategoriesWithEmptyMessages."""
messages = self._GetMessages()
self.assertFalse(
triage_lib.CalculateSuspects._MatchesExceptionCategories(
messages, {constants.EXCEPTION_CATEGORY_LAB}))
def testOnlyLabFailures(self):
"""Tests the OnlyLabFailures function."""
messages = self._GetMessages(lab_fail=2)
no_stat = []
self.assertTrue(
triage_lib.CalculateSuspects.OnlyLabFailures(messages, no_stat))
no_stat = ['foo', 'bar']
# Some builders did not start. This is not a lab failure.
self.assertFalse(
triage_lib.CalculateSuspects.OnlyLabFailures(messages, no_stat))
messages = self._GetMessages(lab_fail=1, infra_fail=1)
no_stat = []
# Non-lab infrastructure failures are present.
self.assertFalse(
triage_lib.CalculateSuspects.OnlyLabFailures(messages, no_stat))
def testOnlyInfraFailures(self):
"""Tests the OnlyInfraFailures function."""
messages = self._GetMessages(infra_fail=2)
no_stat = []
self.assertTrue(
triage_lib.CalculateSuspects.OnlyInfraFailures(messages, no_stat))
messages = self._GetMessages(lab_fail=2)
no_stat = []
# Lab failures are infrastructure failures.
self.assertTrue(
triage_lib.CalculateSuspects.OnlyInfraFailures(messages, no_stat))
messages = self._GetMessages(lab_fail=1, infra_fail=1)
no_stat = []
# Lab failures are infrastructure failures.
self.assertTrue(
triage_lib.CalculateSuspects.OnlyInfraFailures(messages, no_stat))
messages = self._GetMessages(other_fail=1, infra_fail=1)
no_stat = []
self.assertFalse(
triage_lib.CalculateSuspects.OnlyInfraFailures(messages, no_stat))
no_stat = ['orange']
messages = []
# 'Builders failed to report statuses' belong to infrastructure failures.
self.assertTrue(
triage_lib.CalculateSuspects.OnlyInfraFailures(messages, no_stat))
def testFindSuspectsForFailuresWithMessages(self):
"""Test FindSuspectsForFailures with not None messages."""
build_root = mock.Mock()
failed_hwtests = mock.Mock()
messages = []
for _ in range(0, 3):
m = mock.Mock()
m.FindSuspectedChanges.return_value = triage_lib.SuspectChanges({
self.changes[0]: constants.SUSPECT_REASON_UNKNOWN})
messages.append(m)
suspects = triage_lib.CalculateSuspects.FindSuspectsForFailures(
self.changes, messages, build_root, failed_hwtests, False)
self.assertItemsEqual(suspects.keys(), self.changes[0:1])
suspects = triage_lib.CalculateSuspects.FindSuspectsForFailures(
self.changes, messages, build_root, failed_hwtests, True)
self.assertItemsEqual(suspects.keys(), self.changes[0:1])
for index in range(0, 3):
messages[index].FindSuspectedChanges.called_once_with(
self.changes, build_root, failed_hwtests, True)
messages[index].FindSuspectedChanges.called_once_with(
self.changes, build_root, failed_hwtests, False)
def testFindSuspectsForFailuresWithNoneMessage(self):
"""Test FindSuspectsForFailuresWith None message."""
build_root = mock.Mock()
failed_hwtests = mock.Mock()
messages = [None]
suspects = triage_lib.CalculateSuspects.FindSuspectsForFailures(
self.changes, messages, build_root, failed_hwtests, False)
self.assertItemsEqual(suspects.keys(), set())
suspects = triage_lib.CalculateSuspects.FindSuspectsForFailures(
self.changes, messages, build_root, failed_hwtests, True)
self.assertItemsEqual(suspects.keys(), self.changes)
class TestGetFullyVerifiedChanges(cros_test_lib.MockTestCase):
"""Tests GetFullyVerifiedChanges() and related functions."""
def setUp(self):
self.build_root = '/foo/build/root'
self._patch_factory = patch_unittest.MockPatchFactory()
self.changes = self._patch_factory.GetPatches(how_many=5)
def testChangesNoAllTested(self):
"""Tests that those changes are fully verified."""
no_stat = failing = messages = []
inflight = ['foo-paladin']
changes_by_config = {'foo-paladin': []}
verified_results = triage_lib.CalculateSuspects.GetFullyVerifiedChanges(
self.changes, changes_by_config, {}, failing,
inflight, no_stat, messages, self.build_root)
verified_changes = set(verified_results.keys())
self.assertEquals(verified_changes, set(self.changes))
def testChangesOnNotCompletedBuilds(self):
"""Test changes on not completed builds."""
failing = messages = []
inflight = ['foo-paladin']
no_stat = ['puppy-paladin']
changes_by_config = {'foo-paladin': set(self.changes[:2]),
'bar-paladin': set(self.changes),
'puppy-paladin': set(self.changes[-2:])}
verified_results = triage_lib.CalculateSuspects.GetFullyVerifiedChanges(
self.changes, changes_by_config, {}, failing,
inflight, no_stat, messages, self.build_root)
verified_changes = set(verified_results.keys())
self.assertEquals(verified_changes, set(self.changes[2:-2]))
def testChangesOnNotCompletedBuildsWithCQHistory(self):
"""Tests changes on not completed builds with builds passed in history."""
failing = messages = []
inflight = ['foo-paladin']
no_stat = ['puppy-paladin']
changes_by_config = {'foo-paladin': set(self.changes[:2]),
'bar-paladin': set(self.changes),
'puppy-paladin': set(self.changes[-2:])}
passed_slave_by_change = {
self.changes[1]: {'foo-paladin'},
self.changes[3]: {'puppy-paladin'}
}
verified_results = triage_lib.CalculateSuspects.GetFullyVerifiedChanges(
self.changes, changes_by_config, passed_slave_by_change,
failing, inflight, no_stat, messages, self.build_root)
verified_changes = set(verified_results.keys())
self.assertEquals(verified_changes, set(self.changes[1:-1]))
def testChangesNotVerifiedOnFailures(self):
"""Tests that changes are not verified if failures cannot be ignored."""
messages = no_stat = inflight = []
failing = ['cub-paladin']
changes_by_config = {'bar-paladin': set(self.changes),
'cub-paladin': set(self.changes[:2])}
self.PatchObject(
triage_lib.CalculateSuspects, 'CanIgnoreFailures',
return_value=(False, None))
verified_results = triage_lib.CalculateSuspects.GetFullyVerifiedChanges(
self.changes, changes_by_config, {}, failing,
inflight, no_stat, messages, self.build_root)
verified_changes = set(verified_results.keys())
self.assertEquals(verified_changes, set(self.changes[2:]))
def testChangesNotVerifiedOnFailuresWithCQHistory(self):
"""Tests on not ignorable faiulres with CQ history."""
messages = no_stat = inflight = []
failing = ['cub-paladin']
changes_by_config = {'bar-paladin': set(self.changes),
'cub-paladin': set(self.changes[:2])}
passed_slave_by_change = {
self.changes[1]: {'cub-paladin'},
}
self.PatchObject(
triage_lib.CalculateSuspects, 'CanIgnoreFailures',
return_value=(False, None))
verified_results = triage_lib.CalculateSuspects.GetFullyVerifiedChanges(
self.changes, changes_by_config, passed_slave_by_change, failing,
inflight, no_stat, messages, self.build_root)
verified_changes = set(verified_results.keys())
self.assertEquals(verified_changes, set(self.changes[1:]))
def testChangesVerifiedWhenFailuresCanBeIgnored(self):
"""Tests that changes are verified if failures can be ignored."""
messages = no_stat = inflight = []
failing = ['cub-paladin']
changes_by_config = {'bar-paladin': set(self.changes),
'cub-paladin': set(self.changes[:2])}
self.PatchObject(
triage_lib.CalculateSuspects, 'CanIgnoreFailures',
return_value=(True, constants.STRATEGY_CQ_PARTIAL_IGNORED_STAGES))
verified_results = triage_lib.CalculateSuspects.GetFullyVerifiedChanges(
self.changes, changes_by_config, {}, failing,
inflight, no_stat, messages, self.build_root)
verified_changes = set(verified_results.keys())
self.assertEquals(verified_changes, set(self.changes))
def testCanIgnoreFailures(self):
"""Tests CanIgnoreFailures()."""
# pylint: disable=protected-access
change = self.changes[0]
messages = [
MessageHelper.GetFailedMessage(
[MessageHelper.GetGeneralFailure(stage='HWTest')], stage='HWTest'),
MessageHelper.GetFailedMessage(
[MessageHelper.GetGeneralFailure(stage='VMTest')], stage='VMTest')]
self.PatchObject(cq_config.CQConfigParser, 'GetCommonConfigFileForChange')
m = self.PatchObject(cq_config.CQConfigParser, 'GetStagesToIgnore')
m.return_value = ('HWTest',)
self.assertEqual(triage_lib.CalculateSuspects.CanIgnoreFailures(
messages, change, self.build_root), (False, None))
m.return_value = ('HWTest', 'VMTest', 'Foo')
self.assertEqual(triage_lib.CalculateSuspects.CanIgnoreFailures(
messages, change, self.build_root),
(True, constants.STRATEGY_CQ_PARTIAL_IGNORED_STAGES))
m.return_value = None
self.assertEqual(triage_lib.CalculateSuspects.CanIgnoreFailures(
messages, change, self.build_root), (False, None))
# pylint: disable=protected-access
def testGetVerifiedReason(self):
"""Test _GetVerifiedReason."""
verified_reasons = set()
self.assertEqual(
triage_lib.CalculateSuspects._GetVerifiedReason(verified_reasons), None)
verified_reasons = {constants.STRATEGY_CQ_PARTIAL_BUILDS_PASSED,
constants.STRATEGY_CQ_PARTIAL_IGNORED_STAGES}
self.assertEqual(
triage_lib.CalculateSuspects._GetVerifiedReason(verified_reasons),
constants.STRATEGY_CQ_PARTIAL_IGNORED_STAGES)
class SuspectChangesTest(cros_test_lib.MockTestCase):
"""Tests for SuspectChanges."""
def setUp(self):
self._patch_factory = patch_unittest.MockPatchFactory()
self.patches = self._patch_factory.GetPatches(how_many=3)
def _CreateSuspectChanges(self, suspect_dict=None):
return triage_lib.SuspectChanges(suspect_dict)
def testSetitem(self):
"""Test __setitem__."""
suspects = self._CreateSuspectChanges()
suspects[self.patches[0]] = constants.SUSPECT_REASON_BAD_CHANGE
suspects[self.patches[1]] = constants.SUSPECT_REASON_UNKNOWN
self.assertEqual(len(suspects), 2)
self.assertEqual(suspects[self.patches[0]],
constants.SUSPECT_REASON_BAD_CHANGE)
self.assertEqual(suspects[self.patches[1]],
constants.SUSPECT_REASON_UNKNOWN)
suspects[self.patches[0]] = constants.SUSPECT_REASON_UNKNOWN
suspects[self.patches[1]] = constants.SUSPECT_REASON_BUILD_FAIL
self.assertEqual(len(suspects), 2)
self.assertEqual(suspects[self.patches[0]],
constants.SUSPECT_REASON_BAD_CHANGE)
self.assertEqual(suspects[self.patches[1]],
constants.SUSPECT_REASON_BUILD_FAIL)
suspects[self.patches[2]] = constants.SUSPECT_REASON_OVERLAY_CHANGE
self.assertEqual(len(suspects), 3)
self.assertEqual(suspects[self.patches[2]],
constants.SUSPECT_REASON_OVERLAY_CHANGE)
def testSetdefault(self):
"""Test setdefault."""
suspects = self._CreateSuspectChanges()
self.assertRaises(Exception, suspects.setdefault, self.patches[0], None)
suspects.setdefault(self.patches[0], constants.SUSPECT_REASON_BAD_CHANGE)
suspects.setdefault(self.patches[1], constants.SUSPECT_REASON_UNKNOWN)
self.assertEqual(len(suspects), 2)
self.assertEqual(suspects[self.patches[0]],
constants.SUSPECT_REASON_BAD_CHANGE)
self.assertEqual(suspects[self.patches[1]],
constants.SUSPECT_REASON_UNKNOWN)
suspects.setdefault(self.patches[0], constants.SUSPECT_REASON_UNKNOWN)
suspects.setdefault(self.patches[1], constants.SUSPECT_REASON_BUILD_FAIL)
self.assertEqual(len(suspects), 2)
self.assertEqual(suspects[self.patches[0]],
constants.SUSPECT_REASON_BAD_CHANGE)
self.assertEqual(suspects[self.patches[1]],
constants.SUSPECT_REASON_BUILD_FAIL)
suspects.setdefault(self.patches[2],
constants.SUSPECT_REASON_OVERLAY_CHANGE)
self.assertEqual(len(suspects), 3)
self.assertEqual(suspects[self.patches[2]],
constants.SUSPECT_REASON_OVERLAY_CHANGE)
def testUpdate(self):
"""Test update."""
suspects = self._CreateSuspectChanges({
self.patches[0]: constants.SUSPECT_REASON_BAD_CHANGE,
self.patches[1]: constants.SUSPECT_REASON_UNKNOWN})
suspects.update({
self.patches[0]: constants.SUSPECT_REASON_UNKNOWN,
self.patches[1]: constants.SUSPECT_REASON_BUILD_FAIL})
expected = self._CreateSuspectChanges({
self.patches[0]: constants.SUSPECT_REASON_BAD_CHANGE,
self.patches[1]: constants.SUSPECT_REASON_BUILD_FAIL})
self.assertEqual(suspects, expected)