Add fully automated mode to push-to-trunk script.

Now there are three modes to run the script:
(1) default: semi-automated
(2) manual (-m option), like in the old script
(3) forced (-f option), no user input required no editor check

R=ulan@chromium.org

Review URL: https://codereview.chromium.org/102253002

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@18247 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 1868ef5d
...@@ -96,6 +96,9 @@ def RunAutoRoll(config, ...@@ -96,6 +96,9 @@ def RunAutoRoll(config,
def BuildOptions(): def BuildOptions():
result = optparse.OptionParser() result = optparse.OptionParser()
result.add_option("-f", "--force", dest="f",
help="Don't prompt the user.",
default=True, action="store_true")
result.add_option("-s", "--step", dest="s", result.add_option("-s", "--step", dest="s",
help="Specify the step where to start work. Default: 0.", help="Specify the step where to start work. Default: 0.",
default=0, type="int") default=0, type="int")
......
...@@ -229,6 +229,12 @@ class Step(object): ...@@ -229,6 +229,12 @@ class Step(object):
def Config(self, key): def Config(self, key):
return self._config[key] return self._config[key]
def IsForced(self):
return self._options and self._options.f
def IsManual(self):
return self._options and self._options.m
def Run(self): def Run(self):
if self._requires: if self._requires:
self.RestoreIfUnset(self._requires) self.RestoreIfUnset(self._requires)
...@@ -271,7 +277,7 @@ class Step(object): ...@@ -271,7 +277,7 @@ class Step(object):
def ReadLine(self, default=None): def ReadLine(self, default=None):
# Don't prompt in forced mode. # Don't prompt in forced mode.
if self._options and self._options.f and default is not None: if not self.IsManual() and default is not None:
print "%s (forced)" % default print "%s (forced)" % default
return default return default
else: else:
...@@ -282,8 +288,9 @@ class Step(object): ...@@ -282,8 +288,9 @@ class Step(object):
return self.Retry(cmd, retry_on, [5, 30]) return self.Retry(cmd, retry_on, [5, 30])
def Editor(self, args): def Editor(self, args):
return self._side_effect_handler.Command(os.environ["EDITOR"], args, if not self.IsForced():
pipe=False) return self._side_effect_handler.Command(os.environ["EDITOR"], args,
pipe=False)
def ReadURL(self, url, retry_on=None, wait_plan=None): def ReadURL(self, url, retry_on=None, wait_plan=None):
wait_plan = wait_plan or [3, 60, 600] wait_plan = wait_plan or [3, 60, 600]
...@@ -299,9 +306,9 @@ class Step(object): ...@@ -299,9 +306,9 @@ class Step(object):
print "Exiting" print "Exiting"
raise Exception(msg) raise Exception(msg)
def DieInForcedMode(self, msg=""): def DieNoManualMode(self, msg=""):
if self._options and self._options.f: if not self.IsManual():
msg = msg or "Not implemented in forced mode." msg = msg or "Only available in manual mode."
self.Die(msg) self.Die(msg)
def Confirm(self, msg): def Confirm(self, msg):
...@@ -340,11 +347,9 @@ class Step(object): ...@@ -340,11 +347,9 @@ class Step(object):
if not os.path.exists(self._config[DOT_GIT_LOCATION]): if not os.path.exists(self._config[DOT_GIT_LOCATION]):
self.Die("This is not a git checkout, this script won't work for you.") self.Die("This is not a git checkout, this script won't work for you.")
# TODO(machenbach): Don't use EDITOR in forced mode as soon as script is
# well tested.
# Cancel if EDITOR is unset or not executable. # Cancel if EDITOR is unset or not executable.
if (not os.environ.get("EDITOR") or if (not self.IsForced() and (not os.environ.get("EDITOR") or
Command("which", os.environ["EDITOR"]) is None): Command("which", os.environ["EDITOR"]) is None)):
self.Die("Please set your EDITOR environment variable, you'll need it.") self.Die("Please set your EDITOR environment variable, you'll need it.")
def CommonPrepare(self): def CommonPrepare(self):
...@@ -413,9 +418,7 @@ class Step(object): ...@@ -413,9 +418,7 @@ class Step(object):
answer = "" answer = ""
while answer != "LGTM": while answer != "LGTM":
print "> ", print "> ",
# TODO(machenbach): Add default="LGTM" to avoid prompt when script is answer = self.ReadLine("LGTM" if self.IsForced() else None)
# well tested and when prepare push cl has TBR flag.
answer = self.ReadLine()
if answer != "LGTM": if answer != "LGTM":
print "That was not 'LGTM'." print "That was not 'LGTM'."
...@@ -423,7 +426,7 @@ class Step(object): ...@@ -423,7 +426,7 @@ class Step(object):
print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", " print("Applying the patch \"%s\" failed. Either type \"ABORT<Return>\", "
"or resolve the conflicts, stage *all* touched files with " "or resolve the conflicts, stage *all* touched files with "
"'git add', and type \"RESOLVED<Return>\"") "'git add', and type \"RESOLVED<Return>\"")
self.DieInForcedMode() self.DieNoManualMode()
answer = "" answer = ""
while answer != "RESOLVED": while answer != "RESOLVED":
if answer == "ABORT": if answer == "ABORT":
...@@ -449,9 +452,9 @@ class UploadStep(Step): ...@@ -449,9 +452,9 @@ class UploadStep(Step):
reviewer = self._options.r reviewer = self._options.r
else: else:
print "Please enter the email address of a V8 reviewer for your patch: ", print "Please enter the email address of a V8 reviewer for your patch: ",
self.DieInForcedMode("A reviewer must be specified in forced mode.") self.DieNoManualMode("A reviewer must be specified in forced mode.")
reviewer = self.ReadLine() reviewer = self.ReadLine()
force_flag = " -f" if self._options.f else "" force_flag = " -f" if not self.IsManual() else ""
args = "cl upload -r \"%s\" --send-mail%s" % (reviewer, force_flag) args = "cl upload -r \"%s\" --send-mail%s" % (reviewer, force_flag)
# TODO(machenbach): Check output in forced mode. Verify that all required # TODO(machenbach): Check output in forced mode. Verify that all required
# base files were uploaded, if not retry. # base files were uploaded, if not retry.
......
...@@ -157,9 +157,6 @@ class EditChangeLog(Step): ...@@ -157,9 +157,6 @@ class EditChangeLog(Step):
"entry, then edit its contents to your liking. When you're done, " "entry, then edit its contents to your liking. When you're done, "
"save the file and exit your EDITOR. ") "save the file and exit your EDITOR. ")
self.ReadLine(default="") self.ReadLine(default="")
# TODO(machenbach): Don't use EDITOR in forced mode as soon as script is
# well tested.
self.Editor(self.Config(CHANGELOG_ENTRY_FILE)) self.Editor(self.Config(CHANGELOG_ENTRY_FILE))
handle, new_changelog = tempfile.mkstemp() handle, new_changelog = tempfile.mkstemp()
os.close(handle) os.close(handle)
...@@ -214,7 +211,11 @@ class CommitLocal(Step): ...@@ -214,7 +211,11 @@ class CommitLocal(Step):
self._state["new_minor"], self._state["new_minor"],
self._state["new_build"])) self._state["new_build"]))
self.Persist("prep_commit_msg", prep_commit_msg) self.Persist("prep_commit_msg", prep_commit_msg)
if self.Git("commit -a -m \"%s\"" % prep_commit_msg) is None:
# Include optional TBR only in the git command. The persisted commit
# message is used for finding the commit again later.
review = "\n\nTBR=%s" % self._options.r if not self.IsManual() else ""
if self.Git("commit -a -m \"%s%s\"" % (prep_commit_msg, review)) is None:
self.Die("'git commit -a' failed.") self.Die("'git commit -a' failed.")
...@@ -364,7 +365,7 @@ class CommitSVN(Step): ...@@ -364,7 +365,7 @@ class CommitSVN(Step):
print("Sorry, grepping for the SVN revision failed. Please look for it " print("Sorry, grepping for the SVN revision failed. Please look for it "
"in the last command's output above and provide it manually (just " "in the last command's output above and provide it manually (just "
"the number, without the leading \"r\").") "the number, without the leading \"r\").")
self.DieInForcedMode("Can't prompt in forced mode.") self.DieNoManualMode("Can't prompt in forced mode.")
while not trunk_revision: while not trunk_revision:
print "> ", print "> ",
trunk_revision = self.ReadLine() trunk_revision = self.ReadLine()
...@@ -389,7 +390,7 @@ class CheckChromium(Step): ...@@ -389,7 +390,7 @@ class CheckChromium(Step):
def Run(self): def Run(self):
chrome_path = self._options.c chrome_path = self._options.c
if not chrome_path: if not chrome_path:
self.DieInForcedMode("Please specify the path to a Chromium checkout in " self.DieNoManualMode("Please specify the path to a Chromium checkout in "
"forced mode.") "forced mode.")
print ("Do you have a \"NewGit\" Chromium checkout and want " print ("Do you have a \"NewGit\" Chromium checkout and want "
"this script to automate creation of the roll CL? If yes, enter the " "this script to automate creation of the roll CL? If yes, enter the "
...@@ -457,12 +458,12 @@ class UploadCL(Step): ...@@ -457,12 +458,12 @@ class UploadCL(Step):
rev = self._options.r rev = self._options.r
else: else:
print "Please enter the email address of a reviewer for the roll CL: ", print "Please enter the email address of a reviewer for the roll CL: ",
self.DieInForcedMode("A reviewer must be specified in forced mode.") self.DieNoManualMode("A reviewer must be specified in forced mode.")
rev = self.ReadLine() rev = self.ReadLine()
args = "commit -am \"Update V8 to version %s.\n\nTBR=%s\"" % (ver, rev) args = "commit -am \"Update V8 to version %s.\n\nTBR=%s\"" % (ver, rev)
if self.Git(args) is None: if self.Git(args) is None:
self.Die("'git commit' failed.") self.Die("'git commit' failed.")
force_flag = " -f" if self._options.f else "" force_flag = " -f" if not self.IsManual() else ""
if self.Git("cl upload --send-mail%s" % force_flag, pipe=False) is None: if self.Git("cl upload --send-mail%s" % force_flag, pipe=False) is None:
self.Die("'git cl upload' failed, please try again.") self.Die("'git cl upload' failed, please try again.")
print "CL uploaded." print "CL uploaded."
...@@ -547,6 +548,9 @@ def BuildOptions(): ...@@ -547,6 +548,9 @@ def BuildOptions():
result.add_option("-l", "--last-push", dest="l", result.add_option("-l", "--last-push", dest="l",
help=("Manually specify the git commit ID " help=("Manually specify the git commit ID "
"of the last push to trunk.")) "of the last push to trunk."))
result.add_option("-m", "--manual", dest="m",
help="Prompt the user at every important step.",
default=False, action="store_true")
result.add_option("-r", "--reviewer", dest="r", result.add_option("-r", "--reviewer", dest="r",
help=("Specify the account name to be used for reviews.")) help=("Specify the account name to be used for reviews."))
result.add_option("-s", "--step", dest="s", result.add_option("-s", "--step", dest="s",
...@@ -559,11 +563,14 @@ def ProcessOptions(options): ...@@ -559,11 +563,14 @@ def ProcessOptions(options):
if options.s < 0: if options.s < 0:
print "Bad step number %d" % options.s print "Bad step number %d" % options.s
return False return False
if options.f and not options.r: if not options.m and not options.r:
print "A reviewer (-r) is required in forced mode." print "A reviewer (-r) is required in (semi-)automatic mode."
return False
if options.f and options.m:
print "Manual and forced mode cannot be combined."
return False return False
if options.f and not options.c: if not options.m and not options.c:
print "A chromium checkout (-c) is required in forced mode." print "A chromium checkout (-c) is required in (semi-)automatic mode."
return False return False
return True return True
......
...@@ -53,6 +53,20 @@ TEST_CONFIG = { ...@@ -53,6 +53,20 @@ TEST_CONFIG = {
} }
def MakeOptions(s=0, l=None, f=False, m=True, r=None, c=None):
"""Convenience wrapper."""
class Options(object):
pass
options = Options()
options.s = s
options.l = l
options.f = f
options.m = m
options.r = r
options.c = c
return options
class ToplevelTest(unittest.TestCase): class ToplevelTest(unittest.TestCase):
def testMakeComment(self): def testMakeComment(self):
self.assertEquals("# Line 1\n# Line 2\n#", self.assertEquals("# Line 1\n# Line 2\n#",
...@@ -260,12 +274,15 @@ class ScriptTest(unittest.TestCase): ...@@ -260,12 +274,15 @@ class ScriptTest(unittest.TestCase):
f.write("#define IS_CANDIDATE_VERSION 0\n") f.write("#define IS_CANDIDATE_VERSION 0\n")
return name return name
def MakeStep(self, step_class=Step, state=None): def MakeStep(self, step_class=Step, state=None, options=None):
"""Convenience wrapper.""" """Convenience wrapper."""
options = options or MakeOptions()
return MakeStep(step_class=step_class, number=0, state=state, return MakeStep(step_class=step_class, number=0, state=state,
config=TEST_CONFIG, options=None, side_effect_handler=self) config=TEST_CONFIG, options=options,
side_effect_handler=self)
def GitMock(self, cmd, args="", pipe=True): def GitMock(self, cmd, args="", pipe=True):
print "%s %s" % (cmd, args)
return self._git_mock.Call(args) return self._git_mock.Call(args)
def LogMock(self, cmd, args=""): def LogMock(self, cmd, args=""):
...@@ -555,7 +572,7 @@ class ScriptTest(unittest.TestCase): ...@@ -555,7 +572,7 @@ class ScriptTest(unittest.TestCase):
patch = FileToText(TEST_CONFIG[ PATCH_FILE]) patch = FileToText(TEST_CONFIG[ PATCH_FILE])
self.assertTrue(re.search(r"patch content", patch)) self.assertTrue(re.search(r"patch content", patch))
def _PushToTrunk(self, force=False): def _PushToTrunk(self, force=False, manual=False):
TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile() TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
TEST_CONFIG[VERSION_FILE] = self.MakeTempVersionFile() TEST_CONFIG[VERSION_FILE] = self.MakeTempVersionFile()
TEST_CONFIG[CHANGELOG_ENTRY_FILE] = self.MakeEmptyTempFile() TEST_CONFIG[CHANGELOG_ENTRY_FILE] = self.MakeEmptyTempFile()
...@@ -593,7 +610,8 @@ class ScriptTest(unittest.TestCase): ...@@ -593,7 +610,8 @@ class ScriptTest(unittest.TestCase):
self.assertTrue(re.search(r"#define PATCH_LEVEL\s+0", version)) self.assertTrue(re.search(r"#define PATCH_LEVEL\s+0", version))
self.assertTrue(re.search(r"#define IS_CANDIDATE_VERSION\s+0", version)) self.assertTrue(re.search(r"#define IS_CANDIDATE_VERSION\s+0", version))
force_flag = " -f" if force else "" force_flag = " -f" if not manual else ""
review_suffix = "\n\nTBR=reviewer@chromium.org" if not manual else ""
self.ExpectGit([ self.ExpectGit([
["status -s -uno", ""], ["status -s -uno", ""],
["status -s -b -uno", "## some_branch\n"], ["status -s -b -uno", "## some_branch\n"],
...@@ -610,7 +628,7 @@ class ScriptTest(unittest.TestCase): ...@@ -610,7 +628,7 @@ class ScriptTest(unittest.TestCase):
["log -1 rev1 --format=\"%B\"", "Text\nLOG=YES\nBUG=v8:321\nText\n"], ["log -1 rev1 --format=\"%B\"", "Text\nLOG=YES\nBUG=v8:321\nText\n"],
["log -1 rev1 --format=\"%an\"", "author1@chromium.org\n"], ["log -1 rev1 --format=\"%an\"", "author1@chromium.org\n"],
[("commit -a -m \"Prepare push to trunk. " [("commit -a -m \"Prepare push to trunk. "
"Now working on version 3.22.6.\""), "Now working on version 3.22.6.%s\"" % review_suffix),
" 2 files changed\n", " 2 files changed\n",
CheckPreparePush], CheckPreparePush],
["cl upload -r \"reviewer@chromium.org\" --send-mail%s" % force_flag, ["cl upload -r \"reviewer@chromium.org\" --send-mail%s" % force_flag,
...@@ -641,32 +659,33 @@ class ScriptTest(unittest.TestCase): ...@@ -641,32 +659,33 @@ class ScriptTest(unittest.TestCase):
["branch -D %s" % TEST_CONFIG[BRANCHNAME], ""], ["branch -D %s" % TEST_CONFIG[BRANCHNAME], ""],
["branch -D %s" % TEST_CONFIG[TRUNKBRANCH], ""], ["branch -D %s" % TEST_CONFIG[TRUNKBRANCH], ""],
]) ])
self.ExpectReadline([
"Y", # Confirm last push. # Expected keyboard input in manual mode:
"", # Open editor. if manual:
"Y", # Increment build number.
"reviewer@chromium.org", # V8 reviewer.
"LGTX", # Enter LGTM for V8 CL (wrong).
"LGTM", # Enter LGTM for V8 CL.
"Y", # Sanity check.
"reviewer@chromium.org", # Chromium reviewer.
])
if force:
# TODO(machenbach): The lgtm for the prepare push is just temporary.
# There should be no user input in "force" mode.
self.ExpectReadline([ self.ExpectReadline([
"Y", # Confirm last push.
"", # Open editor.
"Y", # Increment build number.
"reviewer@chromium.org", # V8 reviewer.
"LGTX", # Enter LGTM for V8 CL (wrong).
"LGTM", # Enter LGTM for V8 CL. "LGTM", # Enter LGTM for V8 CL.
"Y", # Sanity check.
"reviewer@chromium.org", # Chromium reviewer.
]) ])
class Options( object ): # Expected keyboard input in semi-automatic mode:
pass if not manual and not force:
self.ExpectReadline([
"LGTM", # Enter LGTM for V8 CL.
])
options = Options() # No keyboard input in forced mode:
options.s = 0 if force:
options.l = None self.ExpectReadline([])
options.f = force
options.r = "reviewer@chromium.org" if force else None options = MakeOptions(f=force, m=manual,
options.c = TEST_CONFIG[CHROMIUM] r="reviewer@chromium.org" if not manual else None,
c = TEST_CONFIG[CHROMIUM])
RunPushToTrunk(TEST_CONFIG, options, self) RunPushToTrunk(TEST_CONFIG, options, self)
deps = FileToText(TEST_CONFIG[DEPS_FILE]) deps = FileToText(TEST_CONFIG[DEPS_FILE])
...@@ -681,7 +700,10 @@ class ScriptTest(unittest.TestCase): ...@@ -681,7 +700,10 @@ class ScriptTest(unittest.TestCase):
# since the git command that merges to the bleeding edge branch is mocked # since the git command that merges to the bleeding edge branch is mocked
# out. # out.
def testPushToTrunk(self): def testPushToTrunkManual(self):
self._PushToTrunk(manual=True)
def testPushToTrunkSemiAutomatic(self):
self._PushToTrunk() self._PushToTrunk()
def testPushToTrunkForced(self): def testPushToTrunkForced(self):
...@@ -690,9 +712,6 @@ class ScriptTest(unittest.TestCase): ...@@ -690,9 +712,6 @@ class ScriptTest(unittest.TestCase):
def testAutoRoll(self): def testAutoRoll(self):
TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile() TEST_CONFIG[DOT_GIT_LOCATION] = self.MakeEmptyTempFile()
# TODO(machenbach): Get rid of the editor check in automatic mode.
os.environ["EDITOR"] = "vi"
self.ExpectReadURL([ self.ExpectReadURL([
["https://v8-status.appspot.com/lkgr", Exception("Network problem")], ["https://v8-status.appspot.com/lkgr", Exception("Network problem")],
["https://v8-status.appspot.com/lkgr", "100"], ["https://v8-status.appspot.com/lkgr", "100"],
...@@ -705,14 +724,7 @@ class ScriptTest(unittest.TestCase): ...@@ -705,14 +724,7 @@ class ScriptTest(unittest.TestCase):
["svn log -1 --oneline", "r101 | Text"], ["svn log -1 --oneline", "r101 | Text"],
]) ])
# TODO(machenbach): Make a convenience wrapper for this. auto_roll.RunAutoRoll(TEST_CONFIG, MakeOptions(m=False, f=True), self)
class Options( object ):
pass
options = Options()
options.s = 0
auto_roll.RunAutoRoll(TEST_CONFIG, options, self)
self.assertEquals("100", self.MakeStep().Restore("lkgr")) self.assertEquals("100", self.MakeStep().Restore("lkgr"))
self.assertEquals("101", self.MakeStep().Restore("latest")) self.assertEquals("101", self.MakeStep().Restore("latest"))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment