9107db97ec5762a8783f21536e935df1e2aade25
[platform/framework/web/crosswalk.git] / src / v8 / tools / push-to-trunk / test_scripts.py
1 #!/usr/bin/env python
2 # Copyright 2013 the V8 project authors. All rights reserved.
3 # Redistribution and use in source and binary forms, with or without
4 # modification, are permitted provided that the following conditions are
5 # met:
6 #
7 #     * Redistributions of source code must retain the above copyright
8 #       notice, this list of conditions and the following disclaimer.
9 #     * Redistributions in binary form must reproduce the above
10 #       copyright notice, this list of conditions and the following
11 #       disclaimer in the documentation and/or other materials provided
12 #       with the distribution.
13 #     * Neither the name of Google Inc. nor the names of its
14 #       contributors may be used to endorse or promote products derived
15 #       from this software without specific prior written permission.
16 #
17 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
29 import os
30 import tempfile
31 import traceback
32 import unittest
33
34 import auto_push
35 from auto_push import CheckLastPush
36 from auto_push import SETTINGS_LOCATION
37 import common_includes
38 from common_includes import *
39 import merge_to_branch
40 from merge_to_branch import *
41 import push_to_trunk
42 from push_to_trunk import *
43 import chromium_roll
44 from chromium_roll import CHROMIUM
45 from chromium_roll import DEPS_FILE
46 from chromium_roll import ChromiumRoll
47
48
49 TEST_CONFIG = {
50   BRANCHNAME: "test-prepare-push",
51   TRUNKBRANCH: "test-trunk-push",
52   PERSISTFILE_BASENAME: "/tmp/test-v8-push-to-trunk-tempfile",
53   TEMP_BRANCH: "test-prepare-push-temporary-branch-created-by-script",
54   DOT_GIT_LOCATION: None,
55   VERSION_FILE: None,
56   CHANGELOG_FILE: None,
57   CHANGELOG_ENTRY_FILE: "/tmp/test-v8-push-to-trunk-tempfile-changelog-entry",
58   PATCH_FILE: "/tmp/test-v8-push-to-trunk-tempfile-patch",
59   COMMITMSG_FILE: "/tmp/test-v8-push-to-trunk-tempfile-commitmsg",
60   CHROMIUM: "/tmp/test-v8-push-to-trunk-tempfile-chromium",
61   DEPS_FILE: "/tmp/test-v8-push-to-trunk-tempfile-chromium/DEPS",
62   SETTINGS_LOCATION: None,
63   ALREADY_MERGING_SENTINEL_FILE:
64       "/tmp/test-merge-to-branch-tempfile-already-merging",
65   COMMIT_HASHES_FILE: "/tmp/test-merge-to-branch-tempfile-PATCH_COMMIT_HASHES",
66   TEMPORARY_PATCH_FILE: "/tmp/test-merge-to-branch-tempfile-temporary-patch",
67 }
68
69
70 AUTO_PUSH_ARGS = [
71   "-a", "author@chromium.org",
72   "-r", "reviewer@chromium.org",
73 ]
74
75
76 class ToplevelTest(unittest.TestCase):
77   def testMakeComment(self):
78     self.assertEquals("#   Line 1\n#   Line 2\n#",
79                       MakeComment("    Line 1\n    Line 2\n"))
80     self.assertEquals("#Line 1\n#Line 2",
81                       MakeComment("Line 1\n Line 2"))
82
83   def testStripComments(self):
84     self.assertEquals("    Line 1\n    Line 3\n",
85         StripComments("    Line 1\n#   Line 2\n    Line 3\n#\n"))
86     self.assertEquals("\nLine 2 ### Test\n #",
87         StripComments("###\n# \n\n#  Line 1\nLine 2 ### Test\n #"))
88
89   def testMakeChangeLogBodySimple(self):
90     commits = [
91           ["Title text 1",
92            "Title text 1\n\nBUG=\n",
93            "author1@chromium.org"],
94           ["Title text 2.",
95            "Title text 2\n\nBUG=1234\n",
96            "author2@chromium.org"],
97         ]
98     self.assertEquals("        Title text 1.\n"
99                       "        (author1@chromium.org)\n\n"
100                       "        Title text 2 (Chromium issue 1234).\n"
101                       "        (author2@chromium.org)\n\n",
102                       MakeChangeLogBody(commits))
103
104   def testMakeChangeLogBodyEmpty(self):
105     self.assertEquals("", MakeChangeLogBody([]))
106
107   def testMakeChangeLogBodyAutoFormat(self):
108     commits = [
109           ["Title text 1!",
110            "Title text 1\nLOG=y\nBUG=\n",
111            "author1@chromium.org"],
112           ["Title text 2",
113            "Title text 2\n\nBUG=1234\n",
114            "author2@chromium.org"],
115           ["Title text 3",
116            "Title text 3\n\nBUG=1234\nLOG = Yes\n",
117            "author3@chromium.org"],
118           ["Title text 3",
119            "Title text 4\n\nBUG=1234\nLOG=\n",
120            "author4@chromium.org"],
121         ]
122     self.assertEquals("        Title text 1.\n\n"
123                       "        Title text 3 (Chromium issue 1234).\n\n",
124                       MakeChangeLogBody(commits, True))
125
126   def testRegressWrongLogEntryOnTrue(self):
127     body = """
128 Check elimination: Learn from if(CompareMap(x)) on true branch.
129
130 BUG=
131 R=verwaest@chromium.org
132
133 Committed: https://code.google.com/p/v8/source/detail?r=18210
134 """
135     self.assertEquals("", MakeChangeLogBody([["title", body, "author"]], True))
136
137   def testMakeChangeLogBugReferenceEmpty(self):
138     self.assertEquals("", MakeChangeLogBugReference(""))
139     self.assertEquals("", MakeChangeLogBugReference("LOG="))
140     self.assertEquals("", MakeChangeLogBugReference(" BUG ="))
141     self.assertEquals("", MakeChangeLogBugReference("BUG=none\t"))
142
143   def testMakeChangeLogBugReferenceSimple(self):
144     self.assertEquals("(issue 987654)",
145                       MakeChangeLogBugReference("BUG = v8:987654"))
146     self.assertEquals("(Chromium issue 987654)",
147                       MakeChangeLogBugReference("BUG=987654 "))
148
149   def testMakeChangeLogBugReferenceFromBody(self):
150     self.assertEquals("(Chromium issue 1234567)",
151                       MakeChangeLogBugReference("Title\n\nTBR=\nBUG=\n"
152                                                 " BUG=\tchromium:1234567\t\n"
153                                                 "R=somebody\n"))
154
155   def testMakeChangeLogBugReferenceMultiple(self):
156     # All issues should be sorted and grouped. Multiple references to the same
157     # issue should be filtered.
158     self.assertEquals("(issues 123, 234, Chromium issue 345)",
159                       MakeChangeLogBugReference("Title\n\n"
160                                                 "BUG=v8:234\n"
161                                                 "  BUG\t= 345, \tv8:234,\n"
162                                                 "BUG=v8:123\n"
163                                                 "R=somebody\n"))
164     self.assertEquals("(Chromium issues 123, 234)",
165                       MakeChangeLogBugReference("Title\n\n"
166                                                 "BUG=234,,chromium:123 \n"
167                                                 "R=somebody\n"))
168     self.assertEquals("(Chromium issues 123, 234)",
169                       MakeChangeLogBugReference("Title\n\n"
170                                                 "BUG=chromium:234, , 123\n"
171                                                 "R=somebody\n"))
172     self.assertEquals("(issues 345, 456)",
173                       MakeChangeLogBugReference("Title\n\n"
174                                                 "\t\tBUG=v8:345,v8:456\n"
175                                                 "R=somebody\n"))
176     self.assertEquals("(issue 123, Chromium issues 345, 456)",
177                       MakeChangeLogBugReference("Title\n\n"
178                                                 "BUG=chromium:456\n"
179                                                 "BUG = none\n"
180                                                 "R=somebody\n"
181                                                 "BUG=456,v8:123, 345"))
182
183   # TODO(machenbach): These test don't make much sense when the formatting is
184   # done later.
185   def testMakeChangeLogBugReferenceLong(self):
186     # -----------------00--------10--------20--------30--------
187     self.assertEquals("(issues 234, 1234567890, 1234567"
188                       "8901234567890, Chromium issues 12345678,"
189                       " 123456789)",
190                       MakeChangeLogBugReference("BUG=v8:234\n"
191                                                 "BUG=v8:1234567890\n"
192                                                 "BUG=v8:12345678901234567890\n"
193                                                 "BUG=123456789\n"
194                                                 "BUG=12345678\n"))
195     # -----------------00--------10--------20--------30--------
196     self.assertEquals("(issues 234, 1234567890, 1234567"
197                       "8901234567890, Chromium issues"
198                       " 123456789, 1234567890)",
199                       MakeChangeLogBugReference("BUG=v8:234\n"
200                                                 "BUG=v8:12345678901234567890\n"
201                                                 "BUG=v8:1234567890\n"
202                                                 "BUG=123456789\n"
203                                                 "BUG=1234567890\n"))
204     # -----------------00--------10--------20--------30--------
205     self.assertEquals("(Chromium issues 234, 1234567890"
206                       ", 12345678901234567, "
207                       "1234567890123456789)",
208                       MakeChangeLogBugReference("BUG=234\n"
209                                                 "BUG=12345678901234567\n"
210                                                 "BUG=1234567890123456789\n"
211                                                 "BUG=1234567890\n"))
212
213
214 def Git(*args, **kwargs):
215   """Convenience function returning a git test expectation."""
216   return {
217     "name": "git",
218     "args": args[:-1],
219     "ret": args[-1],
220     "cb": kwargs.get("cb"),
221   }
222
223
224 def RL(text, cb=None):
225   """Convenience function returning a readline test expectation."""
226   return {"name": "readline", "args": [], "ret": text, "cb": cb}
227
228
229 def URL(*args, **kwargs):
230   """Convenience function returning a readurl test expectation."""
231   return {
232     "name": "readurl",
233     "args": args[:-1],
234     "ret": args[-1],
235     "cb": kwargs.get("cb"),
236   }
237
238
239 class SimpleMock(object):
240   def __init__(self, name):
241     self._name = name
242     self._recipe = []
243     self._index = -1
244
245   def Expect(self, recipe):
246     self._recipe = recipe
247
248   def Call(self, name, *args):  # pragma: no cover
249     self._index += 1
250     try:
251       expected_call = self._recipe[self._index]
252     except IndexError:
253       raise NoRetryException("Calling %s %s" % (name, " ".join(args)))
254
255     if not isinstance(expected_call, dict):
256       raise NoRetryException("Found wrong expectation type for %s %s"
257                              % (name, " ".join(args)))
258
259
260     # The number of arguments in the expectation must match the actual
261     # arguments.
262     if len(args) > len(expected_call['args']):
263       raise NoRetryException("When calling %s with arguments, the "
264           "expectations must consist of at least as many arguments.")
265
266     # Compare expected and actual arguments.
267     for (expected_arg, actual_arg) in zip(expected_call['args'], args):
268       if expected_arg != actual_arg:
269         raise NoRetryException("Expected: %s - Actual: %s"
270                                % (expected_arg, actual_arg))
271
272     # The expected call contains an optional callback for checking the context
273     # at the time of the call.
274     if expected_call['cb']:
275       try:
276         expected_call['cb']()
277       except:
278         tb = traceback.format_exc()
279         raise NoRetryException("Caught exception from callback: %s" % tb)
280
281     # If the return value is an exception, raise it instead of returning.
282     if isinstance(expected_call['ret'], Exception):
283       raise expected_call['ret']
284     return expected_call['ret']
285
286   def AssertFinished(self):  # pragma: no cover
287     if self._index < len(self._recipe) -1:
288       raise NoRetryException("Called %s too seldom: %d vs. %d"
289                              % (self._name, self._index, len(self._recipe)))
290
291
292 class ScriptTest(unittest.TestCase):
293   def MakeEmptyTempFile(self):
294     handle, name = tempfile.mkstemp()
295     os.close(handle)
296     self._tmp_files.append(name)
297     return name
298
299   def WriteFakeVersionFile(self, build=4):
300     with open(TEST_CONFIG[VERSION_FILE], "w") as f:
301       f.write("  // Some line...\n")
302       f.write("\n")
303       f.write("#define MAJOR_VERSION    3\n")
304       f.write("#define MINOR_VERSION    22\n")
305       f.write("#define BUILD_NUMBER     %s\n" % build)
306       f.write("#define PATCH_LEVEL      0\n")
307       f.write("  // Some line...\n")
308       f.write("#define IS_CANDIDATE_VERSION 0\n")
309
310   def MakeStep(self):
311     """Convenience wrapper."""
312     options = ScriptsBase(TEST_CONFIG, self, self._state).MakeOptions([])
313     return MakeStep(step_class=Step, state=self._state,
314                     config=TEST_CONFIG, side_effect_handler=self,
315                     options=options)
316
317   def RunStep(self, script=PushToTrunk, step_class=Step, args=None):
318     """Convenience wrapper."""
319     args = args or ["-m"]
320     return script(TEST_CONFIG, self, self._state).RunSteps([step_class], args)
321
322   def GitMock(self, cmd, args="", pipe=True):
323     print "%s %s" % (cmd, args)
324     return self._git_mock.Call("git", args)
325
326   def LogMock(self, cmd, args=""):
327     print "Log: %s %s" % (cmd, args)
328
329   MOCKS = {
330     "git": GitMock,
331     # TODO(machenbach): Little hack to reuse the git mock for the one svn call
332     # in merge-to-branch. The command should be made explicit in the test
333     # expectations.
334     "svn": GitMock,
335     "vi": LogMock,
336   }
337
338   def Call(self, fun, *args, **kwargs):
339     print "Calling %s with %s and %s" % (str(fun), str(args), str(kwargs))
340
341   def Command(self, cmd, args="", prefix="", pipe=True):
342     return ScriptTest.MOCKS[cmd](self, cmd, args)
343
344   def ReadLine(self):
345     return self._rl_mock.Call("readline")
346
347   def ReadURL(self, url, params):
348     if params is not None:
349       return self._url_mock.Call("readurl", url, params)
350     else:
351       return self._url_mock.Call("readurl", url)
352
353   def Sleep(self, seconds):
354     pass
355
356   def GetDate(self):
357     return "1999-07-31"
358
359   def ExpectGit(self, *args):
360     """Convenience wrapper."""
361     self._git_mock.Expect(*args)
362
363   def ExpectReadline(self, *args):
364     """Convenience wrapper."""
365     self._rl_mock.Expect(*args)
366
367   def ExpectReadURL(self, *args):
368     """Convenience wrapper."""
369     self._url_mock.Expect(*args)
370
371   def setUp(self):
372     self._git_mock = SimpleMock("git")
373     self._rl_mock = SimpleMock("readline")
374     self._url_mock = SimpleMock("readurl")
375     self._tmp_files = []
376     self._state = {}
377
378   def tearDown(self):
379     Command("rm", "-rf %s*" % TEST_CONFIG[PERSISTFILE_BASENAME])
380
381     # Clean up temps. Doesn't work automatically.
382     for name in self._tmp_files:
383       if os.path.exists(name):
384         os.remove(name)
385
386     self._git_mock.AssertFinished()
387     self._rl_mock.AssertFinished()
388     self._url_mock.AssertFinished()
389
390   def testGitOrig(self):
391     self.assertTrue(Command("git", "--version").startswith("git version"))
392
393   def testGitMock(self):
394     self.ExpectGit([Git("--version", "git version 1.2.3"), Git("dummy", "")])
395     self.assertEquals("git version 1.2.3", self.MakeStep().Git("--version"))
396     self.assertEquals("", self.MakeStep().Git("dummy"))
397
398   def testCommonPrepareDefault(self):
399     self.ExpectGit([
400       Git("status -s -uno", ""),
401       Git("status -s -b -uno", "## some_branch"),
402       Git("svn fetch", ""),
403       Git("branch", "  branch1\n* %s" % TEST_CONFIG[TEMP_BRANCH]),
404       Git("branch -D %s" % TEST_CONFIG[TEMP_BRANCH], ""),
405       Git("checkout -b %s" % TEST_CONFIG[TEMP_BRANCH], ""),
406       Git("branch", ""),
407     ])
408     self.ExpectReadline([RL("Y")])
409     self.MakeStep().CommonPrepare()
410     self.MakeStep().PrepareBranch()
411     self.assertEquals("some_branch", self._state["current_branch"])
412
413   def testCommonPrepareNoConfirm(self):
414     self.ExpectGit([
415       Git("status -s -uno", ""),
416       Git("status -s -b -uno", "## some_branch"),
417       Git("svn fetch", ""),
418       Git("branch", "  branch1\n* %s" % TEST_CONFIG[TEMP_BRANCH]),
419     ])
420     self.ExpectReadline([RL("n")])
421     self.MakeStep().CommonPrepare()
422     self.assertRaises(Exception, self.MakeStep().PrepareBranch)
423     self.assertEquals("some_branch", self._state["current_branch"])
424
425   def testCommonPrepareDeleteBranchFailure(self):
426     self.ExpectGit([
427       Git("status -s -uno", ""),
428       Git("status -s -b -uno", "## some_branch"),
429       Git("svn fetch", ""),
430       Git("branch", "  branch1\n* %s" % TEST_CONFIG[TEMP_BRANCH]),
431       Git("branch -D %s" % TEST_CONFIG[TEMP_BRANCH], None),
432     ])
433     self.ExpectReadline([RL("Y")])
434     self.MakeStep().CommonPrepare()
435     self.assertRaises(Exception, self.MakeStep().PrepareBranch)
436     self.assertEquals("some_branch", self._state["current_branch"])
437
438   def testInitialEnvironmentChecks(self):
439     TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
440     os.environ["EDITOR"] = "vi"
441     self.MakeStep().InitialEnvironmentChecks()
442
443   def testReadAndPersistVersion(self):
444     TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
445     self.WriteFakeVersionFile(build=5)
446     step = self.MakeStep()
447     step.ReadAndPersistVersion()
448     self.assertEquals("3", step["major"])
449     self.assertEquals("22", step["minor"])
450     self.assertEquals("5", step["build"])
451     self.assertEquals("0", step["patch"])
452
453   def testRegex(self):
454     self.assertEqual("(issue 321)",
455                      re.sub(r"BUG=v8:(.*)$", r"(issue \1)", "BUG=v8:321"))
456     self.assertEqual("(Chromium issue 321)",
457                      re.sub(r"BUG=(.*)$", r"(Chromium issue \1)", "BUG=321"))
458
459     cl = "  too little\n\ttab\ttab\n         too much\n        trailing  "
460     cl = MSub(r"\t", r"        ", cl)
461     cl = MSub(r"^ {1,7}([^ ])", r"        \1", cl)
462     cl = MSub(r"^ {9,80}([^ ])", r"        \1", cl)
463     cl = MSub(r" +$", r"", cl)
464     self.assertEqual("        too little\n"
465                      "        tab        tab\n"
466                      "        too much\n"
467                      "        trailing", cl)
468
469     self.assertEqual("//\n#define BUILD_NUMBER  3\n",
470                      MSub(r"(?<=#define BUILD_NUMBER)(?P<space>\s+)\d*$",
471                           r"\g<space>3",
472                           "//\n#define BUILD_NUMBER  321\n"))
473
474   def testPreparePushRevision(self):
475     # Tests the default push hash used when the --revision option is not set.
476     self.ExpectGit([
477       Git("log -1 --format=%H HEAD", "push_hash")
478     ])
479
480     self.RunStep(PushToTrunk, PreparePushRevision)
481     self.assertEquals("push_hash", self._state["push_hash"])
482
483   def testPrepareChangeLog(self):
484     TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
485     self.WriteFakeVersionFile()
486     TEST_CONFIG[CHANGELOG_ENTRY_FILE] = self.MakeEmptyTempFile()
487
488     self.ExpectGit([
489       Git("log --format=%H 1234..push_hash", "rev1\nrev2\nrev3\nrev4"),
490       Git("log -1 --format=%s rev1", "Title text 1"),
491       Git("log -1 --format=%B rev1", "Title\n\nBUG=\nLOG=y\n"),
492       Git("log -1 --format=%an rev1", "author1@chromium.org"),
493       Git("log -1 --format=%s rev2", "Title text 2."),
494       Git("log -1 --format=%B rev2", "Title\n\nBUG=123\nLOG= \n"),
495       Git("log -1 --format=%an rev2", "author2@chromium.org"),
496       Git("log -1 --format=%s rev3", "Title text 3"),
497       Git("log -1 --format=%B rev3", "Title\n\nBUG=321\nLOG=true\n"),
498       Git("log -1 --format=%an rev3", "author3@chromium.org"),
499       Git("log -1 --format=%s rev4", "Title text 4"),
500       Git("log -1 --format=%B rev4",
501        ("Title\n\nBUG=456\nLOG=Y\n\n"
502         "Review URL: https://codereview.chromium.org/9876543210\n")),
503       Git("log -1 --format=%an rev4", "author4@chromium.org"),
504     ])
505
506     # The cl for rev4 on rietveld has an updated LOG flag.
507     self.ExpectReadURL([
508       URL("https://codereview.chromium.org/9876543210/description",
509           "Title\n\nBUG=456\nLOG=N\n\n"),
510     ])
511
512     self._state["last_push_bleeding_edge"] = "1234"
513     self._state["push_hash"] = "push_hash"
514     self._state["version"] = "3.22.5"
515     self.RunStep(PushToTrunk, PrepareChangeLog)
516
517     actual_cl = FileToText(TEST_CONFIG[CHANGELOG_ENTRY_FILE])
518
519     expected_cl = """1999-07-31: Version 3.22.5
520
521         Title text 1.
522
523         Title text 3 (Chromium issue 321).
524
525         Performance and stability improvements on all platforms.
526 #
527 # The change log above is auto-generated. Please review if all relevant
528 # commit messages from the list below are included.
529 # All lines starting with # will be stripped.
530 #
531 #       Title text 1.
532 #       (author1@chromium.org)
533 #
534 #       Title text 2 (Chromium issue 123).
535 #       (author2@chromium.org)
536 #
537 #       Title text 3 (Chromium issue 321).
538 #       (author3@chromium.org)
539 #
540 #       Title text 4 (Chromium issue 456).
541 #       (author4@chromium.org)
542 #
543 #"""
544
545     self.assertEquals(expected_cl, actual_cl)
546
547   def testEditChangeLog(self):
548     TEST_CONFIG[CHANGELOG_ENTRY_FILE] = self.MakeEmptyTempFile()
549     TextToFile("  New  \n\tLines  \n", TEST_CONFIG[CHANGELOG_ENTRY_FILE])
550     os.environ["EDITOR"] = "vi"
551
552     self.ExpectReadline([
553       RL(""),  # Open editor.
554     ])
555
556     self.RunStep(PushToTrunk, EditChangeLog)
557
558     self.assertEquals("New\n        Lines",
559                       FileToText(TEST_CONFIG[CHANGELOG_ENTRY_FILE]))
560
561   def testIncrementVersion(self):
562     TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
563     self.WriteFakeVersionFile()
564     self._state["last_push_trunk"] = "hash1"
565
566     self.ExpectGit([
567       Git("checkout -f hash1 -- %s" % TEST_CONFIG[VERSION_FILE], "")
568     ])
569
570     self.ExpectReadline([
571       RL("Y"),  # Increment build number.
572     ])
573
574     self.RunStep(PushToTrunk, IncrementVersion)
575
576     self.assertEquals("3", self._state["new_major"])
577     self.assertEquals("22", self._state["new_minor"])
578     self.assertEquals("5", self._state["new_build"])
579     self.assertEquals("0", self._state["new_patch"])
580
581   def _TestSquashCommits(self, change_log, expected_msg):
582     TEST_CONFIG[CHANGELOG_ENTRY_FILE] = self.MakeEmptyTempFile()
583     with open(TEST_CONFIG[CHANGELOG_ENTRY_FILE], "w") as f:
584       f.write(change_log)
585
586     self.ExpectGit([
587       Git("diff svn/trunk hash1", "patch content"),
588       Git("svn find-rev hash1", "123455\n"),
589     ])
590
591     self._state["push_hash"] = "hash1"
592     self._state["date"] = "1999-11-11"
593
594     self.RunStep(PushToTrunk, SquashCommits)
595     self.assertEquals(FileToText(TEST_CONFIG[COMMITMSG_FILE]), expected_msg)
596
597     patch = FileToText(TEST_CONFIG[ PATCH_FILE])
598     self.assertTrue(re.search(r"patch content", patch))
599
600   def testSquashCommitsUnformatted(self):
601     change_log = """1999-11-11: Version 3.22.5
602
603         Log text 1.
604         Chromium issue 12345
605
606         Performance and stability improvements on all platforms.\n"""
607     commit_msg = """Version 3.22.5 (based on bleeding_edge revision r123455)
608
609 Log text 1. Chromium issue 12345
610
611 Performance and stability improvements on all platforms."""
612     self._TestSquashCommits(change_log, commit_msg)
613
614   def testSquashCommitsFormatted(self):
615     change_log = """1999-11-11: Version 3.22.5
616
617         Long commit message that fills more than 80 characters (Chromium issue
618         12345).
619
620         Performance and stability improvements on all platforms.\n"""
621     commit_msg = """Version 3.22.5 (based on bleeding_edge revision r123455)
622
623 Long commit message that fills more than 80 characters (Chromium issue 12345).
624
625 Performance and stability improvements on all platforms."""
626     self._TestSquashCommits(change_log, commit_msg)
627
628   def testSquashCommitsQuotationMarks(self):
629     change_log = """Line with "quotation marks".\n"""
630     commit_msg = """Line with "quotation marks"."""
631     self._TestSquashCommits(change_log, commit_msg)
632
633   def _PushToTrunk(self, force=False, manual=False):
634     TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
635
636     # The version file on bleeding edge has build level 5, while the version
637     # file from trunk has build level 4.
638     TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
639     self.WriteFakeVersionFile(build=5)
640
641     TEST_CONFIG[CHANGELOG_ENTRY_FILE] = self.MakeEmptyTempFile()
642     TEST_CONFIG[CHANGELOG_FILE] = self.MakeEmptyTempFile()
643     bleeding_edge_change_log = "2014-03-17: Sentinel\n"
644     TextToFile(bleeding_edge_change_log, TEST_CONFIG[CHANGELOG_FILE])
645     os.environ["EDITOR"] = "vi"
646
647     def ResetChangeLog():
648       """On 'git co -b new_branch svn/trunk', and 'git checkout -- ChangeLog',
649       the ChangLog will be reset to its content on trunk."""
650       trunk_change_log = """1999-04-05: Version 3.22.4
651
652         Performance and stability improvements on all platforms.\n"""
653       TextToFile(trunk_change_log, TEST_CONFIG[CHANGELOG_FILE])
654
655     def ResetToTrunk():
656       ResetChangeLog()
657       self.WriteFakeVersionFile()
658
659     def CheckSVNCommit():
660       commit = FileToText(TEST_CONFIG[COMMITMSG_FILE])
661       self.assertEquals(
662 """Version 3.22.5 (based on bleeding_edge revision r123455)
663
664 Log text 1 (issue 321).
665
666 Performance and stability improvements on all platforms.""", commit)
667       version = FileToText(TEST_CONFIG[VERSION_FILE])
668       self.assertTrue(re.search(r"#define MINOR_VERSION\s+22", version))
669       self.assertTrue(re.search(r"#define BUILD_NUMBER\s+5", version))
670       self.assertFalse(re.search(r"#define BUILD_NUMBER\s+6", version))
671       self.assertTrue(re.search(r"#define PATCH_LEVEL\s+0", version))
672       self.assertTrue(re.search(r"#define IS_CANDIDATE_VERSION\s+0", version))
673
674       # Check that the change log on the trunk branch got correctly modified.
675       change_log = FileToText(TEST_CONFIG[CHANGELOG_FILE])
676       self.assertEquals(
677 """1999-07-31: Version 3.22.5
678
679         Log text 1 (issue 321).
680
681         Performance and stability improvements on all platforms.
682
683
684 1999-04-05: Version 3.22.4
685
686         Performance and stability improvements on all platforms.\n""",
687           change_log)
688
689     force_flag = " -f" if not manual else ""
690     self.ExpectGit([
691       Git("status -s -uno", ""),
692       Git("status -s -b -uno", "## some_branch\n"),
693       Git("svn fetch", ""),
694       Git("branch", "  branch1\n* branch2\n"),
695       Git("checkout -b %s" % TEST_CONFIG[TEMP_BRANCH], ""),
696       Git("branch", "  branch1\n* branch2\n"),
697       Git("branch", "  branch1\n* branch2\n"),
698       Git("checkout -b %s svn/bleeding_edge" % TEST_CONFIG[BRANCHNAME], ""),
699       Git("svn find-rev r123455", "push_hash\n"),
700       Git(("log -1 --format=%H --grep="
701            "\"^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]* (based\" "
702            "svn/trunk"), "hash2\n"),
703       Git("log -1 hash2", "Log message\n"),
704       Git("log -1 --format=%s hash2",
705        "Version 3.4.5 (based on bleeding_edge revision r1234)\n"),
706       Git("svn find-rev r1234", "hash3\n"),
707       Git("checkout -f hash2 -- %s" % TEST_CONFIG[VERSION_FILE], "",
708           cb=self.WriteFakeVersionFile),
709       Git("log --format=%H hash3..push_hash", "rev1\n"),
710       Git("log -1 --format=%s rev1", "Log text 1.\n"),
711       Git("log -1 --format=%B rev1", "Text\nLOG=YES\nBUG=v8:321\nText\n"),
712       Git("log -1 --format=%an rev1", "author1@chromium.org\n"),
713       Git("svn fetch", "fetch result\n"),
714       Git("checkout -f svn/bleeding_edge", ""),
715       Git("diff svn/trunk push_hash", "patch content\n"),
716       Git("svn find-rev push_hash", "123455\n"),
717       Git("checkout -b %s svn/trunk" % TEST_CONFIG[TRUNKBRANCH], "",
718           cb=ResetToTrunk),
719       Git("apply --index --reject \"%s\"" % TEST_CONFIG[PATCH_FILE], ""),
720       Git("checkout -f svn/trunk -- %s" % TEST_CONFIG[CHANGELOG_FILE], "",
721           cb=ResetChangeLog),
722       Git("checkout -f svn/trunk -- %s" % TEST_CONFIG[VERSION_FILE], "",
723           cb=self.WriteFakeVersionFile),
724       Git("commit -aF \"%s\"" % TEST_CONFIG[COMMITMSG_FILE], "",
725           cb=CheckSVNCommit),
726       Git("svn dcommit 2>&1", "Some output\nCommitted r123456\nSome output\n"),
727       Git("svn tag 3.22.5 -m \"Tagging version 3.22.5\"", ""),
728       Git("checkout -f some_branch", ""),
729       Git("branch -D %s" % TEST_CONFIG[TEMP_BRANCH], ""),
730       Git("branch -D %s" % TEST_CONFIG[BRANCHNAME], ""),
731       Git("branch -D %s" % TEST_CONFIG[TRUNKBRANCH], ""),
732     ])
733
734     # Expected keyboard input in manual mode:
735     if manual:
736       self.ExpectReadline([
737         RL("Y"),  # Confirm last push.
738         RL(""),  # Open editor.
739         RL("Y"),  # Increment build number.
740         RL("Y"),  # Sanity check.
741       ])
742
743     # Expected keyboard input in semi-automatic mode and forced mode:
744     if not manual:
745       self.ExpectReadline([])
746
747     args = ["-a", "author@chromium.org", "--revision", "123455"]
748     if force: args.append("-f")
749     if manual: args.append("-m")
750     else: args += ["-r", "reviewer@chromium.org"]
751     PushToTrunk(TEST_CONFIG, self).Run(args)
752
753     cl = FileToText(TEST_CONFIG[CHANGELOG_FILE])
754     self.assertTrue(re.search(r"^\d\d\d\d\-\d+\-\d+: Version 3\.22\.5", cl))
755     self.assertTrue(re.search(r"        Log text 1 \(issue 321\).", cl))
756     self.assertTrue(re.search(r"1999\-04\-05: Version 3\.22\.4", cl))
757
758     # Note: The version file is on build number 5 again in the end of this test
759     # since the git command that merges to the bleeding edge branch is mocked
760     # out.
761
762   def testPushToTrunkManual(self):
763     self._PushToTrunk(manual=True)
764
765   def testPushToTrunkSemiAutomatic(self):
766     self._PushToTrunk()
767
768   def testPushToTrunkForced(self):
769     self._PushToTrunk(force=True)
770
771
772   def _ChromiumRoll(self, force=False, manual=False):
773     TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
774     if not os.path.exists(TEST_CONFIG[CHROMIUM]):
775       os.makedirs(TEST_CONFIG[CHROMIUM])
776     TextToFile("Some line\n   \"v8_revision\": \"123444\",\n  some line",
777                TEST_CONFIG[DEPS_FILE])
778
779     os.environ["EDITOR"] = "vi"
780     force_flag = " -f" if not manual else ""
781     self.ExpectGit([
782       Git("status -s -uno", ""),
783       Git("status -s -b -uno", "## some_branch\n"),
784       Git("svn fetch", ""),
785       Git(("log -1 --format=%H --grep="
786            "\"^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]* (based\" "
787            "svn/trunk"), "push_hash\n"),
788       Git("svn find-rev push_hash", "123455\n"),
789       Git("log -1 --format=%s push_hash",
790           "Version 3.22.5 (based on bleeding_edge revision r123454)\n"),
791       Git("status -s -uno", ""),
792       Git("checkout -f master", ""),
793       Git("pull", ""),
794       Git("checkout -b v8-roll-123455", ""),
795       Git(("commit -am \"Update V8 to version 3.22.5 "
796            "(based on bleeding_edge revision r123454).\n\n"
797            "TBR=reviewer@chromium.org\""),
798           ""),
799       Git(("cl upload --send-mail --email \"author@chromium.org\"%s"
800            % force_flag), ""),
801     ])
802
803     # Expected keyboard input in manual mode:
804     if manual:
805       self.ExpectReadline([
806         RL("reviewer@chromium.org"),  # Chromium reviewer.
807       ])
808
809     # Expected keyboard input in semi-automatic mode and forced mode:
810     if not manual:
811       self.ExpectReadline([])
812
813     args = ["-a", "author@chromium.org", "-c", TEST_CONFIG[CHROMIUM]]
814     if force: args.append("-f")
815     if manual: args.append("-m")
816     else: args += ["-r", "reviewer@chromium.org"]
817     ChromiumRoll(TEST_CONFIG, self).Run(args)
818
819     deps = FileToText(TEST_CONFIG[DEPS_FILE])
820     self.assertTrue(re.search("\"v8_revision\": \"123455\"", deps))
821
822   def testChromiumRollManual(self):
823     self._ChromiumRoll(manual=True)
824
825   def testChromiumRollSemiAutomatic(self):
826     self._ChromiumRoll()
827
828   def testChromiumRollForced(self):
829     self._ChromiumRoll(force=True)
830
831   def testCheckLastPushRecently(self):
832     self.ExpectGit([
833       Git(("log -1 --format=%H --grep="
834            "\"^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]* (based\" "
835            "svn/trunk"), "hash2\n"),
836       Git("log -1 --format=%s hash2",
837           "Version 3.4.5 (based on bleeding_edge revision r99)\n"),
838     ])
839
840     self._state["lkgr"] = "101"
841
842     self.assertRaises(Exception, lambda: self.RunStep(auto_push.AutoPush,
843                                                       CheckLastPush,
844                                                       AUTO_PUSH_ARGS))
845
846   def testAutoPush(self):
847     TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
848     TEST_CONFIG[SETTINGS_LOCATION] = "~/.doesnotexist"
849
850     self.ExpectReadURL([
851       URL("https://v8-status.appspot.com/current?format=json",
852           "{\"message\": \"Tree is throttled\"}"),
853       URL("https://v8-status.appspot.com/lkgr", Exception("Network problem")),
854       URL("https://v8-status.appspot.com/lkgr", "100"),
855     ])
856
857     self.ExpectGit([
858       Git("status -s -uno", ""),
859       Git("status -s -b -uno", "## some_branch\n"),
860       Git("svn fetch", ""),
861       Git(("log -1 --format=%H --grep=\""
862            "^Version [[:digit:]]*\.[[:digit:]]*\.[[:digit:]]* (based\""
863            " svn/trunk"), "push_hash\n"),
864       Git("log -1 --format=%s push_hash",
865           "Version 3.4.5 (based on bleeding_edge revision r79)\n"),
866     ])
867
868     auto_push.AutoPush(TEST_CONFIG, self).Run(AUTO_PUSH_ARGS + ["--push"])
869
870     state = json.loads(FileToText("%s-state.json"
871                                   % TEST_CONFIG[PERSISTFILE_BASENAME]))
872
873     self.assertEquals("100", state["lkgr"])
874
875   def testAutoPushStoppedBySettings(self):
876     TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
877     TEST_CONFIG[SETTINGS_LOCATION] = self.MakeEmptyTempFile()
878     TextToFile("{\"enable_auto_push\": false}", TEST_CONFIG[SETTINGS_LOCATION])
879
880     self.ExpectReadURL([])
881
882     self.ExpectGit([
883       Git("status -s -uno", ""),
884       Git("status -s -b -uno", "## some_branch\n"),
885       Git("svn fetch", ""),
886     ])
887
888     def RunAutoPush():
889       auto_push.AutoPush(TEST_CONFIG, self).Run(AUTO_PUSH_ARGS)
890     self.assertRaises(Exception, RunAutoPush)
891
892   def testAutoPushStoppedByTreeStatus(self):
893     TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
894     TEST_CONFIG[SETTINGS_LOCATION] = "~/.doesnotexist"
895
896     self.ExpectReadURL([
897       URL("https://v8-status.appspot.com/current?format=json",
898           "{\"message\": \"Tree is throttled (no push)\"}"),
899     ])
900
901     self.ExpectGit([
902       Git("status -s -uno", ""),
903       Git("status -s -b -uno", "## some_branch\n"),
904       Git("svn fetch", ""),
905     ])
906
907     def RunAutoPush():
908       auto_push.AutoPush(TEST_CONFIG, self).Run(AUTO_PUSH_ARGS)
909     self.assertRaises(Exception, RunAutoPush)
910
911   def testMergeToBranch(self):
912     TEST_CONFIG[ALREADY_MERGING_SENTINEL_FILE] = self.MakeEmptyTempFile()
913     TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
914     TEST_CONFIG[VERSION_FILE] = self.MakeEmptyTempFile()
915     self.WriteFakeVersionFile(build=5)
916     os.environ["EDITOR"] = "vi"
917     extra_patch = self.MakeEmptyTempFile()
918
919     def VerifyPatch(patch):
920       return lambda: self.assertEquals(patch,
921           FileToText(TEST_CONFIG[TEMPORARY_PATCH_FILE]))
922
923     msg = """Merged r12345, r23456, r34567, r45678, r56789 into trunk branch.
924
925 Title4
926
927 Title2
928
929 Title3
930
931 Title1
932
933 Title5
934
935 BUG=123,234,345,456,567,v8:123
936 LOG=N
937 """
938
939     def VerifySVNCommit():
940       commit = FileToText(TEST_CONFIG[COMMITMSG_FILE])
941       self.assertEquals(msg, commit)
942       version = FileToText(TEST_CONFIG[VERSION_FILE])
943       self.assertTrue(re.search(r"#define MINOR_VERSION\s+22", version))
944       self.assertTrue(re.search(r"#define BUILD_NUMBER\s+5", version))
945       self.assertTrue(re.search(r"#define PATCH_LEVEL\s+1", version))
946       self.assertTrue(re.search(r"#define IS_CANDIDATE_VERSION\s+0", version))
947
948     self.ExpectGit([
949       Git("status -s -uno", ""),
950       Git("status -s -b -uno", "## some_branch\n"),
951       Git("svn fetch", ""),
952       Git("branch", "  branch1\n* branch2\n"),
953       Git("checkout -b %s" % TEST_CONFIG[TEMP_BRANCH], ""),
954       Git("branch", "  branch1\n* branch2\n"),
955       Git("checkout -b %s svn/trunk" % TEST_CONFIG[BRANCHNAME], ""),
956       Git("log --format=%H --grep=\"Port r12345\" --reverse svn/bleeding_edge",
957           "hash1\nhash2"),
958       Git("svn find-rev hash1 svn/bleeding_edge", "45678"),
959       Git("log -1 --format=%s hash1", "Title1"),
960       Git("svn find-rev hash2 svn/bleeding_edge", "23456"),
961       Git("log -1 --format=%s hash2", "Title2"),
962       Git("log --format=%H --grep=\"Port r23456\" --reverse svn/bleeding_edge",
963           ""),
964       Git("log --format=%H --grep=\"Port r34567\" --reverse svn/bleeding_edge",
965           "hash3"),
966       Git("svn find-rev hash3 svn/bleeding_edge", "56789"),
967       Git("log -1 --format=%s hash3", "Title3"),
968       Git("svn find-rev r12345 svn/bleeding_edge", "hash4"),
969       # Simulate svn being down which stops the script.
970       Git("svn find-rev r23456 svn/bleeding_edge", None),
971       # Restart script in the failing step.
972       Git("svn find-rev r12345 svn/bleeding_edge", "hash4"),
973       Git("svn find-rev r23456 svn/bleeding_edge", "hash2"),
974       Git("svn find-rev r34567 svn/bleeding_edge", "hash3"),
975       Git("svn find-rev r45678 svn/bleeding_edge", "hash1"),
976       Git("svn find-rev r56789 svn/bleeding_edge", "hash5"),
977       Git("log -1 --format=%s hash4", "Title4"),
978       Git("log -1 --format=%s hash2", "Title2"),
979       Git("log -1 --format=%s hash3", "Title3"),
980       Git("log -1 --format=%s hash1", "Title1"),
981       Git("log -1 --format=%s hash5", "Title5"),
982       Git("log -1 hash4", "Title4\nBUG=123\nBUG=234"),
983       Git("log -1 hash2", "Title2\n BUG = v8:123,345"),
984       Git("log -1 hash3", "Title3\nLOG=n\nBUG=567, 456"),
985       Git("log -1 hash1", "Title1"),
986       Git("log -1 hash5", "Title5"),
987       Git("log -1 -p hash4", "patch4"),
988       Git("apply --index --reject \"%s\"" % TEST_CONFIG[TEMPORARY_PATCH_FILE],
989           "", cb=VerifyPatch("patch4")),
990       Git("log -1 -p hash2", "patch2"),
991       Git("apply --index --reject \"%s\"" % TEST_CONFIG[TEMPORARY_PATCH_FILE],
992           "", cb=VerifyPatch("patch2")),
993       Git("log -1 -p hash3", "patch3"),
994       Git("apply --index --reject \"%s\"" % TEST_CONFIG[TEMPORARY_PATCH_FILE],
995           "", cb=VerifyPatch("patch3")),
996       Git("log -1 -p hash1", "patch1"),
997       Git("apply --index --reject \"%s\"" % TEST_CONFIG[TEMPORARY_PATCH_FILE],
998           "", cb=VerifyPatch("patch1")),
999       Git("log -1 -p hash5", "patch5\n"),
1000       Git("apply --index --reject \"%s\"" % TEST_CONFIG[TEMPORARY_PATCH_FILE],
1001           "", cb=VerifyPatch("patch5\n")),
1002       Git("apply --index --reject \"%s\"" % extra_patch, ""),
1003       Git("commit -aF \"%s\"" % TEST_CONFIG[COMMITMSG_FILE], ""),
1004       Git("cl upload --send-mail -r \"reviewer@chromium.org\"", ""),
1005       Git("checkout -f %s" % TEST_CONFIG[BRANCHNAME], ""),
1006       Git("cl presubmit", "Presubmit successfull\n"),
1007       Git("cl dcommit -f --bypass-hooks", "Closing issue\n", cb=VerifySVNCommit),
1008       Git("svn fetch", ""),
1009       Git("log -1 --format=%%H --grep=\"%s\" svn/trunk" % msg, "hash6"),
1010       Git("svn find-rev hash6", "1324"),
1011       Git(("copy -r 1324 https://v8.googlecode.com/svn/trunk "
1012            "https://v8.googlecode.com/svn/tags/3.22.5.1 -m "
1013            "\"Tagging version 3.22.5.1\""), ""),
1014       Git("checkout -f some_branch", ""),
1015       Git("branch -D %s" % TEST_CONFIG[TEMP_BRANCH], ""),
1016       Git("branch -D %s" % TEST_CONFIG[BRANCHNAME], ""),
1017     ])
1018
1019     self.ExpectReadline([
1020       RL("Y"),  # Automatically add corresponding ports (34567, 56789)?
1021       RL("Y"),  # Automatically increment patch level?
1022       RL("reviewer@chromium.org"),  # V8 reviewer.
1023       RL("LGTM"),  # Enter LGTM for V8 CL.
1024     ])
1025
1026     # r12345 and r34567 are patches. r23456 (included) and r45678 are the MIPS
1027     # ports of r12345. r56789 is the MIPS port of r34567.
1028     args = ["-f", "-p", extra_patch, "--branch", "trunk", "12345", "23456",
1029             "34567"]
1030
1031     # The first run of the script stops because of the svn being down.
1032     self.assertRaises(GitFailedException,
1033         lambda: MergeToBranch(TEST_CONFIG, self).Run(args))
1034
1035     # Test that state recovery after restarting the script works.
1036     args += ["-s", "3"]
1037     MergeToBranch(TEST_CONFIG, self).Run(args)
1038
1039
1040 class SystemTest(unittest.TestCase):
1041   def testReload(self):
1042     step = MakeStep(step_class=PrepareChangeLog, number=0, state={}, config={},
1043                     side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER)
1044     body = step.Reload(
1045 """------------------------------------------------------------------------
1046 r17997 | machenbach@chromium.org | 2013-11-22 11:04:04 +0100 (...) | 6 lines
1047
1048 Prepare push to trunk.  Now working on version 3.23.11.
1049
1050 R=danno@chromium.org
1051
1052 Review URL: https://codereview.chromium.org/83173002
1053
1054 ------------------------------------------------------------------------""")
1055     self.assertEquals(
1056 """Prepare push to trunk.  Now working on version 3.23.11.
1057
1058 R=danno@chromium.org
1059
1060 Committed: https://code.google.com/p/v8/source/detail?r=17997""", body)