blob: 75038bd04b90c9c77df2e8ab388d316c7de99907 [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright 2017 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.
"""Test git_bisector module."""
from __future__ import print_function
import copy
import mock
import os
from chromite.cros_bisect import common
from chromite.cros_bisect import builder as builder_module
from chromite.cros_bisect import evaluator as evaluator_module
from chromite.cros_bisect import git_bisector
from chromite.lib import commandline
from chromite.lib import cros_build_lib
from chromite.lib import cros_test_lib
from chromite.lib import partial_mock
from chromite.lib import cros_logging as logging
class GitMock(partial_mock.PartialCmdMock):
"""Mocks git.RunGit.
Usage:
mock = GitMock('/path/to/git_repository')
mock.AddRunGitResult(git_command, output=...)
# call git.RunGit(...)
"""
TARGET = 'chromite.lib.git'
ATTRS = ('RunGit', )
DEFAULT_ATTR = 'RunGit'
def __init__(self, cwd):
"""Constructor.
Args:
cwd: default git repository.
"""
super(GitMock, self).__init__()
self.cwd = cwd
def RunGit(self, *args, **kwargs):
"""Mocked RunGit."""
return self._results['RunGit'].LookupResult(args, kwargs=kwargs)
def AddRunGitResult(self, cmd, cwd=None, returncode=0, output='', error='',
kwargs=None, strict=False, side_effect=None):
"""Adds git command and results."""
cwd = self.cwd if cwd is None else cwd
result = self.CmdResult(returncode, output, error)
self._results['RunGit'].AddResultForParams(
(cwd, cmd,), result, kwargs=kwargs, side_effect=side_effect,
strict=strict)
class TestGitBisector(cros_test_lib.MockTempDirTestCase):
"""Tests GitBisector class."""
BOARD = 'samus'
TEST_NAME = 'graphics_WebGLAquarium'
METRIC = 'avg_fps_1000_fishes/summary/value'
REPORT_FILE = 'reports.json'
DUT_ADDR = '192.168.1.1'
DUT = commandline.DeviceParser(commandline.DEVICE_SCHEME_SSH)(DUT_ADDR)
# Be aware that GOOD_COMMIT_INFO and BAD_COMMIT_INFO should be assigned via
# copy.deepcopy() as their users are likely to change the content.
GOOD_COMMIT_SHA1 = '44af5c9a5505'
GOOD_COMMIT_TIMESTAMP = 1486526594
GOOD_COMMIT_SCORE = common.Score([100])
GOOD_COMMIT_INFO = common.CommitInfo(
sha1=GOOD_COMMIT_SHA1, timestamp=GOOD_COMMIT_TIMESTAMP, title='good',
label='last-known-good ', score=GOOD_COMMIT_SCORE)
BAD_COMMIT_SHA1 = '6a163bb66c3e'
BAD_COMMIT_TIMESTAMP = 1486530021
BAD_COMMIT_SCORE = common.Score([80])
BAD_COMMIT_INFO = common.CommitInfo(
sha1=BAD_COMMIT_SHA1, timestamp=BAD_COMMIT_TIMESTAMP, title='bad',
label='last-known-bad ', score=BAD_COMMIT_SCORE)
CULPRIT_COMMIT_SHA1 = '12345abcde'
CULPRIT_COMMIT_TIMESTAMP = 1486530000
CULPRIT_COMMIT_SCORE = common.Score([81])
CULPRIT_COMMIT_INFO = common.CommitInfo(
sha1=CULPRIT_COMMIT_SHA1, timestamp=CULPRIT_COMMIT_TIMESTAMP, title='bad',
score=CULPRIT_COMMIT_SCORE)
THRESHOLD_SPLITTER = 95 # Score between good and bad, closer to good side.
THRESHOLD = 5 # Distance between good score and splitter.
REPEAT = 3
def setUp(self):
"""Sets up test case."""
self.options = cros_test_lib.EasyAttr(
base_dir=self.tempdir, board=self.BOARD, reuse_repo=True,
good=self.GOOD_COMMIT_SHA1, bad=self.BAD_COMMIT_SHA1, remote=self.DUT,
eval_repeat=self.REPEAT, auto_threshold=False, reuse_eval=False,
eval_raise_on_error=False)
self.evaluator = evaluator_module.Evaluator(self.options)
self.builder = builder_module.Builder(self.options)
self.bisector = git_bisector.GitBisector(self.options, self.builder,
self.evaluator)
self.repo_dir = os.path.join(self.tempdir,
builder_module.Builder.DEFAULT_REPO_DIR)
def setDefaultCommitInfo(self):
"""Sets up default commit info."""
self.bisector.good_commit_info = copy.deepcopy(self.GOOD_COMMIT_INFO)
self.bisector.bad_commit_info = copy.deepcopy(self.BAD_COMMIT_INFO)
def testInit(self):
"""Tests GitBisector() to expect default data members."""
self.assertEqual(self.GOOD_COMMIT_SHA1, self.bisector.good_commit)
self.assertEqual(self.BAD_COMMIT_SHA1, self.bisector.bad_commit)
self.assertEqual(self.DUT_ADDR, self.bisector.remote.raw)
self.assertEqual(self.REPEAT, self.bisector.eval_repeat)
self.assertEqual(self.builder, self.bisector.builder)
self.assertEqual(self.repo_dir, self.bisector.repo_dir)
self.assertIsNone(self.bisector.good_commit_info)
self.assertIsNone(self.bisector.bad_commit_info)
self.assertEqual(0, len(self.bisector.bisect_log))
self.assertIsNone(self.bisector.threshold)
self.assertTrue(not self.bisector.current_commit)
def testInitMissingRequiredArgs(self):
"""Tests that GitBisector raises error when missing required arguments."""
options = cros_test_lib.EasyAttr()
with self.assertRaises(common.MissingRequiredOptionsException) as cm:
git_bisector.GitBisector(options, self.builder, self.evaluator)
exception_message = cm.exception.message
self.assertTrue('Missing command line' in exception_message)
self.assertTrue('GitBisector' in exception_message)
for arg in git_bisector.GitBisector.REQUIRED_ARGS:
self.assertTrue(arg in exception_message)
def testCheckCommitFormat(self):
"""Tests CheckCommitFormat()."""
sha1 = '900d900d'
self.assertEqual(sha1, git_bisector.GitBisector.CheckCommitFormat(sha1))
not_sha1 = 'bad_sha1'
self.assertIsNone(git_bisector.GitBisector.CheckCommitFormat(not_sha1))
def testSetUp(self):
"""Tests that SetUp() calls builder.SetUp()."""
with mock.patch.object(builder_module.Builder, 'SetUp') as builder_mock:
self.bisector.SetUp()
builder_mock.assert_called_with()
def testGit(self):
"""Tests that Git() invokes git.RunGit() as expected."""
git_mock = self.StartPatcher(GitMock(self.repo_dir))
git_mock.AddRunGitResult(['ls'])
self.bisector.Git(['ls'])
def testUpdateCurrentCommit(self):
"""Tests that UpdateCurrentCommit() updates self.current_commit."""
git_mock = self.StartPatcher(GitMock(self.repo_dir))
git_mock.AddRunGitResult(
partial_mock.In('show'),
output='4fcbdaf6 1493010050 Add "cros bisect" command.\n')
self.bisector.UpdateCurrentCommit()
current_commit = self.bisector.current_commit
self.assertEqual('4fcbdaf6', current_commit.sha1)
self.assertEqual(1493010050, current_commit.timestamp)
self.assertEqual('Add "cros bisect" command.', current_commit.title)
def testUpdateCurrentCommitFail(self):
"""Tests UpdateCurrentCommit() when 'git show' returns nonzero."""
git_mock = self.StartPatcher(GitMock(self.repo_dir))
git_mock.AddRunGitResult(partial_mock.In('show'), returncode=1)
self.bisector.UpdateCurrentCommit()
self.assertTrue(not self.bisector.current_commit)
def testUpdateCurrentCommitFailUnexpectedOutput(self):
"""Tests UpdateCurrentCommit() when 'git show' gives unexpected output."""
git_mock = self.StartPatcher(GitMock(self.repo_dir))
git_mock.AddRunGitResult(partial_mock.In('show'), output='Not expected')
self.bisector.UpdateCurrentCommit()
self.assertTrue(not self.bisector.current_commit)
def MockOutBuildDeployEvaluateForSanityCheck(self):
"""Mocks out BuildDeployEvaluate behavior for SaintyCheck test.
It mocks UpdateCurrentCommit() to emit good and bad commits. And mocks
CheckLastEvaluate() to return good and bad commit score.
"""
commit_info_list = [
common.CommitInfo(sha1=self.GOOD_COMMIT_SHA1, title='good',
timestamp=self.GOOD_COMMIT_TIMESTAMP),
common.CommitInfo(sha1=self.BAD_COMMIT_SHA1, title='bad',
timestamp=self.BAD_COMMIT_TIMESTAMP)]
# This mock is problematic. The side effect should modify "self" when
# the member method is called.
def UpdateCurrentCommitSideEffect():
self.bisector.current_commit = commit_info_list.pop(0)
self.PatchObject(
git_bisector.GitBisector, 'UpdateCurrentCommit',
side_effect=UpdateCurrentCommitSideEffect)
self.PatchObject(
evaluator_module.Evaluator, 'CheckLastEvaluate',
side_effect=[self.GOOD_COMMIT_SCORE, self.BAD_COMMIT_SCORE])
def testGetCommitTimestamp(self):
"""Tests GetCommitTimeStamp by mocking git.RunGit."""
git_mock = self.StartPatcher(GitMock(self.repo_dir))
# Mock git result for GetCommitTimestamp.
git_mock.AddRunGitResult(
partial_mock.InOrder(['show', self.GOOD_COMMIT_SHA1]),
output=str(self.GOOD_COMMIT_TIMESTAMP))
self.assertEqual(self.GOOD_COMMIT_TIMESTAMP,
self.bisector.GetCommitTimestamp(self.GOOD_COMMIT_SHA1))
def testGetCommitTimestampNotFound(self):
"""Tests GetCommitTimeStamp when the commit is not found."""
git_mock = self.StartPatcher(GitMock(self.repo_dir))
nonexist_sha1 = '12345'
# Mock git result for GetCommitTimestamp.
git_mock.AddRunGitResult(
partial_mock.InOrder(['show', nonexist_sha1]),
output='commit does not exist')
self.assertIsNone(self.bisector.GetCommitTimestamp(nonexist_sha1))
def testSanityCheck(self):
"""Tests SanityCheck().
It tests by mocking out git commands called by SanityCheck().
"""
git_mock = self.StartPatcher(GitMock(self.repo_dir))
# Mock git result for DoesCommitExistInRepo.
git_mock.AddRunGitResult(
partial_mock.InOrder(['rev-list', self.GOOD_COMMIT_SHA1]))
git_mock.AddRunGitResult(
partial_mock.InOrder(['rev-list', self.BAD_COMMIT_SHA1]))
# Mock git result for GetCommitTimestamp.
git_mock.AddRunGitResult(
partial_mock.InOrder(['show', self.GOOD_COMMIT_SHA1]),
output=str(self.GOOD_COMMIT_TIMESTAMP))
git_mock.AddRunGitResult(
partial_mock.InOrder(['show', self.BAD_COMMIT_SHA1]),
output=str(self.BAD_COMMIT_TIMESTAMP))
self.assertTrue(self.bisector.SanityCheck())
def testSanityCheckGoodCommitNotExist(self):
"""Tests SanityCheck() that good commit does not exist."""
git_mock = self.StartPatcher(GitMock(self.repo_dir))
sync_to_head_mock = self.PatchObject(builder_module.Builder, 'SyncToHead')
# Mock git result for DoesCommitExistInRepo to return False.
git_mock.AddRunGitResult(
partial_mock.InOrder(['rev-list', self.GOOD_COMMIT_SHA1]))
git_mock.AddRunGitResult(
partial_mock.InOrder(['rev-list', self.BAD_COMMIT_SHA1]))
# Mock invalid result for GetCommitTimestamp to return None.
git_mock.AddRunGitResult(
partial_mock.InOrder(['show', self.GOOD_COMMIT_SHA1]),
output='invalid commit')
git_mock.AddRunGitResult(
partial_mock.InOrder(['show', self.BAD_COMMIT_SHA1]),
output='invalid commit')
self.assertFalse(self.bisector.SanityCheck())
sync_to_head_mock.assert_called()
def testSanityCheckSyncToHeadWorks(self):
"""Tests SanityCheck() that good and bad commit do not exist.
As good and bad commit do not exist, it calls SyncToHead().
"""
git_mock = self.StartPatcher(GitMock(self.repo_dir))
sync_to_head_mock = self.PatchObject(builder_module.Builder, 'SyncToHead')
# Mock git result for DoesCommitExistInRepo to return False.
git_mock.AddRunGitResult(
partial_mock.InOrder(['rev-list', self.GOOD_COMMIT_SHA1]),
returncode=128)
git_mock.AddRunGitResult(
partial_mock.InOrder(['rev-list', self.BAD_COMMIT_SHA1]),
returncode=128)
# Mock git result for GetCommitTimestamp.
git_mock.AddRunGitResult(
partial_mock.InOrder(['show', self.GOOD_COMMIT_SHA1]),
output=str(self.GOOD_COMMIT_TIMESTAMP))
git_mock.AddRunGitResult(
partial_mock.InOrder(['show', self.BAD_COMMIT_SHA1]),
output=str(self.BAD_COMMIT_TIMESTAMP))
self.assertTrue(self.bisector.SanityCheck())
# After SyncToHead, found both bad and good commit.
sync_to_head_mock.assert_called()
def testSanityCheckWrongTimeOrder(self):
"""Tests SanityCheck() that good and bad commit have wrong time order."""
git_mock = self.StartPatcher(GitMock(self.repo_dir))
# Mock git result for DoesCommitExistInRepo.
git_mock.AddRunGitResult(
partial_mock.InOrder(['rev-list', self.GOOD_COMMIT_SHA1]))
git_mock.AddRunGitResult(
partial_mock.InOrder(['rev-list', self.BAD_COMMIT_SHA1]))
# Mock git result for GetCommitTimestamp, but swap timestamp.
git_mock.AddRunGitResult(
partial_mock.InOrder(['show', self.GOOD_COMMIT_SHA1]),
output=str(self.BAD_COMMIT_TIMESTAMP))
git_mock.AddRunGitResult(
partial_mock.InOrder(['show', self.BAD_COMMIT_SHA1]),
output=str(self.GOOD_COMMIT_TIMESTAMP))
self.assertFalse(self.bisector.SanityCheck())
def testObtainBisectBoundaryScoreImpl(self):
"""Tests ObtainBisectBoundaryScoreImpl()."""
git_mock = self.StartPatcher(GitMock(self.repo_dir))
git_mock.AddRunGitResult(['checkout', self.GOOD_COMMIT_SHA1])
git_mock.AddRunGitResult(['checkout', self.BAD_COMMIT_SHA1])
build_deploy_eval_mock = self.PatchObject(
git_bisector.GitBisector, 'BuildDeployEval')
build_deploy_eval_mock.side_effect = [
self.GOOD_COMMIT_SCORE, self.BAD_COMMIT_SCORE]
self.assertEqual(self.GOOD_COMMIT_SCORE,
self.bisector.ObtainBisectBoundaryScoreImpl(True))
self.assertEqual(self.BAD_COMMIT_SCORE,
self.bisector.ObtainBisectBoundaryScoreImpl(False))
self.assertEqual(
[mock.call(self.repo_dir, ['checkout', self.GOOD_COMMIT_SHA1]),
mock.call(self.repo_dir, ['checkout', self.BAD_COMMIT_SHA1])],
git_mock.call_args_list)
def testObtainBisectBoundaryScore(self):
"""Tests ObtainBisectBoundaryScore(). Normal case."""
def MockedObtainBisectBoundaryScoreImpl(good_side):
if good_side:
self.bisector.current_commit = copy.deepcopy(self.GOOD_COMMIT_INFO)
else:
self.bisector.current_commit = copy.deepcopy(self.BAD_COMMIT_INFO)
return self.bisector.current_commit.score
obtain_score_mock = self.PatchObject(
git_bisector.GitBisector, 'ObtainBisectBoundaryScoreImpl')
obtain_score_mock.side_effect = MockedObtainBisectBoundaryScoreImpl
self.assertTrue(self.bisector.ObtainBisectBoundaryScore())
self.assertEqual(self.GOOD_COMMIT_SCORE,
self.bisector.good_commit_info.score)
self.assertEqual('last-known-good ', self.bisector.good_commit_info.label)
self.assertEqual(self.BAD_COMMIT_SCORE, self.bisector.bad_commit_info.score)
self.assertEqual('last-known-bad ', self.bisector.bad_commit_info.label)
def testObtainBisectBoundaryScoreBadScoreUnavailable(self):
"""Tests ObtainBisectBoundaryScore(). Bad score unavailable."""
def UpdateCurrentCommitSideEffect(good_side):
if good_side:
self.bisector.current_commit = copy.deepcopy(self.GOOD_COMMIT_INFO)
else:
self.bisector.current_commit = copy.deepcopy(self.BAD_COMMIT_INFO)
self.bisector.current_commit.score = None
return self.bisector.current_commit.score
obtain_score_mock = self.PatchObject(
git_bisector.GitBisector, 'ObtainBisectBoundaryScoreImpl')
obtain_score_mock.side_effect = UpdateCurrentCommitSideEffect
self.assertFalse(self.bisector.ObtainBisectBoundaryScore())
self.assertEqual(self.GOOD_COMMIT_SCORE,
self.bisector.good_commit_info.score)
self.assertEqual('last-known-good ', self.bisector.good_commit_info.label)
self.assertIsNone(self.bisector.bad_commit_info.score)
self.assertEqual('last-known-bad ', self.bisector.bad_commit_info.label)
def testGetThresholdFromUser(self):
"""Tests GetThresholdFromUser()."""
logging.notice('testGetThresholdFromUser')
self.setDefaultCommitInfo()
input_mock = self.PatchObject(cros_build_lib, 'GetInput')
input_mock.return_value = self.THRESHOLD_SPLITTER
self.assertTrue(self.bisector.GetThresholdFromUser())
self.assertEqual(self.THRESHOLD, self.bisector.threshold)
def testGetThresholdFromUserAutoPick(self):
"""Tests GetThresholdFromUser()."""
self.setDefaultCommitInfo()
self.bisector.auto_threshold = True
self.assertTrue(self.bisector.GetThresholdFromUser())
self.assertEqual(10, self.bisector.threshold)
def testGetThresholdFromUserOutOfBoundFail(self):
"""Tests GetThresholdFromUser() with out-of-bound input."""
self.setDefaultCommitInfo()
input_mock = self.PatchObject(cros_build_lib, 'GetInput')
input_mock.side_effect = ['0', '1000', '-10']
self.assertFalse(self.bisector.GetThresholdFromUser())
self.assertIsNone(self.bisector.threshold)
self.assertEqual(3, input_mock.call_count)
def testGetThresholdFromUserRetrySuccess(self):
"""Tests GetThresholdFromUser() with retry."""
self.setDefaultCommitInfo()
input_mock = self.PatchObject(cros_build_lib, 'GetInput')
input_mock.side_effect = ['not_a_number', '1000', self.THRESHOLD_SPLITTER]
self.assertTrue(self.bisector.GetThresholdFromUser())
self.assertEqual(self.THRESHOLD, self.bisector.threshold)
self.assertEqual(3, input_mock.call_count)
def testBuildDeploy(self):
"""Tests BuildDeploy()."""
# Inject this as UpdateCurrentCommit's side effect.
self.bisector.current_commit = copy.deepcopy(self.GOOD_COMMIT_INFO)
build_mock = self.PatchObject(builder_module.Builder, 'Build')
build_to_deploy = '/build/to/deploy'
build_mock.return_value = build_to_deploy
deploy_mock = self.PatchObject(builder_module.Builder, 'Deploy')
self.bisector.BuildDeploy()
build_label = self.GOOD_COMMIT_INFO.sha1
build_mock.assert_called_with(build_label)
deploy_mock.assert_called_with(self.DUT, build_to_deploy, build_label)
def PatchObjectForBuildDeployEval(self):
"""Returns a dict of patch objects.
The patch objects are to mock:
git_bisector.UpdateCurrentCommit()
evaluator.CheckLastEvaluate()
git_bisector.BuildDeploy()
evaluator.Evaluate()
"""
return {
'UpdateCurrentCommit': self.PatchObject(git_bisector.GitBisector,
'UpdateCurrentCommit'),
'CheckLastEvaluate': self.PatchObject(evaluator_module.Evaluator,
'CheckLastEvaluate'),
'BuildDeploy': self.PatchObject(git_bisector.GitBisector,
'BuildDeploy'),
'Evaluate': self.PatchObject(evaluator_module.Evaluator, 'Evaluate')}
def testBuildDeployEvalShortcutCheckLastEvaluate(self):
"""Tests BuildDeployEval() with CheckLastEvaluate() found last score."""
mocks = self.PatchObjectForBuildDeployEval()
# Inject this as UpdateCurrentCommit's side effect.
self.bisector.current_commit = copy.deepcopy(self.GOOD_COMMIT_INFO)
mocks['CheckLastEvaluate'].return_value = self.GOOD_COMMIT_SCORE
score = self.bisector.BuildDeployEval()
self.assertEqual(self.GOOD_COMMIT_SCORE, score)
self.assertEqual(self.GOOD_COMMIT_SCORE, self.bisector.current_commit.score)
for method_called in ['UpdateCurrentCommit', 'CheckLastEvaluate']:
mocks[method_called].assert_called()
mocks['CheckLastEvaluate'].assert_called_with(self.GOOD_COMMIT_SHA1,
self.REPEAT)
for method_called in ['BuildDeploy', 'Evaluate']:
mocks[method_called].assert_not_called()
def AssertBuildDeployEvalMocksAllCalled(self, mocks):
for method_called in ['UpdateCurrentCommit', 'CheckLastEvaluate',
'BuildDeploy', 'Evaluate']:
mocks[method_called].assert_called()
mocks['CheckLastEvaluate'].assert_called_with(self.GOOD_COMMIT_SHA1,
self.REPEAT)
mocks['Evaluate'].assert_called_with(self.DUT, self.GOOD_COMMIT_SHA1,
self.REPEAT)
def testBuildDeployEvalNoCheckLastEvaluate(self):
"""Tests BuildDeployEval() without last score."""
mocks = self.PatchObjectForBuildDeployEval()
# Inject this as UpdateCurrentCommit's side effect.
self.bisector.current_commit = copy.deepcopy(self.GOOD_COMMIT_INFO)
mocks['CheckLastEvaluate'].return_value = common.Score()
mocks['Evaluate'].return_value = self.GOOD_COMMIT_SCORE
self.assertEqual(self.GOOD_COMMIT_SCORE, self.bisector.BuildDeployEval())
self.assertEqual(self.GOOD_COMMIT_SCORE, self.bisector.current_commit.score)
self.AssertBuildDeployEvalMocksAllCalled(mocks)
def testBuildDeployEvalNoCheckLastEvaluateSpecifyEvalLabel(self):
"""Tests BuildDeployEval() with eval_label specified."""
mocks = self.PatchObjectForBuildDeployEval()
# Inject this as UpdateCurrentCommit's side effect.
self.bisector.current_commit = copy.deepcopy(self.GOOD_COMMIT_INFO)
mocks['CheckLastEvaluate'].return_value = common.Score()
mocks['Evaluate'].return_value = self.GOOD_COMMIT_SCORE
eval_label = 'customized_label'
self.assertEqual(self.GOOD_COMMIT_SCORE,
self.bisector.BuildDeployEval(eval_label=eval_label))
self.assertEqual(self.GOOD_COMMIT_SCORE, self.bisector.current_commit.score)
for method_called in ['UpdateCurrentCommit', 'CheckLastEvaluate',
'BuildDeploy', 'Evaluate']:
mocks[method_called].assert_called()
# Use given label instead of SHA1 as eval label.
mocks['CheckLastEvaluate'].assert_called_with(eval_label, self.REPEAT)
mocks['Evaluate'].assert_called_with(self.DUT, eval_label, self.REPEAT)
@staticmethod
def _DummyMethod():
"""A dummy method for test to call and mock."""
pass
def testBuildDeployEvalNoCheckLastEvaluateSpecifyBuildDeploy(self):
"""Tests BuildDeployEval() with customize_build_deploy specified."""
mocks = self.PatchObjectForBuildDeployEval()
# Inject this as UpdateCurrentCommit's side effect.
self.bisector.current_commit = copy.deepcopy(self.GOOD_COMMIT_INFO)
mocks['CheckLastEvaluate'].return_value = common.Score()
mocks['Evaluate'].return_value = self.GOOD_COMMIT_SCORE
dummy_method = self.PatchObject(TestGitBisector, '_DummyMethod')
eval_label = 'customized_label'
self.assertEqual(self.GOOD_COMMIT_SCORE,
self.bisector.BuildDeployEval(
eval_label=eval_label,
customize_build_deploy=TestGitBisector._DummyMethod))
self.assertEqual(self.GOOD_COMMIT_SCORE, self.bisector.current_commit.score)
for method_called in ['UpdateCurrentCommit', 'CheckLastEvaluate',
'Evaluate']:
mocks[method_called].assert_called()
mocks['BuildDeploy'].assert_not_called()
dummy_method.assert_called()
# Use given label instead of SHA1 as eval label.
mocks['CheckLastEvaluate'].assert_called_with(eval_label, self.REPEAT)
mocks['Evaluate'].assert_called_with(self.DUT, eval_label, self.REPEAT)
def testBuildDeployEvalRaiseNoScore(self):
"""Tests BuildDeployEval() without score with eval_raise_on_error set."""
self.options.eval_raise_on_error = True
self.bisector = git_bisector.GitBisector(self.options, self.builder,
self.evaluator)
mocks = self.PatchObjectForBuildDeployEval()
# Inject this as UpdateCurrentCommit's side effect.
self.bisector.current_commit = copy.deepcopy(self.GOOD_COMMIT_INFO)
mocks['CheckLastEvaluate'].return_value = common.Score()
mocks['Evaluate'].return_value = common.Score()
self.assertRaises(Exception, self.bisector.BuildDeployEval)
self.assertFalse(self.bisector.current_commit.score)
self.AssertBuildDeployEvalMocksAllCalled(mocks)
def testBuildDeployEvalSuppressRaiseNoScore(self):
"""Tests BuildDeployEval() without score with eval_raise_on_error unset."""
mocks = self.PatchObjectForBuildDeployEval()
# Inject this as UpdateCurrentCommit's side effect.
self.bisector.current_commit = copy.deepcopy(self.GOOD_COMMIT_INFO)
mocks['CheckLastEvaluate'].return_value = common.Score()
mocks['Evaluate'].return_value = common.Score()
self.assertFalse(self.bisector.BuildDeployEval())
self.assertFalse(self.bisector.current_commit.score)
self.AssertBuildDeployEvalMocksAllCalled(mocks)
def testLabelBuild(self):
"""Tests LabelBuild()."""
# Inject good(100), bad(80) score and threshold.
self.setDefaultCommitInfo()
self.bisector.threshold = self.THRESHOLD
good = 'good'
bad = 'bad'
# Worse than given bad score.
self.assertEqual(bad, self.bisector.LabelBuild(common.Score([70])))
# Better than bad score, but not good enough.
self.assertEqual(bad, self.bisector.LabelBuild(common.Score([85])))
self.assertEqual(bad, self.bisector.LabelBuild(common.Score([90])))
# On the margin.
self.assertEqual(good, self.bisector.LabelBuild(common.Score([95])))
# Better than the margin.
self.assertEqual(good, self.bisector.LabelBuild(common.Score([98])))
# Better than given good score.
self.assertEqual(good, self.bisector.LabelBuild(common.Score([110])))
# No score, default bad.
self.assertEqual(bad, self.bisector.LabelBuild(None))
self.assertEqual(bad, self.bisector.LabelBuild(common.Score()))
def testLabelBuildLowerIsBetter(self):
"""Tests LabelBuild() in lower-is-better condition."""
# Reverse good(80) and bad(100) score (lower is better), same threshold.
self.bisector.good_commit_info = copy.deepcopy(self.BAD_COMMIT_INFO)
self.bisector.bad_commit_info = copy.deepcopy(self.GOOD_COMMIT_INFO)
self.bisector.threshold = self.THRESHOLD
good = 'good'
bad = 'bad'
# Better than given good score.
self.assertEqual(good, self.bisector.LabelBuild(common.Score([70])))
# Worse than good score, but still better than margin.
self.assertEqual(good, self.bisector.LabelBuild(common.Score([80])))
self.assertEqual(good, self.bisector.LabelBuild(common.Score([82])))
# On the margin.
self.assertEqual(good, self.bisector.LabelBuild(common.Score([85])))
# Worse than the margin.
self.assertEqual(bad, self.bisector.LabelBuild(common.Score([88])))
self.assertEqual(bad, self.bisector.LabelBuild(common.Score([90])))
self.assertEqual(bad, self.bisector.LabelBuild(common.Score([95])))
# Worse than given bad score.
self.assertEqual(bad, self.bisector.LabelBuild(common.Score([110])))
def testGitBisect(self):
"""Tests GitBisect()."""
git_mock = self.PatchObject(git_bisector.GitBisector, 'Git')
git_mock.return_value = cros_build_lib.CommandResult(
cmd=['git', 'bisect', 'reset'], output='We are not bisecting.',
returncode=0)
result, done = self.bisector.GitBisect(['reset'])
git_mock.assert_called_with(['bisect', 'reset'])
self.assertFalse(done)
self.assertEqual('We are not bisecting.', result.output)
self.assertListEqual(['git', 'bisect', 'reset'], result.cmd)
def testGitBisectDone(self):
"""Tests GitBisect() when culprit is found."""
git_mock = self.PatchObject(git_bisector.GitBisector, 'Git')
git_mock.return_value = cros_build_lib.CommandResult(
cmd=['git', 'bisect', 'bad'],
output='abcedf is the first bad commit\ncommit abcdef',
returncode=0)
result, done = self.bisector.GitBisect(['bad'])
git_mock.assert_called_with(['bisect', 'bad'])
self.assertTrue(done)
self.assertListEqual(['git', 'bisect', 'bad'], result.cmd)
def testRun(self):
"""Tests Run()."""
bisector_mock = self.StartPatcher(GitBisectorMock())
bisector_mock.good_commit_info = copy.deepcopy(self.GOOD_COMMIT_INFO)
bisector_mock.bad_commit_info = copy.deepcopy(self.BAD_COMMIT_INFO)
bisector_mock.threshold = self.THRESHOLD
bisector_mock.git_bisect_args_result = [
(['reset'], (None, False)),
(['start'], (None, False)),
(['bad', self.BAD_COMMIT_SHA1], (None, False)),
(['good', self.GOOD_COMMIT_SHA1], (None, False)),
(['bad'],
(cros_build_lib.CommandResult(
cmd=['git', 'bisect', 'bad'],
output='%s is the first bad commit\ncommit %s' % (
self.CULPRIT_COMMIT_SHA1, self.CULPRIT_COMMIT_SHA1),
returncode=0),
True)), # bisect bad (assume it found the first bad commit).
(['log'], (None, False)),
(['reset'], (None, False))]
bisector_mock.build_deploy_eval_current_commit = [self.CULPRIT_COMMIT_INFO]
bisector_mock.build_deploy_eval_result = [self.CULPRIT_COMMIT_SCORE]
bisector_mock.label_build_result = ['bad']
run_result = self.bisector.Run()
self.assertTrue(bisector_mock.patched['PrepareBisect'].called)
self.assertEqual(7, bisector_mock.patched['GitBisect'].call_count)
self.assertTrue(bisector_mock.patched['BuildDeployEval'].called)
self.assertTrue(bisector_mock.patched['LabelBuild'].called)
self.assertEqual(self.CULPRIT_COMMIT_SHA1, run_result)
class GitBisectorMock(partial_mock.PartialMock):
"""Partial mock GitBisector to test GitBisector.Run()."""
TARGET = 'chromite.cros_bisect.git_bisector.GitBisector'
ATTRS = ('PrepareBisect', 'GitBisect', 'BuildDeployEval', 'LabelBuild')
def __init__(self):
super(GitBisectorMock, self).__init__()
self.good_commit_info = None
self.bad_commit_info = None
self.threshold = None
self.git_bisect_args_result = []
self.build_deploy_eval_current_commit = []
self.build_deploy_eval_result = []
self.label_build_result = None
def PrepareBisect(self, this):
this.good_commit_info = self.good_commit_info
this.bad_commit_info = self.bad_commit_info
this.threshold = self.threshold
return True
def GitBisect(self, _, bisect_op):
(expected_op, result) = self.git_bisect_args_result.pop(0)
assert cmp(expected_op, bisect_op) == 0
return result
def BuildDeployEval(self, this):
this.current_commit = self.build_deploy_eval_current_commit.pop(0)
return self.build_deploy_eval_result.pop(0)
def LabelBuild(self, _, unused_score):
return self.label_build_result.pop(0)