600 lines
20 KiB
Python
Executable File
600 lines
20 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
# Copyright 2019 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.
|
|
|
|
"""Tests for LLVM bisection."""
|
|
|
|
from __future__ import print_function
|
|
|
|
import get_llvm_hash
|
|
import json
|
|
import llvm_bisection
|
|
import unittest
|
|
import unittest.mock as mock
|
|
|
|
from get_llvm_hash import LLVMHash
|
|
from test_helpers import ArgsOutputTest
|
|
from test_helpers import CallCountsToMockFunctions
|
|
from test_helpers import CreateTemporaryJsonFile
|
|
from test_helpers import WritePrettyJsonFile
|
|
|
|
|
|
class LLVMBisectionTest(unittest.TestCase):
|
|
"""Unittests for LLVM bisection."""
|
|
|
|
def testStartAndEndDoNotMatchJsonStartAndEnd(self):
|
|
start = 100
|
|
end = 150
|
|
|
|
json_start = 110
|
|
json_end = 150
|
|
|
|
# Verify the exception is raised when the start and end revision for LLVM
|
|
# bisection do not match the .JSON's 'start' and 'end' values.
|
|
with self.assertRaises(ValueError) as err:
|
|
llvm_bisection._ValidateStartAndEndAgainstJSONStartAndEnd(
|
|
start, end, json_start, json_end)
|
|
|
|
expected_error_message = ('The start %d or the end %d version provided is '
|
|
'different than "start" %d or "end" %d in the '
|
|
'.JSON file' % (start, end, json_start, json_end))
|
|
|
|
self.assertEqual(str(err.exception), expected_error_message)
|
|
|
|
def testStartAndEndMatchJsonStartAndEnd(self):
|
|
start = 100
|
|
end = 150
|
|
|
|
json_start = 100
|
|
json_end = 150
|
|
|
|
llvm_bisection._ValidateStartAndEndAgainstJSONStartAndEnd(
|
|
start, end, json_start, json_end)
|
|
|
|
def testTryjobStatusIsMissing(self):
|
|
start = 100
|
|
end = 150
|
|
|
|
test_tryjobs = [{
|
|
'rev': 105,
|
|
'status': 'good',
|
|
'link': 'https://some_tryjob_1_url.com'
|
|
}, {
|
|
'rev': 120,
|
|
'status': None,
|
|
'link': 'https://some_tryjob_2_url.com'
|
|
}, {
|
|
'rev': 140,
|
|
'status': 'bad',
|
|
'link': 'https://some_tryjob_3_url.com'
|
|
}]
|
|
|
|
# Verify the exception is raised when a tryjob does not have a value for
|
|
# the 'status' key or the 'status' key is missing.
|
|
with self.assertRaises(ValueError) as err:
|
|
llvm_bisection.GetStartAndEndRevision(start, end, test_tryjobs)
|
|
|
|
expected_error_message = (
|
|
'"status" is missing or has no value, please '
|
|
'go to %s and update it' % test_tryjobs[1]['link'])
|
|
|
|
self.assertEqual(str(err.exception), expected_error_message)
|
|
|
|
def testGoodRevisionGreaterThanBadRevision(self):
|
|
start = 100
|
|
end = 150
|
|
|
|
test_tryjobs = [{
|
|
'rev': 110,
|
|
'status': 'bad',
|
|
'link': 'https://some_tryjob_1_url.com'
|
|
}, {
|
|
'rev': 125,
|
|
'status': 'skip',
|
|
'link': 'https://some_tryjob_2_url.com'
|
|
}, {
|
|
'rev': 140,
|
|
'status': 'good',
|
|
'link': 'https://some_tryjob_3_url.com'
|
|
}]
|
|
|
|
# Verify the exception is raised when the new 'start' revision is greater
|
|
# than the new 'bad' revision for bisection (i.e. bisection is broken).
|
|
with self.assertRaises(AssertionError) as err:
|
|
llvm_bisection.GetStartAndEndRevision(start, end, test_tryjobs)
|
|
|
|
expected_error_message = (
|
|
'Bisection is broken because %d (good) is >= '
|
|
'%d (bad)' % (test_tryjobs[2]['rev'], test_tryjobs[0]['rev']))
|
|
|
|
self.assertEqual(str(err.exception), expected_error_message)
|
|
|
|
def testSuccessfullyGetNewStartAndNewEndRevision(self):
|
|
start = 100
|
|
end = 150
|
|
|
|
test_tryjobs = [{
|
|
'rev': 110,
|
|
'status': 'good',
|
|
'link': 'https://some_tryjob_1_url.com'
|
|
}, {
|
|
'rev': 120,
|
|
'status': 'good',
|
|
'link': 'https://some_tryjob_2_url.com'
|
|
}, {
|
|
'rev': 130,
|
|
'status': 'pending',
|
|
'link': 'https://some_tryjob_3_url.com'
|
|
}, {
|
|
'rev': 135,
|
|
'status': 'skip',
|
|
'link': 'https://some_tryjob_4_url.com'
|
|
}, {
|
|
'rev': 140,
|
|
'status': 'bad',
|
|
'link': 'https://some_tryjob_5_url.com'
|
|
}]
|
|
|
|
# Tuple consists of the new good revision, the new bad revision, a set of
|
|
# 'pending' revisions, and a set of 'skip' revisions.
|
|
expected_revisions_tuple = 120, 140, {130}, {135}
|
|
|
|
self.assertTupleEqual(
|
|
llvm_bisection.GetStartAndEndRevision(start, end, test_tryjobs),
|
|
expected_revisions_tuple)
|
|
|
|
@mock.patch.object(get_llvm_hash, 'GetGitHashFrom')
|
|
def testNoRevisionsBetweenStartAndEnd(self, mock_get_git_hash):
|
|
start = 100
|
|
end = 110
|
|
|
|
test_pending_revisions = {107}
|
|
test_skip_revisions = {101, 102, 103, 104, 108, 109}
|
|
|
|
# Simulate behavior of `GetGitHashFrom()` when the revision does not
|
|
# exist in the LLVM source tree.
|
|
def MockGetGitHashForRevisionRaiseException(src_path, revision):
|
|
raise ValueError('Revision does not exist')
|
|
|
|
mock_get_git_hash.side_effect = MockGetGitHashForRevisionRaiseException
|
|
|
|
parallel = 3
|
|
|
|
abs_path_to_src = '/abs/path/to/src'
|
|
|
|
self.assertListEqual(
|
|
llvm_bisection.GetRevisionsBetweenBisection(
|
|
start, end, parallel, abs_path_to_src, test_pending_revisions,
|
|
test_skip_revisions), [])
|
|
|
|
# Assume llvm_bisection module has imported GetGitHashFrom
|
|
@mock.patch.object(get_llvm_hash, 'GetGitHashFrom')
|
|
def testSuccessfullyRetrievedRevisionsBetweenStartAndEnd(
|
|
self, mock_get_git_hash):
|
|
|
|
start = 100
|
|
end = 110
|
|
|
|
test_pending_revisions = set()
|
|
test_skip_revisions = {101, 102, 103, 104, 106, 108, 109}
|
|
|
|
parallel = 3
|
|
|
|
abs_path_to_src = '/abs/path/to/src'
|
|
|
|
# Valid revision that exist in the LLVM source tree between 'start' and
|
|
# 'end' and were not in the 'pending' set or 'skip' set.
|
|
expected_revisions_between_start_and_end = [105, 107]
|
|
|
|
self.assertListEqual(
|
|
llvm_bisection.GetRevisionsBetweenBisection(
|
|
start, end, parallel, abs_path_to_src, test_pending_revisions,
|
|
test_skip_revisions), expected_revisions_between_start_and_end)
|
|
|
|
self.assertEqual(mock_get_git_hash.call_count, 2)
|
|
|
|
# Simulate behavior of `GetGitHashFrom()` when successfully retrieved
|
|
# a list git hashes for each revision in the revisions list.
|
|
# Assume llvm_bisection module has imported GetGitHashFrom
|
|
@mock.patch.object(get_llvm_hash, 'GetGitHashFrom')
|
|
# Simulate behavior of `GetRevisionsBetweenBisection()` when successfully
|
|
# retrieved a list of valid revisions between 'start' and 'end'.
|
|
@mock.patch.object(llvm_bisection, 'GetRevisionsBetweenBisection')
|
|
# Simulate behavior of `CreatTempLLVMRepo()` when successfully created a
|
|
# worktree when a source path was not provided.
|
|
@mock.patch.object(llvm_bisection, 'CreateTempLLVMRepo')
|
|
def testSuccessfullyGetRevisionsListAndHashList(
|
|
self, mock_create_temp_llvm_repo, mock_get_revisions_between_bisection,
|
|
mock_get_git_hash):
|
|
|
|
expected_revisions_and_hash_tuple = ([102, 105, 108], [
|
|
'a123testhash1', 'a123testhash2', 'a123testhash3'
|
|
])
|
|
|
|
@CallCountsToMockFunctions
|
|
def MockGetGitHashForRevision(call_count, src_path, rev):
|
|
# Simulate retrieving the git hash for the revision.
|
|
if call_count < 3:
|
|
return expected_revisions_and_hash_tuple[1][call_count]
|
|
|
|
assert False, 'Called `GetGitHashFrom()` more than expected.'
|
|
|
|
temp_worktree = '/abs/path/to/tmpDir'
|
|
|
|
mock_create_temp_llvm_repo.return_value.__enter__.return_value.name = \
|
|
temp_worktree
|
|
|
|
# Simulate the valid revisions list.
|
|
mock_get_revisions_between_bisection.return_value = \
|
|
expected_revisions_and_hash_tuple[0]
|
|
|
|
# Simulate behavior of `GetGitHashFrom()` by using the testing
|
|
# function.
|
|
mock_get_git_hash.side_effect = MockGetGitHashForRevision
|
|
|
|
start = 100
|
|
end = 110
|
|
parallel = 3
|
|
src_path = None
|
|
pending_revisions = {103, 104}
|
|
skip_revisions = {101, 106, 107, 109}
|
|
|
|
self.assertTupleEqual(
|
|
llvm_bisection.GetRevisionsListAndHashList(
|
|
start, end, parallel, src_path, pending_revisions, skip_revisions),
|
|
expected_revisions_and_hash_tuple)
|
|
|
|
mock_get_revisions_between_bisection.assert_called_once()
|
|
|
|
self.assertEqual(mock_get_git_hash.call_count, 3)
|
|
|
|
def testSuccessfullyDieWithNoRevisionsError(self):
|
|
start = 100
|
|
end = 110
|
|
|
|
pending_revisions = {105, 108}
|
|
skip_revisions = {101, 102, 103, 104, 106, 107, 109}
|
|
|
|
expected_no_revisions_message = ('No revisions between start %d and end '
|
|
'%d to create tryjobs' % (start, end))
|
|
|
|
expected_no_revisions_message += '\nThe following tryjobs are pending:\n' \
|
|
+ '\n'.join(str(rev) for rev in pending_revisions)
|
|
|
|
expected_no_revisions_message += '\nThe following tryjobs were skipped:\n' \
|
|
+ '\n'.join(str(rev) for rev in skip_revisions)
|
|
|
|
# Verify that an exception is raised when there are no revisions to launch
|
|
# tryjobs for between 'start' and 'end' and some tryjobs are 'pending'.
|
|
with self.assertRaises(ValueError) as err:
|
|
llvm_bisection.DieWithNoRevisionsError(start, end, skip_revisions,
|
|
pending_revisions)
|
|
|
|
self.assertEqual(str(err.exception), expected_no_revisions_message)
|
|
|
|
# Simulate behavior of `FindTryjobIndex()` when the index of the tryjob was
|
|
# found.
|
|
@mock.patch.object(llvm_bisection, 'FindTryjobIndex', return_value=0)
|
|
def testTryjobExistsInRevisionsToLaunch(self, mock_find_tryjob_index):
|
|
test_existing_jobs = [{'rev': 102, 'status': 'good'}]
|
|
|
|
revision_to_launch = [102]
|
|
|
|
expected_revision_that_exists = 102
|
|
|
|
with self.assertRaises(ValueError) as err:
|
|
llvm_bisection.CheckForExistingTryjobsInRevisionsToLaunch(
|
|
revision_to_launch, test_existing_jobs)
|
|
|
|
expected_found_tryjob_index_error_message = (
|
|
'Revision %d exists already '
|
|
'in "jobs"' % expected_revision_that_exists)
|
|
|
|
self.assertEqual(
|
|
str(err.exception), expected_found_tryjob_index_error_message)
|
|
|
|
mock_find_tryjob_index.assert_called_once()
|
|
|
|
@mock.patch.object(llvm_bisection, 'AddTryjob')
|
|
def testSuccessfullyUpdatedStatusFileWhenExceptionIsRaised(
|
|
self, mock_add_tryjob):
|
|
|
|
git_hash_list = ['a123testhash1', 'a123testhash2', 'a123testhash3']
|
|
revisions_list = [102, 104, 106]
|
|
|
|
# Simulate behavior of `AddTryjob()` when successfully launched a tryjob for
|
|
# the updated packages.
|
|
@CallCountsToMockFunctions
|
|
def MockAddTryjob(call_count, packages, git_hash, revision, chroot_path,
|
|
patch_file, extra_cls, options, builder, verbose,
|
|
svn_revision):
|
|
|
|
if call_count < 2:
|
|
return {'rev': revisions_list[call_count], 'status': 'pending'}
|
|
|
|
# Simulate an exception happened along the way when updating the
|
|
# packages' `LLVM_NEXT_HASH`.
|
|
if call_count == 2:
|
|
raise ValueError('Unable to launch tryjob')
|
|
|
|
assert False, 'Called `AddTryjob()` more than expected.'
|
|
|
|
# Use the test function to simulate `AddTryjob()`.
|
|
mock_add_tryjob.side_effect = MockAddTryjob
|
|
|
|
start = 100
|
|
end = 110
|
|
|
|
bisection_contents = {'start': start, 'end': end, 'jobs': []}
|
|
|
|
args_output = ArgsOutputTest()
|
|
|
|
packages = ['sys-devel/llvm']
|
|
patch_file = '/abs/path/to/PATCHES.json'
|
|
|
|
# Create a temporary .JSON file to simulate a status file for bisection.
|
|
with CreateTemporaryJsonFile() as temp_json_file:
|
|
with open(temp_json_file, 'w') as f:
|
|
WritePrettyJsonFile(bisection_contents, f)
|
|
|
|
# Verify that the status file is updated when an exception happened when
|
|
# attempting to launch a revision (i.e. progress is not lost).
|
|
with self.assertRaises(ValueError) as err:
|
|
llvm_bisection.UpdateBisection(
|
|
revisions_list, git_hash_list, bisection_contents, temp_json_file,
|
|
packages, args_output.chroot_path, patch_file,
|
|
args_output.extra_change_lists, args_output.options,
|
|
args_output.builders, args_output.verbose)
|
|
|
|
expected_bisection_contents = {
|
|
'start':
|
|
start,
|
|
'end':
|
|
end,
|
|
'jobs': [{
|
|
'rev': revisions_list[0],
|
|
'status': 'pending'
|
|
}, {
|
|
'rev': revisions_list[1],
|
|
'status': 'pending'
|
|
}]
|
|
}
|
|
|
|
# Verify that the launched tryjobs were added to the status file when
|
|
# an exception happened.
|
|
with open(temp_json_file) as f:
|
|
json_contents = json.load(f)
|
|
|
|
self.assertDictEqual(json_contents, expected_bisection_contents)
|
|
|
|
self.assertEqual(str(err.exception), 'Unable to launch tryjob')
|
|
|
|
self.assertEqual(mock_add_tryjob.call_count, 3)
|
|
|
|
# Simulate behavior of `GetGitHashFrom()` when successfully retrieved
|
|
# the git hash of the bad revision. Assume llvm_bisection has imported
|
|
# GetGitHashFrom
|
|
@mock.patch.object(
|
|
get_llvm_hash, 'GetGitHashFrom', return_value='a123testhash4')
|
|
def testCompletedBisectionWhenProvidedSrcPath(self, mock_get_git_hash):
|
|
last_tested = '/some/last/tested_file.json'
|
|
|
|
src_path = '/abs/path/to/src/path'
|
|
|
|
# The bad revision.
|
|
end = 150
|
|
|
|
llvm_bisection._NoteCompletedBisection(last_tested, src_path, end)
|
|
|
|
mock_get_git_hash.assert_called_once()
|
|
|
|
# Simulate behavior of `GetLLVMHash()` when successfully retrieved
|
|
# the git hash of the bad revision.
|
|
@mock.patch.object(LLVMHash, 'GetLLVMHash', return_value='a123testhash5')
|
|
def testCompletedBisectionWhenNotProvidedSrcPath(self, mock_get_git_hash):
|
|
last_tested = '/some/last/tested_file.json'
|
|
|
|
src_path = None
|
|
|
|
# The bad revision.
|
|
end = 200
|
|
|
|
llvm_bisection._NoteCompletedBisection(last_tested, src_path, end)
|
|
|
|
mock_get_git_hash.assert_called_once()
|
|
|
|
def testSuccessfullyLoadedStatusFile(self):
|
|
start = 100
|
|
end = 150
|
|
|
|
test_bisect_contents = {'start': start, 'end': end, 'jobs': []}
|
|
|
|
# Simulate that the status file exists.
|
|
with CreateTemporaryJsonFile() as temp_json_file:
|
|
with open(temp_json_file, 'w') as f:
|
|
WritePrettyJsonFile(test_bisect_contents, f)
|
|
|
|
self.assertDictEqual(
|
|
llvm_bisection.LoadStatusFile(temp_json_file, start, end),
|
|
test_bisect_contents)
|
|
|
|
def testLoadedStatusFileThatDoesNotExist(self):
|
|
start = 200
|
|
end = 250
|
|
|
|
expected_bisect_contents = {'start': start, 'end': end, 'jobs': []}
|
|
|
|
last_tested = '/abs/path/to/file_that_does_not_exist.json'
|
|
|
|
self.assertDictEqual(
|
|
llvm_bisection.LoadStatusFile(last_tested, start, end),
|
|
expected_bisect_contents)
|
|
|
|
# Simulate behavior of `_NoteCompletedBisection()` when there are no more
|
|
# tryjobs to launch between start and end, so bisection is complete.
|
|
@mock.patch.object(llvm_bisection, '_NoteCompletedBisection')
|
|
@mock.patch.object(llvm_bisection, 'GetRevisionsListAndHashList')
|
|
@mock.patch.object(llvm_bisection, 'GetStartAndEndRevision')
|
|
# Simulate behavior of `_ValidateStartAndEndAgainstJSONStartAndEnd()` when
|
|
# both start and end revisions match.
|
|
@mock.patch.object(llvm_bisection,
|
|
'_ValidateStartAndEndAgainstJSONStartAndEnd')
|
|
@mock.patch.object(llvm_bisection, 'LoadStatusFile')
|
|
# Simulate behavior of `VerifyOutsideChroot()` when successfully invoked the
|
|
# script outside of the chroot.
|
|
@mock.patch.object(llvm_bisection, 'VerifyOutsideChroot', return_value=True)
|
|
def testSuccessfullyBisectedLLVM(
|
|
self, mock_outside_chroot, mock_load_status_file,
|
|
mock_validate_start_and_end, mock_get_start_and_end_revision,
|
|
mock_get_revision_and_hash_list, mock_note_completed_bisection):
|
|
|
|
start = 500
|
|
end = 502
|
|
|
|
bisect_contents = {
|
|
'start': start,
|
|
'end': end,
|
|
'jobs': [{
|
|
'rev': 501,
|
|
'status': 'skip'
|
|
}]
|
|
}
|
|
|
|
skip_revisions = {501}
|
|
pending_revisions = {}
|
|
|
|
# Simulate behavior of `LoadStatusFile()` when successfully loaded the
|
|
# status file.
|
|
mock_load_status_file.return_value = bisect_contents
|
|
|
|
# Simulate behavior of `GetStartAndEndRevision()` when successfully found
|
|
# the new start and end revision of the bisection.
|
|
#
|
|
# Returns new start revision, new end revision, a set of pending revisions,
|
|
# and a set of skip revisions.
|
|
mock_get_start_and_end_revision.return_value = (start, end,
|
|
pending_revisions,
|
|
skip_revisions)
|
|
|
|
# Simulate behavior of `GetRevisionsListAndHashList()` when successfully
|
|
# retrieved valid revisions (along with their git hashes) between start and
|
|
# end (in this case, none).
|
|
mock_get_revision_and_hash_list.return_value = [], []
|
|
|
|
args_output = ArgsOutputTest()
|
|
args_output.start_rev = start
|
|
args_output.end_rev = end
|
|
args_output.parallel = 3
|
|
args_output.src_path = None
|
|
|
|
self.assertEqual(
|
|
llvm_bisection.main(args_output),
|
|
llvm_bisection.BisectionExitStatus.BISECTION_COMPLETE.value)
|
|
|
|
mock_outside_chroot.assert_called_once()
|
|
|
|
mock_load_status_file.assert_called_once()
|
|
|
|
mock_validate_start_and_end.assert_called_once()
|
|
|
|
mock_get_start_and_end_revision.assert_called_once()
|
|
|
|
mock_get_revision_and_hash_list.assert_called_once()
|
|
|
|
mock_note_completed_bisection.assert_called_once()
|
|
|
|
@mock.patch.object(llvm_bisection, 'DieWithNoRevisionsError')
|
|
# Simulate behavior of `_NoteCompletedBisection()` when there are no more
|
|
# tryjobs to launch between start and end, so bisection is complete.
|
|
@mock.patch.object(llvm_bisection, 'GetRevisionsListAndHashList')
|
|
@mock.patch.object(llvm_bisection, 'GetStartAndEndRevision')
|
|
# Simulate behavior of `_ValidateStartAndEndAgainstJSONStartAndEnd()` when
|
|
# both start and end revisions match.
|
|
@mock.patch.object(llvm_bisection,
|
|
'_ValidateStartAndEndAgainstJSONStartAndEnd')
|
|
@mock.patch.object(llvm_bisection, 'LoadStatusFile')
|
|
# Simulate behavior of `VerifyOutsideChroot()` when successfully invoked the
|
|
# script outside of the chroot.
|
|
@mock.patch.object(llvm_bisection, 'VerifyOutsideChroot', return_value=True)
|
|
def testNoMoreTryjobsToLaunch(
|
|
self, mock_outside_chroot, mock_load_status_file,
|
|
mock_validate_start_and_end, mock_get_start_and_end_revision,
|
|
mock_get_revision_and_hash_list, mock_die_with_no_revisions_error):
|
|
|
|
start = 500
|
|
end = 502
|
|
|
|
bisect_contents = {
|
|
'start': start,
|
|
'end': end,
|
|
'jobs': [{
|
|
'rev': 501,
|
|
'status': 'pending'
|
|
}]
|
|
}
|
|
|
|
skip_revisions = {}
|
|
pending_revisions = {501}
|
|
|
|
no_revisions_error_message = ('No more tryjobs to launch between %d and '
|
|
'%d' % (start, end))
|
|
|
|
def MockNoRevisionsErrorException(start, end, skip, pending):
|
|
raise ValueError(no_revisions_error_message)
|
|
|
|
# Simulate behavior of `LoadStatusFile()` when successfully loaded the
|
|
# status file.
|
|
mock_load_status_file.return_value = bisect_contents
|
|
|
|
# Simulate behavior of `GetStartAndEndRevision()` when successfully found
|
|
# the new start and end revision of the bisection.
|
|
#
|
|
# Returns new start revision, new end revision, a set of pending revisions,
|
|
# and a set of skip revisions.
|
|
mock_get_start_and_end_revision.return_value = (start, end,
|
|
pending_revisions,
|
|
skip_revisions)
|
|
|
|
# Simulate behavior of `GetRevisionsListAndHashList()` when successfully
|
|
# retrieved valid revisions (along with their git hashes) between start and
|
|
# end (in this case, none).
|
|
mock_get_revision_and_hash_list.return_value = [], []
|
|
|
|
# Use the test function to simulate `DieWithNoRevisionsWithError()`
|
|
# behavior.
|
|
mock_die_with_no_revisions_error.side_effect = MockNoRevisionsErrorException
|
|
|
|
# Simulate behavior of arguments passed into the command line.
|
|
args_output = ArgsOutputTest()
|
|
args_output.start_rev = start
|
|
args_output.end_rev = end
|
|
args_output.parallel = 3
|
|
args_output.src_path = None
|
|
|
|
# Verify the exception is raised when there are no more tryjobs to launch
|
|
# between start and end when there are tryjobs that are 'pending', so
|
|
# the actual bad revision can change when those tryjobs's 'status' are
|
|
# updated.
|
|
with self.assertRaises(ValueError) as err:
|
|
llvm_bisection.main(args_output)
|
|
|
|
self.assertEqual(str(err.exception), no_revisions_error_message)
|
|
|
|
mock_outside_chroot.assert_called_once()
|
|
|
|
mock_load_status_file.assert_called_once()
|
|
|
|
mock_validate_start_and_end.assert_called_once()
|
|
|
|
mock_get_start_and_end_revision.assert_called_once()
|
|
|
|
mock_get_revision_and_hash_list.assert_called_once()
|
|
|
|
mock_die_with_no_revisions_error.assert_called_once()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|