Refactoring: Make script dependencies more object-oriented in push and merge scripts.
authormachenbach@chromium.org <machenbach@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 4 Mar 2014 23:27:27 +0000 (23:27 +0000)
committermachenbach@chromium.org <machenbach@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 4 Mar 2014 23:27:27 +0000 (23:27 +0000)
- Connect the top-level scripts via inheritance and remove top-level functions
- Options and option processing can be extended from base to subclass script

This CL is split off from https://codereview.chromium.org/173983002/

BUG=
R=ulan@chromium.org

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@19664 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

tools/push-to-trunk/auto_roll.py
tools/push-to-trunk/common_includes.py
tools/push-to-trunk/merge_to_branch.py
tools/push-to-trunk/push_to_trunk.py
tools/push-to-trunk/test_scripts.py

index e90102d..216caff 100755 (executable)
@@ -35,8 +35,6 @@ import urllib
 
 from common_includes import *
 import push_to_trunk
-from push_to_trunk import PushToTrunkOptions
-from push_to_trunk import RunPushToTrunk
 
 SETTINGS_LOCATION = "SETTINGS_LOCATION"
 
@@ -47,16 +45,6 @@ CONFIG = {
 }
 
 
-class AutoRollOptions(CommonOptions):
-  def __init__(self, options):
-    super(AutoRollOptions, self).__init__(options)
-    self.requires_editor = False
-    self.status_password = options.status_password
-    self.chromium = options.chromium
-    self.push = getattr(options, 'push', False)
-    self.author = getattr(options, 'author', None)
-
-
 class Preparation(Step):
   MESSAGE = "Preparation."
 
@@ -151,12 +139,11 @@ class PushToTrunk(Step):
       try:
         if self._options.push:
           self._side_effect_handler.Call(
-              RunPushToTrunk,
-              push_to_trunk.CONFIG,
-              PushToTrunkOptions.MakeForcedOptions(self._options.author,
-                                                   self._options.reviewer,
-                                                   self._options.chromium),
-              self._side_effect_handler)
+              PushToTrunk(push_to_trunk.CONFIG, self._side_effect_handler).Run,
+              ["-a", self._options.author,
+               "-c", self._options.chromium,
+               "-r", self._options.reviewer,
+               "-f"])
       finally:
         self.PushTreeStatus(self["tree_message"])
     else:
@@ -164,49 +151,35 @@ class PushToTrunk(Step):
             % (latest, lkgr))
 
 
