1#!/usr/bin/env python3 2# -*- coding: utf-8 -*- 3# Copyright 2019 The Chromium OS Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6 7"""Unittests for running tests after updating packages.""" 8 9from __future__ import print_function 10 11import json 12import subprocess 13import unittest 14import unittest.mock as mock 15 16import chroot 17import get_llvm_hash 18import git 19import test_helpers 20import update_chromeos_llvm_hash 21import update_packages_and_run_tests 22 23 24# Testing with tryjobs. 25class UpdatePackagesAndRunTryjobsTest(unittest.TestCase): 26 """Unittests when running tryjobs after updating packages.""" 27 28 def testNoLastTestedFile(self): 29 self.assertEqual( 30 update_packages_and_run_tests.UnchangedSinceLastRun(None, {}), False) 31 32 def testEmptyLastTestedFile(self): 33 with test_helpers.CreateTemporaryFile() as temp_file: 34 self.assertEqual( 35 update_packages_and_run_tests.UnchangedSinceLastRun(temp_file, {}), 36 False) 37 38 def testLastTestedFileDoesNotExist(self): 39 # Simulate 'open()' on a lasted tested file that does not exist. 40 mock.mock_open(read_data='') 41 42 self.assertEqual( 43 update_packages_and_run_tests.UnchangedSinceLastRun( 44 '/some/file/that/does/not/exist.txt', {}), False) 45 46 def testMatchedLastTestedFile(self): 47 with test_helpers.CreateTemporaryFile() as last_tested_file: 48 arg_dict = { 49 'svn_version': 1234, 50 'ebuilds': [ 51 '/path/to/package1-r2.ebuild', 52 '/path/to/package2/package2-r3.ebuild' 53 ], 54 'builders': [ 55 'kevin-llvm-next-toolchain-tryjob', 56 'eve-llvm-next-toolchain-tryjob' 57 ], 58 'extra_cls': [10, 1], 59 'tryjob_options': ['latest-toolchain', 'hwtest'] 60 } 61 62 with open(last_tested_file, 'w') as f: 63 f.write(json.dumps(arg_dict, indent=2)) 64 65 self.assertEqual( 66 update_packages_and_run_tests.UnchangedSinceLastRun( 67 last_tested_file, arg_dict), True) 68 69 def testGetTryJobCommandWithNoExtraInformation(self): 70 change_list = 1234 71 72 builder = 'nocturne' 73 74 expected_cmd = [ 75 'cros', 'tryjob', '--yes', '--json', '-g', 76 '%d' % change_list, builder 77 ] 78 79 self.assertEqual( 80 update_packages_and_run_tests.GetTryJobCommand(change_list, None, None, 81 builder), expected_cmd) 82 83 def testGetTryJobCommandWithExtraInformation(self): 84 change_list = 4321 85 extra_cls = [1000, 10] 86 options = ['option1', 'option2'] 87 builder = 'kevin' 88 89 expected_cmd = [ 90 'cros', 91 'tryjob', 92 '--yes', 93 '--json', 94 '-g', 95 '%d' % change_list, 96 '-g', 97 '%d' % extra_cls[0], 98 '-g', 99 '%d' % extra_cls[1], 100 '--%s' % options[0], 101 '--%s' % options[1], 102 builder, 103 ] 104 105 self.assertEqual( 106 update_packages_and_run_tests.GetTryJobCommand(change_list, extra_cls, 107 options, builder), 108 expected_cmd) 109 110 @mock.patch.object( 111 update_packages_and_run_tests, 112 'GetCurrentTimeInUTC', 113 return_value='2019-09-09') 114 @mock.patch.object(update_packages_and_run_tests, 'AddLinksToCL') 115 @mock.patch.object(subprocess, 'check_output') 116 def testSuccessfullySubmittedTryJob(self, mock_cmd, mock_add_links_to_cl, 117 mock_launch_time): 118 119 expected_cmd = [ 120 'cros', 'tryjob', '--yes', '--json', '-g', 121 '%d' % 900, '-g', 122 '%d' % 1200, '--some_option', 'builder1' 123 ] 124 125 bb_id = '1234' 126 url = 'http://ci.chromium.org/b/%s' % bb_id 127 128 mock_cmd.return_value = json.dumps([{'id': bb_id, 'url': url}]) 129 130 chroot_path = '/some/path/to/chroot' 131 cl = 900 132 extra_cls = [1200] 133 options = ['some_option'] 134 builders = ['builder1'] 135 136 tests = update_packages_and_run_tests.RunTryJobs(cl, extra_cls, options, 137 builders, chroot_path) 138 139 expected_tests = [{ 140 'launch_time': mock_launch_time.return_value, 141 'link': url, 142 'buildbucket_id': int(bb_id), 143 'extra_cls': extra_cls, 144 'options': options, 145 'builder': builders 146 }] 147 148 self.assertEqual(tests, expected_tests) 149 150 mock_cmd.assert_called_once_with( 151 expected_cmd, cwd=chroot_path, encoding='utf-8') 152 153 mock_add_links_to_cl.assert_called_once() 154 155 @mock.patch.object(update_packages_and_run_tests, 'AddLinksToCL') 156 @mock.patch.object(subprocess, 'check_output') 157 def testSuccessfullySubmittedRecipeBuilders(self, mock_cmd, 158 mock_add_links_to_cl): 159 160 expected_cmd = [ 161 'bb', 'add', '-json', '-cl', 162 'crrev.com/c/%s' % 900, '-cl', 163 'crrev.com/c/%s' % 1200, 'some_option', 'builder1' 164 ] 165 166 bb_id = '1234' 167 create_time = '2020-04-18T00:03:53.978767Z' 168 169 mock_cmd.return_value = json.dumps({'id': bb_id, 'createTime': create_time}) 170 171 chroot_path = '/some/path/to/chroot' 172 cl = 900 173 extra_cls = [1200] 174 options = ['some_option'] 175 builders = ['builder1'] 176 177 tests = update_packages_and_run_tests.StartRecipeBuilders( 178 cl, extra_cls, options, builders, chroot_path) 179 180 expected_tests = [{ 181 'launch_time': create_time, 182 'link': 'http://ci.chromium.org/b/%s' % bb_id, 183 'buildbucket_id': bb_id, 184 'extra_cls': extra_cls, 185 'options': options, 186 'builder': builders 187 }] 188 189 self.assertEqual(tests, expected_tests) 190 191 mock_cmd.assert_called_once_with( 192 expected_cmd, cwd=chroot_path, encoding='utf-8') 193 194 mock_add_links_to_cl.assert_called_once() 195 196 @mock.patch.object(subprocess, 'check_output', return_value=None) 197 def testSuccessfullyAddedTestLinkToCL(self, mock_exec_cmd): 198 chroot_path = '/abs/path/to/chroot' 199 200 test_cl_number = 1000 201 202 tests = [{'link': 'https://some_tryjob_link.com'}] 203 204 update_packages_and_run_tests.AddLinksToCL(tests, test_cl_number, 205 chroot_path) 206 207 expected_gerrit_message = [ 208 '%s/chromite/bin/gerrit' % chroot_path, 'message', 209 str(test_cl_number), 210 'Started the following tests:\n%s' % tests[0]['link'] 211 ] 212 213 mock_exec_cmd.assert_called_once_with(expected_gerrit_message) 214 215 @mock.patch.object(update_packages_and_run_tests, 'RunTryJobs') 216 @mock.patch.object(update_chromeos_llvm_hash, 'UpdatePackages') 217 @mock.patch.object(update_packages_and_run_tests, 'GetCommandLineArgs') 218 @mock.patch.object(get_llvm_hash, 'GetLLVMHashAndVersionFromSVNOption') 219 @mock.patch.object(chroot, 'VerifyOutsideChroot', return_value=True) 220 @mock.patch.object(chroot, 'GetChrootEbuildPaths') 221 def testUpdatedLastTestedFileWithNewTestedRevision( 222 self, mock_get_chroot_build_paths, mock_outside_chroot, 223 mock_get_hash_and_version, mock_get_commandline_args, 224 mock_update_packages, mock_run_tryjobs): 225 226 # Create a temporary file to simulate the last tested file that contains a 227 # revision. 228 with test_helpers.CreateTemporaryFile() as last_tested_file: 229 builders = [ 230 'kevin-llvm-next-toolchain-tryjob', 'eve-llvm-next-toolchain-tryjob' 231 ] 232 extra_cls = [10, 1] 233 tryjob_options = ['latest-toolchain', 'hwtest'] 234 ebuilds = [ 235 '/path/to/package1/package1-r2.ebuild', 236 '/path/to/package2/package2-r3.ebuild' 237 ] 238 239 arg_dict = { 240 'svn_version': 100, 241 'ebuilds': ebuilds, 242 'builders': builders, 243 'extra_cls': extra_cls, 244 'tryjob_options': tryjob_options 245 } 246 # Parepared last tested file 247 with open(last_tested_file, 'w') as f: 248 json.dump(arg_dict, f, indent=2) 249 250 # Call with a changed LLVM svn version 251 args_output = test_helpers.ArgsOutputTest() 252 args_output.is_llvm_next = True 253 args_output.extra_change_lists = extra_cls 254 args_output.last_tested = last_tested_file 255 args_output.reviewers = [] 256 257 args_output.subparser_name = 'tryjobs' 258 args_output.builders = builders 259 args_output.options = tryjob_options 260 261 mock_get_commandline_args.return_value = args_output 262 263 mock_get_chroot_build_paths.return_value = ebuilds 264 265 mock_get_hash_and_version.return_value = ('a123testhash2', 200) 266 267 mock_update_packages.return_value = git.CommitContents( 268 url='https://some_cl_url.com', cl_number=12345) 269 270 mock_run_tryjobs.return_value = [{ 271 'link': 'https://some_tryjob_url.com', 272 'buildbucket_id': 1234 273 }] 274 275 update_packages_and_run_tests.main() 276 277 # Verify that the lasted tested file has been updated to the new LLVM 278 # revision. 279 with open(last_tested_file) as f: 280 arg_dict = json.load(f) 281 282 self.assertEqual(arg_dict['svn_version'], 200) 283 284 mock_outside_chroot.assert_called_once() 285 286 mock_get_commandline_args.assert_called_once() 287 288 mock_get_hash_and_version.assert_called_once() 289 290 mock_run_tryjobs.assert_called_once() 291 292 mock_update_packages.assert_called_once() 293 294 295class UpdatePackagesAndRunTestCQTest(unittest.TestCase): 296 """Unittests for CQ dry run after updating packages.""" 297 298 def testGetCQDependString(self): 299 test_no_changelists = [] 300 test_single_changelist = [1234] 301 test_multiple_changelists = [1234, 5678] 302 303 self.assertIsNone( 304 update_packages_and_run_tests.GetCQDependString(test_no_changelists)) 305 306 self.assertEqual( 307 update_packages_and_run_tests.GetCQDependString(test_single_changelist), 308 '\nCq-Depend: chromium:1234') 309 310 self.assertEqual( 311 update_packages_and_run_tests.GetCQDependString( 312 test_multiple_changelists), 313 '\nCq-Depend: chromium:1234, chromium:5678') 314 315 def testGetCQIncludeTrybotsString(self): 316 test_no_trybot = None 317 test_valid_trybot = 'llvm-next' 318 test_invalid_trybot = 'invalid-name' 319 320 self.assertIsNone( 321 update_packages_and_run_tests.GetCQIncludeTrybotsString(test_no_trybot)) 322 323 self.assertEqual( 324 update_packages_and_run_tests.GetCQIncludeTrybotsString( 325 test_valid_trybot), 326 '\nCq-Include-Trybots:chromeos/cq:cq-llvm-next-orchestrator') 327 328 with self.assertRaises(ValueError) as context: 329 update_packages_and_run_tests.GetCQIncludeTrybotsString( 330 test_invalid_trybot) 331 332 self.assertIn('is not a valid llvm trybot', str(context.exception)) 333 334 @mock.patch.object(subprocess, 'check_output', return_value=None) 335 def testStartCQDryRunNoDeps(self, mock_exec_cmd): 336 chroot_path = '/abs/path/to/chroot' 337 test_cl_number = 1000 338 339 # test with no deps cls. 340 extra_cls = [] 341 update_packages_and_run_tests.StartCQDryRun(test_cl_number, extra_cls, 342 chroot_path) 343 344 expected_gerrit_message = [ 345 '%s/chromite/bin/gerrit' % chroot_path, 'label-cq', 346 str(test_cl_number), '1' 347 ] 348 349 mock_exec_cmd.assert_called_once_with(expected_gerrit_message) 350 351 # Mock ExecCommandAndCaptureOutput for the gerrit command execution. 352 @mock.patch.object(subprocess, 'check_output', return_value=None) 353 # test with a single deps cl. 354 def testStartCQDryRunSingleDep(self, mock_exec_cmd): 355 chroot_path = '/abs/path/to/chroot' 356 test_cl_number = 1000 357 358 extra_cls = [2000] 359 update_packages_and_run_tests.StartCQDryRun(test_cl_number, extra_cls, 360 chroot_path) 361 362 expected_gerrit_cmd_1 = [ 363 '%s/chromite/bin/gerrit' % chroot_path, 'label-cq', 364 str(test_cl_number), '1' 365 ] 366 expected_gerrit_cmd_2 = [ 367 '%s/chromite/bin/gerrit' % chroot_path, 'label-cq', 368 str(2000), '1' 369 ] 370 371 self.assertEqual(mock_exec_cmd.call_count, 2) 372 self.assertEqual(mock_exec_cmd.call_args_list[0], 373 mock.call(expected_gerrit_cmd_1)) 374 self.assertEqual(mock_exec_cmd.call_args_list[1], 375 mock.call(expected_gerrit_cmd_2)) 376 377 # Mock ExecCommandAndCaptureOutput for the gerrit command execution. 378 @mock.patch.object(subprocess, 'check_output', return_value=None) 379 def testStartCQDryRunMultipleDep(self, mock_exec_cmd): 380 chroot_path = '/abs/path/to/chroot' 381 test_cl_number = 1000 382 383 # test with multiple deps cls. 384 extra_cls = [3000, 4000] 385 update_packages_and_run_tests.StartCQDryRun(test_cl_number, extra_cls, 386 chroot_path) 387 388 expected_gerrit_cmd_1 = [ 389 '%s/chromite/bin/gerrit' % chroot_path, 'label-cq', 390 str(test_cl_number), '1' 391 ] 392 expected_gerrit_cmd_2 = [ 393 '%s/chromite/bin/gerrit' % chroot_path, 'label-cq', 394 str(3000), '1' 395 ] 396 expected_gerrit_cmd_3 = [ 397 '%s/chromite/bin/gerrit' % chroot_path, 'label-cq', 398 str(4000), '1' 399 ] 400 401 self.assertEqual(mock_exec_cmd.call_count, 3) 402 self.assertEqual(mock_exec_cmd.call_args_list[0], 403 mock.call(expected_gerrit_cmd_1)) 404 self.assertEqual(mock_exec_cmd.call_args_list[1], 405 mock.call(expected_gerrit_cmd_2)) 406 self.assertEqual(mock_exec_cmd.call_args_list[2], 407 mock.call(expected_gerrit_cmd_3)) 408 409 # Mock ExecCommandAndCaptureOutput for the gerrit command execution. 410 @mock.patch.object(subprocess, 'check_output', return_value=None) 411 # test with no reviewers. 412 def testAddReviewersNone(self, mock_exec_cmd): 413 chroot_path = '/abs/path/to/chroot' 414 reviewers = [] 415 test_cl_number = 1000 416 417 update_packages_and_run_tests.AddReviewers(test_cl_number, reviewers, 418 chroot_path) 419 self.assertTrue(mock_exec_cmd.not_called) 420 421 # Mock ExecCommandAndCaptureOutput for the gerrit command execution. 422 @mock.patch.object(subprocess, 'check_output', return_value=None) 423 # test with multiple reviewers. 424 def testAddReviewersMultiple(self, mock_exec_cmd): 425 chroot_path = '/abs/path/to/chroot' 426 reviewers = ['none1@chromium.org', 'none2@chromium.org'] 427 test_cl_number = 1000 428 429 update_packages_and_run_tests.AddReviewers(test_cl_number, reviewers, 430 chroot_path) 431 432 expected_gerrit_cmd_1 = [ 433 '%s/chromite/bin/gerrit' % chroot_path, 'reviewers', 434 str(test_cl_number), 'none1@chromium.org' 435 ] 436 expected_gerrit_cmd_2 = [ 437 '%s/chromite/bin/gerrit' % chroot_path, 'reviewers', 438 str(test_cl_number), 'none2@chromium.org' 439 ] 440 441 self.assertEqual(mock_exec_cmd.call_count, 2) 442 self.assertEqual(mock_exec_cmd.call_args_list[0], 443 mock.call(expected_gerrit_cmd_1)) 444 self.assertEqual(mock_exec_cmd.call_args_list[1], 445 mock.call(expected_gerrit_cmd_2)) 446 447 448if __name__ == '__main__': 449 unittest.main() 450