test_scripts.py 33.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
#!/usr/bin/env python
# Copyright 2013 the V8 project authors. All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above
#       copyright notice, this list of conditions and the following
#       disclaimer in the documentation and/or other materials provided
#       with the distribution.
#     * Neither the name of Google Inc. nor the names of its
#       contributors may be used to endorse or promote products derived
#       from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

29 30 31
# for py2/py3 compatibility
from __future__ import print_function

32
import os
33
import shutil
34
import tempfile
35
import traceback
36 37
import unittest

38
import auto_push
39
from auto_push import LastReleaseBailout
40
import auto_roll
41 42
import common_includes
from common_includes import *
43
import create_release
44
from create_release import *
45
import merge_to_branch
46
from merge_to_branch import MergeToBranch
47
from auto_tag import AutoTag
48 49
import roll_merge
from roll_merge import RollMerge
50 51

TEST_CONFIG = {
52
  "DEFAULT_CWD": None,
53
  "BRANCHNAME": "test-prepare-push",
54 55 56 57
  "PERSISTFILE_BASENAME": "/tmp/test-create-releases-tempfile",
  "PATCH_FILE": "/tmp/test-v8-create-releases-tempfile-tempfile-patch",
  "COMMITMSG_FILE": "/tmp/test-v8-create-releases-tempfile-commitmsg",
  "CHROMIUM": "/tmp/test-create-releases-tempfile-chromium",
58 59
  "SETTINGS_LOCATION": None,
  "ALREADY_MERGING_SENTINEL_FILE":
60
      "/tmp/test-merge-to-branch-tempfile-already-merging",
61
  "TEMPORARY_PATCH_FILE": "/tmp/test-merge-to-branch-tempfile-temporary-patch",
62 63
}

64

65
AUTO_PUSH_ARGS = [
66 67 68 69
  "-a", "author@chromium.org",
  "-r", "reviewer@chromium.org",
]

70

71
class ToplevelTest(unittest.TestCase):
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
  def testSaniniziteVersionTags(self):
    self.assertEquals("4.8.230", SanitizeVersionTag("4.8.230"))
    self.assertEquals("4.8.230", SanitizeVersionTag("tags/4.8.230"))
    self.assertEquals(None, SanitizeVersionTag("candidate"))

  def testNormalizeVersionTags(self):
    input = ["4.8.230",
              "tags/4.8.230",
              "tags/4.8.224.1",
              "4.8.224.1",
              "4.8.223.1",
              "tags/4.8.223",
              "tags/4.8.231",
              "candidates"]
    expected = ["4.8.230",
                "4.8.230",
                "4.8.224.1",
                "4.8.224.1",
                "4.8.223.1",
                "4.8.223",
                "4.8.231",
                ]
    self.assertEquals(expected, NormalizeVersionTags(input))

96

97
def Cmd(*args, **kwargs):
98
  """Convenience function returning a shell command test expectation."""
99
  return {
100
    "name": "command",
101
    "args": args,
102 103
    "ret": args[-1],
    "cb": kwargs.get("cb"),
104
    "cwd": kwargs.get("cwd", TEST_CONFIG["DEFAULT_CWD"]),
105 106 107 108 109
  }


def RL(text, cb=None):
  """Convenience function returning a readline test expectation."""
110 111 112 113 114 115 116
  return {
    "name": "readline",
    "args": [],
    "ret": text,
    "cb": cb,
    "cwd": None,
  }
117 118 119 120 121 122 123 124 125


def URL(*args, **kwargs):
  """Convenience function returning a readurl test expectation."""
  return {
    "name": "readurl",
    "args": args[:-1],
    "ret": args[-1],
    "cb": kwargs.get("cb"),
126
    "cwd": None,
127 128 129
  }


130
class SimpleMock(object):
131
  def __init__(self):
132 133 134 135 136 137
    self._recipe = []
    self._index = -1

  def Expect(self, recipe):
    self._recipe = recipe

138
  def Call(self, name, *args, **kwargs):  # pragma: no cover
139
    self._index += 1
140

141 142 143
    try:
      expected_call = self._recipe[self._index]
    except IndexError:
144
      raise NoRetryException("Calling %s %s" % (name, " ".join(args)))
145 146

    if not isinstance(expected_call, dict):
147 148
      raise NoRetryException("Found wrong expectation type for %s %s" %
                             (name, " ".join(args)))
149

150 151 152
    if expected_call["name"] != name:
      raise NoRetryException("Expected action: %s %s - Actual: %s" %
          (expected_call["name"], expected_call["args"], name))
153

154 155 156 157 158 159 160 161
    # Check if the given working directory matches the expected one.
    if expected_call["cwd"] != kwargs.get("cwd"):
      raise NoRetryException("Expected cwd: %s in %s %s - Actual: %s" %
          (expected_call["cwd"],
           expected_call["name"],
           expected_call["args"],
           kwargs.get("cwd")))

162 163
    # The number of arguments in the expectation must match the actual
    # arguments.
164
    if len(args) > len(expected_call['args']):
165
      raise NoRetryException("When calling %s with arguments, the "
166
          "expectations must consist of at least as many arguments." %
167
          name)
168 169

    # Compare expected and actual arguments.
170
    for (expected_arg, actual_arg) in zip(expected_call['args'], args):
171
      if expected_arg != actual_arg:
172 173
        raise NoRetryException("Expected: %s - Actual: %s" %
                               (expected_arg, actual_arg))
174

175 176 177
    # The expected call contains an optional callback for checking the context
    # at the time of the call.
    if expected_call['cb']:
178
      try:
179
        expected_call['cb']()
180 181 182
      except:
        tb = traceback.format_exc()
        raise NoRetryException("Caught exception from callback: %s" % tb)
183 184

    # If the return value is an exception, raise it instead of returning.
185 186 187
    if isinstance(expected_call['ret'], Exception):
      raise expected_call['ret']
    return expected_call['ret']
188

189
  def AssertFinished(self):  # pragma: no cover
190
    if self._index < len(self._recipe) -1:
191 192
      raise NoRetryException("Called mock too seldom: %d vs. %d" %
                             (self._index, len(self._recipe)))
193 194


195 196 197 198 199 200 201
class ScriptTest(unittest.TestCase):
  def MakeEmptyTempFile(self):
    handle, name = tempfile.mkstemp()
    os.close(handle)
    self._tmp_files.append(name)
    return name

202 203 204 205 206 207
  def MakeEmptyTempDirectory(self):
    name = tempfile.mkdtemp()
    self._tmp_files.append(name)
    return name


208
  def WriteFakeVersionFile(self, major=3, minor=22, build=4, patch=0):
209 210 211 212
    version_file = os.path.join(TEST_CONFIG["DEFAULT_CWD"], VERSION_FILE)
    if not os.path.exists(os.path.dirname(version_file)):
      os.makedirs(os.path.dirname(version_file))
    with open(version_file, "w") as f:
213 214
      f.write("  // Some line...\n")
      f.write("\n")
215 216 217 218
      f.write("#define V8_MAJOR_VERSION    %s\n" % major)
      f.write("#define V8_MINOR_VERSION    %s\n" % minor)
      f.write("#define V8_BUILD_NUMBER     %s\n" % build)
      f.write("#define V8_PATCH_LEVEL      %s\n" % patch)
219
      f.write("  // Some line...\n")
220
      f.write("#define V8_IS_CANDIDATE_VERSION 0\n")
221

222 223 224 225 226 227 228 229 230 231 232 233 234 235
  def WriteFakeWatchlistsFile(self):
    watchlists_file = os.path.join(TEST_CONFIG["DEFAULT_CWD"], WATCHLISTS_FILE)
    if not os.path.exists(os.path.dirname(watchlists_file)):
      os.makedirs(os.path.dirname(watchlists_file))
    with open(watchlists_file, "w") as f:

      content = """
    'merges': [
      # Only enabled on branches created with tools/release/create_release.py
      # 'v8-merges@googlegroups.com',
    ],
"""
      f.write(content)

236 237 238 239 240 241 242
  def MakeStep(self):
    """Convenience wrapper."""
    options = ScriptsBase(TEST_CONFIG, self, self._state).MakeOptions([])
    return MakeStep(step_class=Step, state=self._state,
                    config=TEST_CONFIG, side_effect_handler=self,
                    options=options)

243
  def RunStep(self, script=CreateRelease, step_class=Step, args=None):
244
    """Convenience wrapper."""
245
    args = args if args is not None else ["-m", "-a=author", "-r=reviewer", ]
246
    return script(TEST_CONFIG, self, self._state).RunSteps([step_class], args)
247

248
  def Call(self, fun, *args, **kwargs):
249
    print("Calling %s with %s and %s" % (str(fun), str(args), str(kwargs)))
250

251
  def Command(self, cmd, args="", prefix="", pipe=True, cwd=None):
252 253
    print("%s %s" % (cmd, args))
    print("in %s" % cwd)
254
    return self._mock.Call("command", cmd + " " + args, cwd=cwd)
255 256

  def ReadLine(self):
257
    return self._mock.Call("readline")
258

259 260
  def ReadURL(self, url, params):
    if params is not None:
261
      return self._mock.Call("readurl", url, params)
262
    else:
263
      return self._mock.Call("readurl", url)
264

265 266 267
  def Sleep(self, seconds):
    pass

268
  def GetUTCStamp(self):
269
    return "1000000"
270

271
  def Expect(self, *args):
272
    """Convenience wrapper."""
273
    self._mock.Expect(*args)
274 275

  def setUp(self):
276
    self._mock = SimpleMock()
277
    self._tmp_files = []
278
    self._state = {}
279
    TEST_CONFIG["DEFAULT_CWD"] = self.MakeEmptyTempDirectory()
280 281

  def tearDown(self):
282 283
    if os.path.exists(TEST_CONFIG["PERSISTFILE_BASENAME"]):
      shutil.rmtree(TEST_CONFIG["PERSISTFILE_BASENAME"])
284 285 286

    # Clean up temps. Doesn't work automatically.
    for name in self._tmp_files:
287
      if os.path.isfile(name):
288
        os.remove(name)
289 290
      if os.path.isdir(name):
        shutil.rmtree(name)
291

292
    self._mock.AssertFinished()
293 294

  def testGitMock(self):
295 296
    self.Expect([Cmd("git --version", "git version 1.2.3"),
                 Cmd("git dummy", "")])