-def RunAutoRoll(config,
-                options,
-                side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
-  step_classes = [
-    Preparation,
-    CheckAutoRollSettings,
-    CheckTreeStatus,
-    FetchLatestRevision,
-    CheckLastPush,
-    FetchLKGR,
-    PushToTrunk,
-  ]
-  RunScript(step_classes, config, options, side_effect_handler)
-
-
-def BuildOptions():
-  parser = argparse.ArgumentParser()
-  parser.add_argument("-a", "--author",
-                      help="The author email used for rietveld.")
-  parser.add_argument("-c", "--chromium",
-                      help=("The path to your Chromium src/ directory to "
-                            "automate the V8 roll."))
-  parser.add_argument("-p", "--push",
-                      help="Push to trunk if possible. Dry run if unspecified.",
-                      default=False, action="store_true")
-  parser.add_argument("-r", "--reviewer",
-                      help="The account name to be used for reviews.")
-  parser.add_argument("-s", "--step",
-                      help="Specify the step where to start work. Default: 0.",
-                      default=0, type=int)
-  parser.add_argument("--status-password",
-                      help="A file with the password to the status app.")
-  return parser
-
-
-def Main():
-  parser = BuildOptions()
-  options = parser.parse_args()
-  if not options.author or not options.chromium or not options.reviewer:
-    print "You need to specify author, chromium src location and reviewer."
-    parser.print_help()
-    return 1
-  RunAutoRoll(CONFIG, AutoRollOptions(options))
+class AutoRoll(ScriptsBase):
+  def _PrepareOptions(self, parser):
+    parser.add_argument("-c", "--chromium", required=True,
+                        help=("The path to your Chromium src/ "
+                              "directory to automate the V8 roll."))
+    parser.add_argument("-p", "--push",
+                        help="Push to trunk. Dry run if unspecified.",
+                        default=False, action="store_true")
+    parser.add_argument("--status-password",
+                        help="A file with the password to the status app.")
+
+  def _ProcessOptions(self, options):
+    if not options.author or not options.reviewer:
+      print "You need to specify author and reviewer."
+      return False
+    options.requires_editor = False
+    return True
+
+  def _Steps(self):
+    return [
+      Preparation,
+      CheckAutoRollSettings,
+      CheckTreeStatus,
+      FetchLatestRevision,
+      CheckLastPush,
+      FetchLKGR,
+      PushToTrunk,
+    ]
+
 
 if __name__ == "__main__":
-  sys.exit(Main())
+  sys.exit(AutoRoll(CONFIG).Run())
index 9ebf3d2..4117a68 100644 (file)
@@ -26,6 +26,7 @@
 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
+import argparse
 import datetime
 import json
 import os
@@ -226,18 +227,6 @@ class GitFailedException(Exception):
   pass
 
 
-class CommonOptions(object):
-  def __init__(self, options, manual=True):
-    self.requires_editor = True
-    self.wait_for_lgtm = True
-    self.step = options.step
-    self.force_readline_defaults = not manual
-    self.force_upload = not manual
-    self.manual = manual
-    self.reviewer = getattr(options, 'reviewer', "")
-    self.author = getattr(options, 'author', "")
-
-
 class Step(GitRecipesMixin):
   def __init__(self, text, requires, number, config, state, options, handler):
     self._text = text
@@ -251,7 +240,6 @@ class Step(GitRecipesMixin):
     assert self._config is not None
     assert self._state is not None
     assert self._side_effect_handler is not None
-    assert isinstance(options, CommonOptions)
 
   def __getitem__(self, key):
     # Convenience method to allow direct [] access on step classes for
@@ -502,18 +490,81 @@ def MakeStep(step_class=Step, number=0, state=None, config=None,
                       handler=side_effect_handler)
 
 
-def RunScript(step_classes,
-              config,
-              options,
-              side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
-  state_file = "%s-state.json" % config[PERSISTFILE_BASENAME]
-  if options.step == 0 and os.path.exists(state_file):
-    os.remove(state_file)
-  state = {}
-  steps = []
-  for (number, step_class) in enumerate(step_classes):
-    steps.append(MakeStep(step_class, number, state, config,
-                          options, side_effect_handler))
-
-  for step in steps[options.step:]:
-    step.Run()
+class ScriptsBase(object):
+  # TODO(machenbach): Move static config here.
+  def __init__(self, config, side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER,
+               state=None):
+    self._config = config
+    self._side_effect_handler = side_effect_handler
+    self._state = state if state is not None else {}
+
+  def _Description(self):
+    return None
+
+  def _PrepareOptions(self, parser):
+    pass
+
+  def _ProcessOptions(self, options):
+    return True
+
+  def _Steps(self):
+    raise Exception("Not implemented.")
+
+  def MakeOptions(self, args=None):
+    parser = argparse.ArgumentParser(description=self._Description())
+    parser.add_argument("-a", "--author", default="",
+                        help="The author email used for rietveld.")
+    parser.add_argument("-r", "--reviewer", default="",
+                        help="The account name to be used for reviews.")
+    parser.add_argument("-s", "--step",
+                      help="Specify the step where to start work. Default: 0.",
+                      default=0, type=int)
+
+    self._PrepareOptions(parser)
+
+    if args is None:
+      options = parser.parse_args()
+    else:
+      options = parser.parse_args(args)
+
+    # Process common options.
+    if options.step < 0:
+      print "Bad step number %d" % options.step
+      parser.print_help()
+      return None
+
+    # Defaults for options, common to all scripts.
+    options.manual = getattr(options, "manual", True)
+    options.force = getattr(options, "force", False)
+
+    # Derived options.
+    options.requires_editor = not options.force
+    options.wait_for_lgtm = not options.force
+    options.force_readline_defaults = not options.manual
+    options.force_upload = not options.manual
+
+    # Process script specific options.
+    if not self._ProcessOptions(options):
+      parser.print_help()
+      return None
+    return options
+
+  def RunSteps(self, step_classes, args=None):
+    options = self.MakeOptions(args)
+    if not options:
+      return 1
+
+    state_file = "%s-state.json" % self._config[PERSISTFILE_BASENAME]
+    if options.step == 0 and os.path.exists(state_file):
+      os.remove(state_file)
+
+    steps = []
+    for (number, step_class) in enumerate(step_classes):
+      steps.append(MakeStep(step_class, number, self._state, self._config,
+                            options, self._side_effect_handler))
+    for step in steps[options.step:]:
+      step.Run()
+    return 0
+
+  def Run(self, args=None):
+    return self.RunSteps(self._Steps(), args)
index 9f7684f..4c99d9b 100755 (executable)
@@ -50,26 +50,12 @@ CONFIG = {
 }
 
 
-class MergeToBranchOptions(CommonOptions):
-  def __init__(self, options):
-    super(MergeToBranchOptions, self).__init__(options, True)
-    self.requires_editor = True
-    self.wait_for_lgtm = True
-    self.delete_sentinel = options.f
-    self.message = getattr(options, "message", "")
-    self.revert = getattr(options, "r", False)
-    self.revert_bleeding_edge = getattr(options, "revert_bleeding_edge", False)
-    self.patch = getattr(options, "p", "")
-    self.branch = options.branch
-    self.revisions = options.revisions
-
-
 class Preparation(Step):
   MESSAGE = "Preparation."
 
   def RunStep(self):
     if os.path.exists(self.Config(ALREADY_MERGING_SENTINEL_FILE)):
-      if self._options.delete_sentinel:
+      if self._options.force:
         os.remove(self.Config(ALREADY_MERGING_SENTINEL_FILE))
       elif self._options.step == 0:
         self.Die("A merge is already in progress")
@@ -288,76 +274,58 @@ class CleanUp(Step):
         print "patches: %s" % self["revision_list"]
 
 
-def RunMergeToBranch(config,
-                     options,
-                     side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
-  step_classes = [
-    Preparation,
-    CreateBranch,
-    SearchArchitecturePorts,
-    FindGitRevisions,
-    ApplyPatches,
-    PrepareVersion,
-    IncrementVersion,
-    CommitLocal,
-    UploadStep,
-    CommitRepository,
-    PrepareSVN,
-    TagRevision,
-    CleanUp,
-  ]
-
-  RunScript(step_classes, config, options, side_effect_handler)
-
-
-def BuildOptions():
-  parser = argparse.ArgumentParser(
-      description=("Performs the necessary steps to merge revisions from "
-                   "bleeding_edge to other branches, including trunk."))
-  group = parser.add_mutually_exclusive_group(required=True)
-  group.add_argument("--branch", help="The branch to merge to.")
-  group.add_argument("-R", "--revert-bleeding-edge",
-                     help="Revert specified patches from bleeding edge.",
-                     default=False, action="store_true")
-  parser.add_argument("revisions", nargs="*",
-                      help="The revisions to merge.")
-  parser.add_argument("-a", "--author", default="",
-                       help="The author email used for rietveld.")
-  parser.add_argument("-f",
-                      help="Delete sentinel file.",
-                      default=False, action="store_true")
-  parser.add_argument("-m", "--message",
-                      help="A commit message for the patch.")
-  parser.add_argument("-r", "--revert",
-                      help="Revert specified patches.",
-                      default=False, action="store_true")
-  parser.add_argument("-p", "--patch", dest="p",
-                      help="A patch file to apply as part of the merge.")
-  parser.add_argument("-s", "--step",
-                      help="The step where to start work. Default: 0.",
-                      default=0, type=int)
-  return parser
-
-
-def ProcessOptions(options):
-  # TODO(machenbach): Add a test that covers revert from bleeding_edge
-  if len(options.revisions) < 1:
-    if not options.patch:
-      print "Either a patch file or revision numbers must be specified"
-      return False
-    if not options.message:
-      print "You must specify a merge comment if no patches are specified"
-      return False
-  return True
-
-
-def Main():
-  parser = BuildOptions()
-  options = parser.parse_args()
-  if not ProcessOptions(options):
-    parser.print_help()
-    return 1
-  RunMergeToBranch(CONFIG, MergeToBranchOptions(options))
+class MergeToBranch(ScriptsBase):
+  def _Description(self):
+    return ("Performs the necessary steps to merge revisions from "
+            "bleeding_edge to other branches, including trunk.")
+
+  def _PrepareOptions(self, parser):
+    group = parser.add_mutually_exclusive_group(required=True)
+    group.add_argument("--branch", help="The branch to merge to.")
+    group.add_argument("-R", "--revert-bleeding-edge",
+                       help="Revert specified patches from bleeding edge.",
+                       default=False, action="store_true")
+    parser.add_argument("revisions", nargs="*",
+                        help="The revisions to merge.")
+    parser.add_argument("-f", "--force",
+                        help="Delete sentinel file.",
+                        default=False, action="store_true")
+    parser.add_argument("-m", "--message",
+                        help="A commit message for the patch.")
+    parser.add_argument("--revert",
+                        help="Revert specified patches.",
+                        default=False, action="store_true")
+    parser.add_argument("-p", "--patch",
+                        help="A patch file to apply as part of the merge.")
+
+  def _ProcessOptions(self, options):
+    # TODO(machenbach): Add a test that covers revert from bleeding_edge
+    if len(options.revisions) < 1:
+      if not options.patch:
+        print "Either a patch file or revision numbers must be specified"
+        return False
+      if not options.message:
+        print "You must specify a merge comment if no patches are specified"
+        return False
+    return True
+
+  def _Steps(self):
+    return [
+      Preparation,
+      CreateBranch,
+      SearchArchitecturePorts,
+      FindGitRevisions,
+      ApplyPatches,
+      PrepareVersion,
+      IncrementVersion,
+      CommitLocal,
+      UploadStep,
+      CommitRepository,
+      PrepareSVN,
+      TagRevision,
+      CleanUp,
+    ]
+
 
 if __name__ == "__main__":
-  sys.exit(Main())
+  sys.exit(MergeToBranch(CONFIG).Run())
index d227a50..111578a 100755 (executable)
@@ -55,35 +55,6 @@ PUSH_MESSAGE_SUFFIX = " (based on bleeding_edge revision r%d)"
 PUSH_MESSAGE_RE = re.compile(r".* \(based on bleeding_edge revision r(\d+)\)$")
 
 
-class PushToTrunkOptions(CommonOptions):
-  @staticmethod
-  def MakeForcedOptions(author, reviewer, chrome_path):
-    """Convenience wrapper."""
-    class Options(object):
-      pass
-    options = Options()
-    options.step = 0
-    options.last_push = None
-    options.last_bleeding_edge = None
-    options.force = True
-    options.manual = False
-    options.chromium = chrome_path
-    options.reviewer = reviewer
-    options.author = author
-    return PushToTrunkOptions(options)
-
-  def __init__(self, options):
-    super(PushToTrunkOptions, self).__init__(options, options.manual)
-    self.requires_editor = not options.force
-    self.wait_for_lgtm = not options.force
-    self.tbr_commit = not options.manual
-    self.last_push = options.last_push
-    self.reviewer = options.reviewer
-    self.chromium = options.chromium
-    self.last_bleeding_edge = getattr(options, 'last_bleeding_edge', None)
-    self.author = getattr(options, 'author', None)
-
-
 class Preparation(Step):
   MESSAGE = "Preparation."
 
@@ -511,90 +482,67 @@ class CleanUp(Step):
       self.GitDeleteBranch(self.Config(TRUNKBRANCH))
 
 
-def RunPushToTrunk(config,
-                   options,
-                   side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER):
-  step_classes = [
-    Preparation,
-    FreshBranch,
-    DetectLastPush,
-    PrepareChangeLog,
-    EditChangeLog,
-    IncrementVersion,
-    CommitLocal,
-    UploadStep,
-    CommitRepository,
-    StragglerCommits,
-    SquashCommits,
-    NewBranch,
-    ApplyChanges,
-    SetVersion,
-    CommitTrunk,
-    SanityCheck,
-    CommitSVN,
-    TagRevision,
-    CheckChromium,
-    SwitchChromium,
-    UpdateChromiumCheckout,
-    UploadCL,
-    SwitchV8,
-    CleanUp,
-  ]
-
-  RunScript(step_classes, config, options, side_effect_handler)
-
-
-def BuildOptions():
-  parser = argparse.ArgumentParser()
-  group = parser.add_mutually_exclusive_group()
-  group.add_argument("-f", "--force",
-                     help="Don't prompt the user.",
-                     default=False, action="store_true")
-  group.add_argument("-m", "--manual",
-                     help="Prompt the user at every important step.",
-                     default=False, action="store_true")
-  parser.add_argument("-a", "--author",
-                      help="The author email used for rietveld.")
-  parser.add_argument("-b", "--last-bleeding-edge",
-                      help=("The git commit ID of the last bleeding edge "
-                            "revision that was pushed to trunk. This is used "
-                            "for the auto-generated ChangeLog entry."))
-  parser.add_argument("-c", "--chromium",
-                      help=("The path to your Chromium src/ directory to "
-                            "automate the V8 roll."))
-  parser.add_argument("-l", "--last-push",
-                      help="The git commit ID of the last push to trunk.")
-  parser.add_argument("-r", "--reviewer",
-                      help="The account name to be used for reviews.")
-  parser.add_argument("-s", "--step",
-                      help="The step where to start work. Default: 0.",
-                      default=0, type=int)
-  return parser
-
-
-def ProcessOptions(options):
-  if options.step < 0:
-    print "Bad step number %d" % options.step
-    return False
-  if not options.manual and not options.reviewer:
-    print "A reviewer (-r) is required in (semi-)automatic mode."
-    return False
-  if not options.manual and not options.chromium:
-    print "A chromium checkout (-c) is required in (semi-)automatic mode."
-    return False
-  if not options.manual and not options.author:
-    print "Specify your chromium.org email with -a in (semi-)automatic mode."
-    return False
-  return True
-
-
-def Main():
-  parser = BuildOptions()
-  options = parser.parse_args()
-  if not ProcessOptions(options):
-    parser.print_help()
-    return 1
-  RunPushToTrunk(CONFIG, PushToTrunkOptions(options))
+class PushToTrunk(ScriptsBase):
+  def _PrepareOptions(self, parser):
+    group = parser.add_mutually_exclusive_group()
+    group.add_argument("-f", "--force",
+                      help="Don't prompt the user.",
+                      default=False, action="store_true")
+    group.add_argument("-m", "--manual",
+                      help="Prompt the user at every important step.",
+                      default=False, action="store_true")
+    parser.add_argument("-b", "--last-bleeding-edge",
+                        help=("The git commit ID of the last bleeding edge "
+                              "revision that was pushed to trunk. This is "
+                              "used for the auto-generated ChangeLog entry."))
+    parser.add_argument("-c", "--chromium",
+                        help=("The path to your Chromium src/ "
+                              "directory to automate the V8 roll."))
+    parser.add_argument("-l", "--last-push",
+                        help="The git commit ID of the last push to trunk.")
+
+  def _ProcessOptions(self, options):
+    if not options.manual and not options.reviewer:
+      print "A reviewer (-r) is required in (semi-)automatic mode."
+      return False
+    if not options.manual and not options.chromium:
+      print "A chromium checkout (-c) is required in (semi-)automatic mode."
+      return False
+    if not options.manual and not options.author:
+      print "Specify your chromium.org email with -a in (semi-)automatic mode."
+      return False
+
+    options.tbr_commit = not options.manual
+    return True
+
+  def _Steps(self):
+    return [
+      Preparation,
+      FreshBranch,
+      DetectLastPush,
+      PrepareChangeLog,
+      EditChangeLog,
+      IncrementVersion,
+      CommitLocal,
+      UploadStep,
+      CommitRepository,
+      StragglerCommits,
+      SquashCommits,
+      NewBranch,
+      ApplyChanges,
+      SetVersion,
+      CommitTrunk,
+      SanityCheck,
+      CommitSVN,
+      TagRevision,
+      CheckChromium,
+      SwitchChromium,
+      UpdateChromiumCheckout,
+      UploadCL,
+      SwitchV8,
+      CleanUp,
+    ]
+
 
 if __name__ == "__main__":
-  sys.exit(Main())
+  sys.exit(PushToTrunk(CONFIG).Run())
index 711023f..eaba8b3 100644 (file)
@@ -32,7 +32,6 @@ import traceback
 import unittest
 
 import auto_roll
-from auto_roll import AutoRollOptions
 from auto_roll import CheckLastPush
 from auto_roll import FetchLatestRevision
 from auto_roll import SETTINGS_LOCATION
@@ -72,25 +71,6 @@ AUTO_ROLL_ARGS = [
 ]
 
 
-def MakeOptions(s=0, l=None, f=False, m=True, r=None, c=None, a=None,
-                status_password=None, revert_bleeding_edge=None, p=None):
-  """Convenience wrapper."""
-  class Options(object):
-      pass
-  options = Options()
-  options.step = s
-  options.last_push = l
-  options.force = f
-  options.manual = m
-  options.reviewer = r
-  options.chromium = c
-  options.author = a
-  options.push = p
-  options.status_password = status_password
-  options.revert_bleeding_edge = revert_bleeding_edge
-  return options
-
-
 class ToplevelTest(unittest.TestCase):
   def testMakeComment(self):
     self.assertEquals("#   Line 1\n#   Line 2\n#",
@@ -302,13 +282,17 @@ class ScriptTest(unittest.TestCase):
       f.write("#define IS_CANDIDATE_VERSION 0\n")
     return name
 
-  def MakeStep(self, step_class=Step, state=None, options=None):
+  def MakeStep(self):
+    """Convenience wrapper."""
+    options = ScriptsBase(TEST_CONFIG, self, self._state).MakeOptions([])
+    return MakeStep(step_class=Step, state=self._state,
+                    config=TEST_CONFIG, side_effect_handler=self,
+                    options=options)
+
+  def RunStep(self, script=PushToTrunk, step_class=Step, args=None):
     """Convenience wrapper."""
-    options = options or CommonOptions(MakeOptions())
-    state = state if state is not None else self._state
-    return MakeStep(step_class=step_class, number=0, state=state,
-                    config=TEST_CONFIG, options=options,
-                    side_effect_handler=self)
+    args = args or ["-m"]
+    return script(TEST_CONFIG, self, self._state).RunSteps([step_class], args)
 
   def GitMock(self, cmd, args="", pipe=True):
     print "%s %s" % (cmd, args)
@@ -490,7 +474,7 @@ class ScriptTest(unittest.TestCase):
     ])
 
     self._state["last_push_bleeding_edge"] = "1234"
-    self.MakeStep(PrepareChangeLog).Run()
+    self.RunStep(PushToTrunk, PrepareChangeLog)
 
     actual_cl = FileToText(TEST_CONFIG[CHANGELOG_ENTRY_FILE])
 
@@ -537,7 +521,7 @@ class ScriptTest(unittest.TestCase):
       "",  # Open editor.
     ])
 
