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 tempfile
31import traceback
32import unittest
33
34import auto_push
35from auto_push import CheckLastPush
36from auto_push import SETTINGS_LOCATION
37import auto_roll
38import common_includes
39from common_includes import *
40import merge_to_branch
41from merge_to_branch import *
42import push_to_trunk
43from push_to_trunk import *
44import chromium_roll
45from chromium_roll import CHROMIUM
46from chromium_roll import DEPS_FILE
47from chromium_roll import ChromiumRoll
48import releases
49from releases import Releases
50
51
52TEST_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
72AUTO_PUSH_ARGS = [
73  "-a", "author@chromium.org",
74  "-r", "reviewer@chromium.org",
75]
76
77
78class 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 = """
162Check elimination: Learn from if(CompareMap(x)) on true branch.
163
164BUG=
165R=verwaest@chromium.org
166
167Committed: 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
248def 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
258def RL(text, cb=None):
259  """Convenience function returning a readline test expectation."""
260  return {"name": "readline", "args": [], "ret": text, "cb": cb}
261
262
263def 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
273class 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
326class 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
641Log text 1. Chromium issue 12345
642
643Performance 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
655Long commit message that fills more than 80 characters (Chromium issue 12345).
656
657Performance 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
696Log text 1 (issue 321).
697
698Performance 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
7161999-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("""
804def list_to_dict(entries):
805  return {"g_name@google.com": "c_name@chromium.org"}
806def 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 = """
968vars = {
969  "v8_revision": "123455",
970}
971deps = {
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
1032Title4
1033
1034Title2
1035
1036Title3
1037
1038Title1
1039
1040Revert "Something"
1041
1042BUG=123,234,345,456,567,v8:123
1043LOG=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
1260class 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"""------------------------------------------------------------------------
1266r17997 | machenbach@chromium.org | 2013-11-22 11:04:04 +0100 (...) | 6 lines
1267
1268Prepare push to trunk.  Now working on version 3.23.11.
1269
1270R=danno@chromium.org
1271
1272Review URL: https://codereview.chromium.org/83173002
1273
1274------------------------------------------------------------------------""")
1275    self.assertEquals(
1276"""Prepare push to trunk.  Now working on version 3.23.11.
1277
1278R=danno@chromium.org
1279
1280Committed: https://code.google.com/p/v8/source/detail?r=17997""", body)
1281