297 298 299 300
    self.assertEquals("git version 1.2.3", self.MakeStep().Git("--version"))
    self.assertEquals("", self.MakeStep().Git("dummy"))

  def testCommonPrepareDefault(self):
301
    self.Expect([
302
      Cmd("git status -s -uno", ""),
303
      Cmd("git checkout -f origin/main", ""),
304
      Cmd("git fetch", ""),
305
      Cmd("git branch", "  branch1\n* %s" % TEST_CONFIG["BRANCHNAME"]),
306
      RL("Y"),
307
      Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], ""),
308
    ])
309
    self.MakeStep().CommonPrepare()
310
    self.MakeStep().PrepareBranch()
311 312

  def testCommonPrepareNoConfirm(self):
313
    self.Expect([
314
      Cmd("git status -s -uno", ""),
315
      Cmd("git checkout -f origin/main", ""),
316
      Cmd("git fetch", ""),
317
      Cmd("git branch", "  branch1\n* %s" % TEST_CONFIG["BRANCHNAME"]),
318
      RL("n"),
319
    ])
320 321
    self.MakeStep().CommonPrepare()
    self.assertRaises(Exception, self.MakeStep().PrepareBranch)
322 323

  def testCommonPrepareDeleteBranchFailure(self):
324
    self.Expect([
325
      Cmd("git status -s -uno", ""),
326
      Cmd("git checkout -f origin/main", ""),
327
      Cmd("git fetch", ""),
328
      Cmd("git branch", "  branch1\n* %s" % TEST_CONFIG["BRANCHNAME"]),
329
      RL("Y"),
330
      Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], None),
331
    ])
332 333
    self.MakeStep().CommonPrepare()
    self.assertRaises(Exception, self.MakeStep().PrepareBranch)
334 335

  def testInitialEnvironmentChecks(self):
336
    TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git"))
337
    os.environ["EDITOR"] = "vi"
338 339 340
    self.Expect([
      Cmd("which vi", "/usr/bin/vi"),
    ])
341
    self.MakeStep().InitialEnvironmentChecks(TEST_CONFIG["DEFAULT_CWD"])
342

343 344 345
  def testTagTimeout(self):
    self.Expect([
      Cmd("git fetch", ""),
346
      Cmd("git log -1 --format=%H --grep=\"Title\" origin/tag_name", ""),
347
      Cmd("git fetch", ""),
348
      Cmd("git log -1 --format=%H --grep=\"Title\" origin/tag_name", ""),
349
      Cmd("git fetch", ""),
350
      Cmd("git log -1 --format=%H --grep=\"Title\" origin/tag_name", ""),
351
      Cmd("git fetch", ""),
352
      Cmd("git log -1 --format=%H --grep=\"Title\" origin/tag_name", ""),
353
    ])
354
    args = ["--branch", "candidates", "ab12345"]
355 356 357
    self._state["version"] = "tag_name"
    self._state["commit_title"] = "Title"
    self.assertRaises(Exception,
358
        lambda: self.RunStep(RollMerge, TagRevision, args))
359

360
  def testReadAndPersistVersion(self):
361
    self.WriteFakeVersionFile(build=5)
362 363
    step = self.MakeStep()
    step.ReadAndPersistVersion()
364 365 366 367
    self.assertEquals("3", step["major"])
    self.assertEquals("22", step["minor"])
    self.assertEquals("5", step["build"])
    self.assertEquals("0", step["patch"])
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384

  def testRegex(self):
    self.assertEqual("(issue 321)",
                     re.sub(r"BUG=v8:(.*)$", r"(issue \1)", "BUG=v8:321"))
    self.assertEqual("(Chromium issue 321)",
                     re.sub(r"BUG=(.*)$", r"(Chromium issue \1)", "BUG=321"))

    cl = "  too little\n\ttab\ttab\n         too much\n        trailing  "
    cl = MSub(r"\t", r"        ", cl)
    cl = MSub(r"^ {1,7}([^ ])", r"        \1", cl)
    cl = MSub(r"^ {9,80}([^ ])", r"        \1", cl)
    cl = MSub(r" +$", r"", cl)
    self.assertEqual("        too little\n"
                     "        tab        tab\n"
                     "        too much\n"
                     "        trailing", cl)

385 386
    self.assertEqual("//\n#define V8_BUILD_NUMBER  3\n",
                     MSub(r"(?<=#define V8_BUILD_NUMBER)(?P<space>\s+)\d*$",
387
                          r"\g<space>3",
388
                          "//\n#define V8_BUILD_NUMBER  321\n"))
389

390 391 392 393 394 395 396
  TAGS = """
4425.0
0.0.0.0
3.9.6
3.22.4
test_tag
"""
397

398
  # Version as tag: 3.22.4.0. Version on main: 3.22.6.
399
  # Make sure that the latest version is 3.22.6.0.
400
  def testIncrementVersion(self):
401
    self.Expect([
402
      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
403
      Cmd("git tag", self.TAGS),
404
      Cmd("git checkout -f origin/main -- include/v8-version.h",
405
          "", cb=lambda: self.WriteFakeVersionFile(3, 22, 6)),
406
    ])
407

408
    self.RunStep(CreateRelease, IncrementVersion)
409

