1#!/usr/bin/env python3 2# Copyright 2013 the V8 project authors. All rights reserved. 3# Redistribution and use in source and binary forms, with or without 4# modification, are permitted provided that the following conditions are 5# met: 6# 7# * Redistributions of source code must retain the above copyright 8# notice, this list of conditions and the following disclaimer. 9# * Redistributions in binary form must reproduce the above 10# copyright notice, this list of conditions and the following 11# disclaimer in the documentation and/or other materials provided 12# with the distribution. 13# * Neither the name of Google Inc. nor the names of its 14# contributors may be used to endorse or promote products derived 15# from this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29import json 30import os 31import shutil 32import tempfile 33import traceback 34import unittest 35 36import auto_push 37from auto_push import LastReleaseBailout 38import auto_roll 39import common_includes 40from common_includes import * 41import create_release 42from create_release import * 43import merge_to_branch 44from merge_to_branch import MergeToBranch 45import roll_merge 46from roll_merge import RollMerge 47 48TEST_CONFIG = { 49 "DEFAULT_CWD": None, 50 "BRANCHNAME": "test-prepare-push", 51 "PERSISTFILE_BASENAME": "/tmp/test-create-releases-tempfile", 52 "PATCH_FILE": "/tmp/test-v8-create-releases-tempfile-tempfile-patch", 53 "COMMITMSG_FILE": "/tmp/test-v8-create-releases-tempfile-commitmsg", 54 "CHROMIUM": "/tmp/test-create-releases-tempfile-chromium", 55 "SETTINGS_LOCATION": None, 56 "ALREADY_MERGING_SENTINEL_FILE": 57 "/tmp/test-merge-to-branch-tempfile-already-merging", 58 "TEMPORARY_PATCH_FILE": "/tmp/test-merge-to-branch-tempfile-temporary-patch", 59} 60 61 62AUTO_PUSH_ARGS = [ 63 "-a", "author@chromium.org", 64 "-r", "reviewer@chromium.org", 65] 66 67 68class ToplevelTest(unittest.TestCase): 69 def testSaniniziteVersionTags(self): 70 self.assertEquals("4.8.230", SanitizeVersionTag("4.8.230")) 71 self.assertEquals("4.8.230", SanitizeVersionTag("tags/4.8.230")) 72 self.assertEquals(None, SanitizeVersionTag("candidate")) 73 74 def testNormalizeVersionTags(self): 75 input = ["4.8.230", 76 "tags/4.8.230", 77 "tags/4.8.224.1", 78 "4.8.224.1", 79 "4.8.223.1", 80 "tags/4.8.223", 81 "tags/4.8.231", 82 "candidates"] 83 expected = ["4.8.230", 84 "4.8.230", 85 "4.8.224.1", 86 "4.8.224.1", 87 "4.8.223.1", 88 "4.8.223", 89 "4.8.231", 90 ] 91 self.assertEquals(expected, NormalizeVersionTags(input)) 92 93 def testCommand(self): 94 """Ensure json can decode the output of commands.""" 95 json.dumps(Command('ls', pipe=True)) 96 97 98def Cmd(*args, **kwargs): 99 """Convenience function returning a shell command test expectation.""" 100 return { 101 "name": "command", 102 "args": args, 103 "ret": args[-1], 104 "cb": kwargs.get("cb"), 105 "cwd": kwargs.get("cwd", TEST_CONFIG["DEFAULT_CWD"]), 106 } 107 108 109def RL(text, cb=None): 110 """Convenience function returning a readline test expectation.""" 111 return { 112 "name": "readline", 113 "args": [], 114 "ret": text, 115 "cb": cb, 116 "cwd": None, 117 } 118 119 120def URL(*args, **kwargs): 121 """Convenience function returning a readurl test expectation.""" 122 return { 123 "name": "readurl", 124 "args": args[:-1], 125 "ret": args[-1], 126 "cb": kwargs.get("cb"), 127 "cwd": None, 128 } 129 130 131class SimpleMock(object): 132 def __init__(self): 133 self._recipe = [] 134 self._index = -1 135 136 def Expect(self, recipe): 137 self._recipe = recipe 138 139 def Call(self, name, *args, **kwargs): # pragma: no cover 140 self._index += 1 141 142 try: 143 expected_call = self._recipe[self._index] 144 except IndexError: 145 raise NoRetryException("Calling %s %s" % (name, " ".join(args))) 146 147 if not isinstance(expected_call, dict): 148 raise NoRetryException("Found wrong expectation type for %s %s" % 149 (name, " ".join(args))) 150 151 if expected_call["name"] != name: 152 raise NoRetryException("Expected action: %s %s - Actual: %s" % 153 (expected_call["name"], expected_call["args"], name)) 154 155 # Check if the given working directory matches the expected one. 156 if expected_call["cwd"] != kwargs.get("cwd"): 157 raise NoRetryException("Expected cwd: %s in %s %s - Actual: %s" % 158 (expected_call["cwd"], 159 expected_call["name"], 160 expected_call["args"], 161 kwargs.get("cwd"))) 162 163 # The number of arguments in the expectation must match the actual 164 # arguments. 165 if len(args) > len(expected_call['args']): 166 raise NoRetryException("When calling %s with arguments, the " 167 "expectations must consist of at least as many arguments." % 168 name) 169 170 # Compare expected and actual arguments. 171 for (expected_arg, actual_arg) in zip(expected_call['args'], args): 172 if expected_arg != actual_arg: 173 raise NoRetryException("Expected: %s - Actual: %s" % 174 (expected_arg, actual_arg)) 175 176 # The expected call contains an optional callback for checking the context 177 # at the time of the call. 178 if expected_call['cb']: 179 try: 180 expected_call['cb']() 181 except: 182 tb = traceback.format_exc() 183 raise NoRetryException("Caught exception from callback: %s" % tb) 184 185 # If the return value is an exception, raise it instead of returning. 186 if isinstance(expected_call['ret'], Exception): 187 raise expected_call['ret'] 188 return expected_call['ret'] 189 190 def AssertFinished(self): # pragma: no cover 191 if self._index < len(self._recipe) -1: 192 raise NoRetryException("Called mock too seldom: %d vs. %d" % 193 (self._index, len(self._recipe))) 194 195 196class ScriptTest(unittest.TestCase): 197 def MakeEmptyTempFile(self): 198 handle, name = tempfile.mkstemp() 199 os.close(handle) 200 self._tmp_files.append(name) 201 return name 202 203 def MakeEmptyTempDirectory(self): 204 name = tempfile.mkdtemp() 205 self._tmp_files.append(name) 206 return name 207 208 209 def WriteFakeVersionFile(self, major=3, minor=22, build=4, patch=0): 210 version_file = os.path.join(TEST_CONFIG["DEFAULT_CWD"], VERSION_FILE) 211 if not os.path.exists(os.path.dirname(version_file)): 212 os.makedirs(os.path.dirname(version_file)) 213 with open(version_file, "w") as f: 214 f.write(" // Some line...\n") 215 f.write("\n") 216 f.write("#define V8_MAJOR_VERSION %s\n" % major) 217 f.write("#define V8_MINOR_VERSION %s\n" % minor) 218 f.write("#define V8_BUILD_NUMBER %s\n" % build) 219 f.write("#define V8_PATCH_LEVEL %s\n" % patch) 220 f.write(" // Some line...\n") 221 f.write("#define V8_IS_CANDIDATE_VERSION 0\n") 222 223 def WriteFakeWatchlistsFile(self): 224 watchlists_file = os.path.join(TEST_CONFIG["DEFAULT_CWD"], WATCHLISTS_FILE) 225 if not os.path.exists(os.path.dirname(watchlists_file)): 226 os.makedirs(os.path.dirname(watchlists_file)) 227 with open(watchlists_file, "w") as f: 228 229 content = """ 230 'merges': [ 231 # Only enabled on branches created with tools/release/create_release.py 232 # 'v8-merges@googlegroups.com', 233 ], 234""" 235 f.write(content) 236 237 def MakeStep(self): 238 """Convenience wrapper.""" 239 options = ScriptsBase(TEST_CONFIG, self, self._state).MakeOptions([]) 240 return MakeStep(step_class=Step, state=self._state, 241 config=TEST_CONFIG, side_effect_handler=self, 242 options=options) 243 244 def RunStep(self, script=CreateRelease, step_class=Step, args=None): 245 """Convenience wrapper.""" 246 args = args if args is not None else ["-m", "-a=author", "-r=reviewer", ] 247 return script(TEST_CONFIG, self, self._state).RunSteps([step_class], args) 248 249 def Call(self, fun, *args, **kwargs): 250 print("Calling %s with %s and %s" % (str(fun), str(args), str(kwargs))) 251 252 def Command(self, cmd, args="", prefix="", pipe=True, cwd=None): 253 print("%s %s" % (cmd, args)) 254 print("in %s" % cwd) 255 return self._mock.Call("command", cmd + " " + args, cwd=cwd) 256 257 def ReadLine(self): 258 return self._mock.Call("readline") 259 260 def ReadURL(self, url, params): 261 if params is not None: 262 return self._mock.Call("readurl", url, params) 263 else: 264 return self._mock.Call("readurl", url) 265 266 def Sleep(self, seconds): 267 pass 268 269 def GetUTCStamp(self): 270 return "1000000" 271 272 def Expect(self, *args): 273 """Convenience wrapper.""" 274 self._mock.Expect(*args) 275 276 def setUp(self): 277 self._mock = SimpleMock() 278 self._tmp_files = [] 279 self._state = {} 280 TEST_CONFIG["DEFAULT_CWD"] = self.MakeEmptyTempDirectory() 281 282 def tearDown(self): 283 if os.path.exists(TEST_CONFIG["PERSISTFILE_BASENAME"]): 284 shutil.rmtree(TEST_CONFIG["PERSISTFILE_BASENAME"]) 285 286 # Clean up temps. Doesn't work automatically. 287 for name in self._tmp_files: 288 if os.path.isfile(name): 289 os.remove(name) 290 if os.path.isdir(name): 291 shutil.rmtree(name) 292 293 self._mock.AssertFinished() 294 295 def testGitMock(self): 296 self.Expect([Cmd("git --version", "git version 1.2.3"), 297 Cmd("git dummy", "")]) 298 self.assertEquals("git version 1.2.3", self.MakeStep().Git("--version")) 299 self.assertEquals("", self.MakeStep().Git("dummy")) 300 301 def testCommonPrepareDefault(self): 302 self.Expect([ 303 Cmd("git status -s -uno", ""), 304 Cmd("git checkout -f origin/main", ""), 305 Cmd("git fetch", ""), 306 Cmd("git branch", " branch1\n* %s" % TEST_CONFIG["BRANCHNAME"]), 307 RL("Y"), 308 Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], ""), 309 ]) 310 self.MakeStep().CommonPrepare() 311 self.MakeStep().PrepareBranch() 312 313 def testCommonPrepareNoConfirm(self): 314 self.Expect([ 315 Cmd("git status -s -uno", ""), 316 Cmd("git checkout -f origin/main", ""), 317 Cmd("git fetch", ""), 318 Cmd("git branch", " branch1\n* %s" % TEST_CONFIG["BRANCHNAME"]), 319 RL("n"), 320 ]) 321 self.MakeStep().CommonPrepare() 322 self.assertRaises(Exception, self.MakeStep().PrepareBranch) 323 324 def testCommonPrepareDeleteBranchFailure(self): 325 self.Expect([ 326 Cmd("git status -s -uno", ""), 327 Cmd("git checkout -f origin/main", ""), 328 Cmd("git fetch", ""), 329 Cmd("git branch", " branch1\n* %s" % TEST_CONFIG["BRANCHNAME"]), 330 RL("Y"), 331 Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], None), 332 ]) 333 self.MakeStep().CommonPrepare() 334 self.assertRaises(Exception, self.MakeStep().PrepareBranch) 335 336 def testInitialEnvironmentChecks(self): 337 TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git")) 338 os.environ["EDITOR"] = "vi" 339 self.Expect([ 340 Cmd("which vi", "/usr/bin/vi"), 341 ]) 342 self.MakeStep().InitialEnvironmentChecks(TEST_CONFIG["DEFAULT_CWD"]) 343 344 def testTagTimeout(self): 345 self.Expect([ 346 Cmd("git fetch", ""), 347 Cmd("git log -1 --format=%H --grep=\"Title\" origin/tag_name", ""), 348 Cmd("git fetch", ""), 349 Cmd("git log -1 --format=%H --grep=\"Title\" origin/tag_name", ""), 350 Cmd("git fetch", ""), 351 Cmd("git log -1 --format=%H --grep=\"Title\" origin/tag_name", ""), 352 Cmd("git fetch", ""), 353 Cmd("git log -1 --format=%H --grep=\"Title\" origin/tag_name", ""), 354 ]) 355 args = ["--branch", "candidates", "ab12345"] 356 self._state["version"] = "tag_name" 357 self._state["commit_title"] = "Title" 358 self.assertRaises(Exception, 359 lambda: self.RunStep(RollMerge, TagRevision, args)) 360 361 def testReadAndPersistVersion(self): 362 self.WriteFakeVersionFile(build=5) 363 step = self.MakeStep() 364 step.ReadAndPersistVersion() 365 self.assertEquals("3", step["major"]) 366 self.assertEquals("22", step["minor"]) 367 self.assertEquals("5", step["build"]) 368 self.assertEquals("0", step["patch"]) 369 370 def testRegex(self): 371 self.assertEqual("(issue 321)", 372 re.sub(r"BUG=v8:(.*)$", r"(issue \1)", "BUG=v8:321")) 373 self.assertEqual("(Chromium issue 321)", 374 re.sub(r"BUG=(.*)$", r"(Chromium issue \1)", "BUG=321")) 375 376 cl = " too little\n\ttab\ttab\n too much\n trailing " 377 cl = MSub(r"\t", r" ", cl) 378 cl = MSub(r"^ {1,7}([^ ])", r" \1", cl) 379 cl = MSub(r"^ {9,80}([^ ])", r" \1", cl) 380 cl = MSub(r" +$", r"", cl) 381 self.assertEqual(" too little\n" 382 " tab tab\n" 383 " too much\n" 384 " trailing", cl) 385 386 self.assertEqual("//\n#define V8_BUILD_NUMBER 3\n", 387 MSub(r"(?<=#define V8_BUILD_NUMBER)(?P<space>\s+)\d*$", 388 r"\g<space>3", 389 "//\n#define V8_BUILD_NUMBER 321\n")) 390 391 TAGS = """ 3924425.0 3930.0.0.0 3943.9.6 3953.22.4 396test_tag 397""" 398 399 # Version as tag: 3.22.4.0. Version on main: 3.22.6. 400 # Make sure that the latest version is 3.22.6.0. 401 def testIncrementVersion(self): 402 self.Expect([ 403 Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""), 404 Cmd("git tag", self.TAGS), 405 Cmd("git checkout -f origin/main -- include/v8-version.h", 406 "", cb=lambda: self.WriteFakeVersionFile(3, 22, 6)), 407 ]) 408 409 self.RunStep(CreateRelease, IncrementVersion) 410 411 self.assertEquals("3", self._state["new_major"]) 412 self.assertEquals("22", self._state["new_minor"]) 413 self.assertEquals("7", self._state["new_build"]) 414 self.assertEquals("0", self._state["new_patch"]) 415 416 def testBootstrapper(self): 417 work_dir = self.MakeEmptyTempDirectory() 418 class FakeScript(ScriptsBase): 419 def _Steps(self): 420 return [] 421 422 # Use the test configuration without the fake testing default work dir. 423 fake_config = dict(TEST_CONFIG) 424 del(fake_config["DEFAULT_CWD"]) 425 426 self.Expect([ 427 Cmd("fetch v8", "", cwd=work_dir), 428 ]) 429 FakeScript(fake_config, self).Run(["--work-dir", work_dir]) 430 431 def testCreateRelease(self): 432 TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git")) 433 434 # The version file on main has build level 5. 435 self.WriteFakeVersionFile(build=5) 436 437 commit_msg = """Version 3.22.5""" 438 439 def CheckVersionCommit(): 440 commit = FileToText(TEST_CONFIG["COMMITMSG_FILE"]) 441 self.assertEquals(commit_msg, commit) 442 version = FileToText( 443 os.path.join(TEST_CONFIG["DEFAULT_CWD"], VERSION_FILE)) 444 self.assertTrue(re.search(r"#define V8_MINOR_VERSION\s+22", version)) 445 self.assertTrue(re.search(r"#define V8_BUILD_NUMBER\s+5", version)) 446 self.assertFalse(re.search(r"#define V8_BUILD_NUMBER\s+6", version)) 447 self.assertTrue(re.search(r"#define V8_PATCH_LEVEL\s+0", version)) 448 self.assertTrue( 449 re.search(r"#define V8_IS_CANDIDATE_VERSION\s+0", version)) 450 451 expectations = [ 452 Cmd("git fetch origin +refs/heads/*:refs/heads/*", ""), 453 Cmd("git checkout -f origin/main", "", cb=self.WriteFakeWatchlistsFile), 454 Cmd("git branch", ""), 455 Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""), 456 Cmd("git tag", self.TAGS), 457 Cmd("git checkout -f origin/main -- include/v8-version.h", 458 "", cb=self.WriteFakeVersionFile), 459 Cmd("git log -1 --format=%H 3.22.4", "release_hash\n"), 460 Cmd("git log -1 --format=%s release_hash", "Version 3.22.4\n"), 461 Cmd("git log -1 --format=%H release_hash^", "abc3\n"), 462 Cmd("git log --format=%H abc3..push_hash", "rev1\n"), 463 Cmd("git push origin push_hash:refs/heads/3.22.5", ""), 464 Cmd("git reset --hard origin/main", ""), 465 Cmd("git new-branch work-branch --upstream origin/3.22.5", ""), 466 Cmd("git checkout -f 3.22.4 -- include/v8-version.h", "", 467 cb=self.WriteFakeVersionFile), 468 Cmd("git commit -aF \"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], "", 469 cb=CheckVersionCommit), 470 Cmd("git cl upload --send-mail " 471 "-f --set-bot-commit --bypass-hooks --no-autocc --message-file " 472 "\"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], ""), 473 Cmd("git cl land --bypass-hooks -f", ""), 474 Cmd("git fetch", ""), 475 Cmd("git log -1 --format=%H --grep=" 476 "\"Version 3.22.5\" origin/3.22.5", "hsh_to_tag"), 477 Cmd("git tag 3.22.5 hsh_to_tag", ""), 478 Cmd("git push origin refs/tags/3.22.5:refs/tags/3.22.5", ""), 479 Cmd("git checkout -f origin/main", ""), 480 Cmd("git branch", "* main\n work-branch\n"), 481 Cmd("git branch -D work-branch", ""), 482 Cmd("git gc", ""), 483 ] 484 self.Expect(expectations) 485 486 args = ["-a", "author@chromium.org", 487 "-r", "reviewer@chromium.org", 488 "--revision", "push_hash"] 489 CreateRelease(TEST_CONFIG, self).Run(args) 490 491 # Note: The version file is on build number 5 again in the end of this test 492 # since the git command that merges to main is mocked out. 493 494 # Check for correct content of the WATCHLISTS file 495 496 watchlists_content = FileToText(os.path.join(TEST_CONFIG["DEFAULT_CWD"], 497 WATCHLISTS_FILE)) 498 expected_watchlists_content = """ 499 'merges': [ 500 # Only enabled on branches created with tools/release/create_release.py 501 'v8-merges@googlegroups.com', 502 ], 503""" 504 self.assertEqual(watchlists_content, expected_watchlists_content) 505 506 C_V8_22624_LOG = """V8 CL. 507 508git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@22624 123 509 510""" 511 512 C_V8_123455_LOG = """V8 CL. 513 514git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@123455 123 515 516""" 517 518 C_V8_123456_LOG = """V8 CL. 519 520git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@123456 123 521 522""" 523 524 ROLL_COMMIT_MSG = """Update V8 to version 3.22.4. 525 526Summary of changes available at: 527https://chromium.googlesource.com/v8/v8/+log/last_rol..roll_hsh 528 529Please follow these instructions for assigning/CC'ing issues: 530https://v8.dev/docs/triage-issues 531 532Please close rolling in case of a roll revert: 533https://v8-roll.appspot.com/ 534This only works with a Google account. 535 536CQ_INCLUDE_TRYBOTS=luci.chromium.try:linux-blink-rel 537CQ_INCLUDE_TRYBOTS=luci.chromium.try:linux_optional_gpu_tests_rel 538CQ_INCLUDE_TRYBOTS=luci.chromium.try:mac_optional_gpu_tests_rel 539CQ_INCLUDE_TRYBOTS=luci.chromium.try:win_optional_gpu_tests_rel 540CQ_INCLUDE_TRYBOTS=luci.chromium.try:android_optional_gpu_tests_rel 541 542R=reviewer@chromium.org""" 543 544 # Snippet from the original DEPS file. 545 FAKE_DEPS = """ 546vars = { 547 "v8_revision": "last_roll_hsh", 548} 549deps = { 550 "src/v8": 551 (Var("googlecode_url") % "v8") + "/" + Var("v8_branch") + "@" + 552 Var("v8_revision"), 553} 554""" 555 556 def testChromiumRollUpToDate(self): 557 TEST_CONFIG["CHROMIUM"] = self.MakeEmptyTempDirectory() 558 json_output_file = os.path.join(TEST_CONFIG["CHROMIUM"], "out.json") 559 TextToFile(self.FAKE_DEPS, os.path.join(TEST_CONFIG["CHROMIUM"], "DEPS")) 560 chrome_dir = TEST_CONFIG["CHROMIUM"] 561 self.Expect([ 562 Cmd("git fetch origin", ""), 563 Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""), 564 Cmd("gclient getdep -r src/v8", "last_roll_hsh", cwd=chrome_dir), 565 Cmd("git describe --tags last_roll_hsh", "3.22.4"), 566 Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""), 567 Cmd("git rev-list --max-age=395200 --tags", 568 "bad_tag\nroll_hsh\nhash_123"), 569 Cmd("git describe --tags bad_tag", ""), 570 Cmd("git describe --tags roll_hsh", "3.22.4"), 571 Cmd("git describe --tags hash_123", "3.22.3"), 572 Cmd("git describe --tags roll_hsh", "3.22.4"), 573 Cmd("git describe --tags hash_123", "3.22.3"), 574 ]) 575 576 result = auto_roll.AutoRoll(TEST_CONFIG, self).Run( 577 AUTO_PUSH_ARGS + [ 578 "-c", TEST_CONFIG["CHROMIUM"], 579 "--json-output", json_output_file]) 580 self.assertEquals(0, result) 581 json_output = json.loads(FileToText(json_output_file)) 582 self.assertEquals("up_to_date", json_output["monitoring_state"]) 583 584 585 def testChromiumRoll(self): 586 # Setup fake directory structures. 587 TEST_CONFIG["CHROMIUM"] = self.MakeEmptyTempDirectory() 588 json_output_file = os.path.join(TEST_CONFIG["CHROMIUM"], "out.json") 589 TextToFile(self.FAKE_DEPS, os.path.join(TEST_CONFIG["CHROMIUM"], "DEPS")) 590 TextToFile("", os.path.join(TEST_CONFIG["CHROMIUM"], ".git")) 591 chrome_dir = TEST_CONFIG["CHROMIUM"] 592 os.makedirs(os.path.join(chrome_dir, "v8")) 593 594 def WriteDeps(): 595 TextToFile("Some line\n \"v8_revision\": \"22624\",\n some line", 596 os.path.join(chrome_dir, "DEPS")) 597 598 expectations = [ 599 Cmd("git fetch origin", ""), 600 Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""), 601 Cmd("gclient getdep -r src/v8", "last_roll_hsh", cwd=chrome_dir), 602 Cmd("git describe --tags last_roll_hsh", "3.22.3.1"), 603 Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""), 604 Cmd("git rev-list --max-age=395200 --tags", 605 "bad_tag\nroll_hsh\nhash_123"), 606 Cmd("git describe --tags bad_tag", ""), 607 Cmd("git describe --tags roll_hsh", "3.22.4"), 608 Cmd("git describe --tags hash_123", "3.22.3"), 609 Cmd("git describe --tags roll_hsh", "3.22.4"), 610 Cmd("git log -1 --format=%s roll_hsh", "Version 3.22.4\n"), 611 Cmd("git describe --tags roll_hsh", "3.22.4"), 612 Cmd("git describe --tags last_roll_hsh", "3.22.2.1"), 613 Cmd("git status -s -uno", "", cwd=chrome_dir), 614 Cmd("git checkout -f main", "", cwd=chrome_dir), 615 Cmd("git branch", "", cwd=chrome_dir), 616 Cmd("git pull", "", cwd=chrome_dir), 617 Cmd("git fetch origin", ""), 618 Cmd("git new-branch work-branch", "", cwd=chrome_dir), 619 Cmd("gclient setdep -r src/v8@roll_hsh", "", cb=WriteDeps, 620 cwd=chrome_dir), 621 Cmd(("git commit -am \"%s\" " 622 "--author \"author@chromium.org <author@chromium.org>\"" % 623 self.ROLL_COMMIT_MSG), 624 "", cwd=chrome_dir), 625 Cmd("git cl upload --send-mail -f " 626 "--cq-dry-run --set-bot-commit --bypass-hooks", "", 627 cwd=chrome_dir), 628 Cmd("git checkout -f main", "", cwd=chrome_dir), 629 Cmd("git branch -D work-branch", "", cwd=chrome_dir), 630 ] 631 self.Expect(expectations) 632 633 args = ["-a", "author@chromium.org", "-c", chrome_dir, 634 "-r", "reviewer@chromium.org", "--json-output", json_output_file] 635 auto_roll.AutoRoll(TEST_CONFIG, self).Run(args) 636 637 deps = FileToText(os.path.join(chrome_dir, "DEPS")) 638 self.assertTrue(re.search("\"v8_revision\": \"22624\"", deps)) 639 640 json_output = json.loads(FileToText(json_output_file)) 641 self.assertEquals("success", json_output["monitoring_state"]) 642 643 def testCheckLastPushRecently(self): 644 self.Expect([ 645 Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""), 646 Cmd("git tag", self.TAGS), 647 Cmd("git log -1 --format=%H 3.22.4", "release_hash\n"), 648 Cmd("git log -1 --format=%s release_hash", 649 "Version 3.22.4 (based on abc3)\n"), 650 Cmd("git log --format=%H abc3..abc123", "\n"), 651 ]) 652 653 self._state["candidate"] = "abc123" 654 self.assertEquals(0, self.RunStep( 655 auto_push.AutoPush, LastReleaseBailout, AUTO_PUSH_ARGS)) 656 657 def testAutoPush(self): 658 self.Expect([ 659 Cmd("git fetch", ""), 660 Cmd("git fetch origin +refs/heads/lkgr:refs/heads/lkgr", ""), 661 Cmd("git show-ref -s refs/heads/lkgr", "abc123\n"), 662 Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""), 663 Cmd("git tag", self.TAGS), 664 Cmd("git log -1 --format=%H 3.22.4", "release_hash\n"), 665 Cmd("git log -1 --format=%s release_hash", 666 "Version 3.22.4 (based on abc3)\n"), 667 Cmd("git log --format=%H abc3..abc123", "some_stuff\n"), 668 ]) 669 670 auto_push.AutoPush(TEST_CONFIG, self).Run(AUTO_PUSH_ARGS + ["--push"]) 671 672 state = json.loads(FileToText("%s-state.json" 673 % TEST_CONFIG["PERSISTFILE_BASENAME"])) 674 675 self.assertEquals("abc123", state["candidate"]) 676 677 def testRollMerge(self): 678 TEST_CONFIG["ALREADY_MERGING_SENTINEL_FILE"] = self.MakeEmptyTempFile() 679 TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git")) 680 self.WriteFakeVersionFile(build=5) 681 os.environ["EDITOR"] = "vi" 682 extra_patch = self.MakeEmptyTempFile() 683 684 def VerifyPatch(patch): 685 return lambda: self.assertEquals(patch, 686 FileToText(TEST_CONFIG["TEMPORARY_PATCH_FILE"])) 687 688 msg = """Version 3.22.5.1 (cherry-pick) 689 690Merged ab12345 691Merged ab23456 692Merged ab34567 693Merged ab45678 694Merged ab56789 695 696Title4 697 698Title2 699 700Title3 701 702Title1 703 704Revert "Something" 705 706BUG=123,234,345,456,567,v8:123 707""" 708 709 def VerifyLand(): 710 commit = FileToText(TEST_CONFIG["COMMITMSG_FILE"]) 711 self.assertEquals(msg, commit) 712 version = FileToText( 713 os.path.join(TEST_CONFIG["DEFAULT_CWD"], VERSION_FILE)) 714 self.assertTrue(re.search(r"#define V8_MINOR_VERSION\s+22", version)) 715 self.assertTrue(re.search(r"#define V8_BUILD_NUMBER\s+5", version)) 716 self.assertTrue(re.search(r"#define V8_PATCH_LEVEL\s+1", version)) 717 self.assertTrue( 718 re.search(r"#define V8_IS_CANDIDATE_VERSION\s+0", version)) 719 720 self.Expect([ 721 Cmd("git status -s -uno", ""), 722 Cmd("git checkout -f origin/main", ""), 723 Cmd("git fetch", ""), 724 Cmd("git branch", " branch1\n* branch2\n"), 725 Cmd("git new-branch %s --upstream refs/remotes/origin/candidates" % 726 TEST_CONFIG["BRANCHNAME"], ""), 727 Cmd(("git log --format=%H --grep=\"Port ab12345\" " 728 "--reverse origin/main"), 729 "ab45678\nab23456"), 730 Cmd("git log -1 --format=%s ab45678", "Title1"), 731 Cmd("git log -1 --format=%s ab23456", "Title2"), 732 Cmd(("git log --format=%H --grep=\"Port ab23456\" " 733 "--reverse origin/main"), 734 ""), 735 Cmd(("git log --format=%H --grep=\"Port ab34567\" " 736 "--reverse origin/main"), 737 "ab56789"), 738 Cmd("git log -1 --format=%s ab56789", "Title3"), 739 RL("Y"), # Automatically add corresponding ports (ab34567, ab56789)? 740 # Simulate git being down which stops the script. 741 Cmd("git log -1 --format=%s ab12345", None), 742 # Restart script in the failing step. 743 Cmd("git log -1 --format=%s ab12345", "Title4"), 744 Cmd("git log -1 --format=%s ab23456", "Title2"), 745 Cmd("git log -1 --format=%s ab34567", "Title3"), 746 Cmd("git log -1 --format=%s ab45678", "Title1"), 747 Cmd("git log -1 --format=%s ab56789", "Revert \"Something\""), 748 Cmd("git log -1 ab12345", "Title4\nBUG=123\nBUG=234"), 749 Cmd("git log -1 ab23456", "Title2\n BUG = v8:123,345"), 750 Cmd("git log -1 ab34567", "Title3\nBUG=567, 456"), 751 Cmd("git log -1 ab45678", "Title1\nBUG="), 752 Cmd("git log -1 ab56789", "Revert \"Something\"\nBUG=none"), 753 Cmd("git log -1 -p ab12345", "patch4"), 754 Cmd(("git apply --index --reject \"%s\"" % 755 TEST_CONFIG["TEMPORARY_PATCH_FILE"]), 756 "", cb=VerifyPatch("patch4")), 757 Cmd("git log -1 -p ab23456", "patch2"), 758 Cmd(("git apply --index --reject \"%s\"" % 759 TEST_CONFIG["TEMPORARY_PATCH_FILE"]), 760 "", cb=VerifyPatch("patch2")), 761 Cmd("git log -1 -p ab34567", "patch3"), 762 Cmd(("git apply --index --reject \"%s\"" % 763 TEST_CONFIG["TEMPORARY_PATCH_FILE"]), 764 "", cb=VerifyPatch("patch3")), 765 Cmd("git log -1 -p ab45678", "patch1"), 766 Cmd(("git apply --index --reject \"%s\"" % 767 TEST_CONFIG["TEMPORARY_PATCH_FILE"]), 768 "", cb=VerifyPatch("patch1")), 769 Cmd("git log -1 -p ab56789", "patch5\n"), 770 Cmd(("git apply --index --reject \"%s\"" % 771 TEST_CONFIG["TEMPORARY_PATCH_FILE"]), 772 "", cb=VerifyPatch("patch5\n")), 773 Cmd("git apply --index --reject \"%s\"" % extra_patch, ""), 774 RL("Y"), # Automatically increment patch level? 775 Cmd("git commit -aF \"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], ""), 776 RL("reviewer@chromium.org"), # V8 reviewer. 777 Cmd("git cl upload --send-mail -r \"reviewer@chromium.org\" " 778 "--bypass-hooks", ""), 779 Cmd("git checkout -f %s" % TEST_CONFIG["BRANCHNAME"], ""), 780 RL("LGTM"), # Enter LGTM for V8 CL. 781 Cmd("git cl presubmit", "Presubmit successfull\n"), 782 Cmd("git cl land -f --bypass-hooks", "Closing issue\n", 783 cb=VerifyLand), 784 Cmd("git fetch", ""), 785 Cmd("git log -1 --format=%H --grep=\"" 786 "Version 3.22.5.1 (cherry-pick)" 787 "\" refs/remotes/origin/candidates", 788 ""), 789 Cmd("git fetch", ""), 790 Cmd("git log -1 --format=%H --grep=\"" 791 "Version 3.22.5.1 (cherry-pick)" 792 "\" refs/remotes/origin/candidates", 793 "hsh_to_tag"), 794 Cmd("git tag 3.22.5.1 hsh_to_tag", ""), 795 Cmd("git push origin refs/tags/3.22.5.1:refs/tags/3.22.5.1", ""), 796 Cmd("git checkout -f origin/main", ""), 797 Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], ""), 798 ]) 799 800 # ab12345 and ab34567 are patches. ab23456 (included) and ab45678 are the 801 # MIPS ports of ab12345. ab56789 is the MIPS port of ab34567. 802 args = ["-f", "-p", extra_patch, "--branch", "candidates", 803 "ab12345", "ab23456", "ab34567"] 804 805 # The first run of the script stops because of git being down. 806 self.assertRaises(GitFailedException, 807 lambda: RollMerge(TEST_CONFIG, self).Run(args)) 808 809 # Test that state recovery after restarting the script works. 810 args += ["-s", "4"] 811 RollMerge(TEST_CONFIG, self).Run(args) 812 813 def testMergeToBranch(self): 814 TEST_CONFIG["ALREADY_MERGING_SENTINEL_FILE"] = self.MakeEmptyTempFile() 815 TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git")) 816 self.WriteFakeVersionFile(build=5) 817 os.environ["EDITOR"] = "vi" 818 extra_patch = self.MakeEmptyTempFile() 819 820 821 def VerifyPatch(patch): 822 return lambda: self.assertEquals(patch, 823 FileToText(TEST_CONFIG["TEMPORARY_PATCH_FILE"])) 824 825 info_msg = ("NOTE: This script will no longer automatically " 826 "update include/v8-version.h " 827 "and create a tag. This is done automatically by the autotag bot. " 828 "Please call the merge_to_branch.py with --help for more information.") 829 830 msg = """Merged: Squashed multiple commits. 831 832Merged: Title4 833Revision: ab12345 834 835Merged: Title2 836Revision: ab23456 837 838Merged: Title3 839Revision: ab34567 840 841Merged: Title1 842Revision: ab45678 843 844Merged: Revert \"Something\" 845Revision: ab56789 846 847BUG=123,234,345,456,567,v8:123 848NOTRY=true 849NOPRESUBMIT=true 850NOTREECHECKS=true 851""" 852 853 def VerifyLand(): 854 commit = FileToText(TEST_CONFIG["COMMITMSG_FILE"]) 855 self.assertEquals(msg, commit) 856 857 self.Expect([ 858 Cmd("git status -s -uno", ""), 859 Cmd("git checkout -f origin/main", ""), 860 Cmd("git fetch", ""), 861 Cmd("git branch", " branch1\n* branch2\n"), 862 Cmd("git new-branch %s --upstream refs/remotes/origin/candidates" % 863 TEST_CONFIG["BRANCHNAME"], ""), 864 Cmd(("git log --format=%H --grep=\"^[Pp]ort ab12345\" " 865 "--reverse origin/main"), 866 "ab45678\nab23456"), 867 Cmd("git log -1 --format=%s ab45678", "Title1"), 868 Cmd("git log -1 --format=%s ab23456", "Title2"), 869 Cmd(("git log --format=%H --grep=\"^[Pp]ort ab23456\" " 870 "--reverse origin/main"), 871 ""), 872 Cmd(("git log --format=%H --grep=\"^[Pp]ort ab34567\" " 873 "--reverse origin/main"), 874 "ab56789"), 875 Cmd("git log -1 --format=%s ab56789", "Title3"), 876 RL("Y"), # Automatically add corresponding ports (ab34567, ab56789)? 877 # Simulate git being down which stops the script. 878 Cmd("git log -1 --format=%s ab12345", None), 879 # Restart script in the failing step. 880 Cmd("git log -1 --format=%s ab12345", "Title4"), 881 Cmd("git log -1 --format=%s ab23456", "Title2"), 882 Cmd("git log -1 --format=%s ab34567", "Title3"), 883 Cmd("git log -1 --format=%s ab45678", "Title1"), 884 Cmd("git log -1 --format=%s ab56789", "Revert \"Something\""), 885 Cmd("git log -1 ab12345", "Title4\nBUG=123\nBUG=234"), 886 Cmd("git log -1 ab23456", "Title2\n BUG = v8:123,345"), 887 Cmd("git log -1 ab34567", "Title3\nBug: 567, 456,345"), 888 Cmd("git log -1 ab45678", "Title1\nBug:"), 889 Cmd("git log -1 ab56789", "Revert \"Something\"\nBUG=none"), 890 Cmd("git log -1 -p ab12345", "patch4"), 891 Cmd(("git apply --index --reject \"%s\"" % 892 TEST_CONFIG["TEMPORARY_PATCH_FILE"]), 893 "", cb=VerifyPatch("patch4")), 894 Cmd("git log -1 -p ab23456", "patch2"), 895 Cmd(("git apply --index --reject \"%s\"" % 896 TEST_CONFIG["TEMPORARY_PATCH_FILE"]), 897 "", cb=VerifyPatch("patch2")), 898 Cmd("git log -1 -p ab34567", "patch3"), 899 Cmd(("git apply --index --reject \"%s\"" % 900 TEST_CONFIG["TEMPORARY_PATCH_FILE"]), 901 "", cb=VerifyPatch("patch3")), 902 Cmd("git log -1 -p ab45678", "patch1"), 903 Cmd(("git apply --index --reject \"%s\"" % 904 TEST_CONFIG["TEMPORARY_PATCH_FILE"]), 905 "", cb=VerifyPatch("patch1")), 906 Cmd("git log -1 -p ab56789", "patch5\n"), 907 Cmd(("git apply --index --reject \"%s\"" % 908 TEST_CONFIG["TEMPORARY_PATCH_FILE"]), 909 "", cb=VerifyPatch("patch5\n")), 910 Cmd("git apply --index --reject \"%s\"" % extra_patch, ""), 911 Cmd("git commit -aF \"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], ""), 912 RL("reviewer@chromium.org"), # V8 reviewer. 913 Cmd("git cl upload --send-mail -r \"reviewer@chromium.org\" " 914 "--bypass-hooks", ""), 915 Cmd("git checkout -f %s" % TEST_CONFIG["BRANCHNAME"], ""), 916 RL("LGTM"), # Enter LGTM for V8 CL. 917 Cmd("git cl presubmit", "Presubmit successfull\n"), 918 Cmd("git cl land -f --bypass-hooks", "Closing issue\n", 919 cb=VerifyLand), 920 Cmd("git checkout -f origin/main", ""), 921 Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], ""), 922 ]) 923 924 # ab12345 and ab34567 are patches. ab23456 (included) and ab45678 are the 925 # MIPS ports of ab12345. ab56789 is the MIPS port of ab34567. 926 args = ["-f", "-p", extra_patch, "--branch", "candidates", 927 "ab12345", "ab23456", "ab34567"] 928 929 # The first run of the script stops because of git being down. 930 self.assertRaises(GitFailedException, 931 lambda: MergeToBranch(TEST_CONFIG, self).Run(args)) 932 933 # Test that state recovery after restarting the script works. 934 args += ["-s", "4"] 935 MergeToBranch(TEST_CONFIG, self).Run(args) 936 937if __name__ == '__main__': 938 unittest.main() 939