1#!/usr/bin/env python3 2# Copyright 2019 The ChromiumOS Authors 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Tests for modifying a tryjob.""" 7 8import json 9import unittest 10from unittest import mock 11 12import get_llvm_hash 13import modify_a_tryjob 14import test_helpers 15import update_packages_and_run_tests 16import update_tryjob_status 17 18 19class ModifyATryjobTest(unittest.TestCase): 20 """Unittests for modifying a tryjob.""" 21 22 def testNoTryjobsInStatusFile(self): 23 bisect_test_contents = {"start": 369410, "end": 369420, "jobs": []} 24 25 # Create a temporary .JSON file to simulate a .JSON file that has 26 # bisection contents. 27 with test_helpers.CreateTemporaryJsonFile() as temp_json_file: 28 with open(temp_json_file, "w", encoding="utf-8") as f: 29 test_helpers.WritePrettyJsonFile(bisect_test_contents, f) 30 31 revision_to_modify = 369411 32 33 args_output = test_helpers.ArgsOutputTest() 34 args_output.builders = None 35 args_output.options = None 36 37 # Verify the exception is raised there are no tryjobs in the status 38 # file and the mode is not to 'add' a tryjob. 39 with self.assertRaises(SystemExit) as err: 40 modify_a_tryjob.PerformTryjobModification( 41 revision_to_modify, 42 modify_a_tryjob.ModifyTryjob.REMOVE, 43 temp_json_file, 44 args_output.extra_change_lists, 45 args_output.options, 46 args_output.builders, 47 args_output.chromeos_path, 48 ) 49 50 self.assertEqual( 51 str(err.exception), "No tryjobs in %s" % temp_json_file 52 ) 53 54 # Simulate the behavior of `FindTryjobIndex()` when the index of the tryjob 55 # was not found. 56 @mock.patch.object( 57 update_tryjob_status, "FindTryjobIndex", return_value=None 58 ) 59 def testNoTryjobIndexFound(self, mock_find_tryjob_index): 60 bisect_test_contents = { 61 "start": 369410, 62 "end": 369420, 63 "jobs": [ 64 {"rev": 369411, "status": "pending", "buildbucket_id": 1200} 65 ], 66 } 67 68 # Create a temporary .JSON file to simulate a .JSON file that has 69 # bisection contents. 70 with test_helpers.CreateTemporaryJsonFile() as temp_json_file: 71 with open(temp_json_file, "w", encoding="utf-8") as f: 72 test_helpers.WritePrettyJsonFile(bisect_test_contents, f) 73 74 revision_to_modify = 369412 75 76 args_output = test_helpers.ArgsOutputTest() 77 args_output.builders = None 78 args_output.options = None 79 80 # Verify the exception is raised when the index of the tryjob was 81 # not found in the status file and the mode is not to 'add' a 82 # tryjob. 83 with self.assertRaises(ValueError) as err: 84 modify_a_tryjob.PerformTryjobModification( 85 revision_to_modify, 86 modify_a_tryjob.ModifyTryjob.REMOVE, 87 temp_json_file, 88 args_output.extra_change_lists, 89 args_output.options, 90 args_output.builders, 91 args_output.chromeos_path, 92 ) 93 94 self.assertEqual( 95 str(err.exception), 96 "Unable to find tryjob for %d in %s" 97 % (revision_to_modify, temp_json_file), 98 ) 99 100 mock_find_tryjob_index.assert_called_once() 101 102 # Simulate the behavior of `FindTryjobIndex()` when the index of the tryjob 103 # was found. 104 @mock.patch.object(update_tryjob_status, "FindTryjobIndex", return_value=0) 105 def testSuccessfullyRemovedTryjobInStatusFile(self, mock_find_tryjob_index): 106 bisect_test_contents = { 107 "start": 369410, 108 "end": 369420, 109 "jobs": [ 110 {"rev": 369414, "status": "pending", "buildbucket_id": 1200} 111 ], 112 } 113 114 # Create a temporary .JSON file to simulate a .JSON file that has 115 # bisection contents. 116 with test_helpers.CreateTemporaryJsonFile() as temp_json_file: 117 with open(temp_json_file, "w", encoding="utf-8") as f: 118 test_helpers.WritePrettyJsonFile(bisect_test_contents, f) 119 120 revision_to_modify = 369414 121 122 args_output = test_helpers.ArgsOutputTest() 123 args_output.builders = None 124 args_output.options = None 125 126 modify_a_tryjob.PerformTryjobModification( 127 revision_to_modify, 128 modify_a_tryjob.ModifyTryjob.REMOVE, 129 temp_json_file, 130 args_output.extra_change_lists, 131 args_output.options, 132 args_output.builders, 133 args_output.chromeos_path, 134 ) 135 136 # Verify that the tryjob was removed from the status file. 137 with open(temp_json_file, encoding="utf-8") as status_file: 138 bisect_contents = json.load(status_file) 139 140 expected_file_contents = { 141 "start": 369410, 142 "end": 369420, 143 "jobs": [], 144 } 145 146 self.assertDictEqual(bisect_contents, expected_file_contents) 147 148 mock_find_tryjob_index.assert_called_once() 149 150 # Simulate the behavior of `RunTryJobs()` when successfully submitted a 151 # tryjob. 152 @mock.patch.object(update_packages_and_run_tests, "RunTryJobs") 153 # Simulate the behavior of `FindTryjobIndex()` when the index of the tryjob 154 # was found. 155 @mock.patch.object(update_tryjob_status, "FindTryjobIndex", return_value=0) 156 def testSuccessfullyRelaunchedTryjob( 157 self, mock_find_tryjob_index, mock_run_tryjob 158 ): 159 bisect_test_contents = { 160 "start": 369410, 161 "end": 369420, 162 "jobs": [ 163 { 164 "rev": 369411, 165 "status": "bad", 166 "link": "https://some_tryjob_link.com", 167 "buildbucket_id": 1200, 168 "cl": 123, 169 "extra_cls": None, 170 "options": None, 171 "builder": ["some-builder-tryjob"], 172 } 173 ], 174 } 175 176 tryjob_result = [ 177 {"link": "https://some_new_tryjob_link.com", "buildbucket_id": 20} 178 ] 179 180 mock_run_tryjob.return_value = tryjob_result 181 182 # Create a temporary .JSON file to simulate a .JSON file that has 183 # bisection contents. 184 with test_helpers.CreateTemporaryJsonFile() as temp_json_file: 185 with open(temp_json_file, "w", encoding="utf-8") as f: 186 test_helpers.WritePrettyJsonFile(bisect_test_contents, f) 187 188 revision_to_modify = 369411 189 190 args_output = test_helpers.ArgsOutputTest() 191 args_output.builders = None 192 args_output.options = None 193 194 modify_a_tryjob.PerformTryjobModification( 195 revision_to_modify, 196 modify_a_tryjob.ModifyTryjob.RELAUNCH, 197 temp_json_file, 198 args_output.extra_change_lists, 199 args_output.options, 200 args_output.builders, 201 args_output.chromeos_path, 202 ) 203 204 # Verify that the tryjob's information was updated after submtting 205 # the tryjob. 206 with open(temp_json_file, encoding="utf-8") as status_file: 207 bisect_contents = json.load(status_file) 208 209 expected_file_contents = { 210 "start": 369410, 211 "end": 369420, 212 "jobs": [ 213 { 214 "rev": 369411, 215 "status": "pending", 216 "link": "https://some_new_tryjob_link.com", 217 "buildbucket_id": 20, 218 "cl": 123, 219 "extra_cls": None, 220 "options": None, 221 "builder": ["some-builder-tryjob"], 222 } 223 ], 224 } 225 226 self.assertDictEqual(bisect_contents, expected_file_contents) 227 228 mock_find_tryjob_index.assert_called_once() 229 230 mock_run_tryjob.assert_called_once() 231 232 # Simulate the behavior of `FindTryjobIndex()` when the index of the tryjob 233 # was found. 234 @mock.patch.object(update_tryjob_status, "FindTryjobIndex", return_value=0) 235 def testAddingTryjobThatAlreadyExists(self, mock_find_tryjob_index): 236 bisect_test_contents = { 237 "start": 369410, 238 "end": 369420, 239 "jobs": [ 240 {"rev": 369411, "status": "bad", "builder": ["some-builder"]} 241 ], 242 } 243 244 # Create a temporary .JSON file to simulate a .JSON file that has 245 # bisection contents. 246 with test_helpers.CreateTemporaryJsonFile() as temp_json_file: 247 with open(temp_json_file, "w", encoding="utf-8") as f: 248 test_helpers.WritePrettyJsonFile(bisect_test_contents, f) 249 250 revision_to_add = 369411 251 252 # Index of the tryjob in 'jobs' list. 253 tryjob_index = 0 254 255 args_output = test_helpers.ArgsOutputTest() 256 args_output.options = None 257 258 # Verify the exception is raised when the tryjob that is going to 259 # added already exists in the status file (found its index). 260 with self.assertRaises(ValueError) as err: 261 modify_a_tryjob.PerformTryjobModification( 262 revision_to_add, 263 modify_a_tryjob.ModifyTryjob.ADD, 264 temp_json_file, 265 args_output.extra_change_lists, 266 args_output.options, 267 args_output.builders, 268 args_output.chromeos_path, 269 ) 270 271 self.assertEqual( 272 str(err.exception), 273 "Tryjob already exists (index is %d) in %s." 274 % (tryjob_index, temp_json_file), 275 ) 276 277 mock_find_tryjob_index.assert_called_once() 278 279 # Simulate the behavior of `FindTryjobIndex()` when the tryjob was not 280 # found. 281 @mock.patch.object( 282 update_tryjob_status, "FindTryjobIndex", return_value=None 283 ) 284 def testSuccessfullyDidNotAddTryjobOutsideOfBisectionBounds( 285 self, mock_find_tryjob_index 286 ): 287 bisect_test_contents = { 288 "start": 369410, 289 "end": 369420, 290 "jobs": [{"rev": 369411, "status": "bad"}], 291 } 292 293 # Create a temporary .JSON file to simulate a .JSON file that has 294 # bisection contents. 295 with test_helpers.CreateTemporaryJsonFile() as temp_json_file: 296 with open(temp_json_file, "w", encoding="utf-8") as f: 297 test_helpers.WritePrettyJsonFile(bisect_test_contents, f) 298 299 # Add a revision that is outside of 'start' and 'end'. 300 revision_to_add = 369450 301 302 args_output = test_helpers.ArgsOutputTest() 303 args_output.options = None 304 305 # Verify the exception is raised when adding a tryjob that does not 306 # exist and is not within 'start' and 'end'. 307 with self.assertRaises(ValueError) as err: 308 modify_a_tryjob.PerformTryjobModification( 309 revision_to_add, 310 modify_a_tryjob.ModifyTryjob.ADD, 311 temp_json_file, 312 args_output.extra_change_lists, 313 args_output.options, 314 args_output.builders, 315 args_output.chromeos_path, 316 ) 317 318 self.assertEqual( 319 str(err.exception), 320 "Failed to add tryjob to %s" % temp_json_file, 321 ) 322 323 mock_find_tryjob_index.assert_called_once() 324 325 # Simulate the behavior of `AddTryjob()` when successfully submitted the 326 # tryjob and constructed the tryjob information (a dictionary). 327 @mock.patch.object(modify_a_tryjob, "AddTryjob") 328 # Simulate the behavior of `GetLLVMHashAndVersionFromSVNOption()` when 329 # successfully retrieved the git hash of the revision to launch a tryjob 330 # for. 331 @mock.patch.object( 332 get_llvm_hash, 333 "GetLLVMHashAndVersionFromSVNOption", 334 return_value=("a123testhash1", 369418), 335 ) 336 # Simulate the behavior of `FindTryjobIndex()` when the tryjob was not 337 # found. 338 @mock.patch.object( 339 update_tryjob_status, "FindTryjobIndex", return_value=None 340 ) 341 def testSuccessfullyAddedTryjob( 342 self, mock_find_tryjob_index, mock_get_llvm_hash, mock_add_tryjob 343 ): 344 bisect_test_contents = { 345 "start": 369410, 346 "end": 369420, 347 "jobs": [{"rev": 369411, "status": "bad"}], 348 } 349 350 # Create a temporary .JSON file to simulate a .JSON file that has 351 # bisection contents. 352 with test_helpers.CreateTemporaryJsonFile() as temp_json_file: 353 with open(temp_json_file, "w", encoding="utf-8") as f: 354 test_helpers.WritePrettyJsonFile(bisect_test_contents, f) 355 356 # Add a revision that is outside of 'start' and 'end'. 357 revision_to_add = 369418 358 359 args_output = test_helpers.ArgsOutputTest() 360 args_output.options = None 361 362 new_tryjob_info = { 363 "rev": revision_to_add, 364 "status": "pending", 365 "options": args_output.options, 366 "extra_cls": args_output.extra_change_lists, 367 "builder": args_output.builders, 368 } 369 370 mock_add_tryjob.return_value = new_tryjob_info 371 372 modify_a_tryjob.PerformTryjobModification( 373 revision_to_add, 374 modify_a_tryjob.ModifyTryjob.ADD, 375 temp_json_file, 376 args_output.extra_change_lists, 377 args_output.options, 378 args_output.builders, 379 args_output.chromeos_path, 380 ) 381 382 # Verify that the tryjob was added to the status file. 383 with open(temp_json_file, encoding="utf-8") as status_file: 384 bisect_contents = json.load(status_file) 385 386 expected_file_contents = { 387 "start": 369410, 388 "end": 369420, 389 "jobs": [{"rev": 369411, "status": "bad"}, new_tryjob_info], 390 } 391 392 self.assertDictEqual(bisect_contents, expected_file_contents) 393 394 mock_find_tryjob_index.assert_called_once() 395 396 mock_get_llvm_hash.assert_called_once_with(revision_to_add) 397 398 mock_add_tryjob.assert_called_once() 399 400 # Simulate the behavior of `FindTryjobIndex()` when the tryjob was found. 401 @mock.patch.object(update_tryjob_status, "FindTryjobIndex", return_value=0) 402 def testModifyATryjobOptionDoesNotExist(self, mock_find_tryjob_index): 403 bisect_test_contents = { 404 "start": 369410, 405 "end": 369420, 406 "jobs": [{"rev": 369414, "status": "bad"}], 407 } 408 409 # Create a temporary .JSON file to simulate a .JSON file that has 410 # bisection contents. 411 with test_helpers.CreateTemporaryJsonFile() as temp_json_file: 412 with open(temp_json_file, "w", encoding="utf-8") as f: 413 test_helpers.WritePrettyJsonFile(bisect_test_contents, f) 414 415 # Add a revision that is outside of 'start' and 'end'. 416 revision_to_modify = 369414 417 418 args_output = test_helpers.ArgsOutputTest() 419 args_output.builders = None 420 args_output.options = None 421 422 # Verify the exception is raised when the modify a tryjob option 423 # does not exist. 424 with self.assertRaises(ValueError) as err: 425 modify_a_tryjob.PerformTryjobModification( 426 revision_to_modify, 427 "remove_link", 428 temp_json_file, 429 args_output.extra_change_lists, 430 args_output.options, 431 args_output.builders, 432 args_output.chromeos_path, 433 ) 434 435 self.assertEqual( 436 str(err.exception), 437 'Invalid "modify_tryjob" option provided: remove_link', 438 ) 439 440 mock_find_tryjob_index.assert_called_once() 441 442 443if __name__ == "__main__": 444 unittest.main() 445