1#!/usr/bin/env python
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 os
30import shutil
31import tempfile
32import traceback
33import unittest
34
35import auto_push
36from auto_push import LastReleaseBailout
37import auto_roll
38import common_includes
39from common_includes import *
40import create_release
41from create_release import CreateRelease
42import merge_to_branch
43from merge_to_branch import MergeToBranch
44import push_to_candidates
45from push_to_candidates import *
46import releases
47from releases import Releases
48from auto_tag import AutoTag
49import roll_merge
50from roll_merge import RollMerge
51
52TEST_CONFIG = {
53  "DEFAULT_CWD": None,
54  "BRANCHNAME": "test-prepare-push",
55  "CANDIDATESBRANCH": "test-candidates-push",
56  "PERSISTFILE_BASENAME": "/tmp/test-v8-push-to-candidates-tempfile",
57  "CHANGELOG_ENTRY_FILE":
58      "/tmp/test-v8-push-to-candidates-tempfile-changelog-entry",
59  "PATCH_FILE": "/tmp/test-v8-push-to-candidates-tempfile-patch",
60  "COMMITMSG_FILE": "/tmp/test-v8-push-to-candidates-tempfile-commitmsg",
61  "CHROMIUM": "/tmp/test-v8-push-to-candidates-tempfile-chromium",
62  "SETTINGS_LOCATION": None,
63  "ALREADY_MERGING_SENTINEL_FILE":
64      "/tmp/test-merge-to-branch-tempfile-already-merging",
65  "TEMPORARY_PATCH_FILE": "/tmp/test-merge-to-branch-tempfile-temporary-patch",
66}
67
68
69AUTO_PUSH_ARGS = [
70  "-a", "author@chromium.org",
71  "-r", "reviewer@chromium.org",
72]
73
74
75class ToplevelTest(unittest.TestCase):
76  def testSaniniziteVersionTags(self):
77    self.assertEquals("4.8.230", SanitizeVersionTag("4.8.230"))
78    self.assertEquals("4.8.230", SanitizeVersionTag("tags/4.8.230"))
79    self.assertEquals(None, SanitizeVersionTag("candidate"))
80
81  def testNormalizeVersionTags(self):
82    input = ["4.8.230",
83              "tags/4.8.230",
84              "tags/4.8.224.1",
85              "4.8.224.1",
86              "4.8.223.1",
87              "tags/4.8.223",
88              "tags/4.8.231",
89              "candidates"]
90    expected = ["4.8.230",
91                "4.8.230",
92                "4.8.224.1",
93                "4.8.224.1",
94                "4.8.223.1",
95                "4.8.223",
96                "4.8.231",
97                ]
98    self.assertEquals(expected, NormalizeVersionTags(input))
99
100  def testSortBranches(self):
101    S = releases.SortBranches
102    self.assertEquals(["3.1", "2.25"], S(["2.25", "3.1"])[0:2])
103    self.assertEquals(["3.0", "2.25"], S(["2.25", "3.0", "2.24"])[0:2])
104    self.assertEquals(["3.11", "3.2"], S(["3.11", "3.2", "2.24"])[0:2])
105
106  def testFilterDuplicatesAndReverse(self):
107    F = releases.FilterDuplicatesAndReverse
108    self.assertEquals([], F([]))
109    self.assertEquals([["100", "10"]], F([["100", "10"]]))
110    self.assertEquals([["99", "9"], ["100", "10"]],
111                      F([["100", "10"], ["99", "9"]]))
112    self.assertEquals([["98", "9"], ["100", "10"]],
113                      F([["100", "10"], ["99", "9"], ["98", "9"]]))
114    self.assertEquals([["98", "9"], ["99", "10"]],
115                      F([["100", "10"], ["99", "10"], ["98", "9"]]))
116
117  def testBuildRevisionRanges(self):
118    B = releases.BuildRevisionRanges
119    self.assertEquals({}, B([]))
120    self.assertEquals({"10": "100"}, B([["100", "10"]]))
121    self.assertEquals({"10": "100", "9": "99:99"},
122                      B([["100", "10"], ["99", "9"]]))
123    self.assertEquals({"10": "100", "9": "97:99"},
124                      B([["100", "10"], ["98", "9"], ["97", "9"]]))
125    self.assertEquals({"10": "100", "9": "99:99", "3": "91:98"},
126                      B([["100", "10"], ["99", "9"], ["91", "3"]]))
127    self.assertEquals({"13": "101", "12": "100:100", "9": "94:97",
128                       "3": "91:93, 98:99"},
129                      B([["101", "13"], ["100", "12"], ["98", "3"],
130                         ["94", "9"], ["91", "3"]]))
131
132  def testMakeComment(self):
133    self.assertEquals("#   Line 1\n#   Line 2\n#",
134                      MakeComment("    Line 1\n    Line 2\n"))
135    self.assertEquals("#Line 1\n#Line 2",
136                      MakeComment("Line 1\n Line 2"))
137
138  def testStripComments(self):
139    self.assertEquals("    Line 1\n    Line 3\n",
140        StripComments("    Line 1\n#   Line 2\n    Line 3\n#\n"))
141    self.assertEquals("\nLine 2 ### Test\n #",
142        StripComments("###\n# \n\n#  Line 1\nLine 2 ### Test\n #"))
143
144  def testMakeChangeLogBodySimple(self):
145    commits = [
146          ["Title text 1",
147           "Title text 1\n\nBUG=\n",
148           "author1@chromium.org"],
149          ["Title text 2.",
150           "Title text 2\n\nBUG=1234\n",
151           "author2@chromium.org"],
152        ]
153    self.assertEquals("        Title text 1.\n"
154                      "        (author1@chromium.org)\n\n"
155                      "        Title text 2 (Chromium issue 1234).\n"
156                      "        (author2@chromium.org)\n\n",
157                      MakeChangeLogBody(commits))
158
159  def testMakeChangeLogBodyEmpty(self):
160    self.assertEquals("", MakeChangeLogBody([]))
161
162  def testMakeChangeLogBodyAutoFormat(self):
163    commits = [
164          ["Title text 1!",
165           "Title text 1\nLOG=y\nBUG=\n",
166           "author1@chromium.org"],
167          ["Title text 2",
168           "Title text 2\n\nBUG=1234\n",
169           "author2@chromium.org"],
170          ["Title text 3",
171           "Title text 3\n\nBUG=1234\nLOG = Yes\n",
172           "author3@chromium.org"],
173          ["Title text 3",
174           "Title text 4\n\nBUG=1234\nLOG=\n",
175           "author4@chromium.org"],
176        ]
177    self.assertEquals("        Title text 1.\n\n"
178                      "        Title text 3 (Chromium issue 1234).\n\n",
179                      MakeChangeLogBody(commits, True))
180
181  def testRegressWrongLogEntryOnTrue(self):
182    body = """
183Check elimination: Learn from if(CompareMap(x)) on true branch.
184
185BUG=
186R=verwaest@chromium.org
187
188Committed: https://code.google.com/p/v8/source/detail?r=18210
189"""
190    self.assertEquals("", MakeChangeLogBody([["title", body, "author"]], True))
191
192  def testMakeChangeLogBugReferenceEmpty(self):
193    self.assertEquals("", MakeChangeLogBugReference(""))
194    self.assertEquals("", MakeChangeLogBugReference("LOG="))
195    self.assertEquals("", MakeChangeLogBugReference(" BUG ="))
196    self.assertEquals("", MakeChangeLogBugReference("BUG=none\t"))
197
198  def testMakeChangeLogBugReferenceSimple(self):
199    self.assertEquals("(issue 987654)",
200                      MakeChangeLogBugReference("BUG = v8:987654"))
201    self.assertEquals("(Chromium issue 987654)",
202                      MakeChangeLogBugReference("BUG=987654 "))
203
204  def testMakeChangeLogBugReferenceFromBody(self):
205    self.assertEquals("(Chromium issue 1234567)",
206                      MakeChangeLogBugReference("Title\n\nTBR=\nBUG=\n"
207                                                " BUG=\tchromium:1234567\t\n"
208                                                "R=somebody\n"))
209
210  def testMakeChangeLogBugReferenceMultiple(self):
211    # All issues should be sorted and grouped. Multiple references to the same
212    # issue should be filtered.
213    self.assertEquals("(issues 123, 234, Chromium issue 345)",
214                      MakeChangeLogBugReference("Title\n\n"
215                                                "BUG=v8:234\n"
216                                                "  BUG\t= 345, \tv8:234,\n"
217                                                "BUG=v8:123\n"
218                                                "R=somebody\n"))
219    self.assertEquals("(Chromium issues 123, 234)",
220                      MakeChangeLogBugReference("Title\n\n"
221                                                "BUG=234,,chromium:123 \n"
222                                                "R=somebody\n"))
223    self.assertEquals("(Chromium issues 123, 234)",
224                      MakeChangeLogBugReference("Title\n\n"
225                                                "BUG=chromium:234, , 123\n"
226                                                "R=somebody\n"))
227    self.assertEquals("(issues 345, 456)",
228                      MakeChangeLogBugReference("Title\n\n"
229                                                "\t\tBUG=v8:345,v8:456\n"
230                                                "R=somebody\n"))
231    self.assertEquals("(issue 123, Chromium issues 345, 456)",
232                      MakeChangeLogBugReference("Title\n\n"
233                                                "BUG=chromium:456\n"
234                                                "BUG = none\n"
235                                                "R=somebody\n"
236                                                "BUG=456,v8:123, 345"))
237
238  # TODO(machenbach): These test don't make much sense when the formatting is
239  # done later.
240  def testMakeChangeLogBugReferenceLong(self):
241    # -----------------00--------10--------20--------30--------
242    self.assertEquals("(issues 234, 1234567890, 1234567"
243                      "8901234567890, Chromium issues 12345678,"
244                      " 123456789)",
245                      MakeChangeLogBugReference("BUG=v8:234\n"
246                                                "BUG=v8:1234567890\n"
247                                                "BUG=v8:12345678901234567890\n"
248                                                "BUG=123456789\n"
249                                                "BUG=12345678\n"))
250    # -----------------00--------10--------20--------30--------
251    self.assertEquals("(issues 234, 1234567890, 1234567"
252                      "8901234567890, Chromium issues"
253                      " 123456789, 1234567890)",
254                      MakeChangeLogBugReference("BUG=v8:234\n"
255                                                "BUG=v8:12345678901234567890\n"
256                                                "BUG=v8:1234567890\n"
257                                                "BUG=123456789\n"
258                                                "BUG=1234567890\n"))
259    # -----------------00--------10--------20--------30--------
260    self.assertEquals("(Chromium issues 234, 1234567890"
261                      ", 12345678901234567, "
262                      "1234567890123456789)",
263                      MakeChangeLogBugReference("BUG=234\n"
264                                                "BUG=12345678901234567\n"
265                                                "BUG=1234567890123456789\n"
266                                                "BUG=1234567890\n"))
267
268
269def Cmd(*args, **kwargs):
270  """Convenience function returning a shell command test expectation."""
271  return {
272    "name": "command",
273    "args": args,
274    "ret": args[-1],
275    "cb": kwargs.get("cb"),
276    "cwd": kwargs.get("cwd", TEST_CONFIG["DEFAULT_CWD"]),
277  }
278
279
280def RL(text, cb=None):
281  """Convenience function returning a readline test expectation."""
282  return {
283    "name": "readline",
284    "args": [],
285    "ret": text,
286    "cb": cb,
287    "cwd": None,
288  }
289
290
291def URL(*args, **kwargs):
292  """Convenience function returning a readurl test expectation."""
293  return {
294    "name": "readurl",
295    "args": args[:-1],
296    "ret": args[-1],
297    "cb": kwargs.get("cb"),
298    "cwd": None,
299  }
300
301
302class SimpleMock(object):
303  def __init__(self):
304    self._recipe = []
305    self._index = -1
306
307  def Expect(self, recipe):
308    self._recipe = recipe
309
310  def Call(self, name, *args, **kwargs):  # pragma: no cover
311    self._index += 1
312    try:
313      expected_call = self._recipe[self._index]
314    except IndexError:
315      raise NoRetryException("Calling %s %s" % (name, " ".join(args)))
316
317    if not isinstance(expected_call, dict):
318      raise NoRetryException("Found wrong expectation type for %s %s" %
319                             (name, " ".join(args)))
320
321    if expected_call["name"] != name:
322      raise NoRetryException("Expected action: %s %s - Actual: %s" %
323          (expected_call["name"], expected_call["args"], name))
324
325    # Check if the given working directory matches the expected one.
326    if expected_call["cwd"] != kwargs.get("cwd"):
327      raise NoRetryException("Expected cwd: %s in %s %s - Actual: %s" %
328          (expected_call["cwd"],
329           expected_call["name"],
330           expected_call["args"],
331           kwargs.get("cwd")))
332
333    # The number of arguments in the expectation must match the actual
334    # arguments.
335    if len(args) > len(expected_call['args']):
336      raise NoRetryException("When calling %s with arguments, the "
337          "expectations must consist of at least as many arguments." %
338          name)
339
340    # Compare expected and actual arguments.
341    for (expected_arg, actual_arg) in zip(expected_call['args'], args):
342      if expected_arg != actual_arg:
343        raise NoRetryException("Expected: %s - Actual: %s" %
344                               (expected_arg, actual_arg))
345
346    # The expected call contains an optional callback for checking the context
347    # at the time of the call.
348    if expected_call['cb']:
349      try:
350        expected_call['cb']()
351      except:
352        tb = traceback.format_exc()
353        raise NoRetryException("Caught exception from callback: %s" % tb)
354
355    # If the return value is an exception, raise it instead of returning.
356    if isinstance(expected_call['ret'], Exception):
357      raise expected_call['ret']
358    return expected_call['ret']
359
360  def AssertFinished(self):  # pragma: no cover
361    if self._index < len(self._recipe) -1:
362      raise NoRetryException("Called mock too seldom: %d vs. %d" %
363                             (self._index, len(self._recipe)))
364
365
366class ScriptTest(unittest.TestCase):
367  def MakeEmptyTempFile(self):
368    handle, name = tempfile.mkstemp()
369    os.close(handle)
370    self._tmp_files.append(name)
371    return name
372
373  def MakeEmptyTempDirectory(self):
374    name = tempfile.mkdtemp()
375    self._tmp_files.append(name)
376    return name
377
378
379  def WriteFakeVersionFile(self, major=3, minor=22, build=4, patch=0):
380    version_file = os.path.join(TEST_CONFIG["DEFAULT_CWD"], VERSION_FILE)
381    if not os.path.exists(os.path.dirname(version_file)):
382      os.makedirs(os.path.dirname(version_file))
383    with open(version_file, "w") as f:
384      f.write("  // Some line...\n")
385      f.write("\n")
386      f.write("#define V8_MAJOR_VERSION    %s\n" % major)
387      f.write("#define V8_MINOR_VERSION    %s\n" % minor)
388      f.write("#define V8_BUILD_NUMBER     %s\n" % build)
389      f.write("#define V8_PATCH_LEVEL      %s\n" % patch)
390      f.write("  // Some line...\n")
391      f.write("#define V8_IS_CANDIDATE_VERSION 0\n")
392
393  def WriteFakeWatchlistsFile(self):
394    watchlists_file = os.path.join(TEST_CONFIG["DEFAULT_CWD"], WATCHLISTS_FILE)
395    if not os.path.exists(os.path.dirname(watchlists_file)):
396      os.makedirs(os.path.dirname(watchlists_file))
397    with open(watchlists_file, "w") as f:
398
399      content = """
400    'merges': [
401      # Only enabled on branches created with tools/release/create_release.py
402      # 'v8-merges@googlegroups.com',
403    ],
404"""
405      f.write(content)
406
407  def MakeStep(self):
408    """Convenience wrapper."""
409    options = ScriptsBase(TEST_CONFIG, self, self._state).MakeOptions([])
410    return MakeStep(step_class=Step, state=self._state,
411                    config=TEST_CONFIG, side_effect_handler=self,
412                    options=options)
413
414  def RunStep(self, script=PushToCandidates, step_class=Step, args=None):
415    """Convenience wrapper."""
416    args = args if args is not None else ["-m"]
417    return script(TEST_CONFIG, self, self._state).RunSteps([step_class], args)
418
419  def Call(self, fun, *args, **kwargs):
420    print "Calling %s with %s and %s" % (str(fun), str(args), str(kwargs))
421
422  def Command(self, cmd, args="", prefix="", pipe=True, cwd=None):
423    print "%s %s" % (cmd, args)
424    print "in %s" % cwd
425    return self._mock.Call("command", cmd + " " + args, cwd=cwd)
426
427  def ReadLine(self):
428    return self._mock.Call("readline")
429
430  def ReadURL(self, url, params):
431    if params is not None:
432      return self._mock.Call("readurl", url, params)
433    else:
434      return self._mock.Call("readurl", url)
435
436  def Sleep(self, seconds):
437    pass
438
439  def GetDate(self):
440    return "1999-07-31"
441
442  def GetUTCStamp(self):
443    return "1000000"
444
445  def Expect(self, *args):
446    """Convenience wrapper."""
447    self._mock.Expect(*args)
448
449  def setUp(self):
450    self._mock = SimpleMock()
451    self._tmp_files = []
452    self._state = {}
453    TEST_CONFIG["DEFAULT_CWD"] = self.MakeEmptyTempDirectory()
454
455  def tearDown(self):
456    if os.path.exists(TEST_CONFIG["PERSISTFILE_BASENAME"]):
457      shutil.rmtree(TEST_CONFIG["PERSISTFILE_BASENAME"])
458
459    # Clean up temps. Doesn't work automatically.
460    for name in self._tmp_files:
461      if os.path.isfile(name):
462        os.remove(name)
463      if os.path.isdir(name):
464        shutil.rmtree(name)
465
466    self._mock.AssertFinished()
467
468  def testGitMock(self):
469    self.Expect([Cmd("git --version", "git version 1.2.3"),
470                 Cmd("git dummy", "")])
471    self.assertEquals("git version 1.2.3", self.MakeStep().Git("--version"))
472    self.assertEquals("", self.MakeStep().Git("dummy"))
473
474  def testCommonPrepareDefault(self):
475    self.Expect([
476      Cmd("git status -s -uno", ""),
477      Cmd("git checkout -f origin/master", ""),
478      Cmd("git fetch", ""),
479      Cmd("git branch", "  branch1\n* %s" % TEST_CONFIG["BRANCHNAME"]),
480      RL("Y"),
481      Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], ""),
482    ])
483    self.MakeStep().CommonPrepare()
484    self.MakeStep().PrepareBranch()
485
486  def testCommonPrepareNoConfirm(self):
487    self.Expect([
488      Cmd("git status -s -uno", ""),
489      Cmd("git checkout -f origin/master", ""),
490      Cmd("git fetch", ""),
491      Cmd("git branch", "  branch1\n* %s" % TEST_CONFIG["BRANCHNAME"]),
492      RL("n"),
493    ])
494    self.MakeStep().CommonPrepare()
495    self.assertRaises(Exception, self.MakeStep().PrepareBranch)
496
497  def testCommonPrepareDeleteBranchFailure(self):
498    self.Expect([
499      Cmd("git status -s -uno", ""),
500      Cmd("git checkout -f origin/master", ""),
501      Cmd("git fetch", ""),
502      Cmd("git branch", "  branch1\n* %s" % TEST_CONFIG["BRANCHNAME"]),
503      RL("Y"),
504      Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], None),
505    ])
506    self.MakeStep().CommonPrepare()
507    self.assertRaises(Exception, self.MakeStep().PrepareBranch)
508
509  def testInitialEnvironmentChecks(self):
510    TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git"))
511    os.environ["EDITOR"] = "vi"
512    self.Expect([
513      Cmd("which vi", "/usr/bin/vi"),
514    ])
515    self.MakeStep().InitialEnvironmentChecks(TEST_CONFIG["DEFAULT_CWD"])
516
517  def testTagTimeout(self):
518    self.Expect([
519      Cmd("git fetch", ""),
520      Cmd("git log -1 --format=%H --grep=\"Title\" origin/candidates", ""),
521      Cmd("git fetch", ""),
522      Cmd("git log -1 --format=%H --grep=\"Title\" origin/candidates", ""),
523      Cmd("git fetch", ""),
524      Cmd("git log -1 --format=%H --grep=\"Title\" origin/candidates", ""),
525      Cmd("git fetch", ""),
526      Cmd("git log -1 --format=%H --grep=\"Title\" origin/candidates", ""),
527    ])
528    args = ["--branch", "candidates", "ab12345"]
529    self._state["version"] = "tag_name"
530    self._state["commit_title"] = "Title"
531    self.assertRaises(Exception,
532        lambda: self.RunStep(RollMerge, TagRevision, args))
533
534  def testReadAndPersistVersion(self):
535    self.WriteFakeVersionFile(build=5)
536    step = self.MakeStep()
537    step.ReadAndPersistVersion()
538    self.assertEquals("3", step["major"])
539    self.assertEquals("22", step["minor"])
540    self.assertEquals("5", step["build"])
541    self.assertEquals("0", step["patch"])
542
543  def testRegex(self):
544    self.assertEqual("(issue 321)",
545                     re.sub(r"BUG=v8:(.*)$", r"(issue \1)", "BUG=v8:321"))
546    self.assertEqual("(Chromium issue 321)",
547                     re.sub(r"BUG=(.*)$", r"(Chromium issue \1)", "BUG=321"))
548
549    cl = "  too little\n\ttab\ttab\n         too much\n        trailing  "
550    cl = MSub(r"\t", r"        ", cl)
551    cl = MSub(r"^ {1,7}([^ ])", r"        \1", cl)
552    cl = MSub(r"^ {9,80}([^ ])", r"        \1", cl)
553    cl = MSub(r" +$", r"", cl)
554    self.assertEqual("        too little\n"
555                     "        tab        tab\n"
556                     "        too much\n"
557                     "        trailing", cl)
558
559    self.assertEqual("//\n#define V8_BUILD_NUMBER  3\n",
560                     MSub(r"(?<=#define V8_BUILD_NUMBER)(?P<space>\s+)\d*$",
561                          r"\g<space>3",
562                          "//\n#define V8_BUILD_NUMBER  321\n"))
563
564  def testPreparePushRevision(self):
565    # Tests the default push hash used when the --revision option is not set.
566    self.Expect([
567      Cmd("git log -1 --format=%H HEAD", "push_hash")
568    ])
569
570    self.RunStep(PushToCandidates, PreparePushRevision)
571    self.assertEquals("push_hash", self._state["push_hash"])
572
573  def testPrepareChangeLog(self):
574    self.WriteFakeVersionFile()
575    TEST_CONFIG["CHANGELOG_ENTRY_FILE"] = self.MakeEmptyTempFile()
576
577    self.Expect([
578      Cmd("git log --format=%H 1234..push_hash", "rev1\nrev2\nrev3\nrev4"),
579      Cmd("git log -1 --format=%s rev1", "Title text 1"),
580      Cmd("git log -1 --format=%B rev1", "Title\n\nBUG=\nLOG=y\n"),
581      Cmd("git log -1 --format=%an rev1", "author1@chromium.org"),
582      Cmd("git log -1 --format=%s rev2", "Title text 2."),
583      Cmd("git log -1 --format=%B rev2", "Title\n\nBUG=123\nLOG= \n"),
584      Cmd("git log -1 --format=%an rev2", "author2@chromium.org"),
585      Cmd("git log -1 --format=%s rev3", "Title text 3"),
586      Cmd("git log -1 --format=%B rev3", "Title\n\nBUG=321\nLOG=true\n"),
587      Cmd("git log -1 --format=%an rev3", "author3@chromium.org"),
588      Cmd("git log -1 --format=%s rev4", "Title text 4"),
589      Cmd("git log -1 --format=%B rev4",
590       ("Title\n\nBUG=456\nLOG=Y\n\n"
591        "Review URL: https://codereview.chromium.org/9876543210\n")),
592      URL("https://codereview.chromium.org/9876543210/description",
593          "Title\n\nBUG=456\nLOG=N\n\n"),
594      Cmd("git log -1 --format=%an rev4", "author4@chromium.org"),
595    ])
596
597    self._state["last_push_master"] = "1234"
598    self._state["push_hash"] = "push_hash"
599    self._state["version"] = "3.22.5"
600    self.RunStep(PushToCandidates, PrepareChangeLog)
601
602    actual_cl = FileToText(TEST_CONFIG["CHANGELOG_ENTRY_FILE"])
603
604    expected_cl = """1999-07-31: Version 3.22.5
605
606        Title text 1.
607
608        Title text 3 (Chromium issue 321).
609
610        Performance and stability improvements on all platforms.
611#
612# The change log above is auto-generated. Please review if all relevant
613# commit messages from the list below are included.
614# All lines starting with # will be stripped.
615#
616#       Title text 1.
617#       (author1@chromium.org)
618#
619#       Title text 2 (Chromium issue 123).
620#       (author2@chromium.org)
621#
622#       Title text 3 (Chromium issue 321).
623#       (author3@chromium.org)
624#
625#       Title text 4 (Chromium issue 456).
626#       (author4@chromium.org)
627#
628#"""
629
630    self.assertEquals(expected_cl, actual_cl)
631
632  def testEditChangeLog(self):
633    TEST_CONFIG["CHANGELOG_ENTRY_FILE"] = self.MakeEmptyTempFile()
634    TextToFile("  New  \n\tLines  \n", TEST_CONFIG["CHANGELOG_ENTRY_FILE"])
635    os.environ["EDITOR"] = "vi"
636    self.Expect([
637      RL(""),  # Open editor.
638      Cmd("vi %s" % TEST_CONFIG["CHANGELOG_ENTRY_FILE"], ""),
639    ])
640
641    self.RunStep(PushToCandidates, EditChangeLog)
642
643    self.assertEquals("New\n        Lines",
644                      FileToText(TEST_CONFIG["CHANGELOG_ENTRY_FILE"]))
645
646  TAGS = """
6474425.0
6480.0.0.0
6493.9.6
6503.22.4
651test_tag
652"""
653
654  # Version as tag: 3.22.4.0. Version on master: 3.22.6.
655  # Make sure that the latest version is 3.22.6.0.
656  def testIncrementVersion(self):
657    self.Expect([
658      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
659      Cmd("git tag", self.TAGS),
660      Cmd("git checkout -f origin/master -- include/v8-version.h",
661          "", cb=lambda: self.WriteFakeVersionFile(3, 22, 6)),
662    ])
663
664    self.RunStep(PushToCandidates, IncrementVersion)
665
666    self.assertEquals("3", self._state["new_major"])
667    self.assertEquals("22", self._state["new_minor"])
668    self.assertEquals("7", self._state["new_build"])
669    self.assertEquals("0", self._state["new_patch"])
670
671  def _TestSquashCommits(self, change_log, expected_msg):
672    TEST_CONFIG["CHANGELOG_ENTRY_FILE"] = self.MakeEmptyTempFile()
673    with open(TEST_CONFIG["CHANGELOG_ENTRY_FILE"], "w") as f:
674      f.write(change_log)
675
676    self.Expect([
677      Cmd("git diff origin/candidates hash1", "patch content"),
678    ])
679
680    self._state["push_hash"] = "hash1"
681    self._state["date"] = "1999-11-11"
682
683    self.RunStep(PushToCandidates, SquashCommits)
684    self.assertEquals(FileToText(TEST_CONFIG["COMMITMSG_FILE"]), expected_msg)
685
686    patch = FileToText(TEST_CONFIG["PATCH_FILE"])
687    self.assertTrue(re.search(r"patch content", patch))
688
689  def testSquashCommitsUnformatted(self):
690    change_log = """1999-11-11: Version 3.22.5
691
692        Log text 1.
693        Chromium issue 12345
694
695        Performance and stability improvements on all platforms.\n"""
696    commit_msg = """Version 3.22.5 (based on hash1)
697
698Log text 1. Chromium issue 12345
699
700Performance and stability improvements on all platforms."""
701    self._TestSquashCommits(change_log, commit_msg)
702
703  def testSquashCommitsFormatted(self):
704    change_log = """1999-11-11: Version 3.22.5
705
706        Long commit message that fills more than 80 characters (Chromium issue
707        12345).
708
709        Performance and stability improvements on all platforms.\n"""
710    commit_msg = """Version 3.22.5 (based on hash1)
711
712Long commit message that fills more than 80 characters (Chromium issue 12345).
713
714Performance and stability improvements on all platforms."""
715    self._TestSquashCommits(change_log, commit_msg)
716
717  def testSquashCommitsQuotationMarks(self):
718    change_log = """Line with "quotation marks".\n"""
719    commit_msg = """Line with "quotation marks"."""
720    self._TestSquashCommits(change_log, commit_msg)
721
722  def testBootstrapper(self):
723    work_dir = self.MakeEmptyTempDirectory()
724    class FakeScript(ScriptsBase):
725      def _Steps(self):
726        return []
727
728    # Use the test configuration without the fake testing default work dir.
729    fake_config = dict(TEST_CONFIG)
730    del(fake_config["DEFAULT_CWD"])
731
732    self.Expect([
733      Cmd("fetch v8", "", cwd=work_dir),
734    ])
735    FakeScript(fake_config, self).Run(["--work-dir", work_dir])
736
737  def _PushToCandidates(self, force=False, manual=False):
738    TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git"))
739
740    # The version file on master has build level 5, while the version
741    # file from candidates has build level 4.
742    self.WriteFakeVersionFile(build=5)
743
744    TEST_CONFIG["CHANGELOG_ENTRY_FILE"] = self.MakeEmptyTempFile()
745    master_change_log = "2014-03-17: Sentinel\n"
746    TextToFile(master_change_log,
747               os.path.join(TEST_CONFIG["DEFAULT_CWD"], CHANGELOG_FILE))
748    os.environ["EDITOR"] = "vi"
749
750    commit_msg_squashed = """Version 3.22.5 (squashed - based on push_hash)
751
752Log text 1 (issue 321).
753
754Performance and stability improvements on all platforms."""
755
756    commit_msg = """Version 3.22.5 (based on push_hash)
757
758Log text 1 (issue 321).
759
760Performance and stability improvements on all platforms."""
761
762    def ResetChangeLog():
763      """On 'git co -b new_branch origin/candidates',
764      and 'git checkout -- ChangeLog',
765      the ChangLog will be reset to its content on candidates."""
766      candidates_change_log = """1999-04-05: Version 3.22.4
767
768        Performance and stability improvements on all platforms.\n"""
769      TextToFile(candidates_change_log,
770                 os.path.join(TEST_CONFIG["DEFAULT_CWD"], CHANGELOG_FILE))
771
772    def ResetToCandidates():
773      ResetChangeLog()
774      self.WriteFakeVersionFile()
775
776    def CheckVersionCommit():
777      commit = FileToText(TEST_CONFIG["COMMITMSG_FILE"])
778      self.assertEquals(commit_msg, commit)
779      version = FileToText(
780          os.path.join(TEST_CONFIG["DEFAULT_CWD"], VERSION_FILE))
781      self.assertTrue(re.search(r"#define V8_MINOR_VERSION\s+22", version))
782      self.assertTrue(re.search(r"#define V8_BUILD_NUMBER\s+5", version))
783      self.assertFalse(re.search(r"#define V8_BUILD_NUMBER\s+6", version))
784      self.assertTrue(re.search(r"#define V8_PATCH_LEVEL\s+0", version))
785      self.assertTrue(
786          re.search(r"#define V8_IS_CANDIDATE_VERSION\s+0", version))
787
788      # Check that the change log on the candidates branch got correctly
789      # modified.
790      change_log = FileToText(
791          os.path.join(TEST_CONFIG["DEFAULT_CWD"], CHANGELOG_FILE))
792      self.assertEquals(
793"""1999-07-31: Version 3.22.5
794
795        Log text 1 (issue 321).
796
797        Performance and stability improvements on all platforms.
798
799
8001999-04-05: Version 3.22.4
801
802        Performance and stability improvements on all platforms.\n""",
803          change_log)
804
805    force_flag = " -f" if not manual else ""
806    expectations = []
807    if not force:
808      expectations.append(Cmd("which vi", "/usr/bin/vi"))
809    expectations += [
810      Cmd("git status -s -uno", ""),
811      Cmd("git checkout -f origin/master", ""),
812      Cmd("git fetch", ""),
813      Cmd("git branch", "  branch1\n* branch2\n"),
814      Cmd("git branch", "  branch1\n* branch2\n"),
815      Cmd(("git new-branch %s --upstream origin/master" %
816           TEST_CONFIG["BRANCHNAME"]), ""),
817      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
818      Cmd("git tag", self.TAGS),
819      Cmd("git checkout -f origin/master -- include/v8-version.h",
820          "", cb=self.WriteFakeVersionFile),
821      Cmd("git log -1 --format=%H 3.22.4", "release_hash\n"),
822      Cmd("git log -1 --format=%s release_hash",
823          "Version 3.22.4 (based on abc3)\n"),
824      Cmd("git log --format=%H abc3..push_hash", "rev1\n"),
825      Cmd("git log -1 --format=%s rev1", "Log text 1.\n"),
826      Cmd("git log -1 --format=%B rev1", "Text\nLOG=YES\nBUG=v8:321\nText\n"),
827      Cmd("git log -1 --format=%an rev1", "author1@chromium.org\n"),
828    ]
829    if manual:
830      expectations.append(RL(""))  # Open editor.
831    if not force:
832      expectations.append(
833          Cmd("vi %s" % TEST_CONFIG["CHANGELOG_ENTRY_FILE"], ""))
834    expectations += [
835      Cmd("git fetch", ""),
836      Cmd("git checkout -f origin/master", ""),
837      Cmd("git diff origin/candidates push_hash", "patch content\n"),
838      Cmd(("git new-branch %s --upstream origin/candidates" %
839           TEST_CONFIG["CANDIDATESBRANCH"]), "", cb=ResetToCandidates),
840      Cmd("git apply --index --reject \"%s\"" % TEST_CONFIG["PATCH_FILE"], ""),
841      Cmd("git checkout -f origin/candidates -- ChangeLog", "",
842          cb=ResetChangeLog),
843      Cmd("git checkout -f origin/candidates -- include/v8-version.h", "",
844          cb=self.WriteFakeVersionFile),
845      Cmd("git commit -am \"%s\"" % commit_msg_squashed, ""),
846    ]
847    if manual:
848      expectations.append(RL("Y"))  # Sanity check.
849    expectations += [
850      Cmd("git cl land -f --bypass-hooks", ""),
851      Cmd("git checkout -f master", ""),
852      Cmd("git fetch", ""),
853      Cmd("git branch -D %s" % TEST_CONFIG["CANDIDATESBRANCH"], ""),
854      Cmd(("git new-branch %s --upstream origin/candidates" %
855           TEST_CONFIG["CANDIDATESBRANCH"]), "", cb=ResetToCandidates),
856      Cmd("git commit -aF \"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], "",
857          cb=CheckVersionCommit),
858      Cmd("git cl land -f --bypass-hooks", ""),
859      Cmd("git fetch", ""),
860      Cmd("git log -1 --format=%H --grep="
861          "\"Version 3.22.5 (based on push_hash)\""
862          " origin/candidates", "hsh_to_tag"),
863      Cmd("git tag 3.22.5 hsh_to_tag", ""),
864      Cmd("git push origin 3.22.5", ""),
865      Cmd("git checkout -f origin/master", ""),
866      Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], ""),
867      Cmd("git branch -D %s" % TEST_CONFIG["CANDIDATESBRANCH"], ""),
868    ]
869    self.Expect(expectations)
870
871    args = ["-a", "author@chromium.org", "--revision", "push_hash"]
872    if force: args.append("-f")
873    if manual: args.append("-m")
874    else: args += ["-r", "reviewer@chromium.org"]
875    PushToCandidates(TEST_CONFIG, self).Run(args)
876
877    cl = FileToText(os.path.join(TEST_CONFIG["DEFAULT_CWD"], CHANGELOG_FILE))
878    self.assertTrue(re.search(r"^\d\d\d\d\-\d+\-\d+: Version 3\.22\.5", cl))
879    self.assertTrue(re.search(r"        Log text 1 \(issue 321\).", cl))
880    self.assertTrue(re.search(r"1999\-04\-05: Version 3\.22\.4", cl))
881
882    # Note: The version file is on build number 5 again in the end of this test
883    # since the git command that merges to master is mocked out.
884
885  def testPushToCandidatesManual(self):
886    self._PushToCandidates(manual=True)
887
888  def testPushToCandidatesSemiAutomatic(self):
889    self._PushToCandidates()
890
891  def testPushToCandidatesForced(self):
892    self._PushToCandidates(force=True)
893
894  def testCreateRelease(self):
895    TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git"))
896
897    # The version file on master has build level 5.
898    self.WriteFakeVersionFile(build=5)
899
900    master_change_log = "2014-03-17: Sentinel\n"
901    TextToFile(master_change_log,
902               os.path.join(TEST_CONFIG["DEFAULT_CWD"], CHANGELOG_FILE))
903
904    commit_msg = """Version 3.22.5
905
906Log text 1 (issue 321).
907
908Performance and stability improvements on all platforms."""
909
910    def ResetChangeLog():
911      last_change_log = """1999-04-05: Version 3.22.4
912
913        Performance and stability improvements on all platforms.\n"""
914      TextToFile(last_change_log,
915                 os.path.join(TEST_CONFIG["DEFAULT_CWD"], CHANGELOG_FILE))
916
917
918    def CheckVersionCommit():
919      commit = FileToText(TEST_CONFIG["COMMITMSG_FILE"])
920      self.assertEquals(commit_msg, commit)
921      version = FileToText(
922          os.path.join(TEST_CONFIG["DEFAULT_CWD"], VERSION_FILE))
923      self.assertTrue(re.search(r"#define V8_MINOR_VERSION\s+22", version))
924      self.assertTrue(re.search(r"#define V8_BUILD_NUMBER\s+5", version))
925      self.assertFalse(re.search(r"#define V8_BUILD_NUMBER\s+6", version))
926      self.assertTrue(re.search(r"#define V8_PATCH_LEVEL\s+0", version))
927      self.assertTrue(
928          re.search(r"#define V8_IS_CANDIDATE_VERSION\s+0", version))
929
930      # Check that the change log on the candidates branch got correctly
931      # modified.
932      change_log = FileToText(
933          os.path.join(TEST_CONFIG["DEFAULT_CWD"], CHANGELOG_FILE))
934      self.assertEquals(
935"""1999-07-31: Version 3.22.5
936
937        Log text 1 (issue 321).
938
939        Performance and stability improvements on all platforms.
940
941
9421999-04-05: Version 3.22.4
943
944        Performance and stability improvements on all platforms.\n""",
945          change_log)
946
947    expectations = [
948      Cmd("git fetch origin +refs/heads/*:refs/heads/*", ""),
949      Cmd("git checkout -f origin/master", ""),
950      Cmd("git branch", ""),
951      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
952      Cmd("git tag", self.TAGS),
953      Cmd("git checkout -f origin/master -- include/v8-version.h",
954          "", cb=self.WriteFakeVersionFile),
955      Cmd("git log -1 --format=%H 3.22.4", "release_hash\n"),
956      Cmd("git log -1 --format=%s release_hash", "Version 3.22.4\n"),
957      Cmd("git log -1 --format=%H release_hash^", "abc3\n"),
958      Cmd("git log --format=%H abc3..push_hash", "rev1\n"),
959      Cmd("git log -1 --format=%s rev1", "Log text 1.\n"),
960      Cmd("git log -1 --format=%B rev1", "Text\nLOG=YES\nBUG=v8:321\nText\n"),
961      Cmd("git log -1 --format=%an rev1", "author1@chromium.org\n"),
962      Cmd("git push origin push_hash:refs/heads/3.22.5", ""),
963      Cmd("git reset --hard origin/master", ""),
964      Cmd("git new-branch work-branch --upstream origin/3.22.5", ""),
965      Cmd("git checkout -f 3.22.4 -- ChangeLog", "", cb=ResetChangeLog),
966      Cmd("git checkout -f 3.22.4 -- include/v8-version.h", "",
967          cb=self.WriteFakeVersionFile),
968      Cmd("git checkout -f 3.22.4 -- WATCHLISTS", "",
969          cb=self.WriteFakeWatchlistsFile),
970      Cmd("git commit -aF \"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], "",
971          cb=CheckVersionCommit),
972      Cmd("git cl land --bypass-hooks -f", ""),
973      Cmd("git fetch", ""),
974      Cmd("git log -1 --format=%H --grep="
975          "\"Version 3.22.5\" origin/3.22.5", "hsh_to_tag"),
976      Cmd("git tag 3.22.5 hsh_to_tag", ""),
977      Cmd("git push origin 3.22.5", ""),
978      Cmd("git checkout -f origin/master", ""),
979      Cmd("git branch", "* master\n  work-branch\n"),
980      Cmd("git branch -D work-branch", ""),
981      Cmd("git gc", ""),
982    ]
983    self.Expect(expectations)
984
985    args = ["-a", "author@chromium.org",
986            "-r", "reviewer@chromium.org",
987            "--revision", "push_hash"]
988    CreateRelease(TEST_CONFIG, self).Run(args)
989
990    cl = FileToText(os.path.join(TEST_CONFIG["DEFAULT_CWD"], CHANGELOG_FILE))
991    self.assertTrue(re.search(r"^\d\d\d\d\-\d+\-\d+: Version 3\.22\.5", cl))
992    self.assertTrue(re.search(r"        Log text 1 \(issue 321\).", cl))
993    self.assertTrue(re.search(r"1999\-04\-05: Version 3\.22\.4", cl))
994
995    # Note: The version file is on build number 5 again in the end of this test
996    # since the git command that merges to master is mocked out.
997
998    # Check for correct content of the WATCHLISTS file
999
1000    watchlists_content = FileToText(os.path.join(TEST_CONFIG["DEFAULT_CWD"],
1001                                          WATCHLISTS_FILE))
1002    expected_watchlists_content = """
1003    'merges': [
1004      # Only enabled on branches created with tools/release/create_release.py
1005      'v8-merges@googlegroups.com',
1006    ],
1007"""
1008    self.assertEqual(watchlists_content, expected_watchlists_content)
1009
1010  C_V8_22624_LOG = """V8 CL.
1011
1012git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@22624 123
1013
1014"""
1015
1016  C_V8_123455_LOG = """V8 CL.
1017
1018git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@123455 123
1019
1020"""
1021
1022  C_V8_123456_LOG = """V8 CL.
1023
1024git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@123456 123
1025
1026"""
1027
1028  ROLL_COMMIT_MSG = """Update V8 to version 3.22.4.
1029
1030Summary of changes available at:
1031https://chromium.googlesource.com/v8/v8/+log/last_rol..roll_hsh
1032
1033Please follow these instructions for assigning/CC'ing issues:
1034https://github.com/v8/v8/wiki/Triaging%20issues
1035
1036Please close rolling in case of a roll revert:
1037https://v8-roll.appspot.com/
1038This only works with a Google account.
1039
1040CQ_INCLUDE_TRYBOTS=master.tryserver.blink:linux_trusty_blink_rel;master.tryserver.chromium.linux:linux_optional_gpu_tests_rel;master.tryserver.chromium.mac:mac_optional_gpu_tests_rel;master.tryserver.chromium.win:win_optional_gpu_tests_rel
1041
1042TBR=reviewer@chromium.org"""
1043
1044  # Snippet from the original DEPS file.
1045  FAKE_DEPS = """
1046vars = {
1047  "v8_revision": "last_roll_hsh",
1048}
1049deps = {
1050  "src/v8":
1051    (Var("googlecode_url") % "v8") + "/" + Var("v8_branch") + "@" +
1052    Var("v8_revision"),
1053}
1054"""
1055
1056  def testChromiumRollUpToDate(self):
1057    TEST_CONFIG["CHROMIUM"] = self.MakeEmptyTempDirectory()
1058    json_output_file = os.path.join(TEST_CONFIG["CHROMIUM"], "out.json")
1059    TextToFile(self.FAKE_DEPS, os.path.join(TEST_CONFIG["CHROMIUM"], "DEPS"))
1060    self.Expect([
1061      Cmd("git fetch origin", ""),
1062      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
1063      Cmd("git describe --tags last_roll_hsh", "3.22.4"),
1064      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
1065      Cmd("git rev-list --max-age=395200 --tags",
1066          "bad_tag\nroll_hsh\nhash_123"),
1067      Cmd("git describe --tags bad_tag", ""),
1068      Cmd("git describe --tags roll_hsh", "3.22.4"),
1069      Cmd("git describe --tags hash_123", "3.22.3"),
1070      Cmd("git describe --tags roll_hsh", "3.22.4"),
1071      Cmd("git describe --tags hash_123", "3.22.3"),
1072    ])
1073
1074    result = auto_roll.AutoRoll(TEST_CONFIG, self).Run(
1075        AUTO_PUSH_ARGS + [
1076          "-c", TEST_CONFIG["CHROMIUM"],
1077          "--json-output", json_output_file])
1078    self.assertEquals(0, result)
1079    json_output = json.loads(FileToText(json_output_file))
1080    self.assertEquals("up_to_date", json_output["monitoring_state"])
1081
1082
1083  def testChromiumRoll(self):
1084    # Setup fake directory structures.
1085    TEST_CONFIG["CHROMIUM"] = self.MakeEmptyTempDirectory()
1086    json_output_file = os.path.join(TEST_CONFIG["CHROMIUM"], "out.json")
1087    TextToFile(self.FAKE_DEPS, os.path.join(TEST_CONFIG["CHROMIUM"], "DEPS"))
1088    TextToFile("", os.path.join(TEST_CONFIG["CHROMIUM"], ".git"))
1089    chrome_dir = TEST_CONFIG["CHROMIUM"]
1090    os.makedirs(os.path.join(chrome_dir, "v8"))
1091
1092    def WriteDeps():
1093      TextToFile("Some line\n   \"v8_revision\": \"22624\",\n  some line",
1094                 os.path.join(chrome_dir, "DEPS"))
1095
1096    expectations = [
1097      Cmd("git fetch origin", ""),
1098      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
1099      Cmd("git describe --tags last_roll_hsh", "3.22.3.1"),
1100      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
1101      Cmd("git rev-list --max-age=395200 --tags",
1102          "bad_tag\nroll_hsh\nhash_123"),
1103      Cmd("git describe --tags bad_tag", ""),
1104      Cmd("git describe --tags roll_hsh", "3.22.4"),
1105      Cmd("git describe --tags hash_123", "3.22.3"),
1106      Cmd("git describe --tags roll_hsh", "3.22.4"),
1107      Cmd("git log -1 --format=%s roll_hsh", "Version 3.22.4\n"),
1108      Cmd("git describe --tags roll_hsh", "3.22.4"),
1109      Cmd("git describe --tags last_roll_hsh", "3.22.2.1"),
1110      Cmd("git status -s -uno", "", cwd=chrome_dir),
1111      Cmd("git checkout -f master", "", cwd=chrome_dir),
1112      Cmd("git branch", "", cwd=chrome_dir),
1113      Cmd("git pull", "", cwd=chrome_dir),
1114      Cmd("git fetch origin", ""),
1115      Cmd("git new-branch work-branch", "", cwd=chrome_dir),
1116      Cmd("roll-dep-svn v8 roll_hsh", "rolled", cb=WriteDeps, cwd=chrome_dir),
1117      Cmd(("git commit -am \"%s\" "
1118           "--author \"author@chromium.org <author@chromium.org>\"" %
1119           self.ROLL_COMMIT_MSG),
1120          "", cwd=chrome_dir),
1121      Cmd("git cl upload --send-mail --email \"author@chromium.org\" -f "
1122          "--use-commit-queue --bypass-hooks", "", cwd=chrome_dir),
1123      Cmd("git checkout -f master", "", cwd=chrome_dir),
1124      Cmd("git branch -D work-branch", "", cwd=chrome_dir),
1125    ]
1126    self.Expect(expectations)
1127
1128    args = ["-a", "author@chromium.org", "-c", chrome_dir,
1129            "-r", "reviewer@chromium.org", "--json-output", json_output_file]
1130    auto_roll.AutoRoll(TEST_CONFIG, self).Run(args)
1131
1132    deps = FileToText(os.path.join(chrome_dir, "DEPS"))
1133    self.assertTrue(re.search("\"v8_revision\": \"22624\"", deps))
1134
1135    json_output = json.loads(FileToText(json_output_file))
1136    self.assertEquals("success", json_output["monitoring_state"])
1137
1138  def testCheckLastPushRecently(self):
1139    self.Expect([
1140      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
1141      Cmd("git tag", self.TAGS),
1142      Cmd("git log -1 --format=%H 3.22.4", "release_hash\n"),
1143      Cmd("git log -1 --format=%s release_hash",
1144          "Version 3.22.4 (based on abc3)\n"),
1145      Cmd("git log --format=%H abc3..abc123", "\n"),
1146    ])
1147
1148    self._state["candidate"] = "abc123"
1149    self.assertEquals(0, self.RunStep(
1150        auto_push.AutoPush, LastReleaseBailout, AUTO_PUSH_ARGS))
1151
1152  def testAutoPush(self):
1153    self.Expect([
1154      Cmd("git fetch", ""),
1155      Cmd("git fetch origin +refs/heads/lkgr:refs/heads/lkgr", ""),
1156      Cmd("git show-ref -s refs/heads/lkgr", "abc123\n"),
1157      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
1158      Cmd("git tag", self.TAGS),
1159      Cmd("git log -1 --format=%H 3.22.4", "release_hash\n"),
1160      Cmd("git log -1 --format=%s release_hash",
1161          "Version 3.22.4 (based on abc3)\n"),
1162      Cmd("git log --format=%H abc3..abc123", "some_stuff\n"),
1163    ])
1164
1165    auto_push.AutoPush(TEST_CONFIG, self).Run(AUTO_PUSH_ARGS + ["--push"])
1166
1167    state = json.loads(FileToText("%s-state.json"
1168                                  % TEST_CONFIG["PERSISTFILE_BASENAME"]))
1169
1170    self.assertEquals("abc123", state["candidate"])
1171
1172  def testRollMerge(self):
1173    TEST_CONFIG["ALREADY_MERGING_SENTINEL_FILE"] = self.MakeEmptyTempFile()
1174    TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git"))
1175    self.WriteFakeVersionFile(build=5)
1176    os.environ["EDITOR"] = "vi"
1177    extra_patch = self.MakeEmptyTempFile()
1178
1179    def VerifyPatch(patch):
1180      return lambda: self.assertEquals(patch,
1181          FileToText(TEST_CONFIG["TEMPORARY_PATCH_FILE"]))
1182
1183    msg = """Version 3.22.5.1 (cherry-pick)
1184
1185Merged ab12345
1186Merged ab23456
1187Merged ab34567
1188Merged ab45678
1189Merged ab56789
1190
1191Title4
1192
1193Title2
1194
1195Title3
1196
1197Title1
1198
1199Revert "Something"
1200
1201BUG=123,234,345,456,567,v8:123
1202LOG=N
1203"""
1204
1205    def VerifyLand():
1206      commit = FileToText(TEST_CONFIG["COMMITMSG_FILE"])
1207      self.assertEquals(msg, commit)
1208      version = FileToText(
1209          os.path.join(TEST_CONFIG["DEFAULT_CWD"], VERSION_FILE))
1210      self.assertTrue(re.search(r"#define V8_MINOR_VERSION\s+22", version))
1211      self.assertTrue(re.search(r"#define V8_BUILD_NUMBER\s+5", version))
1212      self.assertTrue(re.search(r"#define V8_PATCH_LEVEL\s+1", version))
1213      self.assertTrue(
1214          re.search(r"#define V8_IS_CANDIDATE_VERSION\s+0", version))
1215
1216    self.Expect([
1217      Cmd("git status -s -uno", ""),
1218      Cmd("git checkout -f origin/master", ""),
1219      Cmd("git fetch", ""),
1220      Cmd("git branch", "  branch1\n* branch2\n"),
1221      Cmd("git new-branch %s --upstream refs/remotes/origin/candidates" %
1222          TEST_CONFIG["BRANCHNAME"], ""),
1223      Cmd(("git log --format=%H --grep=\"Port ab12345\" "
1224           "--reverse origin/master"),
1225          "ab45678\nab23456"),
1226      Cmd("git log -1 --format=%s ab45678", "Title1"),
1227      Cmd("git log -1 --format=%s ab23456", "Title2"),
1228      Cmd(("git log --format=%H --grep=\"Port ab23456\" "
1229           "--reverse origin/master"),
1230          ""),
1231      Cmd(("git log --format=%H --grep=\"Port ab34567\" "
1232           "--reverse origin/master"),
1233          "ab56789"),
1234      Cmd("git log -1 --format=%s ab56789", "Title3"),
1235      RL("Y"),  # Automatically add corresponding ports (ab34567, ab56789)?
1236      # Simulate git being down which stops the script.
1237      Cmd("git log -1 --format=%s ab12345", None),
1238      # Restart script in the failing step.
1239      Cmd("git log -1 --format=%s ab12345", "Title4"),
1240      Cmd("git log -1 --format=%s ab23456", "Title2"),
1241      Cmd("git log -1 --format=%s ab34567", "Title3"),
1242      Cmd("git log -1 --format=%s ab45678", "Title1"),
1243      Cmd("git log -1 --format=%s ab56789", "Revert \"Something\""),
1244      Cmd("git log -1 ab12345", "Title4\nBUG=123\nBUG=234"),
1245      Cmd("git log -1 ab23456", "Title2\n BUG = v8:123,345"),
1246      Cmd("git log -1 ab34567", "Title3\nLOG=n\nBUG=567, 456"),
1247      Cmd("git log -1 ab45678", "Title1\nBUG="),
1248      Cmd("git log -1 ab56789", "Revert \"Something\"\nBUG=none"),
1249      Cmd("git log -1 -p ab12345", "patch4"),
1250      Cmd(("git apply --index --reject \"%s\"" %
1251           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
1252          "", cb=VerifyPatch("patch4")),
1253      Cmd("git log -1 -p ab23456", "patch2"),
1254      Cmd(("git apply --index --reject \"%s\"" %
1255           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
1256          "", cb=VerifyPatch("patch2")),
1257      Cmd("git log -1 -p ab34567", "patch3"),
1258      Cmd(("git apply --index --reject \"%s\"" %
1259           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
1260          "", cb=VerifyPatch("patch3")),
1261      Cmd("git log -1 -p ab45678", "patch1"),
1262      Cmd(("git apply --index --reject \"%s\"" %
1263           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
1264          "", cb=VerifyPatch("patch1")),
1265      Cmd("git log -1 -p ab56789", "patch5\n"),
1266      Cmd(("git apply --index --reject \"%s\"" %
1267           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
1268          "", cb=VerifyPatch("patch5\n")),
1269      Cmd("git apply --index --reject \"%s\"" % extra_patch, ""),
1270      RL("Y"),  # Automatically increment patch level?
1271      Cmd("git commit -aF \"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], ""),
1272      RL("reviewer@chromium.org"),  # V8 reviewer.
1273      Cmd("git cl upload --send-mail -r \"reviewer@chromium.org\" "
1274          "--bypass-hooks --cc \"ulan@chromium.org\"", ""),
1275      Cmd("git checkout -f %s" % TEST_CONFIG["BRANCHNAME"], ""),
1276      RL("LGTM"),  # Enter LGTM for V8 CL.
1277      Cmd("git cl presubmit", "Presubmit successfull\n"),
1278      Cmd("git cl land -f --bypass-hooks", "Closing issue\n",
1279          cb=VerifyLand),
1280      Cmd("git fetch", ""),
1281      Cmd("git log -1 --format=%H --grep=\""
1282          "Version 3.22.5.1 (cherry-pick)"
1283          "\" refs/remotes/origin/candidates",
1284          ""),
1285      Cmd("git fetch", ""),
1286      Cmd("git log -1 --format=%H --grep=\""
1287          "Version 3.22.5.1 (cherry-pick)"
1288          "\" refs/remotes/origin/candidates",
1289          "hsh_to_tag"),
1290      Cmd("git tag 3.22.5.1 hsh_to_tag", ""),
1291      Cmd("git push origin 3.22.5.1", ""),
1292      Cmd("git checkout -f origin/master", ""),
1293      Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], ""),
1294    ])
1295
1296    # ab12345 and ab34567 are patches. ab23456 (included) and ab45678 are the
1297    # MIPS ports of ab12345. ab56789 is the MIPS port of ab34567.
1298    args = ["-f", "-p", extra_patch, "--branch", "candidates",
1299            "ab12345", "ab23456", "ab34567"]
1300
1301    # The first run of the script stops because of git being down.
1302    self.assertRaises(GitFailedException,
1303        lambda: RollMerge(TEST_CONFIG, self).Run(args))
1304
1305    # Test that state recovery after restarting the script works.
1306    args += ["-s", "4"]
1307    RollMerge(TEST_CONFIG, self).Run(args)
1308
1309  def testReleases(self):
1310    c_hash1_commit_log = """Update V8 to Version 4.2.71.
1311
1312Cr-Commit-Position: refs/heads/master@{#5678}
1313"""
1314    c_hash2_commit_log = """Revert something.
1315
1316BUG=12345
1317
1318Reason:
1319> Some reason.
1320> Cr-Commit-Position: refs/heads/master@{#12345}
1321> git-svn-id: svn://svn.chromium.org/chrome/trunk/src@12345 003-1c4
1322
1323Review URL: https://codereview.chromium.org/12345
1324
1325Cr-Commit-Position: refs/heads/master@{#4567}
1326git-svn-id: svn://svn.chromium.org/chrome/trunk/src@4567 0039-1c4b
1327
1328"""
1329    c_hash3_commit_log = """Simple.
1330
1331git-svn-id: svn://svn.chromium.org/chrome/trunk/src@3456 0039-1c4b
1332
1333"""
1334    c_hash_234_commit_log = """Version 3.3.1.1 (cherry-pick).
1335
1336Merged abc12.
1337
1338Review URL: fake.com
1339
1340Cr-Commit-Position: refs/heads/candidates@{#234}
1341"""
1342    c_hash_123_commit_log = """Version 3.3.1.0
1343
1344git-svn-id: googlecode@123 0039-1c4b
1345"""
1346    c_hash_345_commit_log = """Version 3.4.0.
1347
1348Cr-Commit-Position: refs/heads/candidates@{#345}
1349"""
1350    c_hash_456_commit_log = """Version 4.2.71.
1351
1352Cr-Commit-Position: refs/heads/4.2.71@{#1}
1353"""
1354    c_deps = "Line\n   \"v8_revision\": \"%s\",\n  line\n"
1355
1356    json_output = self.MakeEmptyTempFile()
1357    csv_output = self.MakeEmptyTempFile()
1358    self.WriteFakeVersionFile()
1359
1360    TEST_CONFIG["CHROMIUM"] = self.MakeEmptyTempDirectory()
1361    chrome_dir = TEST_CONFIG["CHROMIUM"]
1362    chrome_v8_dir = os.path.join(chrome_dir, "v8")
1363    os.makedirs(chrome_v8_dir)
1364
1365    def ResetVersion(major, minor, build, patch=0):
1366      return lambda: self.WriteFakeVersionFile(major=major,
1367                                               minor=minor,
1368                                               build=build,
1369                                               patch=patch)
1370
1371    self.Expect([
1372      Cmd("git status -s -uno", ""),
1373      Cmd("git checkout -f origin/master", ""),
1374      Cmd("git fetch", ""),
1375      Cmd("git branch", "  branch1\n* branch2\n"),
1376      Cmd("git new-branch %s" % TEST_CONFIG["BRANCHNAME"], ""),
1377      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
1378      Cmd("git rev-list --max-age=395200 --tags",
1379          "bad_tag\nhash_234\nhash_123\nhash_345\nhash_456\n"),
1380      Cmd("git describe --tags bad_tag", "3.23.42-1-deadbeef"),
1381      Cmd("git describe --tags hash_234", "3.3.1.1"),
1382      Cmd("git describe --tags hash_123", "3.21.2"),
1383      Cmd("git describe --tags hash_345", "3.22.3"),
1384      Cmd("git describe --tags hash_456", "4.2.71"),
1385      Cmd("git diff --name-only hash_234 hash_234^", VERSION_FILE),
1386      Cmd("git checkout -f hash_234 -- %s" % VERSION_FILE, "",
1387          cb=ResetVersion(3, 3, 1, 1)),
1388      Cmd("git branch -r --contains hash_234", "  branch-heads/3.3\n"),
1389      Cmd("git log -1 --format=%B hash_234", c_hash_234_commit_log),
1390      Cmd("git log -1 --format=%s hash_234", ""),
1391      Cmd("git log -1 --format=%B hash_234", c_hash_234_commit_log),
1392      Cmd("git log -1 --format=%ci hash_234", "18:15"),
1393      Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
1394          cb=ResetVersion(3, 22, 5)),
1395      Cmd("git diff --name-only hash_123 hash_123^", VERSION_FILE),
1396      Cmd("git checkout -f hash_123 -- %s" % VERSION_FILE, "",
1397          cb=ResetVersion(3, 21, 2)),
1398      Cmd("git branch -r --contains hash_123", "  branch-heads/3.21\n"),
1399      Cmd("git log -1 --format=%B hash_123", c_hash_123_commit_log),
1400      Cmd("git log -1 --format=%s hash_123", ""),
1401      Cmd("git log -1 --format=%B hash_123", c_hash_123_commit_log),
1402      Cmd("git log -1 --format=%ci hash_123", "03:15"),
1403      Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
1404          cb=ResetVersion(3, 22, 5)),
1405      Cmd("git diff --name-only hash_345 hash_345^", VERSION_FILE),
1406      Cmd("git checkout -f hash_345 -- %s" % VERSION_FILE, "",
1407          cb=ResetVersion(3, 22, 3)),
1408      Cmd("git branch -r --contains hash_345", "  origin/candidates\n"),
1409      Cmd("git log -1 --format=%B hash_345", c_hash_345_commit_log),
1410      Cmd("git log -1 --format=%s hash_345", ""),
1411      Cmd("git log -1 --format=%B hash_345", c_hash_345_commit_log),
1412      Cmd("git log -1 --format=%ci hash_345", ""),
1413      Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
1414          cb=ResetVersion(3, 22, 5)),
1415      Cmd("git diff --name-only hash_456 hash_456^", VERSION_FILE),
1416      Cmd("git checkout -f hash_456 -- %s" % VERSION_FILE, "",
1417          cb=ResetVersion(4, 2, 71)),
1418      Cmd("git branch -r --contains hash_456", "  origin/4.2.71\n"),
1419      Cmd("git log -1 --format=%B hash_456", c_hash_456_commit_log),
1420      Cmd("git log -1 --format=%H 4.2.71", "hash_456"),
1421      Cmd("git log -1 --format=%s hash_456", "Version 4.2.71"),
1422      Cmd("git log -1 --format=%H hash_456^", "master_456"),
1423      Cmd("git log -1 --format=%B master_456",
1424          "Cr-Commit-Position: refs/heads/master@{#456}"),
1425      Cmd("git log -1 --format=%B hash_456", c_hash_456_commit_log),
1426      Cmd("git log -1 --format=%ci hash_456", "02:15"),
1427      Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
1428          cb=ResetVersion(3, 22, 5)),
1429      Cmd("git fetch origin +refs/heads/*:refs/remotes/origin/* "
1430          "+refs/branch-heads/*:refs/remotes/branch-heads/*", "",
1431          cwd=chrome_dir),
1432      Cmd("git fetch origin", "", cwd=chrome_v8_dir),
1433      Cmd("git log --format=%H --grep=\"V8\" origin/master -- DEPS",
1434          "c_hash1\nc_hash2\nc_hash3\n",
1435          cwd=chrome_dir),
1436      Cmd("git show c_hash1:DEPS", c_deps % "hash_456", cwd=chrome_dir),
1437      Cmd("git log -1 --format=%B c_hash1", c_hash1_commit_log,
1438          cwd=chrome_dir),
1439      Cmd("git show c_hash2:DEPS", c_deps % "hash_345", cwd=chrome_dir),
1440      Cmd("git log -1 --format=%B c_hash2", c_hash2_commit_log,
1441          cwd=chrome_dir),
1442      Cmd("git show c_hash3:DEPS", c_deps % "deadbeef", cwd=chrome_dir),
1443      Cmd("git log -1 --format=%B c_hash3", c_hash3_commit_log,
1444          cwd=chrome_dir),
1445      Cmd("git branch -r", " weird/123\n  branch-heads/7\n", cwd=chrome_dir),
1446      Cmd("git show refs/branch-heads/7:DEPS", c_deps % "hash_345",
1447          cwd=chrome_dir),
1448      URL("http://omahaproxy.appspot.com/all.json", """[{
1449        "os": "win",
1450        "versions": [{
1451          "version": "2.2.2.2",
1452          "v8_version": "22.2.2.2",
1453          "current_reldate": "04/09/15",
1454          "os": "win",
1455          "channel": "canary",
1456          "previous_version": "1.1.1.0"
1457          }]
1458        }]"""),
1459      URL("http://omahaproxy.appspot.com/v8.json?version=1.1.1.0", """{
1460        "chromium_version": "1.1.1.0",
1461        "v8_version": "11.1.1.0"
1462        }"""),
1463      Cmd("git rev-list -1 11.1.1", "v8_previous_version_hash"),
1464      Cmd("git rev-list -1 22.2.2.2", "v8_version_hash"),
1465      Cmd("git checkout -f origin/master", ""),
1466      Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], "")
1467    ])
1468
1469    args = ["-c", TEST_CONFIG["CHROMIUM"],
1470            "--json", json_output,
1471            "--csv", csv_output,
1472            "--max-releases", "1"]
1473    Releases(TEST_CONFIG, self).Run(args)
1474
1475    # Check expected output.
1476    csv = ("4.2.71,4.2.71,1,5678,\r\n"
1477           "3.22.3,candidates,345,4567:5677,\r\n"
1478           "3.21.2,3.21,123,,\r\n"
1479           "3.3.1.1,3.3,234,,abc12\r\n")
1480    self.assertEquals(csv, FileToText(csv_output))
1481
1482    expected_json = {"chrome_releases":{
1483                                        "canaries": [
1484                                                     {
1485                           "chrome_version": "2.2.2.2",
1486                           "os": "win",
1487                           "release_date": "04/09/15",
1488                           "v8_version": "22.2.2.2",
1489                           "v8_version_hash": "v8_version_hash",
1490                           "v8_previous_version": "11.1.1.0",
1491                           "v8_previous_version_hash": "v8_previous_version_hash"
1492                           }]},
1493                     "releases":[
1494      {
1495        "revision": "1",
1496        "revision_git": "hash_456",
1497        "master_position": "456",
1498        "master_hash": "master_456",
1499        "patches_merged": "",
1500        "version": "4.2.71",
1501        "chromium_revision": "5678",
1502        "branch": "4.2.71",
1503        "review_link": "",
1504        "date": "02:15",
1505        "chromium_branch": "",
1506        # FIXME(machenbach): Fix revisions link for git.
1507        "revision_link": "https://code.google.com/p/v8/source/detail?r=1",
1508      },
1509      {
1510        "revision": "345",
1511        "revision_git": "hash_345",
1512        "master_position": "",
1513        "master_hash": "",
1514        "patches_merged": "",
1515        "version": "3.22.3",
1516        "chromium_revision": "4567:5677",
1517        "branch": "candidates",
1518        "review_link": "",
1519        "date": "",
1520        "chromium_branch": "7",
1521        "revision_link": "https://code.google.com/p/v8/source/detail?r=345",
1522      },
1523      {
1524        "revision": "123",
1525        "revision_git": "hash_123",
1526        "patches_merged": "",
1527        "master_position": "",
1528        "master_hash": "",
1529        "version": "3.21.2",
1530        "chromium_revision": "",
1531        "branch": "3.21",
1532        "review_link": "",
1533        "date": "03:15",
1534        "chromium_branch": "",
1535        "revision_link": "https://code.google.com/p/v8/source/detail?r=123",
1536      },
1537      {
1538        "revision": "234",
1539        "revision_git": "hash_234",
1540        "patches_merged": "abc12",
1541        "master_position": "",
1542        "master_hash": "",
1543        "version": "3.3.1.1",
1544        "chromium_revision": "",
1545        "branch": "3.3",
1546        "review_link": "fake.com",
1547        "date": "18:15",
1548        "chromium_branch": "",
1549        "revision_link": "https://code.google.com/p/v8/source/detail?r=234",
1550      },],
1551    }
1552    self.assertEquals(expected_json, json.loads(FileToText(json_output)))
1553
1554  def testMergeToBranch(self):
1555    TEST_CONFIG["ALREADY_MERGING_SENTINEL_FILE"] = self.MakeEmptyTempFile()
1556    TextToFile("", os.path.join(TEST_CONFIG["DEFAULT_CWD"], ".git"))
1557    self.WriteFakeVersionFile(build=5)
1558    os.environ["EDITOR"] = "vi"
1559    extra_patch = self.MakeEmptyTempFile()
1560
1561
1562    def VerifyPatch(patch):
1563      return lambda: self.assertEquals(patch,
1564          FileToText(TEST_CONFIG["TEMPORARY_PATCH_FILE"]))
1565
1566    info_msg = ("NOTE: This script will no longer automatically "
1567     "update include/v8-version.h "
1568     "and create a tag. This is done automatically by the autotag bot. "
1569     "Please call the merge_to_branch.py with --help for more information.")
1570
1571    msg = """Merged: Squashed multiple commits.
1572
1573Merged: Title4
1574Revision: ab12345
1575
1576Merged: Title2
1577Revision: ab23456
1578
1579Merged: Title3
1580Revision: ab34567
1581
1582Merged: Title1
1583Revision: ab45678
1584
1585Merged: Revert \"Something\"
1586Revision: ab56789
1587
1588BUG=123,234,345,456,567,v8:123
1589LOG=N
1590NOTRY=true
1591NOPRESUBMIT=true
1592NOTREECHECKS=true
1593"""
1594
1595    def VerifyLand():
1596      commit = FileToText(TEST_CONFIG["COMMITMSG_FILE"])
1597      self.assertEquals(msg, commit)
1598
1599    self.Expect([
1600      Cmd("git status -s -uno", ""),
1601      Cmd("git checkout -f origin/master", ""),
1602      Cmd("git fetch", ""),
1603      Cmd("git branch", "  branch1\n* branch2\n"),
1604      Cmd("git new-branch %s --upstream refs/remotes/origin/candidates" %
1605          TEST_CONFIG["BRANCHNAME"], ""),
1606      Cmd(("git log --format=%H --grep=\"^[Pp]ort ab12345\" "
1607           "--reverse origin/master"),
1608          "ab45678\nab23456"),
1609      Cmd("git log -1 --format=%s ab45678", "Title1"),
1610      Cmd("git log -1 --format=%s ab23456", "Title2"),
1611      Cmd(("git log --format=%H --grep=\"^[Pp]ort ab23456\" "
1612           "--reverse origin/master"),
1613          ""),
1614      Cmd(("git log --format=%H --grep=\"^[Pp]ort ab34567\" "
1615           "--reverse origin/master"),
1616          "ab56789"),
1617      Cmd("git log -1 --format=%s ab56789", "Title3"),
1618      RL("Y"),  # Automatically add corresponding ports (ab34567, ab56789)?
1619      # Simulate git being down which stops the script.
1620      Cmd("git log -1 --format=%s ab12345", None),
1621      # Restart script in the failing step.
1622      Cmd("git log -1 --format=%s ab12345", "Title4"),
1623      Cmd("git log -1 --format=%s ab23456", "Title2"),
1624      Cmd("git log -1 --format=%s ab34567", "Title3"),
1625      Cmd("git log -1 --format=%s ab45678", "Title1"),
1626      Cmd("git log -1 --format=%s ab56789", "Revert \"Something\""),
1627      Cmd("git log -1 ab12345", "Title4\nBUG=123\nBUG=234"),
1628      Cmd("git log -1 ab23456", "Title2\n BUG = v8:123,345"),
1629      Cmd("git log -1 ab34567", "Title3\nLOG=n\nBUG=567, 456"),
1630      Cmd("git log -1 ab45678", "Title1\nBUG="),
1631      Cmd("git log -1 ab56789", "Revert \"Something\"\nBUG=none"),
1632      Cmd("git log -1 -p ab12345", "patch4"),
1633      Cmd(("git apply --index --reject \"%s\"" %
1634           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
1635          "", cb=VerifyPatch("patch4")),
1636      Cmd("git log -1 -p ab23456", "patch2"),
1637      Cmd(("git apply --index --reject \"%s\"" %
1638           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
1639          "", cb=VerifyPatch("patch2")),
1640      Cmd("git log -1 -p ab34567", "patch3"),
1641      Cmd(("git apply --index --reject \"%s\"" %
1642           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
1643          "", cb=VerifyPatch("patch3")),
1644      Cmd("git log -1 -p ab45678", "patch1"),
1645      Cmd(("git apply --index --reject \"%s\"" %
1646           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
1647          "", cb=VerifyPatch("patch1")),
1648      Cmd("git log -1 -p ab56789", "patch5\n"),
1649      Cmd(("git apply --index --reject \"%s\"" %
1650           TEST_CONFIG["TEMPORARY_PATCH_FILE"]),
1651          "", cb=VerifyPatch("patch5\n")),
1652      Cmd("git apply --index --reject \"%s\"" % extra_patch, ""),
1653      Cmd("git commit -aF \"%s\"" % TEST_CONFIG["COMMITMSG_FILE"], ""),
1654      RL("reviewer@chromium.org"),  # V8 reviewer.
1655      Cmd("git cl upload --send-mail -r \"reviewer@chromium.org\" "
1656          "--bypass-hooks --cc \"ulan@chromium.org\"", ""),
1657      Cmd("git cl comments -a \"%s\"" % info_msg, ""),
1658      Cmd("git checkout -f %s" % TEST_CONFIG["BRANCHNAME"], ""),
1659      RL("LGTM"),  # Enter LGTM for V8 CL.
1660      Cmd("git cl presubmit", "Presubmit successfull\n"),
1661      Cmd("git cl land -f --bypass-hooks", "Closing issue\n",
1662          cb=VerifyLand),
1663      Cmd("git checkout -f origin/master", ""),
1664      Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], ""),
1665    ])
1666
1667    # ab12345 and ab34567 are patches. ab23456 (included) and ab45678 are the
1668    # MIPS ports of ab12345. ab56789 is the MIPS port of ab34567.
1669    args = ["-f", "-p", extra_patch, "--branch", "candidates",
1670            "ab12345", "ab23456", "ab34567"]
1671
1672    # The first run of the script stops because of git being down.
1673    self.assertRaises(GitFailedException,
1674        lambda: MergeToBranch(TEST_CONFIG, self).Run(args))
1675
1676    # Test that state recovery after restarting the script works.
1677    args += ["-s", "4"]
1678    MergeToBranch(TEST_CONFIG, self).Run(args)
1679
1680  def testReleases(self):
1681    c_hash1_commit_log = """Update V8 to Version 4.2.71.
1682
1683Cr-Commit-Position: refs/heads/master@{#5678}
1684"""
1685    c_hash2_commit_log = """Revert something.
1686
1687BUG=12345
1688
1689Reason:
1690> Some reason.
1691> Cr-Commit-Position: refs/heads/master@{#12345}
1692> git-svn-id: svn://svn.chromium.org/chrome/trunk/src@12345 003-1c4
1693
1694Review URL: https://codereview.chromium.org/12345
1695
1696Cr-Commit-Position: refs/heads/master@{#4567}
1697git-svn-id: svn://svn.chromium.org/chrome/trunk/src@4567 0039-1c4b
1698
1699"""
1700    c_hash3_commit_log = """Simple.
1701
1702git-svn-id: svn://svn.chromium.org/chrome/trunk/src@3456 0039-1c4b
1703
1704"""
1705    c_hash_234_commit_log = """Version 3.3.1.1 (cherry-pick).
1706
1707Merged abc12.
1708
1709Review URL: fake.com
1710
1711Cr-Commit-Position: refs/heads/candidates@{#234}
1712"""
1713    c_hash_123_commit_log = """Version 3.3.1.0
1714
1715git-svn-id: googlecode@123 0039-1c4b
1716"""
1717    c_hash_345_commit_log = """Version 3.4.0.
1718
1719Cr-Commit-Position: refs/heads/candidates@{#345}
1720"""
1721    c_hash_456_commit_log = """Version 4.2.71.
1722
1723Cr-Commit-Position: refs/heads/4.2.71@{#1}
1724"""
1725    c_deps = "Line\n   \"v8_revision\": \"%s\",\n  line\n"
1726
1727    json_output = self.MakeEmptyTempFile()
1728    csv_output = self.MakeEmptyTempFile()
1729    self.WriteFakeVersionFile()
1730
1731    TEST_CONFIG["CHROMIUM"] = self.MakeEmptyTempDirectory()
1732    chrome_dir = TEST_CONFIG["CHROMIUM"]
1733    chrome_v8_dir = os.path.join(chrome_dir, "v8")
1734    os.makedirs(chrome_v8_dir)
1735
1736    def ResetVersion(major, minor, build, patch=0):
1737      return lambda: self.WriteFakeVersionFile(major=major,
1738                                               minor=minor,
1739                                               build=build,
1740                                               patch=patch)
1741
1742    self.Expect([
1743      Cmd("git status -s -uno", ""),
1744      Cmd("git checkout -f origin/master", ""),
1745      Cmd("git fetch", ""),
1746      Cmd("git branch", "  branch1\n* branch2\n"),
1747      Cmd("git new-branch %s" % TEST_CONFIG["BRANCHNAME"], ""),
1748      Cmd("git fetch origin +refs/tags/*:refs/tags/*", ""),
1749      Cmd("git rev-list --max-age=395200 --tags",
1750          "bad_tag\nhash_234\nhash_123\nhash_345\nhash_456\n"),
1751      Cmd("git describe --tags bad_tag", "3.23.42-1-deadbeef"),
1752      Cmd("git describe --tags hash_234", "3.3.1.1"),
1753      Cmd("git describe --tags hash_123", "3.21.2"),
1754      Cmd("git describe --tags hash_345", "3.22.3"),
1755      Cmd("git describe --tags hash_456", "4.2.71"),
1756      Cmd("git diff --name-only hash_234 hash_234^", VERSION_FILE),
1757      Cmd("git checkout -f hash_234 -- %s" % VERSION_FILE, "",
1758          cb=ResetVersion(3, 3, 1, 1)),
1759      Cmd("git branch -r --contains hash_234", "  branch-heads/3.3\n"),
1760      Cmd("git log -1 --format=%B hash_234", c_hash_234_commit_log),
1761      Cmd("git log -1 --format=%s hash_234", ""),
1762      Cmd("git log -1 --format=%B hash_234", c_hash_234_commit_log),
1763      Cmd("git log -1 --format=%ci hash_234", "18:15"),
1764      Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
1765          cb=ResetVersion(3, 22, 5)),
1766      Cmd("git diff --name-only hash_123 hash_123^", VERSION_FILE),
1767      Cmd("git checkout -f hash_123 -- %s" % VERSION_FILE, "",
1768          cb=ResetVersion(3, 21, 2)),
1769      Cmd("git branch -r --contains hash_123", "  branch-heads/3.21\n"),
1770      Cmd("git log -1 --format=%B hash_123", c_hash_123_commit_log),
1771      Cmd("git log -1 --format=%s hash_123", ""),
1772      Cmd("git log -1 --format=%B hash_123", c_hash_123_commit_log),
1773      Cmd("git log -1 --format=%ci hash_123", "03:15"),
1774      Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
1775          cb=ResetVersion(3, 22, 5)),
1776      Cmd("git diff --name-only hash_345 hash_345^", VERSION_FILE),
1777      Cmd("git checkout -f hash_345 -- %s" % VERSION_FILE, "",
1778          cb=ResetVersion(3, 22, 3)),
1779      Cmd("git branch -r --contains hash_345", "  origin/candidates\n"),
1780      Cmd("git log -1 --format=%B hash_345", c_hash_345_commit_log),
1781      Cmd("git log -1 --format=%s hash_345", ""),
1782      Cmd("git log -1 --format=%B hash_345", c_hash_345_commit_log),
1783      Cmd("git log -1 --format=%ci hash_345", ""),
1784      Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
1785          cb=ResetVersion(3, 22, 5)),
1786      Cmd("git diff --name-only hash_456 hash_456^", VERSION_FILE),
1787      Cmd("git checkout -f hash_456 -- %s" % VERSION_FILE, "",
1788          cb=ResetVersion(4, 2, 71)),
1789      Cmd("git branch -r --contains hash_456", "  origin/4.2.71\n"),
1790      Cmd("git log -1 --format=%B hash_456", c_hash_456_commit_log),
1791      Cmd("git log -1 --format=%H 4.2.71", "hash_456"),
1792      Cmd("git log -1 --format=%s hash_456", "Version 4.2.71"),
1793      Cmd("git log -1 --format=%H hash_456^", "master_456"),
1794      Cmd("git log -1 --format=%B master_456",
1795          "Cr-Commit-Position: refs/heads/master@{#456}"),
1796      Cmd("git log -1 --format=%B hash_456", c_hash_456_commit_log),
1797      Cmd("git log -1 --format=%ci hash_456", "02:15"),
1798      Cmd("git checkout -f HEAD -- %s" % VERSION_FILE, "",
1799          cb=ResetVersion(3, 22, 5)),
1800      Cmd("git fetch origin +refs/heads/*:refs/remotes/origin/* "
1801          "+refs/branch-heads/*:refs/remotes/branch-heads/*", "",
1802          cwd=chrome_dir),
1803      Cmd("git fetch origin", "", cwd=chrome_v8_dir),
1804      Cmd("git log --format=%H --grep=\"V8\" origin/master -- DEPS",
1805          "c_hash1\nc_hash2\nc_hash3\n",
1806          cwd=chrome_dir),
1807      Cmd("git show c_hash1:DEPS", c_deps % "hash_456", cwd=chrome_dir),
1808      Cmd("git log -1 --format=%B c_hash1", c_hash1_commit_log,
1809          cwd=chrome_dir),
1810      Cmd("git show c_hash2:DEPS", c_deps % "hash_345", cwd=chrome_dir),
1811      Cmd("git log -1 --format=%B c_hash2", c_hash2_commit_log,
1812          cwd=chrome_dir),
1813      Cmd("git show c_hash3:DEPS", c_deps % "deadbeef", cwd=chrome_dir),
1814      Cmd("git log -1 --format=%B c_hash3", c_hash3_commit_log,
1815          cwd=chrome_dir),
1816      Cmd("git branch -r", " weird/123\n  branch-heads/7\n", cwd=chrome_dir),
1817      Cmd("git show refs/branch-heads/7:DEPS", c_deps % "hash_345",
1818          cwd=chrome_dir),
1819      URL("http://omahaproxy.appspot.com/all.json", """[{
1820        "os": "win",
1821        "versions": [{
1822          "version": "2.2.2.2",
1823          "v8_version": "22.2.2.2",
1824          "current_reldate": "04/09/15",
1825          "os": "win",
1826          "channel": "canary",
1827          "previous_version": "1.1.1.0"
1828          }]
1829        }]"""),
1830      URL("http://omahaproxy.appspot.com/v8.json?version=1.1.1.0", """{
1831        "chromium_version": "1.1.1.0",
1832        "v8_version": "11.1.1.0"
1833        }"""),
1834      Cmd("git rev-list -1 11.1.1", "v8_previous_version_hash"),
1835      Cmd("git rev-list -1 22.2.2.2", "v8_version_hash"),
1836      Cmd("git checkout -f origin/master", ""),
1837      Cmd("git branch -D %s" % TEST_CONFIG["BRANCHNAME"], "")
1838    ])
1839
1840    args = ["-c", TEST_CONFIG["CHROMIUM"],
1841            "--json", json_output,
1842            "--csv", csv_output,
1843            "--max-releases", "1"]
1844    Releases(TEST_CONFIG, self).Run(args)
1845
1846    # Check expected output.
1847    csv = ("4.2.71,4.2.71,1,5678,\r\n"
1848           "3.22.3,candidates,345,4567:5677,\r\n"
1849           "3.21.2,3.21,123,,\r\n"
1850           "3.3.1.1,3.3,234,,abc12\r\n")
1851    self.assertEquals(csv, FileToText(csv_output))
1852
1853    expected_json = {"chrome_releases":{
1854                                        "canaries": [
1855                                                     {
1856                           "chrome_version": "2.2.2.2",
1857                           "os": "win",
1858                           "release_date": "04/09/15",
1859                           "v8_version": "22.2.2.2",
1860                           "v8_version_hash": "v8_version_hash",
1861                           "v8_previous_version": "11.1.1.0",
1862                           "v8_previous_version_hash": "v8_previous_version_hash"
1863                           }]},
1864                     "releases":[
1865      {
1866        "revision": "1",
1867        "revision_git": "hash_456",
1868        "master_position": "456",
1869        "master_hash": "master_456",
1870        "patches_merged": "",
1871        "version": "4.2.71",
1872        "chromium_revision": "5678",
1873        "branch": "4.2.71",
1874        "review_link": "",
1875        "date": "02:15",
1876        "chromium_branch": "",
1877        # FIXME(machenbach): Fix revisions link for git.
1878        "revision_link": "https://code.google.com/p/v8/source/detail?r=1",
1879      },
1880      {
1881        "revision": "345",
1882        "revision_git": "hash_345",
1883        "master_position": "",
1884        "master_hash": "",
1885        "patches_merged": "",
1886        "version": "3.22.3",
1887        "chromium_revision": "4567:5677",
1888        "branch": "candidates",
1889        "review_link": "",
1890        "date": "",
1891        "chromium_branch": "7",
1892        "revision_link": "https://code.google.com/p/v8/source/detail?r=345",
1893      },
1894      {
1895        "revision": "123",
1896        "revision_git": "hash_123",
1897        "patches_merged": "",
1898        "master_position": "",
1899        "master_hash": "",
1900        "version": "3.21.2",
1901        "chromium_revision": "",
1902        "branch": "3.21",
1903        "review_link": "",
1904        "date": "03:15",
1905        "chromium_branch": "",
1906        "revision_link": "https://code.google.com/p/v8/source/detail?r=123",
1907      },
1908      {
1909        "revision": "234",
1910        "revision_git": "hash_234",
1911        "patches_merged": "abc12",
1912        "master_position": "",
1913        "master_hash": "",
1914        "version": "3.3.1.1",
1915        "chromium_revision": "",
1916        "branch": "3.3",
1917        "review_link": "fake.com",
1918        "date": "18:15",
1919        "chromium_branch": "",
1920        "revision_link": "https://code.google.com/p/v8/source/detail?r=234",
1921      },],
1922    }
1923    self.assertEquals(expected_json, json.loads(FileToText(json_output)))
1924
1925
1926
1927
1928class SystemTest(unittest.TestCase):
1929  def testReload(self):
1930    options = ScriptsBase(
1931        TEST_CONFIG, DEFAULT_SIDE_EFFECT_HANDLER, {}).MakeOptions([])
1932    step = MakeStep(step_class=PrepareChangeLog, number=0, state={}, config={},
1933                    options=options,
1934                    side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER)
1935    body = step.Reload(
1936"""------------------------------------------------------------------------
1937r17997 | machenbach@chromium.org | 2013-11-22 11:04:04 +0100 (...) | 6 lines
1938
1939Prepare push to trunk.  Now working on version 3.23.11.
1940
1941R=danno@chromium.org
1942
1943Review URL: https://codereview.chromium.org/83173002
1944
1945------------------------------------------------------------------------""")
1946    self.assertEquals(
1947"""Prepare push to trunk.  Now working on version 3.23.11.
1948
1949R=danno@chromium.org
1950
1951Committed: https://code.google.com/p/v8/source/detail?r=17997""", body)
1952