-    self.MakeStep(EditChangeLog).Run()
+    self.RunStep(PushToTrunk, EditChangeLog)
 
     self.assertEquals("New\n        Lines\n\n\n        Original CL",
                       FileToText(TEST_CONFIG[CHANGELOG_FILE]))
@@ -550,7 +534,7 @@ class ScriptTest(unittest.TestCase):
       "Y",  # Increment build number.
     ])
 
-    self.MakeStep(IncrementVersion).Run()
+    self.RunStep(PushToTrunk, IncrementVersion)
 
     self.assertEquals("3", self._state["new_major"])
     self.assertEquals("22", self._state["new_minor"])
@@ -586,7 +570,7 @@ class ScriptTest(unittest.TestCase):
     self._state["prepare_commit_hash"] = "hash1"
     self._state["date"] = "1999-11-11"
 
-    self.MakeStep(SquashCommits).Run()
+    self.RunStep(PushToTrunk, SquashCommits)
     self.assertEquals(FileToText(TEST_CONFIG[COMMITMSG_FILE]), expected_msg)
 
     patch = FileToText(TEST_CONFIG[ PATCH_FILE])
@@ -751,8 +735,7 @@ Performance and stability improvements on all platforms.""", commit)
     if force: args.append("-f")
     if manual: args.append("-m")
     else: args += ["-r", "reviewer@chromium.org"]
-    options = push_to_trunk.BuildOptions().parse_args(args)
-    RunPushToTrunk(TEST_CONFIG, PushToTrunkOptions(options), self)
+    PushToTrunk(TEST_CONFIG, self).Run(args)
 
     deps = FileToText(TEST_CONFIG[DEPS_FILE])
     self.assertTrue(re.search("\"v8_revision\": \"123456\"", deps))
@@ -781,9 +764,10 @@ Performance and stability improvements on all platforms.""", commit)
       ["svn log -1 --oneline ChangeLog", "r99 | Prepare push to trunk..."],
     ])
 