410 411 412 413
    self.assertEquals("3", self._state["new_major"])
    self.assertEquals("22", self._state["new_minor"])
    self.assertEquals("7", self._state["new_build"])
    self.assertEquals("0", self._state["new_patch"])
414

415 416 417 418 419 420 421 422 423 424 425 426 427 428 429
  def testBootstrapper(self):
    work_dir = self.MakeEmptyTempDirectory()
    class FakeScript(ScriptsBase):
      def _Steps(self):
        return []

    # Use the test configuration without the fake testing default work dir.
    fake_config = dict(TEST_CONFIG)
    del(fake_config["DEFAULT_CWD"])

    self.Expect([
      Cmd("fetch v8", "", cwd=work_dir),
    ])
    FakeScript(fake_config, self).Run(["--work-dir", work_dir])

430 431 432
  def testCreateRelease(self):
    TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git"))

433
    # The version file on main has build level 5.
434 435
    self.WriteFakeVersionFile(build=5)

436
    commit_msg = """Version 3.22.5"""
437 438 439 440 441 442

    def CheckVersionCommit():
      commit = FileToText(TEST_CONFIG["COMMITMSG_FILE"])
      self.assertEquals(commit_msg, commit)
      version = FileToText(
          os.path.join(TEST_CONFIG["DEFAULT_CWD"], VERSION_FILE))
443 444 445 446 447 448
      self.assertTrue(re.search(r"#define V8_MINOR_VERSION\s+22", version))
      self.assertTrue(re.search(r"#define V8_BUILD_NUMBER\s+5", version))
      self.assertFalse(re.search(r"#define V8_BUILD_NUMBER\s+6", version))
      self.assertTrue(re.search(r"#define V8_PATCH_LEVEL\s+0", version))
      self.assertTrue(
          re.search(r"#define V8_IS_CANDIDATE_VERSION\s+0", version))
