k8s.io/test-infra@v0.0.0-20240520184403-27c6b4c223d8/jenkins/bootstrap_test.py (about) 1 #!/usr/bin/env python 2 3 # Copyright 2016 The Kubernetes Authors. 4 # 5 # Licensed under the Apache License, Version 2.0 (the "License"); 6 # you may not use this file except in compliance with the License. 7 # You may obtain a copy of the License at 8 # 9 # http://www.apache.org/licenses/LICENSE-2.0 10 # 11 # Unless required by applicable law or agreed to in writing, software 12 # distributed under the License is distributed on an "AS IS" BASIS, 13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 # See the License for the specific language governing permissions and 15 # limitations under the License. 16 17 """Tests for bootstrap.""" 18 19 # pylint: disable=protected-access, attribute-defined-outside-init 20 21 import argparse 22 import json 23 import os 24 import select 25 import signal 26 import subprocess 27 import tempfile 28 import time 29 import unittest 30 31 import bootstrap 32 33 34 BRANCH = 'random_branch' 35 BUILD = 'random_build' 36 FAIL = ['/bin/bash', '-c', 'exit 1'] 37 JOB = 'random_job' 38 PASS = ['/bin/bash', '-c', 'exit 0'] 39 PULL = 12345 40 REPO = 'github.com/random_org/random_repo' 41 ROBOT = 'fake-service-account.json' 42 ROOT = '/random/root' 43 UPLOAD = 'fake-gs://fake-bucket' 44 45 46 class Stub(object): 47 """Replace thing.param with replacement until exiting with.""" 48 def __init__(self, thing, param, replacement): 49 self.thing = thing 50 self.param = param 51 self.replacement = replacement 52 self.old = getattr(thing, param) 53 setattr(thing, param, self.replacement) 54 55 def __enter__(self, *a, **kw): 56 return self.replacement 57 58 def __exit__(self, *a, **kw): 59 setattr(self.thing, self.param, self.old) 60 61 62 class FakeCall(object): 63 def __init__(self): 64 self.calls = [] 65 66 def __call__(self, *a, **kw): 67 self.calls.append((a, kw)) 68 69 70 class FakeSubprocess(object): 71 """Keep track of calls.""" 72 def __init__(self): 73 self.calls = [] 74 self.file_data = [] 75 self.output = {} 76 77 def __call__(self, cmd, *a, **kw): 78 self.calls.append((cmd, a, kw)) 79 for arg in cmd: 80 if arg.startswith('/') and os.path.exists(arg): 81 self.file_data.append(open(arg).read()) 82 if kw.get('output') and self.output.get(cmd[0]): 83 return self.output[cmd[0]].pop(0) 84 return None 85 86 87 # pylint: disable=invalid-name 88 def Pass(*_a, **_kw): 89 """Do nothing.""" 90 pass 91 92 93 def Truth(*_a, **_kw): 94 """Always true.""" 95 return True 96 97 98 def Bomb(*a, **kw): 99 """Always raise.""" 100 raise AssertionError('Should not happen', a, kw) 101 # pylint: enable=invalid-name 102 103 104 class ReadAllTest(unittest.TestCase): 105 endless = 0 106 ended = time.time() - 50 107 number = 0 108 end = -1 109 110 def fileno(self): 111 return self.end 112 113 def readline(self): 114 line = b'line %d\n' % self.number 115 self.number += 1 116 return line 117 118 def test_read_more(self): 119 """Read lines until we clear the buffer, noting there may be more.""" 120 lines = [] 121 total = 10 122 def more_lines(*_a, **_kw): 123 if len(lines) < total: 124 return [self], [], [] 125 return [], [], [] 126 with Stub(select, 'select', more_lines): 127 done = bootstrap.read_all(self.endless, self, lines.append) 128 129 self.assertFalse(done) 130 self.assertEqual(total, len(lines)) 131 expected = ['line %d' % d for d in range(total)] 132 self.assertEqual(expected, lines) 133 134 def test_read_expired(self): 135 """Read nothing as we are expired, noting there may be more.""" 136 lines = [] 137 with Stub(select, 'select', lambda *a, **kw: ([], [], [])): 138 done = bootstrap.read_all(self.ended, self, lines.append) 139 140 self.assertFalse(done) 141 self.assertFalse(lines) 142 143 def test_read_end(self): 144 """Note we reached the end of the stream.""" 145 lines = [] 146 with Stub(select, 'select', lambda *a, **kw: ([self], [], [])): 147 with Stub(self, 'readline', lambda: ''): 148 done = bootstrap.read_all(self.endless, self, lines.append) 149 150 self.assertTrue(done) 151 152 153 class TerminateTest(unittest.TestCase): 154 """Tests for termiante().""" 155 pid = 1234 156 pgid = 5555 157 terminated = False 158 killed = False 159 160 def terminate(self): 161 self.terminated = True 162 163 def kill(self): 164 self.killed = True 165 166 def getpgid(self, pid): 167 self.got = pid 168 return self.pgid 169 170 def killpg(self, pgig, sig): 171 self.killed_pg = (pgig, sig) 172 173 def test_terminate_later(self): 174 """Do nothing if end is in the future.""" 175 timeout = bootstrap.terminate(time.time() + 50, self, False) 176 self.assertFalse(timeout) 177 178 def test_terminate_never(self): 179 """Do nothing if end is zero.""" 180 timeout = bootstrap.terminate(0, self, False) 181 self.assertFalse(timeout) 182 183 def test_terminate_terminate(self): 184 """Terminate pid if after end and kill is false.""" 185 timeout = bootstrap.terminate(time.time() - 50, self, False) 186 self.assertTrue(timeout) 187 self.assertFalse(self.killed) 188 self.assertTrue(self.terminated) 189 190 def test_terminate_kill(self): 191 """Kill process group if after end and kill is true.""" 192 with Stub(os, 'getpgid', self.getpgid), Stub(os, 'killpg', self.killpg): 193 timeout = bootstrap.terminate(time.time() - 50, self, True) 194 self.assertTrue(timeout) 195 self.assertFalse(self.terminated) 196 self.assertTrue(self.killed) 197 self.assertEqual(self.pid, self.got) 198 self.assertEqual(self.killed_pg, (self.pgid, signal.SIGKILL)) 199 200 201 class SubprocessTest(unittest.TestCase): 202 """Tests for call().""" 203 204 def test_stdin(self): 205 """Will write to subprocess.stdin.""" 206 with self.assertRaises(subprocess.CalledProcessError) as cpe: 207 bootstrap._call(0, ['/bin/bash'], stdin='exit 92') 208 self.assertEqual(92, cpe.exception.returncode) 209 210 def test_check_true(self): 211 """Raise on non-zero exit codes if check is set.""" 212 with self.assertRaises(subprocess.CalledProcessError): 213 bootstrap._call(0, FAIL, check=True) 214 215 bootstrap._call(0, PASS, check=True) 216 217 def test_check_default(self): 218 """Default to check=True.""" 219 with self.assertRaises(subprocess.CalledProcessError): 220 bootstrap._call(0, FAIL) 221 222 bootstrap._call(0, PASS) 223 224 @staticmethod 225 def test_check_false(): 226 """Never raise when check is not set.""" 227 bootstrap._call(0, FAIL, check=False) 228 bootstrap._call(0, PASS, check=False) 229 230 def test_output(self): 231 """Output is returned when requested.""" 232 cmd = ['/bin/bash', '-c', 'echo hello world'] 233 self.assertEqual( 234 'hello world\n', bootstrap._call(0, cmd, output=True)) 235 236 def test_zombie(self): 237 with self.assertRaises(subprocess.CalledProcessError): 238 # make a zombie 239 bootstrap._call(0, ['/bin/bash', '-c', 'A=$BASHPID && ( kill -STOP $A ) & exit 1']) 240 241 242 class PullRefsTest(unittest.TestCase): 243 """Tests for pull_ref, branch_ref, ref_has_shas, and pull_numbers.""" 244 245 def test_multiple_no_shas(self): 246 """Test master,1111,2222.""" 247 self.assertEqual( 248 bootstrap.pull_ref('master,123,456'), 249 ([ 250 'master', 251 '+refs/pull/123/head:refs/pr/123', 252 '+refs/pull/456/head:refs/pr/456', 253 ], [ 254 'FETCH_HEAD', 255 'refs/pr/123', 256 'refs/pr/456', 257 ]), 258 ) 259 260 def test_pull_has_shas(self): 261 self.assertTrue(bootstrap.ref_has_shas('master:abcd')) 262 self.assertFalse(bootstrap.ref_has_shas('123')) 263 self.assertFalse(bootstrap.ref_has_shas(123)) 264 self.assertFalse(bootstrap.ref_has_shas(None)) 265 266 def test_pull_numbers(self): 267 self.assertListEqual(bootstrap.pull_numbers(123), ['123']) 268 self.assertListEqual(bootstrap.pull_numbers('master:abcd'), []) 269 self.assertListEqual( 270 bootstrap.pull_numbers('master:abcd,123:qwer,124:zxcv'), 271 ['123', '124']) 272 273 def test_pull_ref(self): 274 self.assertEqual( 275 bootstrap.pull_ref('master:abcd,123:effe'), 276 (['master', '+refs/pull/123/head:refs/pr/123'], ['abcd', 'effe']) 277 ) 278 self.assertEqual( 279 bootstrap.pull_ref('123'), 280 (['+refs/pull/123/merge'], ['FETCH_HEAD']) 281 ) 282 283 def test_branch_ref(self): 284 self.assertEqual( 285 bootstrap.branch_ref('branch:abcd'), 286 (['branch'], ['abcd']) 287 ) 288 self.assertEqual( 289 bootstrap.branch_ref('master'), 290 (['master'], ['FETCH_HEAD']) 291 ) 292 293 294 class ConfigureSshKeyTest(unittest.TestCase): 295 """Tests for configure_ssh_key().""" 296 def test_empty(self): 297 """Do not change environ if no ssh key.""" 298 fake_env = {} 299 with Stub(os, 'environ', fake_env): 300 with bootstrap.configure_ssh_key(''): 301 self.assertFalse(fake_env) 302 303 def test_full(self): 304 fake_env = {} 305 with Stub(os, 'environ', fake_env): 306 with bootstrap.configure_ssh_key('hello there'): 307 self.assertIn('GIT_SSH', fake_env) 308 with open(fake_env['GIT_SSH']) as fp: 309 buf = fp.read() 310 self.assertIn('hello there', buf) 311 self.assertIn('ssh ', buf) 312 self.assertIn(' -i ', buf) 313 self.assertFalse(fake_env) # Resets env 314 315 def test_full_old_value(self): 316 fake_env = {'GIT_SSH': 'random-value'} 317 old_env = dict(fake_env) 318 with Stub(os, 'environ', fake_env): 319 with bootstrap.configure_ssh_key('hello there'): 320 self.assertNotEqual(old_env, fake_env) 321 self.assertEqual(old_env, fake_env) 322 323 324 class CheckoutTest(unittest.TestCase): 325 """Tests for checkout().""" 326 327 def test_clean(self): 328 """checkout cleans and resets if asked to.""" 329 fake = FakeSubprocess() 330 with Stub(os, 'chdir', Pass): 331 bootstrap.checkout(fake, REPO, REPO, None, PULL, clean=True) 332 333 self.assertTrue(any( 334 'clean' in cmd for cmd, _, _ in fake.calls if 'git' in cmd)) 335 self.assertTrue(any( 336 'reset' in cmd for cmd, _, _ in fake.calls if 'git' in cmd)) 337 338 def test_fetch_retries(self): 339 self.tries = 0 340 expected_attempts = 3 341 def third_time_charm(cmd, *_a, **_kw): 342 if 'fetch' not in cmd: # init/checkout are unlikely to fail 343 return 344 self.tries += 1 345 if self.tries != expected_attempts: 346 raise subprocess.CalledProcessError(128, cmd, None) 347 with Stub(os, 'chdir', Pass): 348 with Stub(time, 'sleep', Pass): 349 bootstrap.checkout(third_time_charm, REPO, REPO, None, PULL) 350 self.assertEqual(expected_attempts, self.tries) 351 352 def test_pull_ref(self): 353 """checkout fetches the right ref for a pull.""" 354 fake = FakeSubprocess() 355 with Stub(os, 'chdir', Pass): 356 bootstrap.checkout(fake, REPO, REPO, None, PULL) 357 358 expected_ref = bootstrap.pull_ref(PULL)[0][0] 359 self.assertTrue(any( 360 expected_ref in cmd for cmd, _, _ in fake.calls if 'fetch' in cmd)) 361 362 def test_branch(self): 363 """checkout fetches the right ref for a branch.""" 364 fake = FakeSubprocess() 365 with Stub(os, 'chdir', Pass): 366 bootstrap.checkout(fake, REPO, REPO, BRANCH, None) 367 368 expected_ref = BRANCH 369 self.assertTrue(any( 370 expected_ref in cmd for cmd, _, _ in fake.calls if 'fetch' in cmd)) 371 372 def test_repo(self): 373 """checkout initializes and fetches the right repo.""" 374 fake = FakeSubprocess() 375 with Stub(os, 'chdir', Pass): 376 bootstrap.checkout(fake, REPO, REPO, BRANCH, None) 377 378 expected_uri = 'https://%s' % REPO 379 self.assertTrue(any( 380 expected_uri in cmd for cmd, _, _ in fake.calls if 'fetch' in cmd)) 381 382 def test_branch_xor_pull(self): 383 """Either branch or pull specified, not both.""" 384 with Stub(os, 'chdir', Bomb): 385 with self.assertRaises(ValueError): 386 bootstrap.checkout(Bomb, REPO, REPO, None, None) 387 with self.assertRaises(ValueError): 388 bootstrap.checkout(Bomb, REPO, REPO, BRANCH, PULL) 389 390 def test_happy(self): 391 """checkout sanity check.""" 392 fake = FakeSubprocess() 393 with Stub(os, 'chdir', Pass): 394 bootstrap.checkout(fake, REPO, REPO, BRANCH, None) 395 396 self.assertTrue(any( 397 '--tags' in cmd for cmd, _, _ in fake.calls if 'fetch' in cmd)) 398 self.assertTrue(any( 399 'FETCH_HEAD' in cmd for cmd, _, _ in fake.calls 400 if 'checkout' in cmd)) 401 402 def test_repo_path(self): 403 """checkout repo to different local path.""" 404 fake = FakeSubprocess() 405 repo_path = "foo/bar" 406 with Stub(os, 'chdir', Pass): 407 bootstrap.checkout(fake, REPO, repo_path, BRANCH, None) 408 409 expected_uri = 'https://%s' % REPO 410 self.assertTrue(any( 411 expected_uri in cmd for cmd, _, _ in fake.calls if 'fetch' in cmd)) 412 413 self.assertTrue(any( 414 repo_path in cmd for cmd, _, _ in fake.calls if 'init' in cmd)) 415 416 class ParseReposTest(unittest.TestCase): 417 def test_bare(self): 418 """--bare works.""" 419 args = bootstrap.parse_args(['--job=foo', '--bare']) 420 self.assertFalse(bootstrap.parse_repos(args)) 421 422 def test_pull_branch_none(self): 423 """args.pull and args.branch should be None""" 424 args = bootstrap.parse_args(['--job=foo', '--bare']) 425 self.assertIsNone(args.pull) 426 self.assertIsNone(args.branch) 427 428 def test_plain(self): 429 """"--repo=foo equals foo=master.""" 430 args = bootstrap.parse_args(['--job=foo', '--repo=foo']) 431 self.assertEqual( 432 {'foo': ('master', '')}, 433 bootstrap.parse_repos(args)) 434 435 def test_branch(self): 436 """--repo=foo=branch.""" 437 args = bootstrap.parse_args(['--job=foo', '--repo=foo=this']) 438 self.assertEqual( 439 {'foo': ('this', '')}, 440 bootstrap.parse_repos(args)) 441 442 def test_branch_commit(self): 443 """--repo=foo=branch:commit works.""" 444 args = bootstrap.parse_args(['--job=foo', '--repo=foo=this:abcd']) 445 self.assertEqual( 446 {'foo': ('this:abcd', '')}, 447 bootstrap.parse_repos(args)) 448 449 def test_parse_repos(self): 450 """--repo=foo=111,222 works""" 451 args = bootstrap.parse_args(['--job=foo', '--repo=foo=111,222']) 452 self.assertEqual( 453 {'foo': ('', '111,222')}, 454 bootstrap.parse_repos(args)) 455 456 def test_pull_branch(self): 457 """--repo=foo=master,111,222 works""" 458 args = bootstrap.parse_args(['--job=foo', '--repo=foo=master,111,222']) 459 self.assertEqual( 460 {'foo': ('', 'master,111,222')}, 461 bootstrap.parse_repos(args)) 462 463 def test_pull_release_branch(self): 464 """--repo=foo=release-3.14,&a-fancy%_branch+:abcd,222 works""" 465 args = bootstrap.parse_args(['--job=foo', 466 '--repo=foo=release-3.14,&a-fancy%_branch+:abcd,222']) 467 self.assertEqual( 468 {'foo': ('', 'release-3.14,&a-fancy%_branch+:abcd,222')}, 469 bootstrap.parse_repos(args)) 470 471 def test_pull_branch_commit(self): 472 """--repo=foo=master,111,222 works""" 473 args = bootstrap.parse_args(['--job=foo', 474 '--repo=foo=master:aaa,111:bbb,222:ccc']) 475 self.assertEqual( 476 {'foo': ('', 'master:aaa,111:bbb,222:ccc')}, 477 bootstrap.parse_repos(args)) 478 479 def test_multi_repo(self): 480 """--repo=foo=master,111,222 bar works""" 481 args = bootstrap.parse_args(['--job=foo', 482 '--repo=foo=master:aaa,111:bbb,222:ccc', 483 '--repo=bar']) 484 self.assertEqual( 485 { 486 'foo': ('', 'master:aaa,111:bbb,222:ccc'), 487 'bar': ('master', '')}, 488 bootstrap.parse_repos(args)) 489 490 491 class GSUtilTest(unittest.TestCase): 492 """Tests for GSUtil.""" 493 def test_upload_json(self): 494 fake = FakeSubprocess() 495 gsutil = bootstrap.GSUtil(fake) 496 gsutil.upload_json('fake_path', {'wee': 'fun'}) 497 self.assertTrue(any( 498 'application/json' in a for a in fake.calls[0][0])) 499 self.assertEqual(fake.file_data, ['{\n "wee": "fun"\n}']) 500 501 def test_upload_text_cached(self): 502 fake = FakeSubprocess() 503 gsutil = bootstrap.GSUtil(fake) 504 gsutil.upload_text('fake_path', 'hello world', cached=True) 505 self.assertFalse(any( 506 'Cache-Control' in a and 'max-age' in a 507 for a in fake.calls[0][0])) 508 self.assertEqual(fake.file_data, ['hello world']) 509 510 def test_upload_text_default(self): 511 fake = FakeSubprocess() 512 gsutil = bootstrap.GSUtil(fake) 513 gsutil.upload_text('fake_path', 'hello world') 514 self.assertFalse(any( 515 'Cache-Control' in a and 'max-age' in a 516 for a in fake.calls[0][0])) 517 self.assertEqual(fake.file_data, ['hello world']) 518 519 def test_upload_text_uncached(self): 520 fake = FakeSubprocess() 521 gsutil = bootstrap.GSUtil(fake) 522 gsutil.upload_text('fake_path', 'hello world', cached=False) 523 self.assertTrue(any( 524 'Cache-Control' in a and 'max-age' in a 525 for a in fake.calls[0][0])) 526 self.assertEqual(fake.file_data, ['hello world']) 527 528 def test_upload_text_metalink(self): 529 fake = FakeSubprocess() 530 gsutil = bootstrap.GSUtil(fake) 531 gsutil.upload_text('txt', 'path', additional_headers=['foo: bar']) 532 self.assertTrue(any('foo: bar' in a for a in fake.calls[0][0])) 533 self.assertEqual(fake.file_data, ['path']) 534 535 class FakeGSUtil(object): 536 generation = 123 537 538 def __init__(self): 539 self.cats = [] 540 self.jsons = [] 541 self.stats = [] 542 self.texts = [] 543 544 def cat(self, *a, **kw): 545 self.cats.append((a, kw)) 546 return 'this is not a list' 547 548 def stat(self, *a, **kw): 549 self.stats.append((a, kw)) 550 return 'Generation: %s' % self.generation 551 552 def upload_text(self, *args, **kwargs): 553 self.texts.append((args, kwargs)) 554 555 def upload_json(self, *args, **kwargs): 556 self.jsons.append((args, kwargs)) 557 558 class GubernatorUriTest(unittest.TestCase): 559 def create_path(self, uri): 560 self.fake_path = FakePath() 561 self.fake_path.build_log = uri 562 return self.fake_path 563 564 def test_non_gs(self): 565 uri = 'hello/world' 566 self.assertEqual('hello', bootstrap.gubernator_uri(self.create_path(uri))) 567 568 def test_multiple_gs(self): 569 uri = 'gs://hello/gs://there' 570 self.assertEqual( 571 bootstrap.GUBERNATOR + '/hello/gs:', 572 bootstrap.gubernator_uri(self.create_path(uri))) 573 574 def test_gs(self): 575 uri = 'gs://blah/blah/blah.txt' 576 self.assertEqual( 577 bootstrap.GUBERNATOR + '/blah/blah', 578 bootstrap.gubernator_uri(self.create_path(uri))) 579 580 581 class AppendResultTest(unittest.TestCase): 582 """Tests for append_result().""" 583 584 def test_new_job(self): 585 """Stat fails when the job doesn't exist.""" 586 gsutil = FakeGSUtil() 587 build = 123 588 version = 'v.interesting' 589 success = True 590 def fake_stat(*_a, **_kw): 591 raise subprocess.CalledProcessError(1, ['gsutil'], None) 592 gsutil.stat = fake_stat 593 bootstrap.append_result(gsutil, 'fake_path', build, version, success) 594 cache = gsutil.jsons[0][0][1] 595 self.assertEqual(1, len(cache)) 596 597 def test_collision_cat(self): 598 """cat fails if the cache has been updated.""" 599 gsutil = FakeGSUtil() 600 build = 42 601 version = 'v1' 602 success = False 603 generations = ['555', '444'] 604 orig_stat = gsutil.stat 605 def fake_stat(*a, **kw): 606 gsutil.generation = generations.pop() 607 return orig_stat(*a, **kw) 608 def fake_cat(_, gen): 609 if gen == '555': # Which version is requested? 610 return '[{"hello": 111}]' 611 raise subprocess.CalledProcessError(1, ['gsutil'], None) 612 with Stub(bootstrap, 'random_sleep', Pass): 613 with Stub(gsutil, 'stat', fake_stat): 614 with Stub(gsutil, 'cat', fake_cat): 615 bootstrap.append_result( 616 gsutil, 'fake_path', build, version, success) 617 self.assertIn('generation', gsutil.jsons[-1][1], gsutil.jsons) 618 self.assertEqual('555', gsutil.jsons[-1][1]['generation'], gsutil.jsons) 619 620 def test_collision_upload(self): 621 """Test when upload_json tries to update an old version.""" 622 gsutil = FakeGSUtil() 623 build = 42 624 version = 'v1' 625 success = False 626 generations = [555, 444] 627 orig = gsutil.upload_json 628 def fake_upload(path, cache, generation): 629 if generation == '555': 630 return orig(path, cache, generation=generation) 631 raise subprocess.CalledProcessError(128, ['gsutil'], None) 632 orig_stat = gsutil.stat 633 def fake_stat(*a, **kw): 634 gsutil.generation = generations.pop() 635 return orig_stat(*a, **kw) 636 def fake_cat(*_a, **_kw): 637 return '[{"hello": 111}]' 638 gsutil.stat = fake_stat 639 gsutil.upload_json = fake_upload 640 gsutil.cat = fake_cat 641 with Stub(bootstrap, 'random_sleep', Pass): 642 bootstrap.append_result( 643 gsutil, 'fake_path', build, version, success) 644 self.assertIn('generation', gsutil.jsons[-1][1], gsutil.jsons) 645 self.assertEqual('555', gsutil.jsons[-1][1]['generation'], gsutil.jsons) 646 647 def test_handle_junk(self): 648 gsutil = FakeGSUtil() 649 gsutil.cat = lambda *a, **kw: '!@!$!@$@!$' 650 build = 123 651 version = 'v.interesting' 652 success = True 653 bootstrap.append_result(gsutil, 'fake_path', build, version, success) 654 cache = gsutil.jsons[0][0][1] 655 self.assertEqual(1, len(cache)) 656 self.assertIn(build, list(cache[0].values())) 657 self.assertIn(version, list(cache[0].values())) 658 659 def test_passed_is_bool(self): 660 build = 123 661 version = 'v.interesting' 662 def try_run(success): 663 gsutil = FakeGSUtil() 664 bootstrap.append_result(gsutil, 'fake_path', build, version, success) 665 cache = gsutil.jsons[0][0][1] 666 self.assertTrue(isinstance(cache[0]['passed'], bool)) 667 668 try_run(1) 669 try_run(0) 670 try_run(None) 671 try_run('') 672 try_run('hello') 673 try_run('true') 674 675 def test_truncate(self): 676 old = json.dumps({n: True for n in range(100000)}) 677 gsutil = FakeGSUtil() 678 build = 123 679 version = 'v.interesting' 680 success = True 681 bootstrap.append_result(gsutil, 'fake_path', build, version, success) 682 cache = gsutil.jsons[0][0][1] 683 self.assertLess(len(cache), len(old)) 684 685 686 class FinishTest(unittest.TestCase): 687 """Tests for finish().""" 688 def setUp(self): 689 self.stubs = [ 690 Stub(bootstrap.GSUtil, 'upload_artifacts', Pass), 691 Stub(bootstrap, 'append_result', Pass), 692 Stub(os.path, 'isfile', Pass), 693 Stub(os.path, 'isdir', Pass), 694 ] 695 696 def tearDown(self): 697 for stub in self.stubs: 698 with stub: 699 pass 700 701 def test_no_version(self): 702 gsutil = FakeGSUtil() 703 paths = FakePath() 704 success = True 705 artifacts = 'not-a-dir' 706 no_version = '' 707 version = 'should not have found it' 708 repos = repo({REPO: ('master', '')}) 709 with Stub(bootstrap, 'metadata', lambda *a: {'random-meta': version}): 710 bootstrap.finish(gsutil, paths, success, artifacts, 711 BUILD, no_version, repos, FakeCall()) 712 bootstrap.finish(gsutil, paths, success, artifacts, BUILD, no_version, repos, FakeCall()) 713 calls = gsutil.jsons[-1] 714 # json data is second positional argument 715 self.assertNotIn('job-version', calls[0][1]) 716 self.assertNotIn('version', calls[0][1]) 717 self.assertTrue(calls[0][1].get('metadata')) 718 719 def test_metadata_version(self): 720 """Test that we will extract version info from metadata.""" 721 self.check_metadata_version('job-version') 722 self.check_metadata_version('version') 723 724 def check_metadata_version(self, key): 725 gsutil = FakeGSUtil() 726 paths = FakePath() 727 success = True 728 artifacts = 'not-a-dir' 729 no_version = '' 730 version = 'found it' 731 with Stub(bootstrap, 'metadata', lambda *a: {key: version}): 732 bootstrap.finish(gsutil, paths, success, artifacts, BUILD, no_version, REPO, FakeCall()) 733 calls = gsutil.jsons[-1] 734 # Meta is second positional argument 735 self.assertEqual(version, calls[0][1].get('job-version')) 736 self.assertEqual(version, calls[0][1].get('version')) 737 738 def test_ignore_err_up_artifacts(self): 739 paths = FakePath() 740 gsutil = FakeGSUtil() 741 local_artifacts = None 742 build = 123 743 version = 'v1.terrible' 744 success = True 745 calls = [] 746 with Stub(os.path, 'isdir', lambda _: True): 747 with Stub(os, 'walk', lambda d: [(True, True, True)]): 748 def fake_upload(*a, **kw): 749 calls.append((a, kw)) 750 raise subprocess.CalledProcessError(1, ['fakecmd'], None) 751 gsutil.upload_artifacts = fake_upload 752 repos = repo({REPO: ('master', '')}) 753 bootstrap.finish( 754 gsutil, paths, success, local_artifacts, 755 build, version, repos, FakeCall()) 756 self.assertTrue(calls) 757 758 759 def test_ignore_error_uploadtext(self): 760 paths = FakePath() 761 gsutil = FakeGSUtil() 762 local_artifacts = None 763 build = 123 764 version = 'v1.terrible' 765 success = True 766 calls = [] 767 with Stub(os.path, 'isdir', lambda _: True): 768 with Stub(os, 'walk', lambda d: [(True, True, True)]): 769 def fake_upload(*a, **kw): 770 calls.append((a, kw)) 771 raise subprocess.CalledProcessError(1, ['fakecmd'], None) 772 gsutil.upload_artifacts = Pass 773 gsutil.upload_text = fake_upload 774 repos = repo({REPO: ('master', '')}) 775 bootstrap.finish( 776 gsutil, paths, success, local_artifacts, 777 build, version, repos, FakeCall()) 778 self.assertTrue(calls) 779 self.assertGreater(calls, 1) 780 781 def test_skip_upload_artifacts(self): 782 """Do not upload artifacts dir if it doesn't exist.""" 783 paths = FakePath() 784 gsutil = FakeGSUtil() 785 local_artifacts = None 786 build = 123 787 version = 'v1.terrible' 788 success = True 789 calls = [] 790 with Stub(os.path, 'isdir', lambda _: False): 791 with Stub(bootstrap.GSUtil, 'upload_artifacts', Bomb): 792 repos = repo({REPO: ('master', '')}) 793 bootstrap.finish( 794 gsutil, paths, success, local_artifacts, 795 build, version, repos, FakeCall()) 796 self.assertFalse(calls) 797 798 799 class MetadataTest(unittest.TestCase): 800 801 def test_always_set_metadata(self): 802 repos = repo({REPO: ('master', '')}) 803 meta = bootstrap.metadata(repos, 'missing-artifacts-dir', FakeCall()) 804 self.assertIn('repo', meta) 805 self.assertEqual(REPO, meta['repo']) 806 807 def test_multi_repo(self): 808 repos = repo({REPO: ('foo', ''), 'other-repo': ('', '123,456')}) 809 meta = bootstrap.metadata(repos, 'missing-artifacts-dir', FakeCall()) 810 self.assertIn('repo', meta) 811 self.assertEqual(REPO, meta['repo']) 812 self.assertIn(REPO, meta.get('repos')) 813 self.assertEqual('foo', meta['repos'][REPO]) 814 self.assertIn('other-repo', meta.get('repos')) 815 self.assertEqual('123,456', meta['repos']['other-repo']) 816 817 818 SECONDS = 10 819 820 821 def fake_environment(set_home=True, set_node=True, set_job=True, 822 set_jenkins_home=True, set_workspace=True, 823 set_artifacts=True, **kwargs): 824 if set_home: 825 kwargs.setdefault(bootstrap.HOME_ENV, '/fake/home-dir') 826 if set_node: 827 kwargs.setdefault(bootstrap.NODE_ENV, 'fake-node') 828 if set_job: 829 kwargs.setdefault(bootstrap.JOB_ENV, JOB) 830 if set_jenkins_home: 831 kwargs.setdefault(bootstrap.JENKINS_HOME_ENV, '/fake/home-dir') 832 if set_workspace: 833 kwargs.setdefault(bootstrap.WORKSPACE_ENV, '/fake/workspace') 834 if set_artifacts: 835 kwargs.setdefault(bootstrap.JOB_ARTIFACTS_ENV, '/fake/workspace/_artifacts') 836 return kwargs 837 838 839 class BuildNameTest(unittest.TestCase): 840 """Tests for build_name().""" 841 842 def test_auto(self): 843 """Automatically select a build if not done by user.""" 844 with Stub(os, 'environ', fake_environment()) as fake: 845 bootstrap.build_name(SECONDS) 846 self.assertTrue(fake[bootstrap.BUILD_ENV]) 847 848 def test_manual(self): 849 """Respect user-selected build.""" 850 with Stub(os, 'environ', fake_environment()) as fake: 851 truth = 'erick is awesome' 852 fake[bootstrap.BUILD_ENV] = truth 853 self.assertEqual(truth, fake[bootstrap.BUILD_ENV]) 854 855 def test_unique(self): 856 """New build every minute.""" 857 with Stub(os, 'environ', fake_environment()) as fake: 858 bootstrap.build_name(SECONDS) 859 first = fake[bootstrap.BUILD_ENV] 860 del fake[bootstrap.BUILD_ENV] 861 bootstrap.build_name(SECONDS + 60) 862 self.assertNotEqual(first, fake[bootstrap.BUILD_ENV]) 863 864 865 class SetupCredentialsTest(unittest.TestCase): 866 """Tests for setup_credentials().""" 867 868 def setUp(self): 869 keys = { 870 bootstrap.GCE_KEY_ENV: 'fake-key', 871 bootstrap.SERVICE_ACCOUNT_ENV: 'fake-service-account.json', 872 } 873 self.env = fake_environment(**keys) 874 875 def test_norobot_noupload_noenv(self): 876 """Can avoid setting up credentials.""" 877 del self.env[bootstrap.SERVICE_ACCOUNT_ENV] 878 with Stub(os, 'environ', self.env): 879 bootstrap.setup_credentials(Bomb, None, None) 880 881 def test_upload_no_robot_raises(self): 882 del self.env[bootstrap.SERVICE_ACCOUNT_ENV] 883 with Stub(os, 'environ', self.env): 884 with self.assertRaises(ValueError): 885 bootstrap.setup_credentials(Pass, None, 'gs://fake') 886 887 888 def test_application_credentials(self): 889 """Raise if GOOGLE_APPLICATION_CREDENTIALS does not exist.""" 890 del self.env[bootstrap.SERVICE_ACCOUNT_ENV] 891 with Stub(os, 'environ', self.env) as fake: 892 gac = 'FAKE_CREDS.json' 893 fake['HOME'] = 'kansas' 894 with Stub(os.path, 'isfile', lambda p: p != gac): 895 with self.assertRaises(IOError): 896 bootstrap.setup_credentials(Pass, gac, UPLOAD) 897 898 with Stub(os.path, 'isfile', Truth): 899 call = lambda *a, **kw: 'robot' 900 bootstrap.setup_credentials(call, gac, UPLOAD) 901 # setup_creds should set SERVICE_ACCOUNT_ENV 902 self.assertEqual(gac, fake.get(bootstrap.SERVICE_ACCOUNT_ENV)) 903 # now that SERVICE_ACCOUNT_ENV is set, it should try to activate 904 # this 905 with Stub(os.path, 'isfile', lambda p: p != gac): 906 with self.assertRaises(IOError): 907 bootstrap.setup_credentials(Pass, None, UPLOAD) 908 909 910 class SetupMagicEnvironmentTest(unittest.TestCase): 911 def test_home_workspace_on_jenkins(self): 912 """WORKSPACE/HOME are set correctly for the Jenkins environment.""" 913 env = fake_environment(set_jenkins_home=True, set_workspace=True) 914 cwd = '/fake/random-location' 915 old_home = env[bootstrap.HOME_ENV] 916 old_workspace = env[bootstrap.WORKSPACE_ENV] 917 with Stub(os, 'environ', env): 918 with Stub(os, 'getcwd', lambda: cwd): 919 bootstrap.setup_magic_environment(JOB, FakeCall()) 920 921 self.assertIn(bootstrap.WORKSPACE_ENV, env) 922 self.assertNotEqual( 923 env[bootstrap.HOME_ENV], env[bootstrap.WORKSPACE_ENV]) 924 self.assertNotEqual(old_home, env[bootstrap.HOME_ENV]) 925 self.assertEqual(cwd, env[bootstrap.HOME_ENV]) 926 self.assertEqual(old_workspace, env[bootstrap.WORKSPACE_ENV]) 927 self.assertNotEqual(cwd, env[bootstrap.WORKSPACE_ENV]) 928 929 def test_home_workspace_in_k8s(self): 930 """WORKSPACE/HOME are set correctly for the kubernetes environment.""" 931 env = fake_environment(set_jenkins_home=False, set_workspace=True) 932 cwd = '/fake/random-location' 933 old_home = env[bootstrap.HOME_ENV] 934 old_workspace = env[bootstrap.WORKSPACE_ENV] 935 with Stub(os, 'environ', env): 936 with Stub(os, 'getcwd', lambda: cwd): 937 bootstrap.setup_magic_environment(JOB, FakeCall()) 938 939 self.assertIn(bootstrap.WORKSPACE_ENV, env) 940 self.assertNotEqual( 941 env[bootstrap.HOME_ENV], env[bootstrap.WORKSPACE_ENV]) 942 self.assertEqual(old_home, env[bootstrap.HOME_ENV]) 943 self.assertNotEqual(cwd, env[bootstrap.HOME_ENV]) 944 self.assertEqual(old_workspace, env[bootstrap.WORKSPACE_ENV]) 945 self.assertNotEqual(cwd, env[bootstrap.WORKSPACE_ENV]) 946 947 def test_workspace_always_set(self): 948 """WORKSPACE is set to cwd when unset in initial environment.""" 949 env = fake_environment(set_workspace=False) 950 cwd = '/fake/random-location' 951 with Stub(os, 'environ', env): 952 with Stub(os, 'getcwd', lambda: cwd): 953 bootstrap.setup_magic_environment(JOB, FakeCall()) 954 955 self.assertIn(bootstrap.WORKSPACE_ENV, env) 956 self.assertEqual(cwd, env[bootstrap.HOME_ENV]) 957 self.assertEqual(cwd, env[bootstrap.WORKSPACE_ENV]) 958 959 def test_job_env_mismatch(self): 960 env = fake_environment() 961 with Stub(os, 'environ', env): 962 self.assertNotEqual('this-is-a-job', env[bootstrap.JOB_ENV]) 963 bootstrap.setup_magic_environment('this-is-a-job', FakeCall()) 964 self.assertEqual('this-is-a-job', env[bootstrap.JOB_ENV]) 965 966 def test_expected(self): 967 env = fake_environment() 968 del env[bootstrap.JOB_ENV] 969 del env[bootstrap.NODE_ENV] 970 with Stub(os, 'environ', env): 971 # call is only used to git show the HEAD commit, so give a fake 972 # timestamp in return 973 bootstrap.setup_magic_environment(JOB, lambda *a, **kw: '123456\n') 974 975 def check(name): 976 self.assertIn(name, env) 977 978 # Some of these are probably silly to check... 979 # TODO(fejta): remove as many of these from our infra as possible. 980 check(bootstrap.JOB_ENV) 981 check(bootstrap.CLOUDSDK_ENV) 982 check(bootstrap.BOOTSTRAP_ENV) 983 check(bootstrap.WORKSPACE_ENV) 984 self.assertNotIn(bootstrap.SERVICE_ACCOUNT_ENV, env) 985 self.assertEqual(env[bootstrap.SOURCE_DATE_EPOCH_ENV], '123456') 986 987 def test_node_present(self): 988 expected = 'whatever' 989 env = {bootstrap.NODE_ENV: expected} 990 with Stub(os, 'environ', env): 991 self.assertEqual(expected, bootstrap.node()) 992 self.assertEqual(expected, env[bootstrap.NODE_ENV]) 993 994 def test_node_missing(self): 995 env = {} 996 with Stub(os, 'environ', env): 997 expected = bootstrap.node() 998 self.assertTrue(expected) 999 self.assertEqual(expected, env[bootstrap.NODE_ENV]) 1000 1001 1002 1003 def test_cloud_sdk_config(self): 1004 cwd = 'now-here' 1005 env = fake_environment() 1006 with Stub(os, 'environ', env): 1007 with Stub(os, 'getcwd', lambda: cwd): 1008 bootstrap.setup_magic_environment(JOB, FakeCall()) 1009 1010 1011 self.assertTrue(env[bootstrap.CLOUDSDK_ENV].startswith(cwd)) 1012 1013 1014 class FakePath(object): 1015 artifacts = 'fake_artifacts' 1016 pr_latest = 'fake_pr_latest' 1017 pr_build_link = 'fake_pr_link' 1018 build_log = 'fake_log_path' 1019 pr_path = 'fake_pr_path' 1020 pr_result_cache = 'fake_pr_result_cache' 1021 latest = 'fake_latest' 1022 result_cache = 'fake_result_cache' 1023 started = 'fake_started.json' 1024 finished = 'fake_finished.json' 1025 def __call__(self, *arg, **kw): 1026 self.arg = arg 1027 self.kw = kw 1028 return self 1029 1030 1031 class FakeLogging(object): 1032 close = Pass 1033 def __call__(self, *_a, **_kw): 1034 return self 1035 1036 1037 class FakeFinish(object): 1038 called = False 1039 result = None 1040 def __call__(self, unused_a, unused_b, success, *a, **kw): 1041 self.called = True 1042 self.result = success 1043 1044 def repo(config): 1045 repos = bootstrap.Repos() 1046 for cur_repo, tup in list(config.items()): 1047 repos[cur_repo] = tup 1048 return repos 1049 1050 class PRPathsTest(unittest.TestCase): 1051 def test_kubernetes_kubernetes(self): 1052 """Test the kubernetes/kubernetes prefix.""" 1053 path = bootstrap.pr_paths(UPLOAD, repo({'kubernetes/kubernetes': ('', PULL)}), JOB, BUILD) 1054 self.assertTrue(any( 1055 str(PULL) == p for p in path.build_log.split('/'))) 1056 1057 def test_kubernetes(self): 1058 """Test the kubernetes/something prefix.""" 1059 path = bootstrap.pr_paths(UPLOAD, repo({'kubernetes/prefix': ('', PULL)}), JOB, BUILD) 1060 self.assertTrue(any( 1061 'prefix' in p for p in path.build_log.split('/')), path.build_log) 1062 self.assertTrue(any( 1063 str(PULL) in p for p in path.build_log.split('/')), path.build_log) 1064 1065 def test_other(self): 1066 """Test the none kubernetes prefixes.""" 1067 path = bootstrap.pr_paths(UPLOAD, repo({'github.com/random/repo': ('', PULL)}), JOB, BUILD) 1068 self.assertTrue(any( 1069 'random_repo' in p for p in path.build_log.split('/')), path.build_log) 1070 self.assertTrue(any( 1071 str(PULL) in p for p in path.build_log.split('/')), path.build_log) 1072 1073 1074 class FakeArgs(object): 1075 bare = False 1076 clean = False 1077 git_cache = '' 1078 job = JOB 1079 root = ROOT 1080 service_account = ROBOT 1081 ssh = False 1082 timeout = 0 1083 upload = UPLOAD 1084 json = False 1085 scenario = '' 1086 compress = False 1087 1088 def __init__(self, **kw): 1089 self.branch = BRANCH 1090 self.pull = PULL 1091 self.repo = [REPO] 1092 self.extra_job_args = [] 1093 for key, val in list(kw.items()): 1094 if not hasattr(self, key): 1095 raise AttributeError(self, key) 1096 setattr(self, key, val) 1097 1098 1099 def test_bootstrap(**kw): 1100 if isinstance(kw.get('repo'), str): 1101 kw['repo'] = [kw['repo']] 1102 return bootstrap.bootstrap(FakeArgs(**kw)) 1103 1104 class BootstrapTest(unittest.TestCase): 1105 1106 def setUp(self): 1107 self.boiler = [ 1108 Stub(bootstrap, 'checkout', Pass), 1109 Stub(bootstrap, 'finish', Pass), 1110 Stub(bootstrap.GSUtil, 'copy_file', Pass), 1111 Stub(bootstrap, 'node', lambda: 'fake-node'), 1112 Stub(bootstrap, 'setup_credentials', Pass), 1113 Stub(bootstrap, 'setup_logging', FakeLogging()), 1114 Stub(bootstrap, 'start', Pass), 1115 Stub(bootstrap, '_call', Pass), 1116 Stub(os, 'environ', fake_environment()), 1117 Stub(os, 'chdir', Pass), 1118 Stub(os, 'makedirs', Pass), 1119 ] 1120 1121 def tearDown(self): 1122 for stub in self.boiler: 1123 with stub: # Leaving with restores things 1124 pass 1125 1126 1127 def test_compress(self): 1128 compressed = lambda s, d, o, c: self.assertTrue(c, 'failed to find compression') 1129 uncompressed = lambda s, d, o, c: self.assertFalse(c, 'failed to find uncompression') 1130 with Stub(bootstrap.GSUtil, 'copy_file', uncompressed): 1131 test_bootstrap() 1132 with Stub(bootstrap.GSUtil, 'copy_file', compressed): 1133 test_bootstrap(compress=True) 1134 with Stub(bootstrap.GSUtil, 'copy_file', uncompressed): 1135 test_bootstrap(compress=False) 1136 1137 1138 def test_setcreds_setroot_fails(self): 1139 """We should still call setup_credentials even if setup_root blows up.""" 1140 called = set() 1141 with Stub(bootstrap, 'setup_root', Bomb): 1142 with Stub(bootstrap, 'setup_credentials', 1143 lambda *a, **kw: called.add('setup_credentials')): 1144 with Stub(bootstrap, 'finish', lambda *a, **kw: called.add('finish')): 1145 with self.assertRaises(AssertionError): 1146 test_bootstrap() 1147 1148 for needed in ['setup_credentials', 'finish']: 1149 self.assertIn(needed, called) 1150 1151 def test_empty_repo(self): 1152 repo_name = None 1153 with Stub(bootstrap, 'checkout', Bomb): 1154 test_bootstrap(repo=repo_name, branch=None, pull=None, bare=True) 1155 with self.assertRaises(ValueError): 1156 test_bootstrap(repo=repo_name, branch=None, pull=PULL) 1157 with self.assertRaises(ValueError): 1158 test_bootstrap(repo=repo_name, branch=BRANCH, pull=None) 1159 1160 def test_root_not_exists(self): 1161 with Stub(os, 'chdir', FakeCall()) as fake_chdir: 1162 with Stub(os.path, 'exists', lambda p: False): 1163 with Stub(os, 'makedirs', FakeCall()) as fake_makedirs: 1164 test_bootstrap(branch=None) 1165 self.assertTrue(any(ROOT in c[0] for c in fake_chdir.calls), fake_chdir.calls) 1166 self.assertTrue(any(ROOT in c[0] for c in fake_makedirs.calls), fake_makedirs.calls) 1167 1168 def test_root_exists(self): 1169 with Stub(os, 'chdir', FakeCall()) as fake_chdir: 1170 test_bootstrap(branch=None) 1171 self.assertTrue(any(ROOT in c[0] for c in fake_chdir.calls)) 1172 1173 def test_pr_paths(self): 1174 """Use a pr_paths when pull is set.""" 1175 1176 with Stub(bootstrap, 'ci_paths', Bomb): 1177 with Stub(bootstrap, 'pr_paths', FakePath()) as path: 1178 test_bootstrap(branch=None, pull=PULL, root='.') 1179 self.assertEqual(PULL, path.arg[1][REPO][1], (PULL, path.arg)) 1180 1181 def test_ci_paths(self): 1182 """Use a ci_paths when branch is set.""" 1183 1184 with Stub(bootstrap, 'pr_paths', Bomb): 1185 with Stub(bootstrap, 'ci_paths', FakePath()) as path: 1186 test_bootstrap(pull=None, branch=BRANCH) 1187 self.assertFalse(any( 1188 PULL in o for o in (path.arg, path.kw))) 1189 1190 def test_finish_when_start_fails(self): 1191 """Finish is called even if start fails.""" 1192 with Stub(bootstrap, 'finish', FakeFinish()) as fake: 1193 with Stub(bootstrap, 'start', Bomb): 1194 with self.assertRaises(AssertionError): 1195 test_bootstrap(pull=None) 1196 self.assertTrue(fake.called) 1197 self.assertTrue(fake.result is False) # Distinguish from None 1198 1199 def test_finish_when_build_fails(self): 1200 """Finish is called even if the build fails.""" 1201 def call_error(*_a, **_kw): 1202 raise subprocess.CalledProcessError(1, [], '') 1203 with Stub(bootstrap, 'finish', FakeFinish()) as fake: 1204 with Stub(bootstrap, '_call', call_error): 1205 with self.assertRaises(SystemExit): 1206 test_bootstrap(pull=None) 1207 self.assertTrue(fake.called) 1208 self.assertTrue(fake.result is False) # Distinguish from None 1209 1210 def test_happy(self): 1211 with Stub(bootstrap, 'finish', FakeFinish()) as fake: 1212 test_bootstrap(pull=None) 1213 self.assertTrue(fake.called) 1214 self.assertTrue(fake.result) # Distinguish from None 1215 1216 def test_job_env(self): 1217 """bootstrap sets JOB_NAME.""" 1218 with Stub(os, 'environ', fake_environment()) as env: 1219 test_bootstrap(pull=None) 1220 self.assertIn(bootstrap.JOB_ENV, env) 1221 1222 def test_job_script_expands_vars(self): 1223 fake = { 1224 'HELLO': 'awesome', 1225 'WORLD': 'sauce', 1226 } 1227 with Stub(os, 'environ', fake): 1228 actual = bootstrap.job_args( 1229 ['$HELLO ${WORLD}', 'happy', '${MISSING}']) 1230 self.assertEqual(['awesome sauce', 'happy', '${MISSING}'], actual) 1231 1232 1233 class RepositoryTest(unittest.TestCase): 1234 def test_kubernetes_kubernetes(self): 1235 expected = 'https://github.com/kubernetes/kubernetes' 1236 actual = bootstrap.repository('k8s.io/kubernetes', '') 1237 self.assertEqual(expected, actual) 1238 1239 def test_kubernetes_testinfra(self): 1240 expected = 'https://github.com/kubernetes/test-infra' 1241 actual = bootstrap.repository('k8s.io/test-infra', '') 1242 self.assertEqual(expected, actual) 1243 1244 def test_whatever(self): 1245 expected = 'https://foo.com/bar' 1246 actual = bootstrap.repository('foo.com/bar', '') 1247 self.assertEqual(expected, actual) 1248 1249 def test_k8s_k8s_ssh(self): 1250 expected = 'git@github.com:kubernetes/kubernetes' 1251 actual = bootstrap.repository('k8s.io/kubernetes', 'path') 1252 self.assertEqual(expected, actual) 1253 1254 def test_k8s_k8s_ssh_with_colon(self): 1255 expected = 'git@github.com:kubernetes/kubernetes' 1256 actual = bootstrap.repository('github.com:kubernetes/kubernetes', 'path') 1257 self.assertEqual(expected, actual) 1258 1259 def test_whatever_ssh(self): 1260 expected = 'git@foo.com:bar' 1261 actual = bootstrap.repository('foo.com/bar', 'path') 1262 self.assertEqual(expected, actual) 1263 1264 1265 1266 class IntegrationTest(unittest.TestCase): 1267 REPO = 'hello/world' 1268 MASTER = 'fake-master-file' 1269 BRANCH_FILE = 'fake-branch-file' 1270 PR_FILE = 'fake-pr-file' 1271 BRANCH = 'another-branch' 1272 PR_NUM = 42 1273 PR_TAG = bootstrap.pull_ref(PR_NUM)[0][0].strip('+') 1274 1275 def fake_repo(self, fake, _ssh=False): 1276 return os.path.join(self.root_github, fake) 1277 1278 def setUp(self): 1279 self.boiler = [ 1280 Stub(bootstrap, 'finish', Pass), 1281 Stub(bootstrap.GSUtil, 'copy_file', Pass), 1282 Stub(bootstrap, 'repository', self.fake_repo), 1283 Stub(bootstrap, 'setup_credentials', Pass), 1284 Stub(bootstrap, 'setup_logging', FakeLogging()), 1285 Stub(bootstrap, 'start', Pass), 1286 Stub(os, 'environ', fake_environment(set_job=False)), 1287 ] 1288 self.root_github = tempfile.mkdtemp() 1289 self.root_workspace = tempfile.mkdtemp() 1290 self.root_git_cache = tempfile.mkdtemp() 1291 self.ocwd = os.getcwd() 1292 fakerepo = self.fake_repo(self.REPO) 1293 subprocess.check_call(['git', 'init', fakerepo]) 1294 os.chdir(fakerepo) 1295 subprocess.check_call(['git', 'config', 'user.name', 'foo']) 1296 subprocess.check_call(['git', 'config', 'user.email', 'foo@bar.baz']) 1297 subprocess.check_call(['touch', self.MASTER]) 1298 subprocess.check_call(['git', 'add', self.MASTER]) 1299 subprocess.check_call(['cp', '-r', bootstrap.test_infra('jenkins/fake'), fakerepo]) 1300 subprocess.check_call(['git', 'add', 'fake']) 1301 subprocess.check_call(['git', 'commit', '-m', 'Initial commit']) 1302 subprocess.check_call(['git', 'checkout', 'master']) 1303 1304 def tearDown(self): 1305 for stub in self.boiler: 1306 with stub: # Leaving with restores things 1307 pass 1308 os.chdir(self.ocwd) 1309 subprocess.check_call(['rm', '-rf', self.root_github]) 1310 subprocess.check_call(['rm', '-rf', self.root_workspace]) 1311 subprocess.check_call(['rm', '-rf', self.root_git_cache]) 1312 1313 def test_git_cache(self): 1314 subprocess.check_call(['git', 'checkout', '-b', self.BRANCH]) 1315 subprocess.check_call(['git', 'rm', self.MASTER]) 1316 subprocess.check_call(['touch', self.BRANCH_FILE]) 1317 subprocess.check_call(['git', 'add', self.BRANCH_FILE]) 1318 subprocess.check_call(['git', 'commit', '-m', 'Create %s' % self.BRANCH]) 1319 test_bootstrap( 1320 job='fake-branch', 1321 repo=self.REPO, 1322 branch=self.BRANCH, 1323 pull=None, 1324 root=self.root_workspace, 1325 git_cache=self.root_git_cache) 1326 # Verify that the cache was populated by running a simple git command 1327 # in the git cache directory. 1328 subprocess.check_call( 1329 ['git', '--git-dir=%s/%s' % (self.root_git_cache, self.REPO), 'log']) 1330 1331 def test_pr(self): 1332 subprocess.check_call(['git', 'checkout', 'master']) 1333 subprocess.check_call(['git', 'checkout', '-b', 'unknown-pr-branch']) 1334 subprocess.check_call(['git', 'rm', self.MASTER]) 1335 subprocess.check_call(['touch', self.PR_FILE]) 1336 subprocess.check_call(['git', 'add', self.PR_FILE]) 1337 subprocess.check_call(['git', 'commit', '-m', 'Create branch for PR %d' % self.PR_NUM]) 1338 subprocess.check_call(['git', 'tag', self.PR_TAG]) 1339 os.chdir('/tmp') 1340 test_bootstrap( 1341 job='fake-pr', 1342 repo=self.REPO, 1343 branch=None, 1344 pull=self.PR_NUM, 1345 root=self.root_workspace) 1346 1347 def test_branch(self): 1348 subprocess.check_call(['git', 'checkout', '-b', self.BRANCH]) 1349 subprocess.check_call(['git', 'rm', self.MASTER]) 1350 subprocess.check_call(['touch', self.BRANCH_FILE]) 1351 subprocess.check_call(['git', 'add', self.BRANCH_FILE]) 1352 subprocess.check_call(['git', 'commit', '-m', 'Create %s' % self.BRANCH]) 1353 1354 os.chdir('/tmp') 1355 test_bootstrap( 1356 job='fake-branch', 1357 repo=self.REPO, 1358 branch=self.BRANCH, 1359 pull=None, 1360 root=self.root_workspace) 1361 1362 def test_branch_ref(self): 1363 """Make sure we check out a specific commit.""" 1364 subprocess.check_call(['git', 'checkout', '-b', self.BRANCH]) 1365 subprocess.check_call(['git', 'rm', self.MASTER]) 1366 subprocess.check_call(['touch', self.BRANCH_FILE]) 1367 subprocess.check_call(['git', 'add', self.BRANCH_FILE]) 1368 subprocess.check_call(['git', 'commit', '-m', 'Create %s' % self.BRANCH]) 1369 sha = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() 1370 subprocess.check_call(['rm', self.BRANCH_FILE]) 1371 subprocess.check_call(['git', 'add', self.BRANCH_FILE]) 1372 subprocess.check_call(['git', 'commit', '-m', 'Delete %s' % self.BRANCH]) 1373 1374 os.chdir('/tmp') 1375 # Supplying the commit exactly works. 1376 test_bootstrap( 1377 job='fake-branch', 1378 repo=self.REPO, 1379 branch='%s:%s' % (self.BRANCH, sha), 1380 pull=None, 1381 root=self.root_workspace) 1382 # Supplying the commit through repo works. 1383 test_bootstrap( 1384 job='fake-branch', 1385 repo="%s=%s:%s" % (self.REPO, self.BRANCH, sha), 1386 branch=None, 1387 pull=None, 1388 root=self.root_workspace) 1389 # Using branch head fails. 1390 with self.assertRaises(SystemExit): 1391 test_bootstrap( 1392 job='fake-branch', 1393 repo=self.REPO, 1394 branch=self.BRANCH, 1395 pull=None, 1396 root=self.root_workspace) 1397 with self.assertRaises(SystemExit): 1398 test_bootstrap( 1399 job='fake-branch', 1400 repo="%s=%s" % (self.REPO, self.BRANCH), 1401 branch=None, 1402 pull=None, 1403 root=self.root_workspace) 1404 1405 def test_batch(self): 1406 def head_sha(): 1407 # We can't hardcode the SHAs for the test, so we need to determine 1408 # them after each commit. 1409 return subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() 1410 refs = ['master:%s' % head_sha()] 1411 master_commit_date = int(subprocess.check_output( 1412 ['git', 'show', '-s', '--format=format:%ct', head_sha()])) 1413 for pr in (123, 456): 1414 subprocess.check_call(['git', 'checkout', '-b', 'refs/pull/%d/head' % pr, 'master']) 1415 subprocess.check_call(['git', 'rm', self.MASTER]) 1416 subprocess.check_call(['touch', self.PR_FILE]) 1417 subprocess.check_call(['git', 'add', self.PR_FILE]) 1418 open('pr_%d.txt' % pr, 'w').write('some text') 1419 subprocess.check_call(['git', 'add', 'pr_%d.txt' % pr]) 1420 subprocess.check_call(['git', 'commit', '-m', 'add some stuff (#%d)' % pr]) 1421 refs.append('%d:%s' % (pr, head_sha())) 1422 os.chdir('/tmp') 1423 pull = ','.join(refs) 1424 print('--pull', pull) 1425 subprocess.check_call(['ls']) 1426 test_bootstrap( 1427 job='fake-pr', 1428 repo=self.REPO, 1429 branch=None, 1430 pull=pull, 1431 root=self.root_workspace) 1432 head_commit_date = int(subprocess.check_output( 1433 ['git', 'show', '-s', '--format=format:%ct', 'test'])) 1434 # Since there were 2 PRs merged, we expect the timestamp of the latest 1435 # commit on the 'test' branch to be 2 more than master. 1436 self.assertEqual(head_commit_date, master_commit_date + 2) 1437 1438 def test_pr_bad(self): 1439 random_pr = 111 1440 with Stub(bootstrap, 'start', Bomb): 1441 with Stub(time, 'sleep', Pass): 1442 with self.assertRaises(subprocess.CalledProcessError): 1443 test_bootstrap( 1444 job='fake-pr', 1445 repo=self.REPO, 1446 branch=None, 1447 pull=random_pr, 1448 root=self.root_workspace) 1449 1450 def test_branch_bad(self): 1451 random_branch = 'something' 1452 with Stub(bootstrap, 'start', Bomb): 1453 with Stub(time, 'sleep', Pass): 1454 with self.assertRaises(subprocess.CalledProcessError): 1455 test_bootstrap( 1456 job='fake-branch', 1457 repo=self.REPO, 1458 branch=random_branch, 1459 pull=None, 1460 root=self.root_workspace) 1461 1462 def test_job_missing(self): 1463 with self.assertRaises(ValueError): 1464 test_bootstrap( 1465 job='this-job-no-exists', 1466 repo=self.REPO, 1467 branch='master', 1468 pull=None, 1469 root=self.root_workspace) 1470 1471 def test_job_fails(self): 1472 with self.assertRaises(SystemExit): 1473 test_bootstrap( 1474 job='fake-failure', 1475 repo=self.REPO, 1476 branch='master', 1477 pull=None, 1478 root=self.root_workspace) 1479 1480 def test_commit_in_meta(self): 1481 sha = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip() 1482 cwd = os.getcwd() 1483 os.chdir(bootstrap.test_infra('.')) 1484 infra_sha = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip()[:9] 1485 os.chdir(cwd) 1486 1487 # Commit SHA should in meta 1488 call = lambda *a, **kw: bootstrap._call(5, *a, **kw) 1489 repos = repo({REPO: ('master', ''), 'other-repo': ('other-branch', '')}) 1490 meta = bootstrap.metadata(repos, 'missing-artifacts-dir', call) 1491 self.assertIn('repo-commit', meta) 1492 self.assertEqual(sha, meta['repo-commit']) 1493 self.assertEqual(40, len(meta['repo-commit'])) 1494 self.assertIn('infra-commit', meta) 1495 self.assertEqual(infra_sha, meta['infra-commit']) 1496 self.assertEqual(9, len(meta['infra-commit'])) 1497 self.assertIn(REPO, meta.get('repos')) 1498 self.assertIn('other-repo', meta.get('repos')) 1499 self.assertEqual(REPO, meta.get('repo')) 1500 1501 1502 class ParseArgsTest(unittest.TestCase): 1503 def test_barerepo_both(self): 1504 with self.assertRaises(argparse.ArgumentTypeError): 1505 bootstrap.parse_args(['--bare', '--repo=hello', '--job=j']) 1506 1507 def test_barerepo_neither(self): 1508 with self.assertRaises(argparse.ArgumentTypeError): 1509 bootstrap.parse_args(['--job=j']) 1510 1511 def test_barerepo_bareonly(self): 1512 args = bootstrap.parse_args(['--bare', '--job=j']) 1513 self.assertFalse(args.repo, args) 1514 self.assertTrue(args.bare, args) 1515 1516 def test_barerepo_repoonly(self): 1517 args = bootstrap.parse_args(['--repo=R', '--job=j']) 1518 self.assertFalse(args.bare, args) 1519 self.assertTrue(args.repo, args) 1520 1521 1522 def test_extra_job_args(self): 1523 args = bootstrap.parse_args(['--repo=R', '--job=j', '--', '--foo=bar', '--baz=quux']) 1524 self.assertEqual(args.extra_job_args, ['--foo=bar', '--baz=quux']) 1525 1526 1527 if __name__ == '__main__': 1528 unittest.main()