-    state = {}
-    self.MakeStep(FetchLatestRevision, state=state).Run()
-    self.assertRaises(Exception, self.MakeStep(CheckLastPush, state=state).Run)
+    self.RunStep(auto_roll.AutoRoll, FetchLatestRevision, AUTO_ROLL_ARGS)
+    self.assertRaises(Exception, lambda: self.RunStep(auto_roll.AutoRoll,
+                                                      CheckLastPush,
+                                                      AUTO_ROLL_ARGS))
 
   def testAutoRoll(self):
     password = self.MakeEmptyTempFile()
@@ -816,9 +800,8 @@ Performance and stability improvements on all platforms.""", commit)
       ["svn find-rev push_hash", "65"],
     ])
 
-    options = auto_roll.BuildOptions().parse_args(
+    auto_roll.AutoRoll(TEST_CONFIG, self).Run(
         AUTO_ROLL_ARGS + ["--status-password", password])
-    auto_roll.RunAutoRoll(TEST_CONFIG, AutoRollOptions(options), self)
 
     state = json.loads(FileToText("%s-state.json"
                                   % TEST_CONFIG[PERSISTFILE_BASENAME]))
@@ -839,9 +822,8 @@ Performance and stability improvements on all platforms.""", commit)
       ["svn fetch", ""],
     ])
 
