cros_mark_{chrome_,}as_stable: relocate to scripts/

We don't import these as modules, just execute them directly, so move
them to the scripts/ subdir.

BUG=None
TEST=`./buildbot/run_tests` passes
TEST=`cbuildbot arm-tegra2-paladin` with manual uprev works
TEST=`cbuildbot x86-generic-tot-chrome-pfq-informational` passes

Change-Id: I33b064aafa06b506022ba930c953f625bbb13795
Reviewed-on: https://gerrit.chromium.org/gerrit/23569
Commit-Ready: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Tested-by: Mike Frysinger <vapier@chromium.org>
diff --git a/scripts/cros_mark_as_stable.py b/scripts/cros_mark_as_stable.py
new file mode 100644
index 0000000..beebccd
--- /dev/null
+++ b/scripts/cros_mark_as_stable.py
@@ -0,0 +1,329 @@
+#!/usr/bin/python
+
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""This module uprevs a given package's ebuild to the next revision."""
+
+import multiprocessing
+import optparse
+import os
+import sys
+
+from chromite.buildbot import constants
+from chromite.buildbot import cbuildbot_background as background
+from chromite.buildbot import portage_utilities
+from chromite.lib import cros_build_lib
+
+
+# TODO(sosa): Remove during OO refactor.
+VERBOSE = False
+
+# Dictionary of valid commands with usage information.
+COMMAND_DICTIONARY = {
+                        'commit':
+                          'Marks given ebuilds as stable locally',
+                        'push':
+                          'Pushes previous marking of ebuilds to remote repo',
+                      }
+
+
+# ======================= Global Helper Functions ========================
+
+
+def _Print(message):
+  """Verbose print function."""
+  if VERBOSE:
+    cros_build_lib.Info(message)
+
+
+def CleanStalePackages(boards, package_atoms):
+  """Cleans up stale package info from a previous build.
+  Args:
+    boards: Boards to clean the packages from.
+    package_atoms: The actual package atom to unmerge.
+  """
+  if package_atoms:
+    cros_build_lib.Info('Cleaning up stale packages %s.' % package_atoms)
+
+  # First unmerge all the packages for a board, then eclean it.
+  # We need these two steps to run in order (unmerge/eclean),
+  # but we can let all the boards run in parallel.
+  def _CleanStalePackages(board):
+    if board:
+      suffix = '-' + board
+      runcmd = cros_build_lib.RunCommand
+    else:
+      suffix = ''
+      runcmd = cros_build_lib.SudoRunCommand
+
+    if package_atoms:
+      runcmd(['emerge' + suffix, '-q', '--unmerge'] + package_atoms);
+    runcmd(['eclean' + suffix, '-d', 'packages'],
+           redirect_stdout=True, redirect_stderr=True)
+
+  tasks = []
+  for board in boards:
+    tasks.append([board])
+  tasks.append([None])
+
+  background.RunTasksInProcessPool(_CleanStalePackages, tasks)
+
+
+# TODO(build): This code needs to be gutted and rebased to cros_build_lib.
+def _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
+  """Returns true if there are local commits."""
+  current_branch = cros_build_lib.GetCurrentBranch(cwd)
+
+  if current_branch != stable_branch:
+    return False
+  output = cros_build_lib.RunGitCommand(
+      cwd, ['rev-parse', 'HEAD', tracking_branch]).output.split()
+  return output[0] != output[1]
+
+
+def _CheckSaneArguments(package_list, command, options):
+  """Checks to make sure the flags are sane.  Dies if arguments are not sane."""
+  if not command in COMMAND_DICTIONARY.keys():
+    _PrintUsageAndDie('%s is not a valid command' % command)
+  if not options.packages and command == 'commit' and not options.all:
+    _PrintUsageAndDie('Please specify at least one package')
+  if not options.boards and command == 'commit':
+    _PrintUsageAndDie('Please specify a board')
+  if not os.path.isdir(options.srcroot):
+    _PrintUsageAndDie('srcroot is not a valid path')
+  options.srcroot = os.path.abspath(options.srcroot)
+
+
+def _PrintUsageAndDie(error_message=''):
+  """Prints optional error_message the usage and returns an error exit code."""
+  command_usage = 'Commands: \n'
+  # Add keys and usage information from dictionary.
+  commands = sorted(COMMAND_DICTIONARY.keys())
+  for command in commands:
+    command_usage += '  %s: %s\n' % (command, COMMAND_DICTIONARY[command])
+  commands_str = '|'.join(commands)
+  cros_build_lib.Warning('Usage: %s FLAGS [%s]\n\n%s' % (
+      sys.argv[0], commands_str, command_usage))
+  if error_message:
+    cros_build_lib.Die(error_message)
+  else:
+    sys.exit(1)
+
+
+# ======================= End Global Helper Functions ========================
+
+
+def PushChange(stable_branch, tracking_branch, dryrun, cwd):
+  """Pushes commits in the stable_branch to the remote git repository.
+
+  Pushes local commits from calls to CommitChange to the remote git
+  repository specified by current working directory. If changes are
+  found to commit, they will be merged to the merge branch and pushed.
+  In that case, the local repository will be left on the merge branch.
+
+  Args:
+    stable_branch: The local branch with commits we want to push.
+    tracking_branch: The tracking branch of the local branch.
+    dryrun: Use git push --dryrun to emulate a push.
+    cwd: The directory to run commands in.
+  Raises:
+      OSError: Error occurred while pushing.
+  """
+  if not _DoWeHaveLocalCommits(stable_branch, tracking_branch, cwd):
+    cros_build_lib.Info('No work found to push in %s.  Exiting', cwd)
+    return
+
+  # For the commit queue, our local branch may contain commits that were
+  # just tested and pushed during the CommitQueueCompletion stage. Sync
+  # and rebase our local branch on top of the remote commits.
+  remote, push_branch = cros_build_lib.GetTrackingBranch(cwd, for_push=True)
+  cros_build_lib.SyncPushBranch(cwd, remote, push_branch)
+
+  # Check whether any local changes remain after the sync.
+  if not _DoWeHaveLocalCommits(stable_branch, push_branch, cwd):
+    cros_build_lib.Info('All changes already pushed for %s. Exiting', cwd)
+    return
+
+  description = cros_build_lib.RunCommandCaptureOutput(
+      ['git', 'log', '--format=format:%s%n%n%b', '%s..%s' % (
+       push_branch, stable_branch)], cwd=cwd).output
+  description = 'Marking set of ebuilds as stable\n\n%s' % description
+  cros_build_lib.Info('For %s, using description %s', cwd, description)
+  cros_build_lib.CreatePushBranch(constants.MERGE_BRANCH, cwd)
+  cros_build_lib.RunGitCommand(cwd, ['merge', '--squash', stable_branch])
+  cros_build_lib.RunGitCommand(cwd, ['commit', '-m', description])
+  cros_build_lib.RunGitCommand(cwd, ['config', 'push.default', 'tracking'])
+  cros_build_lib.GitPushWithRetry(constants.MERGE_BRANCH, cwd,
+                                  dryrun=dryrun)
+
+
+class GitBranch(object):
+  """Wrapper class for a git branch."""
+
+  def __init__(self, branch_name, tracking_branch, cwd):
+    """Sets up variables but does not create the branch."""
+    self.branch_name = branch_name
+    self.tracking_branch = tracking_branch
+    self.cwd = cwd
+
+  def CreateBranch(self):
+    self.Checkout()
+
+  def Checkout(self, branch=None):
+    """Function used to check out to another GitBranch."""
+    if not branch:
+      branch = self.branch_name
+    if branch == self.tracking_branch or self.Exists(branch):
+      git_cmd = ['git', 'checkout', '-f', branch]
+    else:
+      git_cmd = ['repo', 'start', branch, '.']
+    cros_build_lib.RunCommandCaptureOutput(git_cmd, print_cmd=False,
+                                           cwd=self.cwd)
+
+  def Exists(self, branch=None):
+    """Returns True if the branch exists."""
+    if not branch:
+      branch = self.branch_name
+    branches = cros_build_lib.RunCommandCaptureOutput(['git', 'branch'],
+                                                      print_cmd=False,
+                                                      cwd=self.cwd).output
+    return branch in branches.split()
+
+
+def main(argv):
+  parser = optparse.OptionParser('cros_mark_as_stable OPTIONS packages')
+  parser.add_option('--all', action='store_true',
+                    help='Mark all packages as stable.')
+  parser.add_option('-b', '--boards',
+                    help='Colon-separated list of boards')
+  parser.add_option('--drop_file',
+                    help='File to list packages that were revved.')
+  parser.add_option('--dryrun', action='store_true',
+                    help='Passes dry-run to git push if pushing a change.')
+  parser.add_option('-o', '--overlays',
+                    help='Colon-separated list of overlays to modify.')
+  parser.add_option('-p', '--packages',
+                    help='Colon separated list of packages to rev.')
+  parser.add_option('-r', '--srcroot',
+                    default='%s/trunk/src' % os.environ['HOME'],
+                    help='Path to root src directory.')
+  parser.add_option('--verbose', action='store_true',
+                    help='Prints out debug info.')
+  (options, args) = parser.parse_args()
+
+  global VERBOSE
+  VERBOSE = options.verbose
+  portage_utilities.EBuild.VERBOSE = options.verbose
+
+  if len(args) != 1:
+    _PrintUsageAndDie('Must specify a valid command [commit, push]')
+
+  command = args[0]
+  package_list = None
+  if options.packages:
+    package_list = options.packages.split(':')
+
+  _CheckSaneArguments(package_list, command, options)
+  if options.overlays:
+    overlays = {}
+    for path in options.overlays.split(':'):
+      if not os.path.isdir(path):
+        cros_build_lib.Die('Cannot find overlay: %s' % path)
+      overlays[path] = []
+  else:
+    cros_build_lib.Warning('Missing --overlays argument')
+    overlays = {
+      '%s/private-overlays/chromeos-overlay' % options.srcroot: [],
+      '%s/third_party/chromiumos-overlay' % options.srcroot: []
+    }
+
+  if command == 'commit':
+    portage_utilities.BuildEBuildDictionary(
+      overlays, options.all, package_list)
+
+  manifest = cros_build_lib.ManifestCheckout.Cached(options.srcroot)
+
+  # Contains the array of packages we actually revved.
+  revved_packages = []
+  new_package_atoms = []
+
+  # Slight optimization hack: process the chromiumos overlay before any other
+  # cros-workon overlay first so we can do background cache generation in it.
+  # A perfect solution would walk all the overlays, figure out any dependencies
+  # between them (with layout.conf), and then process them in dependency order.
+  # However, this operation isn't slow enough to warrant that level of
+  # complexity, so we'll just special case the main overlay.
+  #
+  # Similarly, generate the cache in the portage-stable tree asap.  We know
+  # we won't have any cros-workon packages in there, so generating the cache
+  # is the only thing it'll be doing.  The chromiumos overlay instead might
+  # have revbumping to do before it can generate the cache.
+  keys = overlays.keys()
+  for overlay in ('/third_party/chromiumos-overlay',
+                  '/third_party/portage-stable'):
+    for k in keys:
+      if k.endswith(overlay):
+        keys.remove(k)
+        keys.insert(0, k)
+        break
+
+  cache_queue = multiprocessing.Queue()
+  with background.BackgroundTaskRunner(cache_queue,
+                                       portage_utilities.RegenCache):
+    for overlay in keys:
+      ebuilds = overlays[overlay]
+      if not os.path.isdir(overlay):
+        cros_build_lib.Warning("Skipping %s" % overlay)
+        continue
+
+      # Note we intentionally work from the non push tracking branch;
+      # everything built thus far has been against it (meaning, http mirrors),
+      # thus we should honor that.  During the actual push, the code switches
+      # to the correct urls, and does an appropriate rebasing.
+      tracking_branch = cros_build_lib.GetTrackingBranchViaManifest(
+          overlay, manifest=manifest)[1]
+
+      if command == 'push':
+        PushChange(constants.STABLE_EBUILD_BRANCH, tracking_branch,
+                   options.dryrun, cwd=overlay)
+      elif command == 'commit' and ebuilds:
+        existing_branch = cros_build_lib.GetCurrentBranch(overlay)
+        work_branch = GitBranch(constants.STABLE_EBUILD_BRANCH, tracking_branch,
+                                cwd=overlay)
+        work_branch.CreateBranch()
+        if not work_branch.Exists():
+          cros_build_lib.Die('Unable to create stabilizing branch in %s' %
+                             overlay)
+
+        # In the case of uprevving overlays that have patches applied to them,
+        # include the patched changes in the stabilizing branch.
+        if existing_branch:
+          cros_build_lib.RunCommand(['git', 'rebase', existing_branch],
+                                    print_cmd=False, cwd=overlay)
+
+        for ebuild in ebuilds:
+          try:
+            _Print('Working on %s' % ebuild.package)
+            new_package = ebuild.RevWorkOnEBuild(options.srcroot)
+            if new_package:
+              revved_packages.append(ebuild.package)
+              new_package_atoms.append('=%s' % new_package)
+          except (OSError, IOError):
+            cros_build_lib.Warning('Cannot rev %s\n' % ebuild.package +
+                    'Note you will have to go into %s '
+                    'and reset the git repo yourself.' % overlay)
+            raise
+
+      if command == 'commit':
+        # Regenerate caches if need be.  We do this all the time to
+        # catch when users make changes without updating cache files.
+        cache_queue.put([overlay])
+
+  if command == 'commit':
+    CleanStalePackages(options.boards.split(':'), new_package_atoms)
+    if options.drop_file:
+      fh = open(options.drop_file, 'w')
+      fh.write(' '.join(revved_packages))
+      fh.close()
diff --git a/scripts/cros_mark_as_stable_unittest.py b/scripts/cros_mark_as_stable_unittest.py
new file mode 100755
index 0000000..635de28
--- /dev/null
+++ b/scripts/cros_mark_as_stable_unittest.py
@@ -0,0 +1,112 @@
+#!/usr/bin/python
+
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Unit tests for cros_mark_as_stable.py."""
+
+import mox
+import os
+import sys
+import unittest
+
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                '..', '..'))
+from chromite.buildbot import constants
+from chromite.buildbot import constants
+from chromite.lib import cros_build_lib
+from chromite.scripts import cros_mark_as_stable
+
+
+# pylint: disable=W0212,R0904
+class NonClassTests(mox.MoxTestBase):
+  def setUp(self):
+    mox.MoxTestBase.setUp(self)
+    self.mox.StubOutWithMock(cros_build_lib, 'RunCommand')
+    self.mox.StubOutWithMock(cros_build_lib, 'RunCommandCaptureOutput')
+    self._branch = 'test_branch'
+    self._target_manifest_branch = 'cros/master'
+
+  def testPushChange(self):
+    git_log = 'Marking test_one as stable\nMarking test_two as stable\n'
+    fake_description = 'Marking set of ebuilds as stable\n\n%s' % git_log
+    self.mox.StubOutWithMock(cros_mark_as_stable, '_DoWeHaveLocalCommits')
+    self.mox.StubOutWithMock(cros_mark_as_stable.GitBranch, 'CreateBranch')
+    self.mox.StubOutWithMock(cros_mark_as_stable.GitBranch, 'Exists')
+    self.mox.StubOutWithMock(cros_build_lib, 'GitPushWithRetry')
+    self.mox.StubOutWithMock(cros_build_lib, 'GetTrackingBranch')
+    self.mox.StubOutWithMock(cros_build_lib, 'SyncPushBranch')
+    self.mox.StubOutWithMock(cros_build_lib, 'CreatePushBranch')
+    self.mox.StubOutWithMock(cros_build_lib, 'RunGitCommand')
+
+    cros_mark_as_stable._DoWeHaveLocalCommits(
+        self._branch, self._target_manifest_branch, '.').AndReturn(True)
+    cros_build_lib.GetTrackingBranch('.', for_push=True).AndReturn(
+        ['gerrit', 'refs/remotes/gerrit/master'])
+    cros_build_lib.SyncPushBranch('.', 'gerrit', 'refs/remotes/gerrit/master')
+    cros_mark_as_stable._DoWeHaveLocalCommits(
+        self._branch, 'refs/remotes/gerrit/master', '.').AndReturn(True)
+    result = cros_build_lib.CommandResult(output=git_log)
+    cros_build_lib.RunCommandCaptureOutput(
+        ['git', 'log', '--format=format:%s%n%n%b',
+         'refs/remotes/gerrit/master..%s' % self._branch],
+        cwd='.').AndReturn(result)
+    cros_build_lib.CreatePushBranch('merge_branch', '.')
+    cros_build_lib.RunGitCommand('.', ['merge', '--squash', self._branch])
+    cros_build_lib.RunGitCommand('.', ['commit', '-m', fake_description])
+    cros_build_lib.RunGitCommand('.', ['config', 'push.default', 'tracking'])
+    cros_build_lib.GitPushWithRetry('merge_branch', '.', dryrun=False)
+    self.mox.ReplayAll()
+    cros_mark_as_stable.PushChange(self._branch, self._target_manifest_branch,
+                                   False, '.')
+    self.mox.VerifyAll()
+
+
+class GitBranchTest(mox.MoxTestBase):
+
+  def setUp(self):
+    mox.MoxTestBase.setUp(self)
+    # Always stub RunCommmand out as we use it in every method.
+    self.mox.StubOutWithMock(cros_build_lib, 'RunCommand')
+    self.mox.StubOutWithMock(cros_build_lib, 'RunCommandCaptureOutput')
+    self._branch = self.mox.CreateMock(cros_mark_as_stable.GitBranch)
+    self._branch_name = 'test_branch'
+    self._branch.branch_name = self._branch_name
+    self._target_manifest_branch = 'cros/test'
+    self._branch.tracking_branch = self._target_manifest_branch
+    self._branch.cwd = '.'
+
+  def testCheckoutCreate(self):
+    # Test init with no previous branch existing.
+    self._branch.Exists(self._branch_name).AndReturn(False)
+    cros_build_lib.RunCommandCaptureOutput(['repo', 'start', self._branch_name,
+                                            '.'], print_cmd=False, cwd='.')
+    self.mox.ReplayAll()
+    cros_mark_as_stable.GitBranch.Checkout(self._branch)
+    self.mox.VerifyAll()
+
+  def testCheckoutNoCreate(self):
+    # Test init with previous branch existing.
+    self._branch.Exists(self._branch_name).AndReturn(True)
+    cros_build_lib.RunCommandCaptureOutput(['git', 'checkout', '-f',
+                                            self._branch_name], print_cmd=False,
+                                           cwd='.')
+    self.mox.ReplayAll()
+    cros_mark_as_stable.GitBranch.Checkout(self._branch)
+    self.mox.VerifyAll()
+
+  def testExists(self):
+    branch = cros_mark_as_stable.GitBranch(self._branch_name,
+                                           self._target_manifest_branch, '.')
+    # Test if branch exists that is created
+    result = cros_build_lib.CommandResult(output=self._branch_name + '\n')
+    cros_build_lib.RunCommandCaptureOutput(['git', 'branch'], print_cmd=False,
+                                           cwd='.').AndReturn(result)
+    self.mox.ReplayAll()
+    self.assertTrue(branch.Exists())
+    self.mox.VerifyAll()
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/scripts/cros_mark_chrome_as_stable.py b/scripts/cros_mark_chrome_as_stable.py
new file mode 100644
index 0000000..6bc7879
--- /dev/null
+++ b/scripts/cros_mark_chrome_as_stable.py
@@ -0,0 +1,509 @@
+#!/usr/bin/python
+
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""This module uprevs Chrome for cbuildbot.
+
+After calling, it prints outs CHROME_VERSION_ATOM=(version atom string).  A
+caller could then use this atom with emerge to build the newly uprevved version
+of Chrome e.g.
+
+./cros_mark_chrome_as_stable tot
+Returns chrome-base/chromeos-chrome-8.0.552.0_alpha_r1
+
+emerge-x86-generic =chrome-base/chromeos-chrome-8.0.552.0_alpha_r1
+"""
+
+import filecmp
+import optparse
+import os
+import re
+import sys
+import time
+
+from chromite.buildbot import constants
+from chromite.buildbot import portage_utilities
+from chromite.lib import cros_build_lib
+from chromite.lib.cros_build_lib import RunCommand, Info, Warning
+from chromite.scripts import cros_mark_as_stable
+
+BASE_CHROME_SVN_URL = 'http://src.chromium.org/svn'
+
+# Helper regex's for finding ebuilds.
+_CHROME_VERSION_REGEX = '\d+\.\d+\.\d+\.\d+'
+_NON_STICKY_REGEX = '%s[(_rc.*)|(_alpha.*)]+' % _CHROME_VERSION_REGEX
+
+# Dir where all the action happens.
+_CHROME_OVERLAY_DIR = ('%(srcroot)s/third_party/chromiumos-overlay'
+                       '/chromeos-base/chromeos-chrome')
+
+_GIT_COMMIT_MESSAGE = ('Marking %(chrome_rev)s for chrome ebuild with version '
+                       '%(chrome_version)s as stable.')
+
+# URLs that print lists of chrome revisions between two versions of the browser.
+_CHROME_VERSION_URL = ('http://omahaproxy.appspot.com/changelog?'
+                       'old_version=%(old)s&new_version=%(new)s')
+
+# Only print links when we rev these types.
+_REV_TYPES_FOR_LINKS = [constants.CHROME_REV_LATEST,
+                        constants.CHROME_REV_STICKY]
+
+_CHROME_SVN_TAG = 'CROS_SVN_COMMIT'
+
+def _GetSvnUrl(base_url):
+  """Returns the path to the svn url for the given chrome branch."""
+  return os.path.join(base_url, 'trunk')
+
+
+def  _GetTipOfTrunkSvnRevision(base_url):
+  """Returns the current svn revision for the chrome tree."""
+  svn_url = _GetSvnUrl(base_url)
+  svn_info = RunCommand(['svn', 'info', svn_url], redirect_stdout=True).output
+
+  revision_re = re.compile('^Revision:\s+(\d+).*')
+  for line in svn_info.splitlines():
+    match = revision_re.search(line)
+    if match:
+      svn_revision = match.group(1)
+      Info('Found SVN Revision %s' % svn_revision)
+      return svn_revision
+
+  raise Exception('Could not find revision information from %s' % svn_url)
+
+
+def _GetVersionContents(chrome_version_info):
+  """Returns the current Chromium version, from the contents of a VERSION file.
+
+  Args:
+     chrome_version_info: The contents of a chromium VERSION file.
+  """
+  chrome_version_array = []
+  for line in chrome_version_info.splitlines():
+    chrome_version_array.append(line.rpartition('=')[2])
+
+  return '.'.join(chrome_version_array)
+
+def _GetSpecificVersionUrl(base_url, revision, time_to_wait=600):
+  """Returns the Chromium version, from a repository URL and version.
+
+  Args:
+     base_url: URL for the root of the chromium checkout.
+     revision: the SVN revision we want to use.
+     time_to_wait: the minimum period before abandoning our wait for the
+         desired revision to be present.
+  """
+  svn_url = os.path.join(_GetSvnUrl(base_url), 'src', 'chrome', 'VERSION')
+  if not revision or not (int(revision) > 0):
+    raise Exception('Revision must be positive, got %s' % revision)
+
+  start = time.time()
+  # Use the fact we are SVN, hence ordered.
+  # Dodge the fact it will silently ignore the revision if it is not
+  # yet known.  (i.e. too high)
+  repo_version = _GetTipOfTrunkSvnRevision(base_url)
+  while revision > repo_version:
+    if time.time() - start > time_to_wait:
+      raise Exception('Timeout Exceeeded')
+
+    Info('Repository only has version %s, looking for %s.  Sleeping...' %
+         (repo_version, revision))
+    time.sleep(30)
+    repo_version = _GetTipOfTrunkSvnRevision(base_url)
+
+  chrome_version_info = RunCommand(
+      ['svn', 'cat', '-r', revision, svn_url],
+      redirect_stdout=True,
+      error_message='Could not read version file at %s revision %s.' %
+                    (svn_url, revision)).output
+
+  return _GetVersionContents(chrome_version_info)
+
+
+def _GetTipOfTrunkVersionFile(root):
+  """Returns the current Chromium version, from a file in a checkout.
+
+  Args:
+     root: path to the root of the chromium checkout.
+  """
+  version_file = os.path.join(root, 'src', 'chrome', 'VERSION')
+  chrome_version_info = RunCommand(
+      ['cat', version_file],
+      redirect_stdout=True,
+      error_message='Could not read version file at %s.' % version_file).output
+
+  return _GetVersionContents(chrome_version_info)
+
+def _GetLatestRelease(base_url, branch=None):
+  """Gets the latest release version from the buildspec_url for the branch.
+
+  Args:
+    branch:  If set, gets the latest release for branch, otherwise latest
+      release.
+  Returns:
+    Latest version string.
+  """
+  buildspec_url = os.path.join(base_url, 'releases')
+  svn_ls = RunCommand(['svn', 'ls', buildspec_url],
+                      redirect_stdout=True).output
+  sorted_ls = RunCommand(['sort', '--version-sort', '-r'], input=svn_ls,
+                         redirect_stdout=True).output
+  if branch:
+    chrome_version_re = re.compile('^%s\.\d+.*' % branch)
+  else:
+    chrome_version_re = re.compile('^[0-9]+\..*')
+
+  for chrome_version in sorted_ls.splitlines():
+    if chrome_version_re.match(chrome_version):
+      deps_url = os.path.join(buildspec_url, chrome_version, 'DEPS')
+      deps_check = RunCommand(['svn', 'ls', deps_url],
+                              error_ok=True,
+                              redirect_stdout=True).output
+      if deps_check == 'DEPS\n':
+        return chrome_version.rstrip('/')
+
+  return None
+
+
+def _GetStickyEBuild(stable_ebuilds):
+  """Returns the sticky ebuild."""
+  sticky_ebuilds = []
+  non_sticky_re = re.compile(_NON_STICKY_REGEX)
+  for ebuild in stable_ebuilds:
+    if not non_sticky_re.match(ebuild.version):
+      sticky_ebuilds.append(ebuild)
+
+  if not sticky_ebuilds:
+    raise Exception('No sticky ebuilds found')
+  elif len(sticky_ebuilds) > 1:
+    Warning('More than one sticky ebuild found')
+
+  return portage_utilities.BestEBuild(sticky_ebuilds)
+
+
+class ChromeEBuild(portage_utilities.EBuild):
+  """Thin sub-class of EBuild that adds a chrome_version field."""
+  chrome_version_re = re.compile('.*chromeos-chrome-(%s|9999).*' % (
+      _CHROME_VERSION_REGEX))
+  chrome_version = ''
+
+  def __init__(self, path):
+    portage_utilities.EBuild.__init__(self, path)
+    re_match = self.chrome_version_re.match(self.ebuild_path_no_revision)
+    if re_match:
+      self.chrome_version = re_match.group(1)
+
+  def __str__(self):
+    return self.ebuild_path
+
+
+def FindChromeCandidates(overlay_dir):
+  """Return a tuple of chrome's unstable ebuild and stable ebuilds.
+
+  Args:
+    overlay_dir: The path to chrome's portage overlay dir.
+  Returns:
+    Tuple [unstable_ebuild, stable_ebuilds].
+  Raises:
+    Exception: if no unstable ebuild exists for Chrome.
+  """
+  stable_ebuilds = []
+  unstable_ebuilds = []
+  for path in [
+      os.path.join(overlay_dir, entry) for entry in os.listdir(overlay_dir)]:
+    if path.endswith('.ebuild'):
+      ebuild = ChromeEBuild(path)
+      if not ebuild.chrome_version:
+        Warning('Poorly formatted ebuild found at %s' % path)
+      else:
+        if '9999' in ebuild.version:
+          unstable_ebuilds.append(ebuild)
+        else:
+          stable_ebuilds.append(ebuild)
+
+  # Apply some sanity checks.
+  if not unstable_ebuilds:
+    raise Exception('Missing 9999 ebuild for %s' % overlay_dir)
+  if not stable_ebuilds:
+    Warning('Missing stable ebuild for %s' % overlay_dir)
+
+  return portage_utilities.BestEBuild(unstable_ebuilds), stable_ebuilds
+
+
+def FindChromeUprevCandidate(stable_ebuilds, chrome_rev, sticky_branch):
+  """Finds the Chrome uprev candidate for the given chrome_rev.
+
+  Using the pre-flight logic, this means the stable ebuild you are uprevving
+  from.  The difference here is that the version could be different and in
+  that case we want to find it to delete it.
+
+  Args:
+    stable_ebuilds: A list of stable ebuilds.
+    chrome_rev: The chrome_rev designating which candidate to find.
+    sticky_branch:  The the branch that is currently sticky with Major/Minor
+      components.  For example: 9.0.553. Can be None but not if chrome_rev
+      is CHROME_REV_STICKY.
+  Returns:
+    Returns the EBuild, otherwise None if none found.
+  """
+  candidates = []
+  if chrome_rev in [constants.CHROME_REV_LOCAL, constants.CHROME_REV_TOT,
+                    constants.CHROME_REV_SPEC]:
+    # These are labelled alpha, for historic reasons,
+    # not just for the fun of confusion.
+    chrome_branch_re = re.compile('%s.*_alpha.*' % _CHROME_VERSION_REGEX)
+    for ebuild in stable_ebuilds:
+      if chrome_branch_re.search(ebuild.version):
+        candidates.append(ebuild)
+
+  elif chrome_rev == constants.CHROME_REV_STICKY:
+    assert sticky_branch is not None
+    chrome_branch_re = re.compile('%s\..*' % sticky_branch)
+    for ebuild in stable_ebuilds:
+      if chrome_branch_re.search(ebuild.version):
+        candidates.append(ebuild)
+
+  else:
+    chrome_branch_re = re.compile('%s.*_rc.*' % _CHROME_VERSION_REGEX)
+    for ebuild in stable_ebuilds:
+      if chrome_branch_re.search(ebuild.version):
+        candidates.append(ebuild)
+
+  if candidates:
+    return portage_utilities.BestEBuild(candidates)
+  else:
+    return None
+
+def _AnnotateAndPrint(text, url):
+  """Add buildbot trappings to print <a href='url'>text</a> in the waterfall.
+
+  Args:
+    text: Anchor text for the link
+    url: the URL to which to link
+  """
+  print >> sys.stderr, '\n@@@STEP_LINK@%(text)s@%(url)s@@@' % { 'text': text,
+                                                              'url': url }
+
+def GetChromeRevisionLinkFromVersions(old_chrome_version, chrome_version):
+  """Return appropriately formatted link to revision info, given versions
+
+  Given two chrome version strings (e.g. 9.0.533.0), generate a link to a
+  page that prints the Chromium revisions between those two versions.
+
+  Args:
+    old_chrome_version: version to diff from
+    chrome_version: version to which to diff
+  Returns:
+    The desired URL.
+  """
+  return _CHROME_VERSION_URL % { 'old': old_chrome_version,
+                                 'new': chrome_version }
+
+def GetChromeRevisionListLink(old_chrome, new_chrome, chrome_rev):
+  """Returns a link to the list of revisions between two Chromium versions
+
+  Given two ChromeEBuilds and the kind of rev we're doing, generate a
+  link to a page that prints the Chromium changes between those two
+  revisions, inclusive.
+
+  Args:
+    old_chrome: ebuild for the version to diff from
+    new_chrome: ebuild for the version to which to diff
+    chrome_rev: one of constants.VALID_CHROME_REVISIONS
+  Returns:
+    The desired URL.
+  """
+  assert chrome_rev in _REV_TYPES_FOR_LINKS
+  return GetChromeRevisionLinkFromVersions(old_chrome.chrome_version,
+                                           new_chrome.chrome_version)
+
+def MarkChromeEBuildAsStable(stable_candidate, unstable_ebuild, chrome_rev,
+                             chrome_version, commit, overlay_dir):
+  """Uprevs the chrome ebuild specified by chrome_rev.
+
+  This is the main function that uprevs the chrome_rev from a stable candidate
+  to its new version.
+
+  Args:
+    stable_candidate: ebuild that corresponds to the stable ebuild we are
+      revving from.  If None, builds the a new ebuild given the version
+      and logic for chrome_rev type with revision set to 1.
+    unstable_ebuild:  ebuild corresponding to the unstable ebuild for chrome.
+    chrome_rev: one of constants.VALID_CHROME_REVISIONS or LOCAL
+      constants.CHROME_REV_SPEC -  Requires commit value.  Revs the ebuild for
+        the specified version and uses the portage suffix of _alpha.
+      constants.CHROME_REV_TOT -  Requires commit value.  Revs the ebuild for
+        the TOT version and uses the portage suffix of _alpha.
+      constants.CHROME_REV_LOCAL - Requires a chrome_root. Revs the ebuild for
+        the local version and uses the portage suffix of _alpha.
+      constants.CHROME_REV_LATEST - This uses the portage suffix of _rc as they
+        are release candidates for the next sticky version.
+      constants.CHROME_REV_STICKY -  Revs the sticky version.
+    chrome_version:  The \d.\d.\d.\d version of Chrome.
+    commit:  Used with constants.CHROME_REV_TOT.  The svn revision of chrome.
+    overlay_dir:  Path to the chromeos-chrome package dir.
+  Returns:
+    Full portage version atom (including rc's, etc) that was revved.
+  """
+  def IsTheNewEBuildRedundant(new_ebuild, stable_ebuild):
+    """Returns True if the new ebuild is redundant.
+
+    This is True if there if the current stable ebuild is the exact same copy
+    of the new one OR the chrome versions are the same and we're revving
+    constants.CHROME_REV_LATEST (as we don't care about 9999 changes for it).
+    """
+    if not stable_ebuild:
+      return False
+
+    if stable_candidate.chrome_version == new_ebuild.chrome_version:
+      if chrome_rev == constants.CHROME_REV_LATEST:
+        return True
+      else:
+        return filecmp.cmp(
+            new_ebuild.ebuild_path, stable_ebuild.ebuild_path, shallow=False)
+
+  base_path = os.path.join(overlay_dir, 'chromeos-chrome-%s' % chrome_version)
+  # Case where we have the last stable candidate with same version just rev.
+  if stable_candidate and stable_candidate.chrome_version == chrome_version:
+    new_ebuild_path = '%s-r%d.ebuild' % (
+        stable_candidate.ebuild_path_no_revision,
+        stable_candidate.current_revision + 1)
+  else:
+    if chrome_rev in [constants.CHROME_REV_LOCAL, constants.CHROME_REV_TOT,
+                      constants.CHROME_REV_SPEC]:
+      portage_suffix = '_alpha'
+    else:
+      portage_suffix = '_rc'
+
+    new_ebuild_path = base_path + ('%s-r1.ebuild' % portage_suffix)
+
+  # Mark latest release and sticky branches as stable.
+  mark_stable = chrome_rev not in [constants.CHROME_REV_TOT,
+                                   constants.CHROME_REV_SPEC,
+                                   constants.CHROME_REV_LOCAL]
+
+  chrome_variables = dict()
+  if commit:
+    chrome_variables[_CHROME_SVN_TAG] = commit
+
+  portage_utilities.EBuild.MarkAsStable(
+      unstable_ebuild.ebuild_path, new_ebuild_path,
+      chrome_variables, make_stable=mark_stable)
+  new_ebuild = ChromeEBuild(new_ebuild_path)
+
+  # Determine whether this is ebuild is redundant.
+  if IsTheNewEBuildRedundant(new_ebuild, stable_candidate):
+    Info('Previous ebuild with same version found and ebuild is redundant.')
+    os.unlink(new_ebuild_path)
+    return None
+
+  if stable_candidate and chrome_rev in _REV_TYPES_FOR_LINKS:
+    _AnnotateAndPrint('Chromium revisions',
+                      GetChromeRevisionListLink(stable_candidate,
+                                                new_ebuild,
+                                                chrome_rev))
+
+  RunCommand(['git', 'add', new_ebuild_path], cwd=overlay_dir)
+  if stable_candidate and not stable_candidate.IsSticky():
+    RunCommand(['git', 'rm', stable_candidate.ebuild_path], cwd=overlay_dir)
+
+  portage_utilities.EBuild.CommitChange(
+      _GIT_COMMIT_MESSAGE % {'chrome_rev': chrome_rev,
+                             'chrome_version': chrome_version},
+      overlay_dir)
+
+  return '%s-%s' % (new_ebuild.package, new_ebuild.version)
+
+
+def ParseMaxRevision(revision_list):
+  """Returns the max revision from a list of url@revision string."""
+  revision_re = re.compile('.*@(\d+)')
+
+  def RevisionKey(revision):
+    return revision_re.match(revision).group(1)
+
+  max_revision = max(revision_list.split(), key=RevisionKey)
+  return max_revision.rpartition('@')[2]
+
+
+def main(argv):
+  usage_options = '|'.join(constants.VALID_CHROME_REVISIONS)
+  usage = '%s OPTIONS [%s]' % (__file__, usage_options)
+  parser = optparse.OptionParser(usage)
+  parser.add_option('-b', '--boards', default='x86-generic')
+  parser.add_option('-c', '--chrome_url', default=BASE_CHROME_SVN_URL)
+  parser.add_option('-f', '--force_revision', default=None)
+  parser.add_option('-s', '--srcroot', default=os.path.join(os.environ['HOME'],
+                                                            'trunk', 'src'),
+                    help='Path to the src directory')
+  parser.add_option('-t', '--tracking_branch', default='cros/master',
+                    help='Branch we are tracking changes against')
+  (options, args) = parser.parse_args()
+
+  if len(args) != 1 or args[0] not in constants.VALID_CHROME_REVISIONS:
+    parser.error('Commit requires arg set to one of %s.'
+                 % constants.VALID_CHROME_REVISIONS)
+
+  overlay_dir = os.path.abspath(_CHROME_OVERLAY_DIR %
+                                {'srcroot': options.srcroot})
+  chrome_rev = args[0]
+  version_to_uprev = None
+  commit_to_use = None
+  sticky_branch = None
+
+  (unstable_ebuild, stable_ebuilds) = FindChromeCandidates(overlay_dir)
+
+  if chrome_rev == constants.CHROME_REV_LOCAL:
+    if 'CHROME_ROOT' in os.environ:
+      chrome_root = os.environ['CHROME_ROOT']
+    else:
+      chrome_root = os.path.join(os.environ['HOME'], 'chrome_root')
+
+    version_to_uprev = _GetTipOfTrunkVersionFile(chrome_root)
+    commit_to_use = 'Unknown'
+    Info('Using local source, versioning is untrustworthy.')
+  elif chrome_rev == constants.CHROME_REV_SPEC:
+    commit_to_use = options.force_revision
+    if '@' in commit_to_use: commit_to_use = ParseMaxRevision(commit_to_use)
+    version_to_uprev = _GetSpecificVersionUrl(options.chrome_url,
+                                              commit_to_use)
+  elif chrome_rev == constants.CHROME_REV_TOT:
+    commit_to_use = _GetTipOfTrunkSvnRevision(options.chrome_url)
+    version_to_uprev = _GetSpecificVersionUrl(options.chrome_url,
+                                              commit_to_use)
+  elif chrome_rev == constants.CHROME_REV_LATEST:
+    version_to_uprev = _GetLatestRelease(options.chrome_url)
+  else:
+    sticky_ebuild = _GetStickyEBuild(stable_ebuilds)
+    sticky_version = sticky_ebuild.chrome_version
+    sticky_branch = sticky_version.rpartition('.')[0]
+    version_to_uprev = _GetLatestRelease(options.chrome_url, sticky_branch)
+
+  stable_candidate = FindChromeUprevCandidate(stable_ebuilds, chrome_rev,
+                                              sticky_branch)
+
+  if stable_candidate:
+    Info('Stable candidate found %s' % stable_candidate)
+  else:
+    Info('No stable candidate found.')
+
+  tracking_branch = 'remotes/m/%s' % os.path.basename(options.tracking_branch)
+  existing_branch = cros_build_lib.GetCurrentBranch(overlay_dir)
+  work_branch = cros_mark_as_stable.GitBranch(constants.STABLE_EBUILD_BRANCH,
+                                              tracking_branch, overlay_dir)
+  work_branch.CreateBranch()
+
+  # In the case of uprevving overlays that have patches applied to them,
+  # include the patched changes in the stabilizing branch.
+  if existing_branch:
+    RunCommand(['git', 'rebase', existing_branch], cwd=overlay_dir)
+
+  chrome_version_atom = MarkChromeEBuildAsStable(
+      stable_candidate, unstable_ebuild, chrome_rev, version_to_uprev,
+      commit_to_use, overlay_dir)
+  # Explicit print to communicate to caller.
+  if chrome_version_atom:
+    cros_mark_as_stable.CleanStalePackages(options.boards.split(':'),
+                                           [chrome_version_atom])
+    print 'CHROME_VERSION_ATOM=%s' % chrome_version_atom
diff --git a/scripts/cros_mark_chrome_as_stable_unittest.py b/scripts/cros_mark_chrome_as_stable_unittest.py
new file mode 100755
index 0000000..89866b7
--- /dev/null
+++ b/scripts/cros_mark_chrome_as_stable_unittest.py
@@ -0,0 +1,326 @@
+#!/usr/bin/python
+
+# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Unit tests for cros_mark_chrome_as_stable.py."""
+
+# run with:
+#    cros_sdk ../../chromite/buildbot/cros_mark_chrome_as_stable_unittest.py
+
+import mox
+import os
+import shutil
+import sys
+import tempfile
+import unittest
+
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                '..', '..'))
+from chromite.buildbot import constants
+from chromite.buildbot import portage_utilities
+from chromite.scripts import cros_mark_as_stable
+from chromite.scripts import cros_mark_chrome_as_stable
+
+# pylint: disable=W0212,R0904
+unstable_data = 'KEYWORDS=~x86 ~arm'
+stable_data = 'KEYWORDS=x86 arm'
+fake_svn_rev = '12345'
+new_fake_svn_rev = '23456'
+
+def _TouchAndWrite(path, data=None):
+  """Writes data (if it exists) to the file specified by the path."""
+  fh = open(path, 'w')
+  if data:
+    fh.write(data)
+
+  fh.close()
+
+
+class _StubCommandResult(object):
+  def __init__(self, msg):
+    self.output = msg
+
+
+class CrosMarkChromeAsStable(mox.MoxTestBase):
+
+  def setUp(self):
+    """Setup vars and create mock dir."""
+    mox.MoxTestBase.setUp(self)
+    self.tmp_overlay = tempfile.mkdtemp(prefix='chromiumos-overlay')
+    self.mock_chrome_dir = os.path.join(self.tmp_overlay, 'chromeos-base',
+                                        'chromeos-chrome')
+    os.makedirs(self.mock_chrome_dir)
+
+    self.unstable = os.path.join(self.mock_chrome_dir,
+                                 'chromeos-chrome-9999.ebuild')
+    self.sticky_branch = '8.0.224'
+    self.sticky_version = '%s.503' % self.sticky_branch
+    self.sticky = os.path.join(self.mock_chrome_dir,
+                               'chromeos-chrome-%s.ebuild' %
+                                   self.sticky_version)
+    self.sticky_rc_version = '%s.504' % self.sticky_branch
+    self.sticky_rc = os.path.join(self.mock_chrome_dir,
+                                  'chromeos-chrome-%s_rc-r1.ebuild' %
+                                      self.sticky_rc_version)
+    self.latest_stable_version = '8.0.300.1'
+    self.latest_stable = os.path.join(self.mock_chrome_dir,
+                                      'chromeos-chrome-%s_rc-r2.ebuild' %
+                                          self.latest_stable_version)
+    self.tot_stable_version = '9.0.305.0'
+    self.tot_stable = os.path.join(self.mock_chrome_dir,
+                                   'chromeos-chrome-%s_alpha-r1.ebuild' %
+                                       self.tot_stable_version)
+
+    self.sticky_new_rc_version = '%s.520' % self.sticky_branch
+    self.sticky_new_rc = os.path.join(self.mock_chrome_dir,
+                                      'chromeos-chrome-%s_rc-r1.ebuild' %
+                                          self.sticky_new_rc_version)
+    self.latest_new_version = '9.0.305.1'
+    self.latest_new = os.path.join(self.mock_chrome_dir,
+                                      'chromeos-chrome-%s_rc-r1.ebuild' %
+                                          self.latest_new_version)
+    self.tot_new_version = '9.0.306.0'
+    self.tot_new = os.path.join(self.mock_chrome_dir,
+                                      'chromeos-chrome-%s_alpha-r1.ebuild' %
+                                          self.tot_new_version)
+
+    _TouchAndWrite(self.unstable, unstable_data)
+    _TouchAndWrite(self.sticky, stable_data)
+    _TouchAndWrite(self.sticky_rc, stable_data)
+    _TouchAndWrite(self.latest_stable, stable_data)
+    _TouchAndWrite(self.tot_stable,
+                   '\n'.join(
+                       (stable_data,
+                        '%s=%s' % (cros_mark_chrome_as_stable._CHROME_SVN_TAG,
+                                   fake_svn_rev))))
+
+  def tearDown(self):
+    """Cleans up mock dir."""
+    shutil.rmtree(self.tmp_overlay)
+
+  def testFindChromeCandidates(self):
+    """Test creation of stable ebuilds from mock dir."""
+    unstable, stable_ebuilds = cros_mark_chrome_as_stable.FindChromeCandidates(
+        self.mock_chrome_dir)
+
+    stable_ebuild_paths = map(lambda eb: eb.ebuild_path, stable_ebuilds)
+    self.assertEqual(unstable.ebuild_path, self.unstable)
+    self.assertEqual(len(stable_ebuilds), 4)
+    self.assertTrue(self.sticky in stable_ebuild_paths)
+    self.assertTrue(self.sticky_rc in stable_ebuild_paths)
+    self.assertTrue(self.latest_stable in stable_ebuild_paths)
+    self.assertTrue(self.tot_stable in stable_ebuild_paths)
+
+  def _GetStableEBuilds(self):
+    """Common helper to create a list of stable ebuilds."""
+    return [
+        cros_mark_chrome_as_stable.ChromeEBuild(self.sticky),
+        cros_mark_chrome_as_stable.ChromeEBuild(self.sticky_rc),
+        cros_mark_chrome_as_stable.ChromeEBuild(self.latest_stable),
+        cros_mark_chrome_as_stable.ChromeEBuild(self.tot_stable),
+    ]
+
+  def testTOTFindChromeUprevCandidate(self):
+    """Tests if we can find tot uprev candidate from our mock dir data."""
+    stable_ebuilds = self._GetStableEBuilds()
+
+    candidate = cros_mark_chrome_as_stable.FindChromeUprevCandidate(
+        stable_ebuilds, constants.CHROME_REV_TOT,
+        self.sticky_branch)
+
+    self.assertEqual(candidate.ebuild_path, self.tot_stable)
+
+  def testLatestFindChromeUprevCandidate(self):
+    """Tests if we can find latest uprev candidate from our mock dir data."""
+    stable_ebuilds = self._GetStableEBuilds()
+
+    candidate = cros_mark_chrome_as_stable.FindChromeUprevCandidate(
+        stable_ebuilds, constants.CHROME_REV_LATEST,
+        self.sticky_branch)
+
+    self.assertEqual(candidate.ebuild_path, self.latest_stable)
+
+  def testStickyFindChromeUprevCandidate(self):
+    """Tests if we can find sticky uprev candidate from our mock dir data."""
+    stable_ebuilds = self._GetStableEBuilds()
+
+    candidate = cros_mark_chrome_as_stable.FindChromeUprevCandidate(
+        stable_ebuilds, constants.CHROME_REV_STICKY,
+        self.sticky_branch)
+
+    self.assertEqual(candidate.ebuild_path, self.sticky_rc)
+
+  def testGetTipOfTrunkSvnRevision(self):
+    """Tests if we can get the latest svn revision from TOT."""
+    A_URL = 'dorf://mink/delaane/forkat/sertiunu.ortg./desk'
+    self.mox.StubOutWithMock(cros_mark_chrome_as_stable, 'RunCommand')
+    cros_mark_chrome_as_stable.RunCommand(
+        ['svn', 'info', cros_mark_chrome_as_stable._GetSvnUrl(A_URL)],
+        redirect_stdout=True).AndReturn(
+          _StubCommandResult(
+            'Some Junk 2134\nRevision: %s\nOtherInfo: test_data' %
+            fake_svn_rev))
+    self.mox.ReplayAll()
+    revision = cros_mark_chrome_as_stable._GetTipOfTrunkSvnRevision(A_URL)
+    self.mox.VerifyAll()
+    self.assertEquals(revision, fake_svn_rev)
+
+  def testGetTipOfTrunkVersion(self):
+    """Tests if we get the latest version from TOT."""
+    ARBITRARY_URL = 'Pratooey'
+    path = os.path.join(cros_mark_chrome_as_stable._GetSvnUrl(ARBITRARY_URL),
+                        'src', 'chrome', 'VERSION')
+    self.mox.StubOutWithMock(cros_mark_chrome_as_stable, 'RunCommand')
+    cros_mark_chrome_as_stable.RunCommand(
+        ['svn', 'info', cros_mark_chrome_as_stable._GetSvnUrl(ARBITRARY_URL)],
+        redirect_stdout=True).AndReturn(
+          _StubCommandResult(
+            'Some Junk 2134\nRevision: %s\nOtherInfo: test_data' %
+            fake_svn_rev))
+    cros_mark_chrome_as_stable.RunCommand(
+        ['svn', 'cat', '-r', fake_svn_rev, path], redirect_stdout=True,
+        error_message=mox.IsA(str)).AndReturn(
+          _StubCommandResult('A=8\nB=0\nC=256\nD=0'))
+
+    self.mox.ReplayAll()
+    version = cros_mark_chrome_as_stable._GetSpecificVersionUrl(ARBITRARY_URL,
+                                                                fake_svn_rev)
+    self.mox.VerifyAll()
+    self.assertEquals(version, '8.0.256.0')
+
+  def testGetLatestRelease(self):
+    """Tests if we can find the latest release from our mock url data."""
+    ARBITRARY_URL = 'phthp://sores.chromium.org/tqs'
+    input_data = ['7.0.224.1/', '7.0.224.2/', '8.0.365.5/', 'LATEST.txt']
+    test_data = '\n'.join(input_data)
+    sorted_data = '\n'.join(reversed(input_data))
+    self.mox.StubOutWithMock(cros_mark_chrome_as_stable, 'RunCommand')
+    cros_mark_chrome_as_stable.RunCommand(
+        ['svn', 'ls', ARBITRARY_URL + '/releases'],
+        redirect_stdout=True).AndReturn(_StubCommandResult(test_data))
+    cros_mark_chrome_as_stable.RunCommand(
+        ['sort', '--version-sort', '-r'], input=test_data,
+        redirect_stdout=True).AndReturn(_StubCommandResult(sorted_data))
+    # pretend this one is missing to test the skipping logic.
+    cros_mark_chrome_as_stable.RunCommand(
+        ['svn', 'ls', ARBITRARY_URL + '/releases/8.0.365.5/DEPS'],
+        error_ok=True, redirect_stdout=True).AndReturn(
+          _StubCommandResult('BAH BAH BAH'))
+    cros_mark_chrome_as_stable.RunCommand(
+        ['svn', 'ls', ARBITRARY_URL + '/releases/7.0.224.2/DEPS'],
+        error_ok=True, redirect_stdout=True).AndReturn(
+          _StubCommandResult('DEPS\n'))
+    self.mox.ReplayAll()
+    release = cros_mark_chrome_as_stable._GetLatestRelease(ARBITRARY_URL)
+    self.mox.VerifyAll()
+    self.assertEqual('7.0.224.2', release)
+
+  def testGetLatestStickyRelease(self):
+    """Tests if we can find the latest sticky release from our mock url data."""
+    ARBITRARY_URL = 'http://src.chromium.org/svn'
+    test_data = '\n'.join(['7.0.222.1/',
+                           '8.0.224.2/',
+                           '8.0.365.5/',
+                           'LATEST.txt'])
+    self.mox.StubOutWithMock(cros_mark_chrome_as_stable, 'RunCommand')
+    cros_mark_chrome_as_stable.RunCommand(
+        ['svn', 'ls', ARBITRARY_URL + '/releases'],
+        redirect_stdout=True).AndReturn(_StubCommandResult('some_data'))
+    cros_mark_chrome_as_stable.RunCommand(
+        ['sort', '--version-sort', '-r'], input='some_data',
+        redirect_stdout=True).AndReturn(_StubCommandResult(test_data))
+    cros_mark_chrome_as_stable.RunCommand(
+        ['svn', 'ls', ARBITRARY_URL + '/releases/8.0.224.2/DEPS'],
+        error_ok=True, redirect_stdout=True).AndReturn(
+          _StubCommandResult('DEPS\n'))
+    self.mox.ReplayAll()
+    release = cros_mark_chrome_as_stable._GetLatestRelease(ARBITRARY_URL,
+                                                           '8.0.224')
+    self.mox.VerifyAll()
+    self.assertEqual('8.0.224.2', release)
+
+  def testLatestChromeRevisionListLink(self):
+    """Tests that we can generate a link to the revision list between the
+    latest Chromium release and the last one we successfully built."""
+    _TouchAndWrite(self.latest_new, stable_data)
+    expected = cros_mark_chrome_as_stable.GetChromeRevisionLinkFromVersions(
+        self.latest_stable_version, self.latest_new_version)
+    made = cros_mark_chrome_as_stable.GetChromeRevisionListLink(
+        cros_mark_chrome_as_stable.ChromeEBuild(self.latest_stable),
+        cros_mark_chrome_as_stable.ChromeEBuild(self.latest_new),
+        constants.CHROME_REV_LATEST)
+    self.assertEqual(expected, made)
+
+  def testStickyEBuild(self):
+    """Tests if we can find the sticky ebuild from our mock directories."""
+    stable_ebuilds = self._GetStableEBuilds()
+    sticky_ebuild = cros_mark_chrome_as_stable._GetStickyEBuild(
+        stable_ebuilds)
+    self.assertEqual(sticky_ebuild.chrome_version, self.sticky_version)
+
+  def testChromeEBuildInit(self):
+    """Tests if the chrome_version is set correctly in a ChromeEBuild."""
+    ebuild = cros_mark_chrome_as_stable.ChromeEBuild(self.sticky)
+    self.assertEqual(ebuild.chrome_version, self.sticky_version)
+
+  def _CommonMarkAsStableTest(self, chrome_rev, new_version, old_ebuild_path,
+                              new_ebuild_path, commit_string_indicator):
+    """Common function used for test functions for MarkChromeEBuildAsStable.
+
+    This function stubs out others calls, and runs MarkChromeEBuildAsStable
+    with the specified args.
+
+    Args:
+      chrome_rev: standard chrome_rev argument
+      new_version: version we are revving up to
+      old_ebuild_path: path to the stable ebuild
+      new_ebuild_path: path to the to be created path
+      commit_string_indicator: a string that the commit message must contain
+    """
+    self.mox.StubOutWithMock(cros_mark_chrome_as_stable, 'RunCommand')
+    self.mox.StubOutWithMock(portage_utilities.EBuild, 'CommitChange')
+    stable_candidate = cros_mark_chrome_as_stable.ChromeEBuild(old_ebuild_path)
+    unstable_ebuild = cros_mark_chrome_as_stable.ChromeEBuild(self.unstable)
+    chrome_version = new_version
+    commit = None
+    overlay_dir = self.mock_chrome_dir
+
+    cros_mark_chrome_as_stable.RunCommand(['git', 'add', new_ebuild_path],
+                                          cwd=overlay_dir)
+    cros_mark_chrome_as_stable.RunCommand(['git', 'rm', old_ebuild_path],
+                                          cwd=overlay_dir)
+    portage_utilities.EBuild.CommitChange(
+        mox.StrContains(commit_string_indicator), overlay_dir)
+
+    self.mox.ReplayAll()
+    cros_mark_chrome_as_stable.MarkChromeEBuildAsStable(
+        stable_candidate, unstable_ebuild, chrome_rev, chrome_version, commit,
+        overlay_dir)
+    self.mox.VerifyAll()
+
+  def testStickyMarkAsStable(self):
+    """Tests to see if we can mark chrome as stable for a new sticky release."""
+    self._CommonMarkAsStableTest(
+        constants.CHROME_REV_STICKY,
+        self.sticky_new_rc_version, self.sticky_rc,
+        self.sticky_new_rc, 'stable_release')
+
+  def testLatestMarkAsStable(self):
+    """Tests to see if we can mark chrome for a latest release."""
+    self._CommonMarkAsStableTest(
+        constants.CHROME_REV_LATEST,
+        self.latest_new_version, self.latest_stable,
+        self.latest_new, 'latest_release')
+
+  def testTotMarkAsStable(self):
+    """Tests to see if we can mark chrome for tot."""
+    self._CommonMarkAsStableTest(
+        constants.CHROME_REV_TOT,
+        self.tot_new_version, self.tot_stable,
+        self.tot_new, 'tot')
+
+
+if __name__ == '__main__':
+  unittest.main()