449 450

    expectations = [
451
      Cmd("git fetch origin +refs/heads/*:refs/heads/*", ""),
452
      Cmd("git checkout -f origin/main", "", cb=self.WriteFakeWatchlistsFile),
453 454 455
      Cmd("git branch", ""),
      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
      Cmd("git tag", self.TAGS),
456
      Cmd("git checkout -f origin/main -- include/v8-version.h",
457 458
          "", cb=self.WriteFakeVersionFile),
      Cmd("git log -1 --format=%H 3.22.4", "release_hash\n"),
459 460
      Cmd("git log -1 --format=%s release_hash", "Version 3.22.4\n"),
      Cmd("git log -1 --format=%H release_hash^", "abc3\n"),
461
      Cmd("git log --format=%H abc3..push_hash", "rev1\n"),
462
      Cmd("git push origin push_hash:refs/heads/3.22.5", ""),
463
      Cmd("git reset --hard origin/main", ""),
464
      Cmd("git new-branch work-branch --upstream origin/3.22.5", ""),
465
      Cmd("git checkout -f 3.22.4 -- include/v8-version.h", "",
466 467 468
          cb=self.WriteFakeVersionFile),
      Cmd("git commit -aF \"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], "",
          cb=CheckVersionCommit),
469
      Cmd("git cl upload --send-mail "
470
          "-f --set-bot-commit --bypass-hooks --no-autocc --message-file "
471
          "\"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], ""),
472
      Cmd("git cl land --bypass-hooks -f", ""),
473 474
      Cmd("git fetch", ""),
      Cmd("git log -1 --format=%H --grep="
475
          "\"Version 3.22.5\" origin/3.22.5", "hsh_to_tag"),
476
      Cmd("git tag 3.22.5 hsh_to_tag", ""),
477
      Cmd("git push origin refs/tags/3.22.5:refs/tags/3.22.5", ""),
478 479
      Cmd("git checkout -f origin/main", ""),
      Cmd("git branch", "* main\n  work-branch\n"),
480 481 482 483 484 485 486 487 488 489 490
      Cmd("git branch -D work-branch", ""),
      Cmd("git gc", ""),
    ]
    self.Expect(expectations)

    args = ["-a", "author@chromium.org",
            "-r", "reviewer@chromium.org",
            "--revision", "push_hash"]
    CreateRelease(TEST_CONFIG, self).Run(args)

    # Note: The version file is on build number 5 again in the end of this test
491
    # since the git command that merges to main is mocked out.
492

493 494 495 496 497 498 499 500 501 502 503 504
    # Check for correct content of the WATCHLISTS file

    watchlists_content = FileToText(os.path.join(TEST_CONFIG["DEFAULT_CWD"],
                                          WATCHLISTS_FILE))
    expected_watchlists_content = """
    'merges': [
      # Only enabled on branches created with tools/release/create_release.py
      'v8-merges@googlegroups.com',
    ],
"""
    self.assertEqual(watchlists_content, expected_watchlists_content)

505 506 507 508
  C_V8_22624_LOG = """V8 CL.

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@22624 123

509 510 511 512 513 514
"""

  C_V8_123455_LOG = """V8 CL.

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@123455 123

515 516 517 518 519 520 521 522
"""

  C_V8_123456_LOG = """V8 CL.

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@123456 123

"""

523
  ROLL_COMMIT_MSG = """Update V8 to version 3.22.4.
524 525

Summary of changes available at:
526
https://chromium.googlesource.com/v8/v8/+log/last_rol..roll_hsh
527 528

Please follow these instructions for assigning/CC'ing issues:
529
https://v8.dev/docs/triage-issues
530

531 532
Please close rolling in case of a roll revert:
https://v8-roll.appspot.com/
533
This only works with a Google account.
534

535 536 537 538 539
CQ_INCLUDE_TRYBOTS=luci.chromium.try:linux-blink-rel
CQ_INCLUDE_TRYBOTS=luci.chromium.try:linux_optional_gpu_tests_rel
CQ_INCLUDE_TRYBOTS=luci.chromium.try:mac_optional_gpu_tests_rel
CQ_INCLUDE_TRYBOTS=luci.chromium.try:win_optional_gpu_tests_rel
CQ_INCLUDE_TRYBOTS=luci.chromium.try:android_optional_gpu_tests_rel
540

541
R=reviewer@chromium.org"""
542

543 544 545 546 547 548 549 550 551 552 553 554 555 556
  # Snippet from the original DEPS file.
  FAKE_DEPS = """
vars = {
  "v8_revision": "last_roll_hsh",
}
deps = {
  "src/v8":
    (Var("googlecode_url") % "v8") + "/" + Var("v8_branch") + "@" +
    Var("v8_revision"),
}
"""

  def testChromiumRollUpToDate(self):
    TEST_CONFIG["CHROMIUM"] = self.MakeEmptyTempDirectory()
557
    json_output_file = os.path.join(TEST_CONFIG["CHROMIUM"], "out.json")
558
    TextToFile(self.FAKE_DEPS, os.path.join(TEST_CONFIG["CHROMIUM"], "DEPS"))
559
    chrome_dir = TEST_CONFIG["CHROMIUM"]
560 561 562
    self.Expect([
      Cmd("git fetch origin", ""),
      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
563
      Cmd("gclient getdep -r src/v8", "last_roll_hsh", cwd=chrome_dir),
564 565
      Cmd("git describe --tags last_roll_hsh", "3.22.4"),
      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
566
      Cmd("git rev-list --max-age=395200 --tags",
567 568 569 570 571 572 573 574 575
          "bad_tag\nroll_hsh\nhash_123"),
      Cmd("git describe --tags bad_tag", ""),
      Cmd("git describe --tags roll_hsh", "3.22.4"),
      Cmd("git describe --tags hash_123", "3.22.3"),
      Cmd("git describe --tags roll_hsh", "3.22.4"),
      Cmd("git describe --tags hash_123", "3.22.3"),
    ])

    result = auto_roll.AutoRoll(TEST_CONFIG, self).Run(
576 577 578
        AUTO_PUSH_ARGS + [
          "-c", TEST_CONFIG["CHROMIUM"],
          "--json-output", json_output_file])
579
    self.assertEquals(0, result)
580 581 582
    json_output = json.loads(FileToText(json_output_file))
    self.assertEquals("up_to_date", json_output["monitoring_state"])

583

584
  def testChromiumRoll(self):
585
    # Setup fake directory structures.
586
    TEST_CONFIG["CHROMIUM"] = self.MakeEmptyTempDirectory()
587
    json_output_file = os.path.join(TEST_CONFIG["CHROMIUM"], "out.json")
588
    TextToFile(self.FAKE_DEPS, os.path.join(TEST_CONFIG["CHROMIUM"], "DEPS"))
589 590
    TextToFile("", os.path.join(TEST_CONFIG["CHROMIUM"], ".git"))
    chrome_dir = TEST_CONFIG["CHROMIUM"]
591 592
    os.makedirs(os.path.join(chrome_dir, "v8"))

593
    def WriteDeps():
594
      TextToFile("Some line\n   \"v8_revision\": \"22624\",\n  some line",
595
                 os.path.join(chrome_dir, "DEPS"))
596

597
    expectations = [
598
      Cmd("git fetch origin", ""),
599
      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
600
      Cmd("gclient getdep -r src/v8", "last_roll_hsh", cwd=chrome_dir),
601 602
      Cmd("git describe --tags last_roll_hsh", "3.22.3.1"),
      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
603
      Cmd("git rev-list --max-age=395200 --tags",
604 605 606 607 608 609
          "bad_tag\nroll_hsh\nhash_123"),
      Cmd("git describe --tags bad_tag", ""),
      Cmd("git describe --tags roll_hsh", "3.22.4"),
      Cmd("git describe --tags hash_123", "3.22.3"),
      Cmd("git describe --tags roll_hsh", "3.22.4"),
      Cmd("git log -1 --format=%s roll_hsh", "Version 3.22.4\n"),
610
      Cmd("git describe --tags roll_hsh", "3.22.4"),
611
      Cmd("git describe --tags last_roll_hsh", "3.22.2.1"),
612
      Cmd("git status -s -uno", "", cwd=chrome_dir),
613
      Cmd("git checkout -f main", "", cwd=chrome_dir),
614
      Cmd("git branch", "", cwd=chrome_dir),
615
      Cmd("git pull", "", cwd=chrome_dir),
616
      Cmd("git fetch origin", ""),
617
      Cmd("git new-branch work-branch", "", cwd=chrome_dir),
618 619
      Cmd("gclient setdep -r src/v8@roll_hsh", "", cb=WriteDeps,
          cwd=chrome_dir),
620 621 622
      Cmd(("git commit -am \"%s\" "
           "--author \"author@chromium.org <author@chromium.org>\"" %
           self.ROLL_COMMIT_MSG),
623
          "", cwd=chrome_dir),
624
      Cmd("git cl upload --send-mail -f "
625
          "--cq-dry-run --set-bot-commit --bypass-hooks", "",
626
          cwd=chrome_dir),
627
      Cmd("git checkout -f main", "", cwd=chrome_dir),
628
      Cmd("git branch -D work-branch", "", cwd=chrome_dir),
629 630
    ]
    self.Expect(expectations)
631

632
    args = ["-a", "author@chromium.org", "-c", chrome_dir,
633
            "-r", "reviewer@chromium.org", "--json-output", json_output_file]
634
    auto_roll.AutoRoll(TEST_CONFIG, self).Run(args)
635

636
    deps = FileToText(os.path.join(chrome_dir, "DEPS"))
637
    self.assertTrue(re.search("\"v8_revision\": \"22624\"", deps))
638

639 640 641
    json_output = json.loads(FileToText(json_output_file))
    self.assertEquals("success", json_output["monitoring_state"])

642
  def testCheckLastPushRecently(self):
643
    self.Expect([
644 645 646 647 648 649
      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
      Cmd("git tag", self.TAGS),
      Cmd("git log -1 --format=%H 3.22.4", "release_hash\n"),
      Cmd("git log -1 --format=%s release_hash",
          "Version 3.22.4 (based on abc3)\n"),
      Cmd("git log --format=%H abc3..abc123", "\n"),
650 651
    ])

652
    self._state["candidate"] = "abc123"
653
    self.assertEquals(0, self.RunStep(
654
        auto_push.AutoPush, LastReleaseBailout, AUTO_PUSH_ARGS))
655

656
  def testAutoPush(self):
657
    self.Expect([
658
      Cmd("git fetch", ""),
659 660
      Cmd("git fetch origin +refs/heads/lkgr:refs/heads/lkgr", ""),
      Cmd("git show-ref -s refs/heads/lkgr", "abc123\n"),
661 662 663 664 665 666
      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
      Cmd("git tag", self.TAGS),
      Cmd("git log -1 --format=%H 3.22.4", "release_hash\n"),
      Cmd("git log -1 --format=%s release_hash",
          "Version 3.22.4 (based on abc3)\n"),
      Cmd("git log --format=%H abc3..abc123", "some_stuff\n"),
667 668
    ])

669
    auto_push.AutoPush(TEST_CONFIG, self).Run(AUTO_PUSH_ARGS + ["--push"])
670

671
    state = json.loads(FileToText("%s-state.json"
672
                                  % TEST_CONFIG["PERSISTFILE_BASENAME"]))
673

674
    self.assertEquals("abc123", state["candidate"])
675

676
  def testRollMerge(self):
677
    TEST_CONFIG["ALREADY_MERGING_SENTINEL_FILE"] = self.MakeEmptyTempFile()
678
    TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git"))
679
    self.WriteFakeVersionFile(build=5)
680 681 682 683 684
    os.environ["EDITOR"] = "vi"
    extra_patch = self.MakeEmptyTempFile()

    def VerifyPatch(patch):
      return lambda: self.assertEquals(patch,
685
          FileToText(TEST_CONFIG["TEMPORARY_PATCH_FILE"]))
686

687
    msg = """Version 3.22.5.1 (cherry-pick)
688

689 690 691 692 693
Merged ab12345
Merged ab23456
Merged ab34567
Merged ab45678
Merged ab56789
694 695 696 697 698 699 700 701 702 703 704 705 706 707

Title4

Title2

Title3

Title1

Revert "Something"

BUG=123,234,345,456,567,v8:123
"""

machenbach's avatar
machenbach committed
708
    def VerifyLand():
709 710 711 712
      commit = FileToText(TEST_CONFIG["COMMITMSG_FILE"])
      self.assertEquals(msg, commit)
      version = FileToText(
          os.path.join(TEST_CONFIG["DEFAULT_CWD"], VERSION_FILE))
713 714 715 716 717
      self.assertTrue(re.search(r"#define V8_MINOR_VERSION\s+22", version))
      self.assertTrue(re.search(r"#define V8_BUILD_NUMBER\s+5", version))
      self.assertTrue(re.search(r"#define V8_PATCH_LEVEL\s+1", version))
      self.assertTrue(
          re.search(r"#define V8_IS_CANDIDATE_VERSION\s+0", version))
718 719 720

    self.Expect([
      Cmd("git status -s -uno", ""),
721
      Cmd("git checkout -f origin/main", ""),
722 723
      Cmd("git fetch", ""),
      Cmd("git branch", "  branch1\n* branch2\n"),
724
      Cmd("git new-branch %s --upstream refs/remotes/origin/candidates" %
725
          TEST_CONFIG["BRANCHNAME"], ""),
726
      Cmd(("git log --format=%H --grep=\"Port ab12345\" "
727
           "--reverse origin/main"),
728 729 730 731
          "ab45678\nab23456"),
      Cmd("git log -1 --format=%s ab45678", "Title1"),
      Cmd("git log -1 --format=%s ab23456", "Title2"),
      Cmd(("git log --format=%H --grep=\"Port ab23456\" "
732
           "--reverse origin/main"),
733
          ""),
734
      Cmd(("git log --format=%H --grep=\"Port ab34567\" "
735
           "--reverse origin/main"),
736 737 738 739 740
          "ab56789"),
      Cmd("git log -1 --format=%s ab56789", "Title3"),
      RL("Y"),  # Automatically add corresponding ports (ab34567, ab56789)?
      # Simulate git being down which stops the script.
      Cmd("git log -1 --format=%s ab12345", None),
741
      # Restart script in the failing step.
742 743 744 745 746 747 748
      Cmd("git log -1 --format=%s ab12345", "Title4"),
      Cmd("git log -1 --format=%s ab23456", "Title2"),
      Cmd("git log -1 --format=%s ab34567", "Title3"),
      Cmd("git log -1 --format=%s ab45678", "Title1"),
      Cmd("git log -1 --format=%s ab56789", "Revert \"Something\""),
      Cmd("git log -1 ab12345", "Title4\nBUG=123\nBUG=234"),
      Cmd("git log -1 ab23456", "Title2\n BUG = v8:123,345"),
749
      Cmd("git log -1 ab34567", "Title3\nBUG=567, 456"),
750 751 752
      Cmd("git log -1 ab45678", "Title1\nBUG="),
      Cmd("git log -1 ab56789", "Revert \"Something\"\nBUG=none"),
      Cmd("git log -1 -p ab12345", "patch4"),
753 754 755
      Cmd(("git apply --index --reject \"%s\"" %
           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
          "", cb=VerifyPatch("patch4")),
756
      Cmd("git log -1 -p ab23456", "patch2"),
757 758 759
      Cmd(("git apply --index --reject \"%s\"" %
           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
          "", cb=VerifyPatch("patch2")),
760
      Cmd("git log -1 -p ab34567", "patch3"),
761 762 763
      Cmd(("git apply --index --reject \"%s\"" %
           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
          "", cb=VerifyPatch("patch3")),
764
      Cmd("git log -1 -p ab45678", "patch1"),
765 766 767
      Cmd(("git apply --index --reject \"%s\"" %
           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
          "", cb=VerifyPatch("patch1")),
768
      Cmd("git log -1 -p ab56789", "patch5\n"),
769 770 771 772 773 774 775 776
      Cmd(("git apply --index --reject \"%s\"" %
           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
          "", cb=VerifyPatch("patch5\n")),
      Cmd("git apply --index --reject \"%s\"" % extra_patch, ""),
      RL("Y"),  # Automatically increment patch level?
      Cmd("git commit -aF \"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], ""),
      RL("reviewer@chromium.org"),  # V8 reviewer.
      Cmd("git cl upload --send-mail -r \"reviewer@chromium.org\" "
777
          "--bypass-hooks", ""),
778 779 780
      Cmd("git checkout -f %s" % TEST_CONFIG["BRANCHNAME"], ""),
      RL("LGTM"),  # Enter LGTM for V8 CL.
      Cmd("git cl presubmit", "Presubmit successfull\n"),
781
      Cmd("git cl land -f --bypass-hooks", "Closing issue\n",
machenbach's avatar
machenbach committed
782
          cb=VerifyLand),
783 784
      Cmd("git fetch", ""),
      Cmd("git log -1 --format=%H --grep=\""
785
          "Version 3.22.5.1 (cherry-pick)"
786
          "\" refs/remotes/origin/candidates",
787 788 789
          ""),
      Cmd("git fetch", ""),
      Cmd("git log -1 --format=%H --grep=\""
790
          "Version 3.22.5.1 (cherry-pick)"
791
          "\" refs/remotes/origin/candidates",
792 793
          "hsh_to_tag"),
      Cmd("git tag 3.22.5.1 hsh_to_tag", ""),
794
      Cmd("git push origin refs/tags/3.22.5.1:refs/tags/3.22.5.1", ""),
795
      Cmd("git checkout -f origin/main", ""),
796 797 798
      Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], ""),
    ])

799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857
    # ab12345 and ab34567 are patches. ab23456 (included) and ab45678 are the
    # MIPS ports of ab12345. ab56789 is the MIPS port of ab34567.
    args = ["-f", "-p", extra_patch, "--branch", "candidates",
            "ab12345", "ab23456", "ab34567"]

    # The first run of the script stops because of git being down.
    self.assertRaises(GitFailedException,
        lambda: RollMerge(TEST_CONFIG, self).Run(args))

    # Test that state recovery after restarting the script works.
    args += ["-s", "4"]
    RollMerge(TEST_CONFIG, self).Run(args)

  def testMergeToBranch(self):
    TEST_CONFIG["ALREADY_MERGING_SENTINEL_FILE"] = self.MakeEmptyTempFile()
    TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git"))
    self.WriteFakeVersionFile(build=5)
    os.environ["EDITOR"] = "vi"
    extra_patch = self.MakeEmptyTempFile()


    def VerifyPatch(patch):
      return lambda: self.assertEquals(patch,
          FileToText(TEST_CONFIG["TEMPORARY_PATCH_FILE"]))

    info_msg = ("NOTE: This script will no longer automatically "
     "update include/v8-version.h "
     "and create a tag. This is done automatically by the autotag bot. "
     "Please call the merge_to_branch.py with --help for more information.")

    msg = """Merged: Squashed multiple commits.

Merged: Title4
Revision: ab12345

Merged: Title2
Revision: ab23456

Merged: Title3
Revision: ab34567

Merged: Title1
Revision: ab45678

Merged: Revert \"Something\"
Revision: ab56789

BUG=123,234,345,456,567,v8:123
NOTRY=true
NOPRESUBMIT=true
NOTREECHECKS=true
"""

    def VerifyLand():
      commit = FileToText(TEST_CONFIG["COMMITMSG_FILE"])
      self.assertEquals(msg, commit)

    self.Expect([
      Cmd("git status -s -uno", ""),
858
      Cmd("git checkout -f origin/main", ""),
859 860 861 862 863
      Cmd("git fetch", ""),
      Cmd("git branch", "  branch1\n* branch2\n"),
      Cmd("git new-branch %s --upstream refs/remotes/origin/candidates" %
          TEST_CONFIG["BRANCHNAME"], ""),
      Cmd(("git log --format=%H --grep=\"^[Pp]ort ab12345\" "
864
           "--reverse origin/main"),
865 866 867 868
          "ab45678\nab23456"),
      Cmd("git log -1 --format=%s ab45678", "Title1"),
      Cmd("git log -1 --format=%s ab23456", "Title2"),
      Cmd(("git log --format=%H --grep=\"^[Pp]ort ab23456\" "
869
           "--reverse origin/main"),
870 871
          ""),
      Cmd(("git log --format=%H --grep=\"^[Pp]ort ab34567\" "
872
           "--reverse origin/main"),
873 874 875 876 877 878 879 880 881 882 883 884 885
          "ab56789"),
      Cmd("git log -1 --format=%s ab56789", "Title3"),
      RL("Y"),  # Automatically add corresponding ports (ab34567, ab56789)?
      # Simulate git being down which stops the script.
      Cmd("git log -1 --format=%s ab12345", None),
      # Restart script in the failing step.
      Cmd("git log -1 --format=%s ab12345", "Title4"),
      Cmd("git log -1 --format=%s ab23456", "Title2"),
      Cmd("git log -1 --format=%s ab34567", "Title3"),
      Cmd("git log -1 --format=%s ab45678", "Title1"),
      Cmd("git log -1 --format=%s ab56789", "Revert \"Something\""),
      Cmd("git log -1 ab12345", "Title4\nBUG=123\nBUG=234"),
      Cmd("git log -1 ab23456", "Title2\n BUG = v8:123,345"),
886
      Cmd("git log -1 ab34567", "Title3\nBug: 567, 456,345"),
887
      Cmd("git log -1 ab45678", "Title1\nBug:"),
888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912
      Cmd("git log -1 ab56789", "Revert \"Something\"\nBUG=none"),
      Cmd("git log -1 -p ab12345", "patch4"),
      Cmd(("git apply --index --reject \"%s\"" %
           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
          "", cb=VerifyPatch("patch4")),
      Cmd("git log -1 -p ab23456", "patch2"),
      Cmd(("git apply --index --reject \"%s\"" %
           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
          "", cb=VerifyPatch("patch2")),
      Cmd("git log -1 -p ab34567", "patch3"),
      Cmd(("git apply --index --reject \"%s\"" %
           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
          "", cb=VerifyPatch("patch3")),
      Cmd("git log -1 -p ab45678", "patch1"),
      Cmd(("git apply --index --reject \"%s\"" %
           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
          "", cb=VerifyPatch("patch1")),
      Cmd("git log -1 -p ab56789", "patch5\n"),
      Cmd(("git apply --index --reject \"%s\"" %
           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
          "", cb=VerifyPatch("patch5\n")),
      Cmd("git apply --index --reject \"%s\"" % extra_patch, ""),
      Cmd("git commit -aF \"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], ""),
      RL("reviewer@chromium.org"),  # V8 reviewer.
      Cmd("git cl upload --send-mail -r \"reviewer@chromium.org\" "
913
          "--bypass-hooks", ""),
914 915 916 917 918
      Cmd("git checkout -f %s" % TEST_CONFIG["BRANCHNAME"], ""),
      RL("LGTM"),  # Enter LGTM for V8 CL.
      Cmd("git cl presubmit", "Presubmit successfull\n"),
      Cmd("git cl land -f --bypass-hooks", "Closing issue\n",
          cb=VerifyLand),
919
      Cmd("git checkout -f origin/main", ""),
920 921 922
      Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], ""),
    ])

923 924
    # ab12345 and ab34567 are patches. ab23456 (included) and ab45678 are the
    # MIPS ports of ab12345. ab56789 is the MIPS port of ab34567.
925
    args = ["-f", "-p", extra_patch, "--branch", "candidates",
926
            "ab12345", "ab23456", "ab34567"]
927

928
    # The first run of the script stops because of git being down.
929
    self.assertRaises(GitFailedException,
930
        lambda: MergeToBranch(TEST_CONFIG, self).Run(args))
931 932

    # Test that state recovery after restarting the script works.
933
    args += ["-s", "4"]
934
    MergeToBranch(TEST_CONFIG, self).Run(args)
935

936 937
if __name__ == '__main__':
  unittest.main()