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