-    options = auto_roll.BuildOptions().parse_args(AUTO_ROLL_ARGS)
     def RunAutoRoll():
-      auto_roll.RunAutoRoll(TEST_CONFIG, AutoRollOptions(options), self)
+      auto_roll.AutoRoll(TEST_CONFIG, self).Run(AUTO_ROLL_ARGS)
     self.assertRaises(Exception, RunAutoRoll)
 
   def testAutoRollStoppedByTreeStatus(self):
@@ -859,9 +841,8 @@ Performance and stability improvements on all platforms.""", commit)
       ["svn fetch", ""],
     ])
 
-    options = auto_roll.BuildOptions().parse_args(AUTO_ROLL_ARGS)
     def RunAutoRoll():
-      auto_roll.RunAutoRoll(TEST_CONFIG, AutoRollOptions(options), self)
+      auto_roll.AutoRoll(TEST_CONFIG, self).Run(AUTO_ROLL_ARGS)
     self.assertRaises(Exception, RunAutoRoll)
 
   def testMergeToBranch(self):
@@ -982,24 +963,19 @@ LOG=N
     # ports of r12345. r56789 is the MIPS port of r34567.
     args = ["-f", "-p", extra_patch, "--branch", "trunk", "12345", "23456",
             "34567"]
-    options = merge_to_branch.BuildOptions().parse_args(args)
-    self.assertTrue(merge_to_branch.ProcessOptions(options))
 
     # The first run of the script stops because of the svn being down.
     self.assertRaises(GitFailedException,
-        lambda: RunMergeToBranch(TEST_CONFIG,
-                                 MergeToBranchOptions(options),
-                                 self))
+        lambda: MergeToBranch(TEST_CONFIG, self).Run(args))
 
     # Test that state recovery after restarting the script works.
-    options.step = 3
-    RunMergeToBranch(TEST_CONFIG, MergeToBranchOptions(options), self)
+    args += ["-s", "3"]
+    MergeToBranch(TEST_CONFIG, self).Run(args)
 
 
 class SystemTest(unittest.TestCase):
   def testReload(self):
     step = MakeStep(step_class=PrepareChangeLog, number=0, state={}, config={},
-                    options=CommonOptions(MakeOptions()),
                     side_effect_handler=DEFAULT_SIDE_EFFECT_HANDLER)
     body = step.Reload(
 """------------------------------------------------------------------------