1*760c253cSXin Li#!/usr/bin/env python3 2*760c253cSXin Li# Copyright 2019 The ChromiumOS Authors 3*760c253cSXin Li# Use of this source code is governed by a BSD-style license that can be 4*760c253cSXin Li# found in the LICENSE file. 5*760c253cSXin Li 6*760c253cSXin Li# pylint: disable=protected-access 7*760c253cSXin Li 8*760c253cSXin Li"""Tests for LLVM bisection.""" 9*760c253cSXin Li 10*760c253cSXin Liimport json 11*760c253cSXin Liimport os 12*760c253cSXin Liimport subprocess 13*760c253cSXin Liimport unittest 14*760c253cSXin Lifrom unittest import mock 15*760c253cSXin Li 16*760c253cSXin Liimport chroot 17*760c253cSXin Liimport get_llvm_hash 18*760c253cSXin Liimport git_llvm_rev 19*760c253cSXin Liimport llvm_bisection 20*760c253cSXin Liimport modify_a_tryjob 21*760c253cSXin Liimport test_helpers 22*760c253cSXin Li 23*760c253cSXin Li 24*760c253cSXin Liclass LLVMBisectionTest(unittest.TestCase): 25*760c253cSXin Li """Unittests for LLVM bisection.""" 26*760c253cSXin Li 27*760c253cSXin Li def testGetRemainingRangePassed(self): 28*760c253cSXin Li start = 100 29*760c253cSXin Li end = 150 30*760c253cSXin Li 31*760c253cSXin Li test_tryjobs = [ 32*760c253cSXin Li { 33*760c253cSXin Li "rev": 110, 34*760c253cSXin Li "status": "good", 35*760c253cSXin Li "link": "https://some_tryjob_1_url.com", 36*760c253cSXin Li }, 37*760c253cSXin Li { 38*760c253cSXin Li "rev": 120, 39*760c253cSXin Li "status": "good", 40*760c253cSXin Li "link": "https://some_tryjob_2_url.com", 41*760c253cSXin Li }, 42*760c253cSXin Li { 43*760c253cSXin Li "rev": 130, 44*760c253cSXin Li "status": "pending", 45*760c253cSXin Li "link": "https://some_tryjob_3_url.com", 46*760c253cSXin Li }, 47*760c253cSXin Li { 48*760c253cSXin Li "rev": 135, 49*760c253cSXin Li "status": "skip", 50*760c253cSXin Li "link": "https://some_tryjob_4_url.com", 51*760c253cSXin Li }, 52*760c253cSXin Li { 53*760c253cSXin Li "rev": 140, 54*760c253cSXin Li "status": "bad", 55*760c253cSXin Li "link": "https://some_tryjob_5_url.com", 56*760c253cSXin Li }, 57*760c253cSXin Li ] 58*760c253cSXin Li 59*760c253cSXin Li # Tuple consists of the new good revision, the new bad revision, a set 60*760c253cSXin Li # of 'pending' revisions, and a set of 'skip' revisions. 61*760c253cSXin Li expected_revisions_tuple = 120, 140, {130}, {135} 62*760c253cSXin Li 63*760c253cSXin Li self.assertEqual( 64*760c253cSXin Li llvm_bisection.GetRemainingRange(start, end, test_tryjobs), 65*760c253cSXin Li expected_revisions_tuple, 66*760c253cSXin Li ) 67*760c253cSXin Li 68*760c253cSXin Li def testGetRemainingRangeFailedWithMissingStatus(self): 69*760c253cSXin Li start = 100 70*760c253cSXin Li end = 150 71*760c253cSXin Li 72*760c253cSXin Li test_tryjobs = [ 73*760c253cSXin Li { 74*760c253cSXin Li "rev": 105, 75*760c253cSXin Li "status": "good", 76*760c253cSXin Li "link": "https://some_tryjob_1_url.com", 77*760c253cSXin Li }, 78*760c253cSXin Li { 79*760c253cSXin Li "rev": 120, 80*760c253cSXin Li "status": None, 81*760c253cSXin Li "link": "https://some_tryjob_2_url.com", 82*760c253cSXin Li }, 83*760c253cSXin Li { 84*760c253cSXin Li "rev": 140, 85*760c253cSXin Li "status": "bad", 86*760c253cSXin Li "link": "https://some_tryjob_3_url.com", 87*760c253cSXin Li }, 88*760c253cSXin Li ] 89*760c253cSXin Li 90*760c253cSXin Li with self.assertRaises(ValueError) as err: 91*760c253cSXin Li llvm_bisection.GetRemainingRange(start, end, test_tryjobs) 92*760c253cSXin Li 93*760c253cSXin Li error_message = ( 94*760c253cSXin Li '"status" is missing or has no value, please ' 95*760c253cSXin Li "go to %s and update it" % test_tryjobs[1]["link"] 96*760c253cSXin Li ) 97*760c253cSXin Li self.assertEqual(str(err.exception), error_message) 98*760c253cSXin Li 99*760c253cSXin Li def testGetRemainingRangeFailedWithInvalidRange(self): 100*760c253cSXin Li start = 100 101*760c253cSXin Li end = 150 102*760c253cSXin Li 103*760c253cSXin Li test_tryjobs = [ 104*760c253cSXin Li { 105*760c253cSXin Li "rev": 110, 106*760c253cSXin Li "status": "bad", 107*760c253cSXin Li "link": "https://some_tryjob_1_url.com", 108*760c253cSXin Li }, 109*760c253cSXin Li { 110*760c253cSXin Li "rev": 125, 111*760c253cSXin Li "status": "skip", 112*760c253cSXin Li "link": "https://some_tryjob_2_url.com", 113*760c253cSXin Li }, 114*760c253cSXin Li { 115*760c253cSXin Li "rev": 140, 116*760c253cSXin Li "status": "good", 117*760c253cSXin Li "link": "https://some_tryjob_3_url.com", 118*760c253cSXin Li }, 119*760c253cSXin Li ] 120*760c253cSXin Li 121*760c253cSXin Li with self.assertRaises(AssertionError) as err: 122*760c253cSXin Li llvm_bisection.GetRemainingRange(start, end, test_tryjobs) 123*760c253cSXin Li 124*760c253cSXin Li expected_error_message = ( 125*760c253cSXin Li "Bisection is broken because %d (good) is >= " 126*760c253cSXin Li "%d (bad)" % (test_tryjobs[2]["rev"], test_tryjobs[0]["rev"]) 127*760c253cSXin Li ) 128*760c253cSXin Li 129*760c253cSXin Li self.assertEqual(str(err.exception), expected_error_message) 130*760c253cSXin Li 131*760c253cSXin Li @mock.patch.object(get_llvm_hash, "GetGitHashFrom") 132*760c253cSXin Li def testGetCommitsBetweenPassed(self, mock_get_git_hash): 133*760c253cSXin Li start = git_llvm_rev.base_llvm_revision 134*760c253cSXin Li end = start + 10 135*760c253cSXin Li test_pending_revisions = {start + 7} 136*760c253cSXin Li test_skip_revisions = { 137*760c253cSXin Li start + 1, 138*760c253cSXin Li start + 2, 139*760c253cSXin Li start + 4, 140*760c253cSXin Li start + 8, 141*760c253cSXin Li start + 9, 142*760c253cSXin Li } 143*760c253cSXin Li parallel = 3 144*760c253cSXin Li abs_path_to_src = "/abs/path/to/src" 145*760c253cSXin Li 146*760c253cSXin Li revs = ["a123testhash3", "a123testhash5"] 147*760c253cSXin Li mock_get_git_hash.side_effect = revs 148*760c253cSXin Li 149*760c253cSXin Li git_hashes = [ 150*760c253cSXin Li git_llvm_rev.base_llvm_revision + 3, 151*760c253cSXin Li git_llvm_rev.base_llvm_revision + 5, 152*760c253cSXin Li ] 153*760c253cSXin Li 154*760c253cSXin Li self.assertEqual( 155*760c253cSXin Li llvm_bisection.GetCommitsBetween( 156*760c253cSXin Li start, 157*760c253cSXin Li end, 158*760c253cSXin Li parallel, 159*760c253cSXin Li abs_path_to_src, 160*760c253cSXin Li test_pending_revisions, 161*760c253cSXin Li test_skip_revisions, 162*760c253cSXin Li ), 163*760c253cSXin Li (git_hashes, revs), 164*760c253cSXin Li ) 165*760c253cSXin Li 166*760c253cSXin Li def testLoadStatusFilePassedWithExistingFile(self): 167*760c253cSXin Li start = 100 168*760c253cSXin Li end = 150 169*760c253cSXin Li 170*760c253cSXin Li test_bisect_state = {"start": start, "end": end, "jobs": []} 171*760c253cSXin Li 172*760c253cSXin Li # Simulate that the status file exists. 173*760c253cSXin Li with test_helpers.CreateTemporaryJsonFile() as temp_json_file: 174*760c253cSXin Li with open(temp_json_file, "w", encoding="utf-8") as f: 175*760c253cSXin Li test_helpers.WritePrettyJsonFile(test_bisect_state, f) 176*760c253cSXin Li 177*760c253cSXin Li self.assertEqual( 178*760c253cSXin Li llvm_bisection.LoadStatusFile(temp_json_file, start, end), 179*760c253cSXin Li test_bisect_state, 180*760c253cSXin Li ) 181*760c253cSXin Li 182*760c253cSXin Li def testLoadStatusFilePassedWithoutExistingFile(self): 183*760c253cSXin Li start = 200 184*760c253cSXin Li end = 250 185*760c253cSXin Li 186*760c253cSXin Li expected_bisect_state = {"start": start, "end": end, "jobs": []} 187*760c253cSXin Li 188*760c253cSXin Li last_tested = "/abs/path/to/file_that_does_not_exist.json" 189*760c253cSXin Li 190*760c253cSXin Li self.assertEqual( 191*760c253cSXin Li llvm_bisection.LoadStatusFile(last_tested, start, end), 192*760c253cSXin Li expected_bisect_state, 193*760c253cSXin Li ) 194*760c253cSXin Li 195*760c253cSXin Li @mock.patch.object(modify_a_tryjob, "AddTryjob") 196*760c253cSXin Li def testBisectPassed(self, mock_add_tryjob): 197*760c253cSXin Li git_hash_list = ["a123testhash1", "a123testhash2", "a123testhash3"] 198*760c253cSXin Li revisions_list = [102, 104, 106] 199*760c253cSXin Li 200*760c253cSXin Li # Simulate behavior of `AddTryjob()` when successfully launched a 201*760c253cSXin Li # tryjob for the updated packages. 202*760c253cSXin Li @test_helpers.CallCountsToMockFunctions 203*760c253cSXin Li def MockAddTryjob( 204*760c253cSXin Li call_count, 205*760c253cSXin Li _packages, 206*760c253cSXin Li _git_hash, 207*760c253cSXin Li _revision, 208*760c253cSXin Li _chroot_path, 209*760c253cSXin Li _extra_cls, 210*760c253cSXin Li _options, 211*760c253cSXin Li _builder, 212*760c253cSXin Li _svn_revision, 213*760c253cSXin Li ): 214*760c253cSXin Li if call_count < 2: 215*760c253cSXin Li return {"rev": revisions_list[call_count], "status": "pending"} 216*760c253cSXin Li 217*760c253cSXin Li # Simulate an exception happened along the way when updating the 218*760c253cSXin Li # packages' `LLVM_NEXT_HASH`. 219*760c253cSXin Li if call_count == 2: 220*760c253cSXin Li raise ValueError("Unable to launch tryjob") 221*760c253cSXin Li 222*760c253cSXin Li assert False, "Called `AddTryjob()` more than expected." 223*760c253cSXin Li 224*760c253cSXin Li # Use the test function to simulate `AddTryjob()`. 225*760c253cSXin Li mock_add_tryjob.side_effect = MockAddTryjob 226*760c253cSXin Li 227*760c253cSXin Li start = 100 228*760c253cSXin Li end = 110 229*760c253cSXin Li 230*760c253cSXin Li bisection_contents = {"start": start, "end": end, "jobs": []} 231*760c253cSXin Li 232*760c253cSXin Li args_output = test_helpers.ArgsOutputTest() 233*760c253cSXin Li 234*760c253cSXin Li packages = ["sys-devel/llvm"] 235*760c253cSXin Li 236*760c253cSXin Li # Create a temporary .JSON file to simulate a status file for bisection. 237*760c253cSXin Li with test_helpers.CreateTemporaryJsonFile() as temp_json_file: 238*760c253cSXin Li with open(temp_json_file, "w", encoding="utf-8") as f: 239*760c253cSXin Li test_helpers.WritePrettyJsonFile(bisection_contents, f) 240*760c253cSXin Li 241*760c253cSXin Li # Verify that the status file is updated when an exception happened 242*760c253cSXin Li # when attempting to launch a revision (i.e. progress is not lost). 243*760c253cSXin Li with self.assertRaises(ValueError) as err: 244*760c253cSXin Li llvm_bisection.Bisect( 245*760c253cSXin Li revisions_list, 246*760c253cSXin Li git_hash_list, 247*760c253cSXin Li bisection_contents, 248*760c253cSXin Li temp_json_file, 249*760c253cSXin Li packages, 250*760c253cSXin Li args_output.chromeos_path, 251*760c253cSXin Li args_output.extra_change_lists, 252*760c253cSXin Li args_output.options, 253*760c253cSXin Li args_output.builders, 254*760c253cSXin Li ) 255*760c253cSXin Li 256*760c253cSXin Li expected_bisection_contents = { 257*760c253cSXin Li "start": start, 258*760c253cSXin Li "end": end, 259*760c253cSXin Li "jobs": [ 260*760c253cSXin Li {"rev": revisions_list[0], "status": "pending"}, 261*760c253cSXin Li {"rev": revisions_list[1], "status": "pending"}, 262*760c253cSXin Li ], 263*760c253cSXin Li } 264*760c253cSXin Li 265*760c253cSXin Li # Verify that the launched tryjobs were added to the status file 266*760c253cSXin Li # when an exception happened. 267*760c253cSXin Li with open(temp_json_file, encoding="utf-8") as f: 268*760c253cSXin Li json_contents = json.load(f) 269*760c253cSXin Li 270*760c253cSXin Li self.assertEqual(json_contents, expected_bisection_contents) 271*760c253cSXin Li 272*760c253cSXin Li self.assertEqual(str(err.exception), "Unable to launch tryjob") 273*760c253cSXin Li 274*760c253cSXin Li self.assertEqual(mock_add_tryjob.call_count, 3) 275*760c253cSXin Li 276*760c253cSXin Li @mock.patch.object(subprocess, "check_output", return_value=None) 277*760c253cSXin Li @mock.patch.object( 278*760c253cSXin Li get_llvm_hash.LLVMHash, "GetLLVMHash", return_value="a123testhash4" 279*760c253cSXin Li ) 280*760c253cSXin Li @mock.patch.object(llvm_bisection, "GetCommitsBetween") 281*760c253cSXin Li @mock.patch.object(llvm_bisection, "GetRemainingRange") 282*760c253cSXin Li @mock.patch.object(llvm_bisection, "LoadStatusFile") 283*760c253cSXin Li @mock.patch.object(chroot, "VerifyChromeOSRoot") 284*760c253cSXin Li @mock.patch.object(chroot, "VerifyOutsideChroot", return_value=True) 285*760c253cSXin Li def testMainPassed( 286*760c253cSXin Li self, 287*760c253cSXin Li mock_outside_chroot, 288*760c253cSXin Li mock_chromeos_root, 289*760c253cSXin Li mock_load_status_file, 290*760c253cSXin Li mock_get_range, 291*760c253cSXin Li mock_get_revision_and_hash_list, 292*760c253cSXin Li _mock_get_bad_llvm_hash, 293*760c253cSXin Li mock_abandon_cl, 294*760c253cSXin Li ): 295*760c253cSXin Li start = 500 296*760c253cSXin Li end = 502 297*760c253cSXin Li cl = 1 298*760c253cSXin Li 299*760c253cSXin Li bisect_state = { 300*760c253cSXin Li "start": start, 301*760c253cSXin Li "end": end, 302*760c253cSXin Li "jobs": [{"rev": 501, "status": "bad", "cl": cl}], 303*760c253cSXin Li } 304*760c253cSXin Li 305*760c253cSXin Li skip_revisions = {501} 306*760c253cSXin Li pending_revisions = {} 307*760c253cSXin Li 308*760c253cSXin Li mock_load_status_file.return_value = bisect_state 309*760c253cSXin Li 310*760c253cSXin Li mock_get_range.return_value = ( 311*760c253cSXin Li start, 312*760c253cSXin Li end, 313*760c253cSXin Li pending_revisions, 314*760c253cSXin Li skip_revisions, 315*760c253cSXin Li ) 316*760c253cSXin Li 317*760c253cSXin Li mock_get_revision_and_hash_list.return_value = [], [] 318*760c253cSXin Li 319*760c253cSXin Li args_output = test_helpers.ArgsOutputTest() 320*760c253cSXin Li args_output.start_rev = start 321*760c253cSXin Li args_output.end_rev = end 322*760c253cSXin Li args_output.parallel = 3 323*760c253cSXin Li args_output.src_path = None 324*760c253cSXin Li args_output.chromeos_path = "somepath" 325*760c253cSXin Li args_output.cleanup = True 326*760c253cSXin Li 327*760c253cSXin Li self.assertEqual( 328*760c253cSXin Li llvm_bisection.main(args_output), 329*760c253cSXin Li llvm_bisection.BisectionExitStatus.BISECTION_COMPLETE.value, 330*760c253cSXin Li ) 331*760c253cSXin Li 332*760c253cSXin Li mock_chromeos_root.assert_called_once() 333*760c253cSXin Li 334*760c253cSXin Li mock_outside_chroot.assert_called_once() 335*760c253cSXin Li 336*760c253cSXin Li mock_load_status_file.assert_called_once() 337*760c253cSXin Li 338*760c253cSXin Li mock_get_range.assert_called_once() 339*760c253cSXin Li 340*760c253cSXin Li mock_get_revision_and_hash_list.assert_called_once() 341*760c253cSXin Li 342*760c253cSXin Li mock_abandon_cl.assert_called_once() 343*760c253cSXin Li self.assertEqual( 344*760c253cSXin Li mock_abandon_cl.call_args, 345*760c253cSXin Li mock.call( 346*760c253cSXin Li [ 347*760c253cSXin Li os.path.join( 348*760c253cSXin Li args_output.chromeos_path, "chromite/bin/gerrit" 349*760c253cSXin Li ), 350*760c253cSXin Li "abandon", 351*760c253cSXin Li str(cl), 352*760c253cSXin Li ], 353*760c253cSXin Li stderr=subprocess.STDOUT, 354*760c253cSXin Li encoding="utf-8", 355*760c253cSXin Li ), 356*760c253cSXin Li ) 357*760c253cSXin Li 358*760c253cSXin Li @mock.patch.object(llvm_bisection, "LoadStatusFile") 359*760c253cSXin Li @mock.patch.object(chroot, "VerifyChromeOSRoot") 360*760c253cSXin Li @mock.patch.object(chroot, "VerifyOutsideChroot", return_value=True) 361*760c253cSXin Li def testMainFailedWithInvalidRange( 362*760c253cSXin Li self, mock_chromeos_root, mock_outside_chroot, mock_load_status_file 363*760c253cSXin Li ): 364*760c253cSXin Li start = 500 365*760c253cSXin Li end = 502 366*760c253cSXin Li 367*760c253cSXin Li bisect_state = { 368*760c253cSXin Li "start": start - 1, 369*760c253cSXin Li "end": end, 370*760c253cSXin Li } 371*760c253cSXin Li 372*760c253cSXin Li mock_load_status_file.return_value = bisect_state 373*760c253cSXin Li 374*760c253cSXin Li args_output = test_helpers.ArgsOutputTest() 375*760c253cSXin Li args_output.start_rev = start 376*760c253cSXin Li args_output.end_rev = end 377*760c253cSXin Li args_output.parallel = 3 378*760c253cSXin Li args_output.src_path = None 379*760c253cSXin Li 380*760c253cSXin Li with self.assertRaises(ValueError) as err: 381*760c253cSXin Li llvm_bisection.main(args_output) 382*760c253cSXin Li 383*760c253cSXin Li error_message = ( 384*760c253cSXin Li f"The start {start} or the end {end} version provided is " 385*760c253cSXin Li f'different than "start" {bisect_state["start"]} or "end" ' 386*760c253cSXin Li f'{bisect_state["end"]} in the .JSON file' 387*760c253cSXin Li ) 388*760c253cSXin Li 389*760c253cSXin Li self.assertEqual(str(err.exception), error_message) 390*760c253cSXin Li 391*760c253cSXin Li mock_chromeos_root.assert_called_once() 392*760c253cSXin Li 393*760c253cSXin Li mock_outside_chroot.assert_called_once() 394*760c253cSXin Li 395*760c253cSXin Li mock_load_status_file.assert_called_once() 396*760c253cSXin Li 397*760c253cSXin Li @mock.patch.object(llvm_bisection, "GetCommitsBetween") 398*760c253cSXin Li @mock.patch.object(llvm_bisection, "GetRemainingRange") 399*760c253cSXin Li @mock.patch.object(llvm_bisection, "LoadStatusFile") 400*760c253cSXin Li @mock.patch.object(chroot, "VerifyChromeOSRoot") 401*760c253cSXin Li @mock.patch.object(chroot, "VerifyOutsideChroot", return_value=True) 402*760c253cSXin Li def testMainFailedWithPendingBuilds( 403*760c253cSXin Li self, 404*760c253cSXin Li mock_chromeos_root, 405*760c253cSXin Li mock_outside_chroot, 406*760c253cSXin Li mock_load_status_file, 407*760c253cSXin Li mock_get_range, 408*760c253cSXin Li mock_get_revision_and_hash_list, 409*760c253cSXin Li ): 410*760c253cSXin Li start = 500 411*760c253cSXin Li end = 502 412*760c253cSXin Li rev = 501 413*760c253cSXin Li 414*760c253cSXin Li bisect_state = { 415*760c253cSXin Li "start": start, 416*760c253cSXin Li "end": end, 417*760c253cSXin Li "jobs": [{"rev": rev, "status": "pending"}], 418*760c253cSXin Li } 419*760c253cSXin Li 420*760c253cSXin Li skip_revisions = {} 421*760c253cSXin Li pending_revisions = {rev} 422*760c253cSXin Li 423*760c253cSXin Li mock_load_status_file.return_value = bisect_state 424*760c253cSXin Li 425*760c253cSXin Li mock_get_range.return_value = ( 426*760c253cSXin Li start, 427*760c253cSXin Li end, 428*760c253cSXin Li pending_revisions, 429*760c253cSXin Li skip_revisions, 430*760c253cSXin Li ) 431*760c253cSXin Li 432*760c253cSXin Li mock_get_revision_and_hash_list.return_value = [], [] 433*760c253cSXin Li 434*760c253cSXin Li args_output = test_helpers.ArgsOutputTest() 435*760c253cSXin Li args_output.start_rev = start 436*760c253cSXin Li args_output.end_rev = end 437*760c253cSXin Li args_output.parallel = 3 438*760c253cSXin Li args_output.src_path = None 439*760c253cSXin Li 440*760c253cSXin Li with self.assertRaises(ValueError) as err: 441*760c253cSXin Li llvm_bisection.main(args_output) 442*760c253cSXin Li 443*760c253cSXin Li error_message = ( 444*760c253cSXin Li f"No revisions between start {start} and end {end} to " 445*760c253cSXin Li "create tryjobs\nThe following tryjobs are pending:\n" 446*760c253cSXin Li f"{rev}\n" 447*760c253cSXin Li ) 448*760c253cSXin Li 449*760c253cSXin Li self.assertEqual(str(err.exception), error_message) 450*760c253cSXin Li 451*760c253cSXin Li mock_chromeos_root.assert_called_once() 452*760c253cSXin Li 453*760c253cSXin Li mock_outside_chroot.assert_called_once() 454*760c253cSXin Li 455*760c253cSXin Li mock_load_status_file.assert_called_once() 456*760c253cSXin Li 457*760c253cSXin Li mock_get_range.assert_called_once() 458*760c253cSXin Li 459*760c253cSXin Li mock_get_revision_and_hash_list.assert_called_once() 460*760c253cSXin Li 461*760c253cSXin Li @mock.patch.object(llvm_bisection, "GetCommitsBetween") 462*760c253cSXin Li @mock.patch.object(llvm_bisection, "GetRemainingRange") 463*760c253cSXin Li @mock.patch.object(llvm_bisection, "LoadStatusFile") 464*760c253cSXin Li @mock.patch.object(chroot, "VerifyChromeOSRoot") 465*760c253cSXin Li @mock.patch.object(chroot, "VerifyOutsideChroot", return_value=True) 466*760c253cSXin Li def testMainFailedWithDuplicateBuilds( 467*760c253cSXin Li self, 468*760c253cSXin Li mock_outside_chroot, 469*760c253cSXin Li mock_chromeos_root, 470*760c253cSXin Li mock_load_status_file, 471*760c253cSXin Li mock_get_range, 472*760c253cSXin Li mock_get_revision_and_hash_list, 473*760c253cSXin Li ): 474*760c253cSXin Li start = 500 475*760c253cSXin Li end = 502 476*760c253cSXin Li rev = 501 477*760c253cSXin Li git_hash = "a123testhash1" 478*760c253cSXin Li 479*760c253cSXin Li bisect_state = { 480*760c253cSXin Li "start": start, 481*760c253cSXin Li "end": end, 482*760c253cSXin Li "jobs": [{"rev": rev, "status": "pending"}], 483*760c253cSXin Li } 484*760c253cSXin Li 485*760c253cSXin Li skip_revisions = {} 486*760c253cSXin Li pending_revisions = {rev} 487*760c253cSXin Li 488*760c253cSXin Li mock_load_status_file.return_value = bisect_state 489*760c253cSXin Li 490*760c253cSXin Li mock_get_range.return_value = ( 491*760c253cSXin Li start, 492*760c253cSXin Li end, 493*760c253cSXin Li pending_revisions, 494*760c253cSXin Li skip_revisions, 495*760c253cSXin Li ) 496*760c253cSXin Li 497*760c253cSXin Li mock_get_revision_and_hash_list.return_value = [rev], [git_hash] 498*760c253cSXin Li 499*760c253cSXin Li args_output = test_helpers.ArgsOutputTest() 500*760c253cSXin Li args_output.start_rev = start 501*760c253cSXin Li args_output.end_rev = end 502*760c253cSXin Li args_output.parallel = 3 503*760c253cSXin Li args_output.src_path = None 504*760c253cSXin Li 505*760c253cSXin Li with self.assertRaises(ValueError) as err: 506*760c253cSXin Li llvm_bisection.main(args_output) 507*760c253cSXin Li 508*760c253cSXin Li error_message = 'Revision %d exists already in "jobs"' % rev 509*760c253cSXin Li self.assertEqual(str(err.exception), error_message) 510*760c253cSXin Li 511*760c253cSXin Li mock_chromeos_root.assert_called_once() 512*760c253cSXin Li 513*760c253cSXin Li mock_outside_chroot.assert_called_once() 514*760c253cSXin Li 515*760c253cSXin Li mock_load_status_file.assert_called_once() 516*760c253cSXin Li 517*760c253cSXin Li mock_get_range.assert_called_once() 518*760c253cSXin Li 519*760c253cSXin Li mock_get_revision_and_hash_list.assert_called_once() 520*760c253cSXin Li 521*760c253cSXin Li @mock.patch.object(subprocess, "check_output", return_value=None) 522*760c253cSXin Li @mock.patch.object( 523*760c253cSXin Li get_llvm_hash.LLVMHash, "GetLLVMHash", return_value="a123testhash4" 524*760c253cSXin Li ) 525*760c253cSXin Li @mock.patch.object(llvm_bisection, "GetCommitsBetween") 526*760c253cSXin Li @mock.patch.object(llvm_bisection, "GetRemainingRange") 527*760c253cSXin Li @mock.patch.object(llvm_bisection, "LoadStatusFile") 528*760c253cSXin Li @mock.patch.object(chroot, "VerifyChromeOSRoot") 529*760c253cSXin Li @mock.patch.object(chroot, "VerifyOutsideChroot", return_value=True) 530*760c253cSXin Li def testMainFailedToAbandonCL( 531*760c253cSXin Li self, 532*760c253cSXin Li mock_outside_chroot, 533*760c253cSXin Li mock_chromeos_root, 534*760c253cSXin Li mock_load_status_file, 535*760c253cSXin Li mock_get_range, 536*760c253cSXin Li mock_get_revision_and_hash_list, 537*760c253cSXin Li _mock_get_bad_llvm_hash, 538*760c253cSXin Li mock_abandon_cl, 539*760c253cSXin Li ): 540*760c253cSXin Li start = 500 541*760c253cSXin Li end = 502 542*760c253cSXin Li 543*760c253cSXin Li bisect_state = { 544*760c253cSXin Li "start": start, 545*760c253cSXin Li "end": end, 546*760c253cSXin Li "jobs": [{"rev": 501, "status": "bad", "cl": 0}], 547*760c253cSXin Li } 548*760c253cSXin Li 549*760c253cSXin Li skip_revisions = {501} 550*760c253cSXin Li pending_revisions = {} 551*760c253cSXin Li 552*760c253cSXin Li mock_load_status_file.return_value = bisect_state 553*760c253cSXin Li 554*760c253cSXin Li mock_get_range.return_value = ( 555*760c253cSXin Li start, 556*760c253cSXin Li end, 557*760c253cSXin Li pending_revisions, 558*760c253cSXin Li skip_revisions, 559*760c253cSXin Li ) 560*760c253cSXin Li 561*760c253cSXin Li mock_get_revision_and_hash_list.return_value = ([], []) 562*760c253cSXin Li 563*760c253cSXin Li error_message = "Error message." 564*760c253cSXin Li mock_abandon_cl.side_effect = subprocess.CalledProcessError( 565*760c253cSXin Li returncode=1, cmd=[], output=error_message 566*760c253cSXin Li ) 567*760c253cSXin Li 568*760c253cSXin Li args_output = test_helpers.ArgsOutputTest() 569*760c253cSXin Li args_output.start_rev = start 570*760c253cSXin Li args_output.end_rev = end 571*760c253cSXin Li args_output.parallel = 3 572*760c253cSXin Li args_output.src_path = None 573*760c253cSXin Li args_output.cleanup = True 574*760c253cSXin Li 575*760c253cSXin Li with self.assertRaises(subprocess.CalledProcessError) as err: 576*760c253cSXin Li llvm_bisection.main(args_output) 577*760c253cSXin Li 578*760c253cSXin Li self.assertEqual(err.exception.output, error_message) 579*760c253cSXin Li 580*760c253cSXin Li mock_chromeos_root.assert_called_once() 581*760c253cSXin Li 582*760c253cSXin Li mock_outside_chroot.assert_called_once() 583*760c253cSXin Li 584*760c253cSXin Li mock_load_status_file.assert_called_once() 585*760c253cSXin Li 586*760c253cSXin Li mock_get_range.assert_called_once() 587*760c253cSXin Li 588*760c253cSXin Li 589*760c253cSXin Liif __name__ == "__main__": 590*760c253cSXin Li unittest.main() 591