bc79cfd5d7b4cca480253ad4e9a7240ac7bdd772
[platform/framework/web/crosswalk.git] / src / v8 / tools / push-to-trunk / test_scripts.py
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
29 import os
30 import tempfile
31 import traceback
32 import unittest
33
34 import auto_push
35 from auto_push import CheckLastPush
36 from auto_push import SETTINGS_LOCATION
37 import auto_roll
38 import common_includes
39 from common_includes import *
40 import merge_to_branch
41 from merge_to_branch import *
42 import push_to_trunk
43 from push_to_trunk import *
44 import chromium_roll
45 from chromium_roll import CHROMIUM
46 from chromium_roll import DEPS_FILE
47 from chromium_roll import ChromiumRoll
48 import releases
49 from releases import Releases
50
51
52 TEST_CONFIG = {
53   BRANCHNAME: "test-prepare-push",
54   TRUNKBRANCH: "test-trunk-push",
55   PERSISTFILE_BASENAME: "/tmp/test-v8-push-to-trunk-tempfile",
56   DOT_GIT_LOCATION: None,
57   VERSION_FILE: None,
58   CHANGELOG_FILE: None,
59   CHANGELOG_ENTRY_FILE: "/tmp/test-v8-push-to-trunk-tempfile-changelog-entry",
60   PATCH_FILE: "/tmp/test-v8-push-to-trunk-tempfile-patch",
61   COMMITMSG_FILE: "/tmp/test-v8-push-to-trunk-tempfile-commitmsg",
62   CHROMIUM: "/tmp/test-v8-push-to-trunk-tempfile-chromium",
63   DEPS_FILE: "/tmp/test-v8-push-to-trunk-tempfile-chromium/DEPS",
64   SETTINGS_LOCATION: None,
65   ALREADY_MERGING_SENTINEL_FILE:
66       "/tmp/test-merge-to-branch-tempfile-already-merging",
67   COMMIT_HASHES_FILE: "/tmp/test-merge-to-branch-tempfile-PATCH_COMMIT_HASHES",
68   TEMPORARY_PATCH_FILE: "/tmp/test-merge-to-branch-tempfile-temporary-patch",
69 }
70
71
72 AUTO_PUSH_ARGS = [
73   "-a", "author@chromium.org",
74   "-r", "reviewer@chromium.org",
75 ]
76
77
78 class ToplevelTest(unittest.TestCase):
79   def testSortBranches(self):
80     S = releases.SortBranches
81     self.assertEquals(["3.1", "2.25"], S(["2.25", "3.1"])[0:2])
82     self.assertEquals(["3.0", "2.25"], S(["2.25", "3.0", "2.24"])[0:2])
83     self.assertEquals(["3.11", "3.2"], S(["3.11", "3.2", "2.24"])[0:2])
84
85   def testFilterDuplicatesAndReverse(self):
86     F = releases.FilterDuplicatesAndReverse
87     self.assertEquals([], F([]))
88     self.assertEquals([["100", "10"]], F([["100", "10"]]))
89     self.assertEquals([["99", "9"], ["100", "10"]],
90                       F([["100", "10"], ["99", "9"]]))
91     self.assertEquals([["98", "9"], ["100", "10"]],
92                       F([["100", "10"], ["99", "9"], ["98", "9"]]))
93     self.assertEquals([["98", "9"], ["99", "10"]],
94                       F([["100", "10"], ["99", "10"], ["98", "9"]]))
95
96   def testBuildRevisionRanges(self):
97     B = releases.BuildRevisionRanges
98     self.assertEquals({}, B([]))
99     self.assertEquals({"10": "100"}, B([["100", "10"]]))
100     self.assertEquals({"10": "100", "9": "99:99"},
101                       B([["100", "10"], ["99", "9"]]))
102     self.assertEquals({"10": "100", "9": "97:99"},
103                       B([["100", "10"], ["98", "9"], ["97", "9"]]))
104     self.assertEquals({"10": "100", "9": "99:99", "3": "91:98"},
105                       B([["100", "10"], ["99", "9"], ["91", "3"]]))
106     self.assertEquals({"13": "101", "12": "100:100", "9": "94:97",
107                        "3": "91:93, 98:99"},
108                       B([["101", "13"], ["100", "12"], ["98", "3"],
109                          ["94", "9"], ["91", "3"]]))
110
111   def testMakeComment(self):
112     self.assertEquals("#   Line 1\n#   Line 2\n#",
113                       MakeComment("    Line 1\n    Line 2\n"))
114     self.assertEquals("#Line 1\n#Line 2",
115                       MakeComment("Line 1\n Line 2"))
116
117   def testStripComments(self):
118     self.assertEquals("    Line 1\n    Line 3\n",
119         StripComments("    Line 1\n#   Line 2\n    Line 3\n#\n"))
120     self.assertEquals("\nLine 2 ### Test\n #",
121         StripComments("###\n# \n\n#  Line 1\nLine 2 ### Test\n #"))
122
123   def testMakeChangeLogBodySimple(self):
124     commits = [
125           ["Title text 1",
126            "Title text 1\n\nBUG=\n",
127            "author1@chromium.org"],
128           ["Title text 2.",
129            "Title text 2\n\nBUG=1234\n",
130            "author2@chromium.org"],
131         ]
132     self.assertEquals("        Title text 1.\n"
133                       "        (author1@chromium.org)\n\n"
134                       "        Title text 2 (Chromium issue 1234).\n"
135                       "        (author2@chromium.org)\n\n",
136                       MakeChangeLogBody(commits))
137
138   def testMakeChangeLogBodyEmpty(self):
139     self.assertEquals("", MakeChangeLogBody([]))
140
141   def testMakeChangeLogBodyAutoFormat(self):
142     commits = [
143           ["Title text 1!",
144            "Title text 1\nLOG=y\nBUG=\n",
145            "author1@chromium.org"],
146           ["Title text 2",
147            "Title text 2\n\nBUG=1234\n",
148            "author2@chromium.org"],
149           ["Title text 3",
150            "Title text 3\n\nBUG=1234\nLOG = Yes\n",
151            "author3@chromium.org"],
152           ["Title text 3",
153            "Title text 4\n\nBUG=1234\nLOG=\n",
154            "author4@chromium.org"],
155         ]
156     self.assertEquals("        Title text 1.\n\n"
157                       "        Title text 3 (Chromium issue 1234).\n\n",
158                       MakeChangeLogBody(commits, True))
159
160   def testRegressWrongLogEntryOnTrue(self):
161     body = """
162 Check elimination: Learn from if(CompareMap(x)) on true branch.
163
164 BUG=
165 R=verwaest@chromium.org
166
167 Committed: https://code.google.com/p/v8/source/detail?r=18210
168 """
169     self.assertEquals("", MakeChangeLogBody([["title", body, "author"]], True))
170
171   def testMakeChangeLogBugReferenceEmpty(self):
172     self.assertEquals("", MakeChangeLogBugReference(""))
173     self.assertEquals("", MakeChangeLogBugReference("LOG="))
174     self.assertEquals("", MakeChangeLogBugReference(" BUG ="))
175     self.assertEquals("", MakeChangeLogBugReference("BUG=none\t"))
176
177   def testMakeChangeLogBugReferenceSimple(self):
178     self.assertEquals("(issue 987654)",
179                       MakeChangeLogBugReference("BUG = v8:987654"))
180     self.assertEquals("(Chromium issue 987654)",
181                       MakeChangeLogBugReference("BUG=987654 "))
182
183   def testMakeChangeLogBugReferenceFromBody(self):
184     self.assertEquals("(Chromium issue 1234567)",
185                       MakeChangeLogBugReference("Title\n\nTBR=\nBUG=\n"
186                                                 " BUG=\tchromium:1234567\t\n"
187                                                 "R=somebody\n"))
188
189   def testMakeChangeLogBugReferenceMultiple(self):
190     # All issues should be sorted and grouped. Multiple references to the same
191     # issue should be filtered.
192     self.assertEquals("(issues 123, 234, Chromium issue 345)",
193                       MakeChangeLogBugReference("Title\n\n"
194                                                 "BUG=v8:234\n"
195                                                 "  BUG\t= 345, \tv8:234,\n"
196                                                 "BUG=v8:123\n"
197                                                 "R=somebody\n"))
198     self.assertEquals("(Chromium issues 123, 234)",
199                       MakeChangeLogBugReference("Title\n\n"
200                                                 "BUG=234,,chromium:123 \n"
201                                                 "R=somebody\n"))
202     self.assertEquals("(Chromium issues 123, 234)",
203                       MakeChangeLogBugReference("Title\n\n"
204                                                 "BUG=chromium:234, , 123\n"
205                                                 "R=somebody\n"))
206     self.assertEquals("(issues 345, 456)",
207                       MakeChangeLogBugReference("Title\n\n"
208                                                 "\t\tBUG=v8:345,v8:456\n"
209                                                 "R=somebody\n"))
210     self.assertEquals("(issue 123, Chromium issues 345, 456)",
211                       MakeChangeLogBugReference("Title\n\n"
212                                                 "BUG=chromium:456\n"
213                                                 "BUG = none\n"
214                                                 "R=somebody\n"
215                                                 "BUG=456,v8:123, 345"))
216
217   # TODO(machenbach): These test don't make much sense when the formatting is
218   # done later.
219   def testMakeChangeLogBugReferenceLong(self):
220     # -----------------00--------10--------20--------30--------
221     self.assertEquals("(issues 234, 1234567890, 1234567"
222                       "8901234567890, Chromium issues 12345678,"
223                       " 123456789)",
224                       MakeChangeLogBugReference("BUG=v8:234\n"
225                                                 "BUG=v8:1234567890\n"
226                                                 "BUG=v8:12345678901234567890\n"
227                                                 "BUG=123456789\n"
228                                                 "BUG=12345678\n"))
229     # -----------------00--------10--------20--------30--------
230     self.assertEquals("(issues 234, 1234567890, 1234567"
231                       "8901234567890, Chromium issues"
232                       " 123456789, 1234567890)",
233                       MakeChangeLogBugReference("BUG=v8:234\n"
234                                                 "BUG=v8:12345678901234567890\n"
235                                                 "BUG=v8:1234567890\n"
236                                                 "BUG=123456789\n"
237                                                 "BUG=1234567890\n"))
238     # -----------------00--------10--------20--------30--------
239     self.assertEquals("(Chromium issues 234, 1234567890"
240                       ", 12345678901234567, "
241                       "1234567890123456789)",
242                       MakeChangeLogBugReference("BUG=234\n"
243                                                 "BUG=12345678901234567\n"
244                                                 "BUG=1234567890123456789\n"
245                                                 "BUG=1234567890\n"))
246
247
248 def Git(*args, **kwargs):
249   """Convenience function returning a git test expectation."""
250   return {
251     "name": "git",
252     "args": args[:-1],
253     "ret": args[-1],
254     "cb": kwargs.get("cb"),
255   }
256
257
258 def RL(text, cb=None):
259   """Convenience function returning a readline test expectation."""
260   return {"name": "readline", "args": [], "ret": text, "cb": cb}
261
262
263 def URL(*args, **kwargs):
264   """Convenience function returning a readurl test expectation."""
265   return {
266     "name": "readurl",
267     "args": args[:-1],
268     "ret": args[-1],
269     "cb": kwargs.get("cb"),
270   }
271
272
273 class SimpleMock(object):
274   def __init__(self, name):
275     self._name = name
276     self._recipe = []
277     self._index = -1
278
279   def Expect(self, recipe):
280     self._recipe = recipe
281
282   def Call(self, name, *args):  # pragma: no cover
283     self._index += 1
284     try:
285       expected_call = self._recipe[self._index]
286     except IndexError:
287       raise NoRetryException("Calling %s %s" % (name, " ".join(args)))
288
289     if not isinstance(expected_call, dict):
290       raise NoRetryException("Found wrong expectation type for %s %s"
291                              % (name, " ".join(args)))
292
293
294     # The number of arguments in the expectation must match the actual
295     # arguments.
296     if len(args) > len(expected_call['args']):
297       raise NoRetryException("When calling %s with arguments, the "
298           "expectations must consist of at least as many arguments." % name)
299
300     # Compare expected and actual arguments.
301     for (expected_arg, actual_arg) in zip(expected_call['args'], args):
302       if expected_arg != actual_arg:
303         raise NoRetryException("Expected: %s - Actual: %s"
304                                % (expected_arg, actual_arg))
305
306     # The expected call contains an optional callback for checking the context
307     # at the time of the call.
308     if expected_call['cb']:
309       try:
310         expected_call['cb']()
311       except:
312         tb = traceback.format_exc()
313         raise NoRetryException("Caught exception from callback: %s" % tb)
314
315     # If the return value is an exception, raise it instead of returning.
316     if isinstance(expected_call['ret'], Exception):
317       raise expected_call['ret']
318     return expected_call['ret']
319
320   def AssertFinished(self):  # pragma: no cover
321     if self._index < len(self._recipe) -1:
322       raise NoRetryException("Called %s too seldom: %d vs. %d"
323                              % (self._name, self._index, len(self._recipe)))
324
325
326 class ScriptTest(unittest.TestCase):
327   def MakeEmptyTempFile(self):
328     handle, name = tempfile.mkstemp()
329     os.close(handle)
330     self._tmp_files.append(name)
331     return name
332
333   def WriteFakeVersionFile(self, minor=22, build=4, patch=0):
334     with open(TEST_CONFIG[VERSION_FILE], "w") as f:
335       f.write("  // Some line...\n")
336       f.write("\n")
337       f.write("#define MAJOR_VERSION    3\n")
338       f.write("#define MINOR_VERSION    %s\n" % minor)
339       f.write("#define BUILD_NUMBER     %s\n" % build)
340       f.write("#define PATCH_LEVEL      %s\n" % patch)
341       f.write("  // Some line...\n")
342       f.write("#define IS_CANDIDATE_VERSION 0\n")
343
344   def MakeStep(self):
345     """Convenience wrapper."""
346     options = ScriptsBase(TEST_CONFIG, self, self._state).MakeOptions([])
347     return MakeStep(step_class=Step, state=self._state,
348                     config=TEST_CONFIG, side_effect_handler=self,
349                     options=options)
350
351   def RunStep(self, script=PushToTrunk, step_class=Step, args=None):
352     """Convenience wrapper."""
353     args = args or ["-m"]
354     return script(TEST_CONFIG, self, self._state).RunSteps([step_class], args)
355
356   def GitMock(self, cmd, args="", pipe=True):
357     print "%s %s" % (cmd, args)
358     return self._git_mock.Call("git", args)
359
360   def LogMock(self, cmd, args=""):
361     print "Log: %s %s" % (cmd, args)
362
363   MOCKS = {
364     "git": GitMock,
365     # TODO(machenbach): Little hack to reuse the git mock for the one svn call
366     # in merge-to-branch. The command should be made explicit in the test
367     # expectations.
368     "svn": GitMock,
369     "vi": LogMock,
370   }
371
372   def Call(self, fun, *args, **kwargs):
373     print "Calling %s with %s and %s" % (str(fun), str(args), str(kwargs))
374
375   def Command(self, cmd, args="", prefix="", pipe=True):
376     return ScriptTest.MOCKS[cmd](self, cmd, args)
377
378   def ReadLine(self):
379     return self._rl_mock.Call("readline")
380
381   def ReadURL(self, url, params):
382     if params is not None:
383       return self._url_mock.Call("readurl", url, params)
384     else:
385       return self._url_mock.Call("readurl", url)
386
387   def Sleep(self, seconds):
388     pass
389
390   def GetDate(self):
391     return "1999-07-31"
392
393   def ExpectGit(self, *args):
394     """Convenience wrapper."""
395     self._git_mock.Expect(*args)
396
397   def ExpectReadline(self, *args):
398     """Convenience wrapper."""
399     self._rl_mock.Expect(*args)
400
401   def ExpectReadURL(self, *args):
402     """Convenience wrapper."""
403     self._url_mock.Expect(*args)
404
405   def setUp(self):
406     self._git_mock = SimpleMock("git")
407     self._rl_mock = SimpleMock("readline")
408     self._url_mock = SimpleMock("readurl")
409     self._tmp_files = []
410     self._state = {}
411
412   def tearDown(self):
413     Command("rm", "-rf %s*" % TEST_CONFIG[PERSISTFILE_BASENAME])
414
415     # Clean up temps. Doesn't work automatically.
416     for name in self._tmp_files:
417       if os.path.exists(name):
418         os.remove(name)
419
420     self._git_mock.AssertFinished()
421     self._rl_mock.AssertFinished()
422     self._url_mock.AssertFinished()
423
424   def testGitOrig(self):
425     self.assertTrue(Command("git", "--version").startswith("git version"))
426
427   def testGitMock(self):
428     self.ExpectGit([Git("--version", "git version 1.2.3"), Git("dummy", "")])
429     self.assertEquals("git version 1.2.3", self.MakeStep().Git("--version"))
430     self.assertEquals("", self.MakeStep().Git("dummy"))
431
432   def testCommonPrepareDefault(self):
433     self.ExpectGit([
434       Git("status -s -uno", ""),
435       Git("status -s -b -uno", "## some_branch"),
436       Git("svn fetch", ""),
437       Git("branch", "  branch1\n* %s" % TEST_CONFIG[BRANCHNAME]),
438       Git("branch -D %s" % TEST_CONFIG[BRANCHNAME], ""),
439     ])
440     self.ExpectReadline([RL("Y")])
441     self.MakeStep().CommonPrepare()
442     self.MakeStep().PrepareBranch()
443     self.assertEquals("some_branch", self._state["current_branch"])
444
445   def testCommonPrepareNoConfirm(self):
446     self.ExpectGit([
447       Git("status -s -uno", ""),
448       Git("status -s -b -uno", "## some_branch"),
449       Git("svn fetch", ""),
450       Git("branch", "  branch1\n* %s" % TEST_CONFIG[BRANCHNAME]),
451     ])
452     self.ExpectReadline([RL("n")])
453     self.MakeStep().CommonPrepare()
454     self.assertRaises(Exception, self.MakeStep().PrepareBranch)
455     self.assertEquals("some_branch", self._state["current_branch"])
456
457   def testCommonPrepareDeleteBranchFailure(self):
458     self.ExpectGit([
459       Git("status -s -uno", ""),
460       Git("status -s -b -uno", "## some_branch"),
461       Git("svn fetch", ""),
462       Git("branch", "  branch1\n* %s" % TEST_CONFIG[BRANCHNAME]),
463       Git("branch -D %s" % TEST_CONFIG[BRANCHNAME], None),
464     ])
465     self.ExpectReadline([RL("Y")])
466     self.MakeStep().CommonPrepare()
467     self.assertRaises(Exception, self.MakeStep().PrepareBranch)
468     self.assertEquals("some_branch", self._state["current_branch"])
469
470   def testInitialEnvironmentChecks(self):
471     TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
472     os.environ["EDITOR"] = "vi"
473     self.MakeStep().InitialEnvironmentChecks()
474
475   def testReadAndPersistVersion(self):
476     TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
477     self.WriteFakeVersionFile(build=5)
478     step = self.MakeStep()
479     step.ReadAndPersistVersion()
480     self.assertEquals("3", step["major"])
481     self.assertEquals("22", step["minor"])
482     self.assertEquals("5", step["build"])
483     self.assertEquals("0", step["patch"])
484
485   def testRegex(self):
486     self.assertEqual("(issue 321)",
487                      re.sub(r"BUG=v8:(.*)$", r"(issue \1)", "BUG=v8:321"))
488     self.assertEqual("(Chromium issue 321)",
489                      re.sub(r"BUG=(.*)$", r"(Chromium issue \1)", "BUG=321"))
490
491     cl = "  too little\n\ttab\ttab\n         too much\n        trailing  "
492     cl = MSub(r"\t", r"        ", cl)
493     cl = MSub(r"^ {1,7}([^ ])", r"        \1", cl)
494     cl = MSub(r"^ {9,80}([^ ])", r"        \1", cl)
495     cl = MSub(r" +$", r"", cl)
496     self.assertEqual("        too little\n"
497                      "        tab        tab\n"
498                      "        too much\n"
499                      "        trailing", cl)
500
501     self.assertEqual("//\n#define BUILD_NUMBER  3\n",
502                      MSub(r"(?<=#define BUILD_NUMBER)(?P<space>\s+)\d*$",
503                           r"\g<space>3",
504                           "//\n#define BUILD_NUMBER  321\n"))
505
506   def testPreparePushRevision(self):
507     # Tests the default push hash used when the --revision option is not set.
508     self.ExpectGit([
509       Git("log -1 --format=%H HEAD", "push_hash")
510     ])
511
512     self.RunStep(PushToTrunk, PreparePushRevision)
513     self.assertEquals("push_hash", self._state["push_hash"])
514
515   def testPrepareChangeLog(self):
516     TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
517     self.WriteFakeVersionFile()
518     TEST_CONFIG[CHANGELOG_ENTRY_FILE] = self.MakeEmptyTempFile()
519
520     self.ExpectGit([
521       Git("log --format=%H 1234..push_hash", "rev1\nrev2\nrev3\nrev4"),
522       Git("log -1 --format=%s rev1", "Title text 1"),
523       Git("log -1 --format=%B rev1", "Title\n\nBUG=\nLOG=y\n"),
524       Git("log -1 --format=%an rev1", "author1@chromium.org"),
525       Git("log -1 --format=%s rev2", "Title text 2."),
526       Git("log -1 --format=%B rev2", "Title\n\nBUG=123\nLOG= \n"),
527       Git("log -1 --format=%an rev2", "author2@chromium.org"),
528       Git("log -1 --format=%s rev3", "Title text 3"),
529       Git("log -1 --format=%B rev3", "Title\n\nBUG=321\nLOG=true\n"),
530       Git("log -1 --format=%an rev3", "author3@chromium.org"),
531       Git("log -1 --format=%s rev4", "Title text 4"),
532       Git("log -1 --format=%B rev4",
533        ("Title\n\nBUG=456\nLOG=Y\n\n"
534         "Review URL: https://codereview.chromium.org/9876543210\n")),
535       Git("log -1 --format=%an rev4", "author4@chromium.org"),
536     ])
537
538     # The cl for rev4 on rietveld has an updated LOG flag.
539     self.ExpectReadURL([
540       URL("https://codereview.chromium.org/9876543210/description",
541           "Title\n\nBUG=456\nLOG=N\n\n"),
542     ])
543
544     self._state["last_push_bleeding_edge"] = "1234"
545     self._state["push_hash"] = "push_hash"
546     self._state["version"] = "3.22.5"
547     self.RunStep(PushToTrunk, PrepareChangeLog)
548
549     actual_cl = FileToText(TEST_CONFIG[CHANGELOG_ENTRY_FILE])
550
551     expected_cl = """1999-07-31: Version 3.22.5
552
553         Title text 1.
554
555         Title text 3 (Chromium issue 321).
556
557         Performance and stability improvements on all platforms.
558 #
559 # The change log above is auto-generated. Please review if all relevant
560 # commit messages from the list below are included.
561 # All lines starting with # will be stripped.
562 #
563 #       Title text 1.
564 #       (author1@chromium.org)
565 #
566 #       Title text 2 (Chromium issue 123).
567 #       (author2@chromium.org)
568 #
569 #       Title text 3 (Chromium issue 321).
570 #       (author3@chromium.org)
571 #
572 #       Title text 4 (Chromium issue 456).
573 #       (author4@chromium.org)
574 #
575 #"""
576
577     self.assertEquals(expected_cl, actual_cl)
578
579   def testEditChangeLog(self):
580     TEST_CONFIG[CHANGELOG_ENTRY_FILE] = self.MakeEmptyTempFile()
581     TextToFile("  New  \n\tLines  \n", TEST_CONFIG[CHANGELOG_ENTRY_FILE])
582     os.environ["EDITOR"] = "vi"
583
584     self.ExpectReadline([
585       RL(""),  # Open editor.
586     ])
587
588     self.RunStep(PushToTrunk, EditChangeLog)
589
590     self.assertEquals("New\n        Lines",
591                       FileToText(TEST_CONFIG[CHANGELOG_ENTRY_FILE]))
592
593   def testIncrementVersion(self):
594     TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
595     self.WriteFakeVersionFile()
596     self._state["last_push_trunk"] = "hash1"
597
598     self.ExpectGit([
599       Git("checkout -f hash1 -- %s" % TEST_CONFIG[VERSION_FILE], "")
600     ])
601
602     self.ExpectReadline([
603       RL("Y"),  # Increment build number.
604     ])
605
606     self.RunStep(PushToTrunk, IncrementVersion)
607
608     self.assertEquals("3", self._state["new_major"])
609     self.assertEquals("22", self._state["new_minor"])
610     self.assertEquals("5", self._state["new_build"])
611     self.assertEquals("0", self._state["new_patch"])
612
613   def _TestSquashCommits(self, change_log, expected_msg):
614     TEST_CONFIG[CHANGELOG_ENTRY_FILE] = self.MakeEmptyTempFile()
615     with open(TEST_CONFIG[CHANGELOG_ENTRY_FILE], "w") as f:
616       f.write(change_log)
617
618     self.ExpectGit([
619       Git("diff svn/trunk hash1", "patch content"),
620       Git("svn find-rev hash1", "123455\n"),
621     ])
622
623     self._state["push_hash"] = "hash1"
624     self._state["date"] = "1999-11-11"
625
626     self.RunStep(PushToTrunk, SquashCommits)
627     self.assertEquals(FileToText(TEST_CONFIG[COMMITMSG_FILE]), expected_msg)
628
629     patch = FileToText(TEST_CONFIG[ PATCH_FILE])
630     self.assertTrue(re.search(r"patch content", patch))
631
632   def testSquashCommitsUnformatted(self):
633     change_log = """1999-11-11: Version 3.22.5
634
635         Log text 1.
636         Chromium issue 12345
637
638         Performance and stability improvements on all platforms.\n"""
639     commit_msg = """Version 3.22.5 (based on bleeding_edge revision r123455)
640
641 Log text 1. Chromium issue 12345
642
643 Performance and stability improvements on all platforms."""
644     self._TestSquashCommits(change_log, commit_msg)
645
646   def testSquashCommitsFormatted(self):
647     change_log = """1999-11-11: Version 3.22.5
648
649         Long commit message that fills more than 80 characters (Chromium issue
650         12345).
651
652         Performance and stability improvements on all platforms.\n"""
653     commit_msg = """Version 3.22.5 (based on bleeding_edge revision r123455)
654
655 Long commit message that fills more than 80 characters (Chromium issue 12345).
656
657 Performance and stability improvements on all platforms."""
658     self._TestSquashCommits(change_log, commit_msg)
659
660   def testSquashCommitsQuotationMarks(self):
661     change_log = """Line with "quotation marks".\n"""
662     commit_msg = """Line with "quotation marks"."""
663     self._TestSquashCommits(change_log, commit_msg)
664
665   def _PushToTrunk(self, force=False, manual=False):
666     TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
667
668     # The version file on bleeding edge has build level 5, while the version
669     # file from trunk has build level 4.
670     TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
671     self.WriteFakeVersionFile(build=5)
672
673     TEST_CONFIG[CHANGELOG_ENTRY_FILE] = self.MakeEmptyTempFile()
674     TEST_CONFIG[CHANGELOG_FILE] = self.MakeEmptyTempFile()
675     bleeding_edge_change_log = "2014-03-17: Sentinel\n"
676     TextToFile(bleeding_edge_change_log, TEST_CONFIG[CHANGELOG_FILE])
677     os.environ["EDITOR"] = "vi"
678
679     def ResetChangeLog():
680       """On 'git co -b new_branch svn/trunk', and 'git checkout -- ChangeLog',
681       the ChangLog will be reset to its content on trunk."""
682       trunk_change_log = """1999-04-05: Version 3.22.4
683
684         Performance and stability improvements on all platforms.\n"""
685       TextToFile(trunk_change_log, TEST_CONFIG[CHANGELOG_FILE])
686
687     def ResetToTrunk():
688       ResetChangeLog()
689       self.WriteFakeVersionFile()
690
691     def CheckSVNCommit():
692       commit = FileToText(TEST_CONFIG[COMMITMSG_FILE])
693       self.assertEquals(
694 """Version 3.22.5 (based on bleeding_edge revision r123455)
695
696 Log text 1 (issue 321).
697
698 Performance and stability improvements on all platforms.""", commit)
699       version = FileToText(TEST_CONFIG[VERSION_FILE])
700       self.assertTrue(re.search(r"#define MINOR_VERSION\s+22", version))
701       self.assertTrue(re.search(r"#define BUILD_NUMBER\s+5", version))
702       self.assertFalse(re.search(r"#define BUILD_NUMBER\s+6", version))
703       self.assertTrue(re.search(r"#define PATCH_LEVEL\s+0", version))
704       self.assertTrue(re.search(r"#define IS_CANDIDATE_VERSION\s+0", version))
705
706       # Check that the change log on the trunk branch got correctly modified.
707       change_log = FileToText(TEST_CONFIG[CHANGELOG_FILE])
708       self.assertEquals(
709 """1999-07-31: Version 3.22.5
710
711         Log text 1 (issue 321).
712
713         Performance and stability improvements on all platforms.
714
715
716 1999-04-05: Version 3.22.4
717
718         Performance and stability improvements on all platforms.\n""",
719           change_log)
720
721     force_flag = " -f" if not manual else ""
722     self.ExpectGit([
723       Git("status -s -uno", ""),
724       Git("status -s -b -uno", "## some_branch\n"),
725       Git("svn fetch", ""),
726       Git("branch", "  branch1\n* branch2\n"),
727       Git("branch", "  branch1\n* branch2\n"),
728       Git("checkout -b %s svn/bleeding_edge" % TEST_CONFIG[BRANCHNAME], ""),
729       Git("svn find-rev r123455", "push_hash\n"),
730       Git(("log -1 --format=%H --grep="
731            "\"^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]* (based\" "
732            "svn/trunk"), "hash2\n"),
733       Git("log -1 hash2", "Log message\n"),
734       Git("log -1 --format=%s hash2",
735        "Version 3.4.5 (based on bleeding_edge revision r1234)\n"),
736       Git("svn find-rev r1234", "hash3\n"),
737       Git("checkout -f hash2 -- %s" % TEST_CONFIG[VERSION_FILE], "",
738           cb=self.WriteFakeVersionFile),
739       Git("log --format=%H hash3..push_hash", "rev1\n"),
740       Git("log -1 --format=%s rev1", "Log text 1.\n"),
741       Git("log -1 --format=%B rev1", "Text\nLOG=YES\nBUG=v8:321\nText\n"),
742       Git("log -1 --format=%an rev1", "author1@chromium.org\n"),
743       Git("svn fetch", "fetch result\n"),
744       Git("checkout -f svn/bleeding_edge", ""),
745       Git("diff svn/trunk push_hash", "patch content\n"),
746       Git("svn find-rev push_hash", "123455\n"),
747       Git("checkout -b %s svn/trunk" % TEST_CONFIG[TRUNKBRANCH], "",
748           cb=ResetToTrunk),
749       Git("apply --index --reject \"%s\"" % TEST_CONFIG[PATCH_FILE], ""),
750       Git("checkout -f svn/trunk -- %s" % TEST_CONFIG[CHANGELOG_FILE], "",
751           cb=ResetChangeLog),
752       Git("checkout -f svn/trunk -- %s" % TEST_CONFIG[VERSION_FILE], "",
753           cb=self.WriteFakeVersionFile),
754       Git("commit -aF \"%s\"" % TEST_CONFIG[COMMITMSG_FILE], "",
755           cb=CheckSVNCommit),
756       Git("svn dcommit 2>&1", "Some output\nCommitted r123456\nSome output\n"),
757       Git("svn tag 3.22.5 -m \"Tagging version 3.22.5\"", ""),
758       Git("checkout -f some_branch", ""),
759       Git("branch -D %s" % TEST_CONFIG[BRANCHNAME], ""),
760       Git("branch -D %s" % TEST_CONFIG[TRUNKBRANCH], ""),
761     ])
762
763     # Expected keyboard input in manual mode:
764     if manual:
765       self.ExpectReadline([
766         RL("Y"),  # Confirm last push.
767         RL(""),  # Open editor.
768         RL("Y"),  # Increment build number.
769         RL("Y"),  # Sanity check.
770       ])
771
772     # Expected keyboard input in semi-automatic mode and forced mode:
773     if not manual:
774       self.ExpectReadline([])
775
776     args = ["-a", "author@chromium.org", "--revision", "123455"]
777     if force: args.append("-f")
778     if manual: args.append("-m")
779     else: args += ["-r", "reviewer@chromium.org"]
780     PushToTrunk(TEST_CONFIG, self).Run(args)
781
782     cl = FileToText(TEST_CONFIG[CHANGELOG_FILE])
783     self.assertTrue(re.search(r"^\d\d\d\d\-\d+\-\d+: Version 3\.22\.5", cl))
784     self.assertTrue(re.search(r"        Log text 1 \(issue 321\).", cl))
785     self.assertTrue(re.search(r"1999\-04\-05: Version 3\.22\.4", cl))
786
787     # Note: The version file is on build number 5 again in the end of this test
788     # since the git command that merges to the bleeding edge branch is mocked
789     # out.
790
791   def testPushToTrunkManual(self):
792     self._PushToTrunk(manual=True)
793
794   def testPushToTrunkSemiAutomatic(self):
795     self._PushToTrunk()
796
797   def testPushToTrunkForced(self):
798     self._PushToTrunk(force=True)
799
800   def _ChromiumRoll(self, force=False, manual=False):
801     googlers_mapping_py = "%s-mapping.py" % TEST_CONFIG[PERSISTFILE_BASENAME]
802     with open(googlers_mapping_py, "w") as f:
803       f.write("""
804 def list_to_dict(entries):
805   return {"g_name@google.com": "c_name@chromium.org"}
806 def get_list():
807   pass""")
808
809     TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
810     if not os.path.exists(TEST_CONFIG[CHROMIUM]):
811       os.makedirs(TEST_CONFIG[CHROMIUM])
812     TextToFile("Some line\n   \"v8_revision\": \"123444\",\n  some line",
813                TEST_CONFIG[DEPS_FILE])
814
815     os.environ["EDITOR"] = "vi"
816     force_flag = " -f" if not manual else ""
817     self.ExpectGit([
818       Git("status -s -uno", ""),
819       Git("status -s -b -uno", "## some_branch\n"),
820       Git("svn fetch", ""),
821       Git(("log -1 --format=%H --grep="
822            "\"^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]*\" "
823            "svn/trunk"), "push_hash\n"),
824       Git("svn find-rev push_hash", "123455\n"),
825       Git("log -1 --format=%s push_hash",
826           "Version 3.22.5 (based on bleeding_edge revision r123454)\n"),
827       Git("status -s -uno", ""),
828       Git("checkout -f master", ""),
829       Git("pull", ""),
830       Git("checkout -b v8-roll-123455", ""),
831       Git(("commit -am \"Update V8 to version 3.22.5 "
832            "(based on bleeding_edge revision r123454).\n\n"
833            "Please reply to the V8 sheriff c_name@chromium.org in "
834            "case of problems.\n\nTBR=c_name@chromium.org\""),
835           ""),
836       Git(("cl upload --send-mail --email \"author@chromium.org\"%s"
837            % force_flag), ""),
838     ])
839
840     self.ExpectReadURL([
841       URL("https://chromium-build.appspot.com/p/chromium/sheriff_v8.js",
842           "document.write('g_name')"),
843     ])
844
845     # Expected keyboard input in manual mode:
846     if manual:
847       self.ExpectReadline([
848         RL("c_name@chromium.org"),  # Chromium reviewer.
849       ])
850
851     # Expected keyboard input in semi-automatic mode and forced mode:
852     if not manual:
853       self.ExpectReadline([])
854
855     args = ["-a", "author@chromium.org", "-c", TEST_CONFIG[CHROMIUM],
856             "--sheriff", "--googlers-mapping", googlers_mapping_py]
857     if force: args.append("-f")
858     if manual: args.append("-m")
859     else: args += ["-r", "reviewer@chromium.org"]
860     ChromiumRoll(TEST_CONFIG, self).Run(args)
861
862     deps = FileToText(TEST_CONFIG[DEPS_FILE])
863     self.assertTrue(re.search("\"v8_revision\": \"123455\"", deps))
864
865   def testChromiumRollManual(self):
866     self._ChromiumRoll(manual=True)
867
868   def testChromiumRollSemiAutomatic(self):
869     self._ChromiumRoll()
870
871   def testChromiumRollForced(self):
872     self._ChromiumRoll(force=True)
873
874   def testCheckLastPushRecently(self):
875     self.ExpectGit([
876       Git(("log -1 --format=%H --grep="
877            "\"^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]* (based\" "
878            "svn/trunk"), "hash2\n"),
879       Git("log -1 --format=%s hash2",
880           "Version 3.4.5 (based on bleeding_edge revision r99)\n"),
881     ])
882
883     self._state["lkgr"] = "101"
884
885     self.assertRaises(Exception, lambda: self.RunStep(auto_push.AutoPush,
886                                                       CheckLastPush,
887                                                       AUTO_PUSH_ARGS))
888
889   def testAutoPush(self):
890     TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
891     TEST_CONFIG[SETTINGS_LOCATION] = "~/.doesnotexist"
892
893     self.ExpectReadURL([
894       URL("https://v8-status.appspot.com/current?format=json",
895           "{\"message\": \"Tree is throttled\"}"),
896       URL("https://v8-status.appspot.com/lkgr", Exception("Network problem")),
897       URL("https://v8-status.appspot.com/lkgr", "100"),
898     ])
899
900     self.ExpectGit([
901       Git("status -s -uno", ""),
902       Git("status -s -b -uno", "## some_branch\n"),
903       Git("svn fetch", ""),
904       Git(("log -1 --format=%H --grep=\""
905            "^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]* (based\""
906            " svn/trunk"), "push_hash\n"),
907       Git("log -1 --format=%s push_hash",
908           "Version 3.4.5 (based on bleeding_edge revision r79)\n"),
909     ])
910
911     auto_push.AutoPush(TEST_CONFIG, self).Run(AUTO_PUSH_ARGS + ["--push"])
912
913     state = json.loads(FileToText("%s-state.json"
914                                   % TEST_CONFIG[PERSISTFILE_BASENAME]))
915
916     self.assertEquals("100", state["lkgr"])
917
918   def testAutoPushStoppedBySettings(self):
919     TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
920     TEST_CONFIG[SETTINGS_LOCATION] = self.MakeEmptyTempFile()
921     TextToFile("{\"enable_auto_push\": false}", TEST_CONFIG[SETTINGS_LOCATION])
922
923     self.ExpectReadURL([])
924
925     self.ExpectGit([
926       Git("status -s -uno", ""),
927       Git("status -s -b -uno", "## some_branch\n"),
928       Git("svn fetch", ""),
929     ])
930
931     def RunAutoPush():
932       auto_push.AutoPush(TEST_CONFIG, self).Run(AUTO_PUSH_ARGS)
933     self.assertRaises(Exception, RunAutoPush)
934
935   def testAutoPushStoppedByTreeStatus(self):
936     TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
937     TEST_CONFIG[SETTINGS_LOCATION] = "~/.doesnotexist"
938
939     self.ExpectReadURL([
940       URL("https://v8-status.appspot.com/current?format=json",
941           "{\"message\": \"Tree is throttled (no push)\"}"),
942     ])
943
944     self.ExpectGit([
945       Git("status -s -uno", ""),
946       Git("status -s -b -uno", "## some_branch\n"),
947       Git("svn fetch", ""),
948     ])
949
950     def RunAutoPush():
951       auto_push.AutoPush(TEST_CONFIG, self).Run(AUTO_PUSH_ARGS)
952     self.assertRaises(Exception, RunAutoPush)
953
954   def testAutoRollExistingRoll(self):
955     self.ExpectReadURL([
956       URL("https://codereview.chromium.org/search",
957           "owner=author%40chromium.org&limit=30&closed=3&format=json",
958           ("{\"results\": [{\"subject\": \"different\"},"
959            "{\"subject\": \"Update V8 to Version...\"}]}")),
960     ])
961
962     result = auto_roll.AutoRoll(TEST_CONFIG, self).Run(
963         AUTO_PUSH_ARGS + ["-c", TEST_CONFIG[CHROMIUM]])
964     self.assertEquals(1, result)
965
966   # Snippet from the original DEPS file.
967   FAKE_DEPS = """
968 vars = {
969   "v8_revision": "123455",
970 }
971 deps = {
972   "src/v8":
973     (Var("googlecode_url") % "v8") + "/" + Var("v8_branch") + "@" +
974     Var("v8_revision"),
975 }
976 """
977
978   def testAutoRollUpToDate(self):
979     self.ExpectReadURL([
980       URL("https://codereview.chromium.org/search",
981           "owner=author%40chromium.org&limit=30&closed=3&format=json",
982           ("{\"results\": [{\"subject\": \"different\"}]}")),
983       URL("http://src.chromium.org/svn/trunk/src/DEPS",
984           self.FAKE_DEPS),
985     ])
986
987     self.ExpectGit([
988       Git(("log -1 --format=%H --grep="
989            "\"^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]*\" "
990            "svn/trunk"), "push_hash\n"),
991       Git("svn find-rev push_hash", "123455\n"),
992     ])
993
994     result = auto_roll.AutoRoll(TEST_CONFIG, self).Run(
995         AUTO_PUSH_ARGS + ["-c", TEST_CONFIG[CHROMIUM]])
996     self.assertEquals(1, result)
997
998   def testAutoRoll(self):
999     self.ExpectReadURL([
1000       URL("https://codereview.chromium.org/search",
1001           "owner=author%40chromium.org&limit=30&closed=3&format=json",
1002           ("{\"results\": [{\"subject\": \"different\"}]}")),
1003       URL("http://src.chromium.org/svn/trunk/src/DEPS",
1004           self.FAKE_DEPS),
1005     ])
1006
1007     self.ExpectGit([
1008       Git(("log -1 --format=%H --grep="
1009            "\"^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]*\" "
1010            "svn/trunk"), "push_hash\n"),
1011       Git("svn find-rev push_hash", "123456\n"),
1012     ])
1013
1014     result = auto_roll.AutoRoll(TEST_CONFIG, self).Run(
1015         AUTO_PUSH_ARGS + ["-c", TEST_CONFIG[CHROMIUM], "--roll"])
1016     self.assertEquals(0, result)
1017
1018   def testMergeToBranch(self):
1019     TEST_CONFIG[ALREADY_MERGING_SENTINEL_FILE] = self.MakeEmptyTempFile()
1020     TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
1021     TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
1022     self.WriteFakeVersionFile(build=5)
1023     os.environ["EDITOR"] = "vi"
1024     extra_patch = self.MakeEmptyTempFile()
1025
1026     def VerifyPatch(patch):
1027       return lambda: self.assertEquals(patch,
1028           FileToText(TEST_CONFIG[TEMPORARY_PATCH_FILE]))
1029
1030     msg = """Version 3.22.5.1 (merged r12345, r23456, r34567, r45678, r56789)
1031
1032 Title4
1033
1034 Title2
1035
1036 Title3
1037
1038 Title1
1039
1040 Revert "Something"
1041
1042 BUG=123,234,345,456,567,v8:123
1043 LOG=N
1044 """
1045
1046     def VerifySVNCommit():
1047       commit = FileToText(TEST_CONFIG[COMMITMSG_FILE])
1048       self.assertEquals(msg, commit)
1049       version = FileToText(TEST_CONFIG[VERSION_FILE])
1050       self.assertTrue(re.search(r"#define MINOR_VERSION\s+22", version))
1051       self.assertTrue(re.search(r"#define BUILD_NUMBER\s+5", version))
1052       self.assertTrue(re.search(r"#define PATCH_LEVEL\s+1", version))
1053       self.assertTrue(re.search(r"#define IS_CANDIDATE_VERSION\s+0", version))
1054
1055     self.ExpectGit([
1056       Git("status -s -uno", ""),
1057       Git("status -s -b -uno", "## some_branch\n"),
1058       Git("svn fetch", ""),
1059       Git("branch", "  branch1\n* branch2\n"),
1060       Git("checkout -b %s svn/trunk" % TEST_CONFIG[BRANCHNAME], ""),
1061       Git("log --format=%H --grep=\"Port r12345\" --reverse svn/bleeding_edge",
1062           "hash1\nhash2"),
1063       Git("svn find-rev hash1 svn/bleeding_edge", "45678"),
1064       Git("log -1 --format=%s hash1", "Title1"),
1065       Git("svn find-rev hash2 svn/bleeding_edge", "23456"),
1066       Git("log -1 --format=%s hash2", "Title2"),
1067       Git("log --format=%H --grep=\"Port r23456\" --reverse svn/bleeding_edge",
1068           ""),
1069       Git("log --format=%H --grep=\"Port r34567\" --reverse svn/bleeding_edge",
1070           "hash3"),
1071       Git("svn find-rev hash3 svn/bleeding_edge", "56789"),
1072       Git("log -1 --format=%s hash3", "Title3"),
1073       Git("svn find-rev r12345 svn/bleeding_edge", "hash4"),
1074       # Simulate svn being down which stops the script.
1075       Git("svn find-rev r23456 svn/bleeding_edge", None),
1076       # Restart script in the failing step.
1077       Git("svn find-rev r12345 svn/bleeding_edge", "hash4"),
1078       Git("svn find-rev r23456 svn/bleeding_edge", "hash2"),
1079       Git("svn find-rev r34567 svn/bleeding_edge", "hash3"),
1080       Git("svn find-rev r45678 svn/bleeding_edge", "hash1"),
1081       Git("svn find-rev r56789 svn/bleeding_edge", "hash5"),
1082       Git("log -1 --format=%s hash4", "Title4"),
1083       Git("log -1 --format=%s hash2", "Title2"),
1084       Git("log -1 --format=%s hash3", "Title3"),
1085       Git("log -1 --format=%s hash1", "Title1"),
1086       Git("log -1 --format=%s hash5", "Revert \"Something\""),
1087       Git("log -1 hash4", "Title4\nBUG=123\nBUG=234"),
1088       Git("log -1 hash2", "Title2\n BUG = v8:123,345"),
1089       Git("log -1 hash3", "Title3\nLOG=n\nBUG=567, 456"),
1090       Git("log -1 hash1", "Title1\nBUG="),
1091       Git("log -1 hash5", "Revert \"Something\"\nBUG=none"),
1092       Git("log -1 -p hash4", "patch4"),
1093       Git("apply --index --reject \"%s\"" % TEST_CONFIG[TEMPORARY_PATCH_FILE],
1094           "", cb=VerifyPatch("patch4")),
1095       Git("log -1 -p hash2", "patch2"),
1096       Git("apply --index --reject \"%s\"" % TEST_CONFIG[TEMPORARY_PATCH_FILE],
1097           "", cb=VerifyPatch("patch2")),
1098       Git("log -1 -p hash3", "patch3"),
1099       Git("apply --index --reject \"%s\"" % TEST_CONFIG[TEMPORARY_PATCH_FILE],
1100           "", cb=VerifyPatch("patch3")),
1101       Git("log -1 -p hash1", "patch1"),
1102       Git("apply --index --reject \"%s\"" % TEST_CONFIG[TEMPORARY_PATCH_FILE],
1103           "", cb=VerifyPatch("patch1")),
1104       Git("log -1 -p hash5", "patch5\n"),
1105       Git("apply --index --reject \"%s\"" % TEST_CONFIG[TEMPORARY_PATCH_FILE],
1106           "", cb=VerifyPatch("patch5\n")),
1107       Git("apply --index --reject \"%s\"" % extra_patch, ""),
1108       Git("commit -aF \"%s\"" % TEST_CONFIG[COMMITMSG_FILE], ""),
1109       Git("cl upload --send-mail -r \"reviewer@chromium.org\"", ""),
1110       Git("checkout -f %s" % TEST_CONFIG[BRANCHNAME], ""),
1111       Git("cl presubmit", "Presubmit successfull\n"),
1112       Git("cl dcommit -f --bypass-hooks", "Closing issue\n", cb=VerifySVNCommit),
1113       Git("svn fetch", ""),
1114       Git(("log -1 --format=%%H --grep=\"%s\" svn/trunk"
1115            % msg.replace("\"", "\\\"")), "hash6"),
1116       Git("svn find-rev hash6", "1324"),
1117       Git(("copy -r 1324 https://v8.googlecode.com/svn/trunk "
1118            "https://v8.googlecode.com/svn/tags/3.22.5.1 -m "
1119            "\"Tagging version 3.22.5.1\""), ""),
1120       Git("checkout -f some_branch", ""),
1121       Git("branch -D %s" % TEST_CONFIG[BRANCHNAME], ""),
1122     ])
1123
1124     self.ExpectReadline([
1125       RL("Y"),  # Automatically add corresponding ports (34567, 56789)?
1126       RL("Y"),  # Automatically increment patch level?
1127       RL("reviewer@chromium.org"),  # V8 reviewer.
1128       RL("LGTM"),  # Enter LGTM for V8 CL.
1129     ])
1130
1131     # r12345 and r34567 are patches. r23456 (included) and r45678 are the MIPS
1132     # ports of r12345. r56789 is the MIPS port of r34567.
1133     args = ["-f", "-p", extra_patch, "--branch", "trunk", "12345", "23456",
1134             "34567"]
1135
1136     # The first run of the script stops because of the svn being down.
1137     self.assertRaises(GitFailedException,
1138         lambda: MergeToBranch(TEST_CONFIG, self).Run(args))
1139
1140     # Test that state recovery after restarting the script works.
1141     args += ["-s", "3"]
1142     MergeToBranch(TEST_CONFIG, self).Run(args)
1143
1144   def testReleases(self):
1145     json_output = self.MakeEmptyTempFile()
1146     csv_output = self.MakeEmptyTempFile()
1147     TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
1148     self.WriteFakeVersionFile()
1149
1150     TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
1151     if not os.path.exists(TEST_CONFIG[CHROMIUM]):
1152       os.makedirs(TEST_CONFIG[CHROMIUM])
1153     def WriteDEPS(revision):
1154       TextToFile("Line\n   \"v8_revision\": \"%s\",\n  line\n" % revision,
1155                  TEST_CONFIG[DEPS_FILE])
1156     WriteDEPS(567)
1157
1158     def ResetVersion(minor, build, patch=0):
1159       return lambda: self.WriteFakeVersionFile(minor=minor,
1160                                                build=build,
1161                                                patch=patch)
1162
1163     def ResetDEPS(revision):
1164       return lambda: WriteDEPS(revision)
1165
1166     self.ExpectGit([
1167       Git("status -s -uno", ""),
1168       Git("status -s -b -uno", "## some_branch\n"),
1169       Git("svn fetch", ""),
1170       Git("branch", "  branch1\n* branch2\n"),
1171       Git("checkout -b %s" % TEST_CONFIG[BRANCHNAME], ""),
1172       Git("branch -r", "  svn/3.21\n  svn/3.3\n"),
1173       Git("reset --hard svn/3.3", ""),
1174       Git("log --format=%H", "hash1\nhash2"),
1175       Git("diff --name-only hash1 hash1^", ""),
1176       Git("diff --name-only hash2 hash2^", TEST_CONFIG[VERSION_FILE]),
1177       Git("checkout -f hash2 -- %s" % TEST_CONFIG[VERSION_FILE], "",
1178           cb=ResetVersion(3, 1, 1)),
1179       Git("log -1 --format=%B hash2",
1180           "Version 3.3.1.1 (merged 12)\n\nReview URL: fake.com\n"),
1181       Git("log -1 --format=%s hash2", ""),
1182       Git("svn find-rev hash2", "234"),
1183       Git("log -1 --format=%ci hash2", "18:15"),
1184       Git("checkout -f HEAD -- %s" % TEST_CONFIG[VERSION_FILE], "",
1185           cb=ResetVersion(22, 5)),
1186       Git("reset --hard svn/3.21", ""),
1187       Git("log --format=%H", "hash3\nhash4\nhash5\n"),
1188       Git("diff --name-only hash3 hash3^", TEST_CONFIG[VERSION_FILE]),
1189       Git("checkout -f hash3 -- %s" % TEST_CONFIG[VERSION_FILE], "",
1190           cb=ResetVersion(21, 2)),
1191       Git("log -1 --format=%B hash3", ""),
1192       Git("log -1 --format=%s hash3", ""),
1193       Git("svn find-rev hash3", "123"),
1194       Git("log -1 --format=%ci hash3", "03:15"),
1195       Git("checkout -f HEAD -- %s" % TEST_CONFIG[VERSION_FILE], "",
1196           cb=ResetVersion(22, 5)),
1197       Git("reset --hard svn/trunk", ""),
1198       Git("log --format=%H", "hash6\n"),
1199       Git("diff --name-only hash6 hash6^", TEST_CONFIG[VERSION_FILE]),
1200       Git("checkout -f hash6 -- %s" % TEST_CONFIG[VERSION_FILE], "",
1201           cb=ResetVersion(22, 3)),
1202       Git("log -1 --format=%B hash6", ""),
1203       Git("log -1 --format=%s hash6", ""),
1204       Git("svn find-rev hash6", "345"),
1205       Git("log -1 --format=%ci hash6", ""),
1206       Git("checkout -f HEAD -- %s" % TEST_CONFIG[VERSION_FILE], "",
1207           cb=ResetVersion(22, 5)),
1208       Git("status -s -uno", ""),
1209       Git("checkout -f master", ""),
1210       Git("pull", ""),
1211       Git("checkout -b %s" % TEST_CONFIG[BRANCHNAME], ""),
1212       Git("log --format=%H --grep=\"V8\"", "c_hash1\nc_hash2\n"),
1213       Git("diff --name-only c_hash1 c_hash1^", ""),
1214       Git("diff --name-only c_hash2 c_hash2^", TEST_CONFIG[DEPS_FILE]),
1215       Git("checkout -f c_hash2 -- %s" % TEST_CONFIG[DEPS_FILE], "",
1216           cb=ResetDEPS(345)),
1217       Git("svn find-rev c_hash2", "4567"),
1218       Git("checkout -f HEAD -- %s" % TEST_CONFIG[DEPS_FILE], "",
1219           cb=ResetDEPS(567)),
1220       Git("branch -r", " weird/123\n  branch-heads/7\n"),
1221       Git("checkout -f branch-heads/7 -- %s" % TEST_CONFIG[DEPS_FILE], "",
1222           cb=ResetDEPS(345)),
1223       Git("checkout -f HEAD -- %s" % TEST_CONFIG[DEPS_FILE], "",
1224           cb=ResetDEPS(567)),
1225       Git("checkout -f master", ""),
1226       Git("branch -D %s" % TEST_CONFIG[BRANCHNAME], ""),
1227       Git("checkout -f some_branch", ""),
1228       Git("branch -D %s" % TEST_CONFIG[BRANCHNAME], ""),
1229     ])
1230
1231     args = ["-c", TEST_CONFIG[CHROMIUM],
1232             "--json", json_output,
1233             "--csv", csv_output,
1234             "--max-releases", "1"]
1235     Releases(TEST_CONFIG, self).Run(args)
1236
1237     # Check expected output.
1238     csv = ("3.22.3,trunk,345,4567,\r\n"
1239            "3.21.2,3.21,123,,\r\n"
1240            "3.3.1.1,3.3,234,,12\r\n")
1241     self.assertEquals(csv, FileToText(csv_output))
1242
1243     expected_json = [
1244       {"bleeding_edge": "", "patches_merged": "", "version": "3.22.3",
1245        "chromium_revision": "4567", "branch": "trunk", "revision": "345",
1246        "review_link": "", "date": "", "chromium_branch": "7",
1247        "revision_link": "https://code.google.com/p/v8/source/detail?r=345"},
1248       {"patches_merged": "", "bleeding_edge": "", "version": "3.21.2",
1249        "chromium_revision": "", "branch": "3.21", "revision": "123",
1250        "review_link": "", "date": "03:15", "chromium_branch": "",
1251        "revision_link": "https://code.google.com/p/v8/source/detail?r=123"},
1252       {"patches_merged": "12", "bleeding_edge": "", "version": "3.3.1.1",
1253        "chromium_revision": "", "branch": "3.3", "revision": "234",
1254        "review_link": "fake.com", "date": "18:15", "chromium_branch": "",
1255        "revision_link": "https://code.google.com/p/v8/source/detail?r=234"},
1256     ]
1257     self.assertEquals(expected_json, json.loads(FileToText(json_output)))
1258
1259
1260 class SystemTest(unittest.TestCase):
1261   def testReload(self):
1262     step = MakeStep(step_class=PrepareChangeLog, number=0, state={}, config={},
1263                     side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER)
1264     body = step.Reload(
1265 """------------------------------------------------------------------------
1266 r17997 | machenbach@chromium.org | 2013-11-22 11:04:04 +0100 (...) | 6 lines
1267
1268 Prepare push to trunk.  Now working on version 3.23.11.
1269
1270 R=danno@chromium.org
1271
1272 Review URL: https://codereview.chromium.org/83173002
1273
1274 ------------------------------------------------------------------------""")
1275     self.assertEquals(
1276 """Prepare push to trunk.  Now working on version 3.23.11.
1277
1278 R=danno@chromium.org
1279
1280 Committed: https://code.google.com/p/v8/source/detail?r=17997""", body)