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