123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964 |
- #!/usr/bin/env python
- # Copyright (c) 2012 The Chromium 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 git_cl.py."""
- import os
- import StringIO
- import stat
- import sys
- import unittest
- sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
- from testing_support.auto_stub import TestCase
- import git_cl
- import git_common
- import subprocess2
- class PresubmitMock(object):
- def __init__(self, *args, **kwargs):
- self.reviewers = []
- @staticmethod
- def should_continue():
- return True
- class RietveldMock(object):
- def __init__(self, *args, **kwargs):
- pass
- @staticmethod
- def get_description(issue):
- return 'Issue: %d' % issue
- @staticmethod
- def get_issue_properties(_issue, _messages):
- return {
- 'reviewers': ['joe@chromium.org', 'john@chromium.org'],
- 'messages': [
- {
- 'approval': True,
- 'sender': 'john@chromium.org',
- },
- ],
- }
- class WatchlistsMock(object):
- def __init__(self, _):
- pass
- @staticmethod
- def GetWatchersForPaths(_):
- return ['joe@example.com']
- class CodereviewSettingsFileMock(object):
- def __init__(self):
- pass
- # pylint: disable=R0201
- def read(self):
- return ("CODE_REVIEW_SERVER: gerrit.chromium.org\n" +
- "GERRIT_HOST: True\n")
- class AuthenticatorMock(object):
- def __init__(self, *_args):
- pass
- def has_cached_credentials(self):
- return True
- class TestGitCl(TestCase):
- def setUp(self):
- super(TestGitCl, self).setUp()
- self.calls = []
- self._calls_done = 0
- self.mock(subprocess2, 'call', self._mocked_call)
- self.mock(subprocess2, 'check_call', self._mocked_call)
- self.mock(subprocess2, 'check_output', self._mocked_call)
- self.mock(subprocess2, 'communicate', self._mocked_call)
- self.mock(git_common, 'is_dirty_git_tree', lambda x: False)
- self.mock(git_common, 'get_or_create_merge_base',
- lambda *a: (
- self._mocked_call(['get_or_create_merge_base']+list(a))))
- self.mock(git_cl, 'BranchExists', lambda _: True)
- self.mock(git_cl, 'FindCodereviewSettingsFile', lambda: '')
- self.mock(git_cl, 'ask_for_data', self._mocked_call)
- self.mock(git_cl.breakpad, 'post', self._mocked_call)
- self.mock(git_cl.breakpad, 'SendStack', self._mocked_call)
- self.mock(git_cl.presubmit_support, 'DoPresubmitChecks', PresubmitMock)
- self.mock(git_cl.rietveld, 'Rietveld', RietveldMock)
- self.mock(git_cl.rietveld, 'CachingRietveld', RietveldMock)
- self.mock(git_cl.upload, 'RealMain', self.fail)
- self.mock(git_cl.watchlists, 'Watchlists', WatchlistsMock)
- self.mock(git_cl.auth, 'get_authenticator_for_host', AuthenticatorMock)
- # It's important to reset settings to not have inter-tests interference.
- git_cl.settings = None
- def tearDown(self):
- try:
- if not self.has_failed():
- self.assertEquals([], self.calls)
- finally:
- super(TestGitCl, self).tearDown()
- def _mocked_call(self, *args, **_kwargs):
- self.assertTrue(
- self.calls,
- '@%d Expected: <Missing> Actual: %r' % (self._calls_done, args))
- top = self.calls.pop(0)
- if len(top) > 2 and top[2]:
- raise top[2]
- expected_args, result = top
- # Also logs otherwise it could get caught in a try/finally and be hard to
- # diagnose.
- if expected_args != args:
- msg = '@%d Expected: %r Actual: %r' % (
- self._calls_done, expected_args, args)
- git_cl.logging.error(msg)
- self.fail(msg)
- self._calls_done += 1
- return result
- @classmethod
- def _upload_calls(cls, similarity, find_copies, private):
- return (cls._git_base_calls(similarity, find_copies) +
- cls._git_upload_calls(private))
- @classmethod
- def _upload_no_rev_calls(cls, similarity, find_copies):
- return (cls._git_base_calls(similarity, find_copies) +
- cls._git_upload_no_rev_calls())
- @classmethod
- def _git_base_calls(cls, similarity, find_copies):
- if similarity is None:
- similarity = '50'
- similarity_call = ((['git', 'config', '--int', '--get',
- 'branch.master.git-cl-similarity'],), '')
- else:
- similarity_call = ((['git', 'config', '--int',
- 'branch.master.git-cl-similarity', similarity],), '')
- if find_copies is None:
- find_copies = True
- find_copies_call = ((['git', 'config', '--int', '--get',
- 'branch.master.git-find-copies'],), '')
- else:
- val = str(int(find_copies))
- find_copies_call = ((['git', 'config', '--int',
- 'branch.master.git-find-copies', val],), '')
- if find_copies:
- stat_call = ((['git', 'diff', '--no-ext-diff', '--stat',
- '--find-copies-harder', '-l100000', '-C'+similarity,
- 'fake_ancestor_sha', 'HEAD'],), '+dat')
- else:
- stat_call = ((['git', 'diff', '--no-ext-diff', '--stat',
- '-M'+similarity, 'fake_ancestor_sha', 'HEAD'],), '+dat')
- return [
- ((['git', 'config', 'rietveld.autoupdate'],), ''),
- ((['git', 'config', 'rietveld.server'],),
- 'codereview.example.com'),
- ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
- similarity_call,
- ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
- find_copies_call,
- ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
- ((['git', 'config', 'branch.master.merge'],), 'master'),
- ((['git', 'config', 'branch.master.remote'],), 'origin'),
- ((['get_or_create_merge_base', 'master', 'master'],),
- 'fake_ancestor_sha'),
- ((['git', 'config', 'gerrit.host'],), ''),
- ((['git', 'config', 'branch.master.rietveldissue'],), ''),
- ] + cls._git_sanity_checks('fake_ancestor_sha', 'master') + [
- ((['git', 'rev-parse', '--show-cdup'],), ''),
- ((['git', 'rev-parse', 'HEAD'],), '12345'),
- ((['git', 'diff', '--name-status', '--no-renames', '-r',
- 'fake_ancestor_sha...', '.'],),
- 'M\t.gitignore\n'),
- ((['git', 'config', 'branch.master.rietveldpatchset'],),
- ''),
- ((['git', 'log', '--pretty=format:%s%n%n%b',
- 'fake_ancestor_sha...'],),
- 'foo'),
- ((['git', 'config', 'user.email'],), 'me@example.com'),
- stat_call,
- ((['git', 'log', '--pretty=format:%s\n\n%b',
- 'fake_ancestor_sha..HEAD'],),
- 'desc\n'),
- ((['git', 'config', 'rietveld.bug-prefix'],), ''),
- ]
- @classmethod
- def _git_upload_no_rev_calls(cls):
- return [
- ((['git', 'config', 'core.editor'],), ''),
- ]
- @classmethod
- def _git_upload_calls(cls, private):
- if private:
- cc_call = []
- private_call = []
- else:
- cc_call = [((['git', 'config', 'rietveld.cc'],), '')]
- private_call = [
- ((['git', 'config', 'rietveld.private'],), '')]
- return [
- ((['git', 'config', 'core.editor'],), ''),
- ] + cc_call + private_call + [
- ((['git', 'config', 'branch.master.base-url'],), ''),
- ((['git', 'config', 'rietveld.pending-ref-prefix'],), ''),
- ((['git',
- 'config', '--local', '--get-regexp', '^svn-remote\\.'],),
- (('', None), 0)),
- ((['git', 'rev-parse', '--show-cdup'],), ''),
- ((['git', 'svn', 'info'],), ''),
- ((['git', 'config', 'rietveld.project'],), ''),
- ((['git',
- 'config', 'branch.master.rietveldissue', '1'],), ''),
- ((['git', 'config', 'branch.master.rietveldserver',
- 'https://codereview.example.com'],), ''),
- ((['git',
- 'config', 'branch.master.rietveldpatchset', '2'],), ''),
- ((['git', 'rev-parse', 'HEAD'],), 'hash'),
- ((['git', 'symbolic-ref', 'HEAD'],), 'hash'),
- ((['git',
- 'config', 'branch.hash.last-upload-hash', 'hash'],), ''),
- ((['git', 'config', 'rietveld.run-post-upload-hook'],), ''),
- ]
- @staticmethod
- def _git_sanity_checks(diff_base, working_branch):
- fake_ancestor = 'fake_ancestor'
- fake_cl = 'fake_cl_for_patch'
- return [
- # Calls to verify branch point is ancestor
- ((['git',
- 'rev-parse', '--verify', diff_base],), fake_ancestor),
- ((['git',
- 'merge-base', fake_ancestor, 'HEAD'],), fake_ancestor),
- ((['git',
- 'rev-list', '^' + fake_ancestor, 'HEAD'],), fake_cl),
- # Mock a config miss (error code 1)
- ((['git',
- 'config', 'gitcl.remotebranch'],), (('', None), 1)),
- # Call to GetRemoteBranch()
- ((['git',
- 'config', 'branch.%s.merge' % working_branch],),
- 'refs/heads/master'),
- ((['git',
- 'config', 'branch.%s.remote' % working_branch],), 'origin'),
- ((['git', 'rev-list', '^' + fake_ancestor,
- 'refs/remotes/origin/master'],), ''),
- ]
- @classmethod
- def _dcommit_calls_1(cls):
- return [
- ((['git', 'config', 'rietveld.autoupdate'],),
- ''),
- ((['git', 'config', 'rietveld.pending-ref-prefix'],),
- ''),
- ((['git',
- 'config', '--local', '--get-regexp', '^svn-remote\\.'],),
- ((('svn-remote.svn.url svn://svn.chromium.org/chrome\n'
- 'svn-remote.svn.fetch trunk/src:refs/remotes/origin/master'),
- None),
- 0)),
- ((['git',
- 'config', 'rietveld.server'],), 'codereview.example.com'),
- ((['git', 'symbolic-ref', 'HEAD'],), 'refs/heads/working'),
- ((['git', 'config', '--int', '--get',
- 'branch.working.git-cl-similarity'],), ''),
- ((['git', 'symbolic-ref', 'HEAD'],), 'refs/heads/working'),
- ((['git', 'config', '--int', '--get',
- 'branch.working.git-find-copies'],), ''),
- ((['git', 'symbolic-ref', 'HEAD'],), 'refs/heads/working'),
- ((['git',
- 'config', 'branch.working.merge'],), 'refs/heads/master'),
- ((['git', 'config', 'branch.working.remote'],), 'origin'),
- ((['git', 'config', 'branch.working.merge'],),
- 'refs/heads/master'),
- ((['git', 'config', 'branch.working.remote'],), 'origin'),
- ((['git', 'rev-list', '--merges',
- '--grep=^SVN changes up to revision [0-9]*$',
- 'refs/remotes/origin/master^!'],), ''),
- ((['git', 'rev-list', '^refs/heads/working',
- 'refs/remotes/origin/master'],),
- ''),
- ((['git',
- 'log', '--grep=^git-svn-id:', '-1', '--pretty=format:%H'],),
- '3fc18b62c4966193eb435baabe2d18a3810ec82e'),
- ((['git',
- 'rev-list', '^3fc18b62c4966193eb435baabe2d18a3810ec82e',
- 'refs/remotes/origin/master'],), ''),
- ((['git',
- 'merge-base', 'refs/remotes/origin/master', 'HEAD'],),
- 'fake_ancestor_sha'),
- ]
- @classmethod
- def _dcommit_calls_normal(cls):
- return [
- ((['git', 'rev-parse', '--show-cdup'],), ''),
- ((['git', 'rev-parse', 'HEAD'],),
- '00ff397798ea57439712ed7e04ab96e13969ef40'),
- ((['git',
- 'diff', '--name-status', '--no-renames', '-r', 'fake_ancestor_sha...',
- '.'],),
- 'M\tPRESUBMIT.py'),
- ((['git',
- 'config', 'branch.working.rietveldissue'],), '12345'),
- ((['git',
- 'config', 'branch.working.rietveldpatchset'],), '31137'),
- ((['git', 'config', 'branch.working.rietveldserver'],),
- 'codereview.example.com'),
- ((['git', 'config', 'user.email'],), 'author@example.com'),
- ((['git', 'config', 'rietveld.tree-status-url'],), ''),
- ]
- @classmethod
- def _dcommit_calls_bypassed(cls):
- return [
- ((['git',
- 'config', 'branch.working.rietveldissue'],), '12345'),
- ((['git', 'config', 'branch.working.rietveldserver'],),
- 'codereview.example.com'),
- ((['git', 'config', 'rietveld.tree-status-url'],), ''),
- (('GitClHooksBypassedCommit',
- 'Issue https://codereview.example.com/12345 bypassed hook when '
- 'committing (tree status was "unset")'), None),
- ]
- @classmethod
- def _dcommit_calls_3(cls):
- return [
- ((['git',
- 'diff', '--no-ext-diff', '--stat', '--find-copies-harder',
- '-l100000', '-C50', 'fake_ancestor_sha',
- 'refs/heads/working'],),
- (' PRESUBMIT.py | 2 +-\n'
- ' 1 files changed, 1 insertions(+), 1 deletions(-)\n')),
- ((['git', 'show-ref', '--quiet', '--verify',
- 'refs/heads/git-cl-commit'],),
- (('', None), 0)),
- ((['git', 'branch', '-D', 'git-cl-commit'],), ''),
- ((['git', 'show-ref', '--quiet', '--verify',
- 'refs/heads/git-cl-cherry-pick'],), ''),
- ((['git', 'rev-parse', '--show-cdup'],), '\n'),
- ((['git', 'checkout', '-q', '-b', 'git-cl-commit'],), ''),
- ((['git', 'reset', '--soft', 'fake_ancestor_sha'],), ''),
- ((['git', 'commit', '-m',
- 'Issue: 12345\n\nR=john@chromium.org\n\n'
- 'Review URL: https://codereview.example.com/12345 .'],),
- ''),
- ((['git', 'config', 'rietveld.force-https-commit-url'],), ''),
- ((['git',
- 'svn', 'dcommit', '-C50', '--no-rebase', '--rmdir'],),
- (('', None), 0)),
- ((['git', 'checkout', '-q', 'working'],), ''),
- ((['git', 'branch', '-D', 'git-cl-commit'],), ''),
- ]
- @staticmethod
- def _cmd_line(description, args, similarity, find_copies, private):
- """Returns the upload command line passed to upload.RealMain()."""
- return [
- 'upload', '--assume_yes', '--server',
- 'https://codereview.example.com',
- '--message', description
- ] + args + [
- '--cc', 'joe@example.com',
- ] + (['--private'] if private else []) + [
- '--git_similarity', similarity or '50'
- ] + (['--git_no_find_copies'] if find_copies == False else []) + [
- 'fake_ancestor_sha', 'HEAD'
- ]
- def _run_reviewer_test(
- self,
- upload_args,
- expected_description,
- returned_description,
- final_description,
- reviewers,
- private=False):
- """Generic reviewer test framework."""
- try:
- similarity = upload_args[upload_args.index('--similarity')+1]
- except ValueError:
- similarity = None
- if '--find-copies' in upload_args:
- find_copies = True
- elif '--no-find-copies' in upload_args:
- find_copies = False
- else:
- find_copies = None
- private = '--private' in upload_args
- self.calls = self._upload_calls(similarity, find_copies, private)
- def RunEditor(desc, _, **kwargs):
- self.assertEquals(
- '# Enter a description of the change.\n'
- '# This will be displayed on the codereview site.\n'
- '# The first line will also be used as the subject of the review.\n'
- '#--------------------This line is 72 characters long'
- '--------------------\n' +
- expected_description,
- desc)
- return returned_description
- self.mock(git_cl.gclient_utils, 'RunEditor', RunEditor)
- def check_upload(args):
- cmd_line = self._cmd_line(final_description, reviewers, similarity,
- find_copies, private)
- self.assertEquals(cmd_line, args)
- return 1, 2
- self.mock(git_cl.upload, 'RealMain', check_upload)
- git_cl.main(['upload'] + upload_args)
- def test_no_reviewer(self):
- self._run_reviewer_test(
- [],
- 'desc\n\nBUG=',
- '# Blah blah comment.\ndesc\n\nBUG=',
- 'desc\n\nBUG=',
- [])
- def test_keep_similarity(self):
- self._run_reviewer_test(
- ['--similarity', '70'],
- 'desc\n\nBUG=',
- '# Blah blah comment.\ndesc\n\nBUG=',
- 'desc\n\nBUG=',
- [])
- def test_keep_find_copies(self):
- self._run_reviewer_test(
- ['--no-find-copies'],
- 'desc\n\nBUG=',
- '# Blah blah comment.\ndesc\n\nBUG=\n',
- 'desc\n\nBUG=',
- [])
- def test_private(self):
- self._run_reviewer_test(
- ['--private'],
- 'desc\n\nBUG=',
- '# Blah blah comment.\ndesc\n\nBUG=\n',
- 'desc\n\nBUG=',
- [])
- def test_reviewers_cmd_line(self):
- # Reviewer is passed as-is
- description = 'desc\n\nR=foo@example.com\nBUG='
- self._run_reviewer_test(
- ['-r' 'foo@example.com'],
- description,
- '\n%s\n' % description,
- description,
- ['--reviewers=foo@example.com'])
- def test_reviewer_tbr_overriden(self):
- # Reviewer is overriden with TBR
- # Also verifies the regexp work without a trailing LF
- description = 'Foo Bar\n\nTBR=reviewer@example.com'
- self._run_reviewer_test(
- ['-r' 'foo@example.com'],
- 'desc\n\nR=foo@example.com\nBUG=',
- description.strip('\n'),
- description,
- ['--reviewers=reviewer@example.com'])
- def test_reviewer_multiple(self):
- # Handles multiple R= or TBR= lines.
- description = (
- 'Foo Bar\nTBR=reviewer@example.com\nBUG=\nR=another@example.com')
- self._run_reviewer_test(
- [],
- 'desc\n\nBUG=',
- description,
- description,
- ['--reviewers=another@example.com,reviewer@example.com'])
- def test_reviewer_send_mail(self):
- # --send-mail can be used without -r if R= is used
- description = 'Foo Bar\nR=reviewer@example.com'
- self._run_reviewer_test(
- ['--send-mail'],
- 'desc\n\nBUG=',
- description.strip('\n'),
- description,
- ['--reviewers=reviewer@example.com', '--send_mail'])
- def test_reviewer_send_mail_no_rev(self):
- # Fails without a reviewer.
- stdout = StringIO.StringIO()
- stderr = StringIO.StringIO()
- try:
- self.calls = self._upload_no_rev_calls(None, None)
- def RunEditor(desc, _, **kwargs):
- return desc
- self.mock(git_cl.gclient_utils, 'RunEditor', RunEditor)
- self.mock(sys, 'stdout', stdout)
- self.mock(sys, 'stderr', stderr)
- git_cl.main(['upload', '--send-mail'])
- self.fail()
- except SystemExit:
- self.assertEqual(
- 'Using 50% similarity for rename/copy detection. Override with '
- '--similarity.\n',
- stdout.getvalue())
- self.assertEqual(
- 'Must specify reviewers to send email.\n', stderr.getvalue())
- def test_dcommit(self):
- self.calls = (
- self._dcommit_calls_1() +
- self._git_sanity_checks('fake_ancestor_sha', 'working') +
- self._dcommit_calls_normal() +
- self._dcommit_calls_3())
- git_cl.main(['dcommit'])
- def test_dcommit_bypass_hooks(self):
- self.calls = (
- self._dcommit_calls_1() +
- self._dcommit_calls_bypassed() +
- self._dcommit_calls_3())
- git_cl.main(['dcommit', '--bypass-hooks'])
- @classmethod
- def _gerrit_base_calls(cls):
- return [
- ((['git', 'config', 'rietveld.autoupdate'],),
- ''),
- ((['git',
- 'config', 'rietveld.server'],), 'codereview.example.com'),
- ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
- ((['git', 'config', '--int', '--get',
- 'branch.master.git-cl-similarity'],), ''),
- ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
- ((['git', 'config', '--int', '--get',
- 'branch.master.git-find-copies'],), ''),
- ((['git', 'symbolic-ref', 'HEAD'],), 'master'),
- ((['git', 'config', 'branch.master.merge'],), 'master'),
- ((['git', 'config', 'branch.master.remote'],), 'origin'),
- ((['get_or_create_merge_base', 'master', 'master'],),
- 'fake_ancestor_sha'),
- ((['git', 'config', 'gerrit.host'],), 'True'),
- ] + cls._git_sanity_checks('fake_ancestor_sha', 'master') + [
- ((['git', 'rev-parse', '--show-cdup'],), ''),
- ((['git', 'rev-parse', 'HEAD'],), '12345'),
- ((['git',
- 'diff', '--name-status', '--no-renames', '-r',
- 'fake_ancestor_sha...', '.'],),
- 'M\t.gitignore\n'),
- ((['git', 'config', 'branch.master.rietveldissue'],), ''),
- ((['git',
- 'config', 'branch.master.rietveldpatchset'],), ''),
- ((['git',
- 'log', '--pretty=format:%s%n%n%b', 'fake_ancestor_sha...'],),
- 'foo'),
- ((['git', 'config', 'user.email'],), 'me@example.com'),
- ((['git',
- 'diff', '--no-ext-diff', '--stat', '--find-copies-harder',
- '-l100000', '-C50', 'fake_ancestor_sha', 'HEAD'],),
- '+dat'),
- ]
- @staticmethod
- def _gerrit_upload_calls(description, reviewers, squash,
- expected_upstream_ref='origin/refs/heads/master'):
- calls = [
- ((['git', 'config', '--bool', 'gerrit.squash-uploads'],), 'false'),
- ((['git', 'log', '--pretty=format:%s\n\n%b',
- 'fake_ancestor_sha..HEAD'],),
- description)
- ]
- if git_cl.CHANGE_ID not in description:
- calls += [
- ((['git', 'log', '--pretty=format:%s\n\n%b',
- 'fake_ancestor_sha..HEAD'],),
- description),
- ((['git', 'commit', '--amend', '-m', description],),
- ''),
- ((['git', 'log', '--pretty=format:%s\n\n%b',
- 'fake_ancestor_sha..HEAD'],),
- description)
- ]
- if squash:
- ref_to_push = 'abcdef0123456789'
- calls += [
- ((['git', 'show', '--format=%s\n\n%b', '-s',
- 'refs/heads/git_cl_uploads/master'],),
- (description, 0)),
- ((['git', 'config', 'branch.master.merge'],),
- 'refs/heads/master'),
- ((['git', 'config', 'branch.master.remote'],),
- 'origin'),
- ((['get_or_create_merge_base', 'master', 'master'],),
- 'origin/master'),
- ((['git', 'rev-parse', 'HEAD:'],),
- '0123456789abcdef'),
- ((['git', 'commit-tree', '0123456789abcdef', '-p',
- 'origin/master', '-m', 'd'],),
- ref_to_push),
- ]
- else:
- ref_to_push = 'HEAD'
- calls += [
- ((['git', 'rev-list',
- expected_upstream_ref + '..' + ref_to_push],), ''),
- ((['git', 'config', 'rietveld.cc'],), '')
- ]
- receive_pack = '--receive-pack=git receive-pack '
- receive_pack += '--cc=joe@example.com' # from watch list
- if reviewers:
- receive_pack += ' '
- receive_pack += ' '.join(
- '--reviewer=' + email for email in sorted(reviewers))
- receive_pack += ''
- calls += [
- ((['git',
- 'push', receive_pack, 'origin',
- ref_to_push + ':refs/for/refs/heads/master'],),
- '')
- ]
- if squash:
- calls += [
- ((['git', 'rev-parse', 'HEAD'],), 'abcdef0123456789'),
- ((['git', 'update-ref', '-m', 'Uploaded abcdef0123456789',
- 'refs/heads/git_cl_uploads/master', 'abcdef0123456789'],),
- '')
- ]
- return calls
- def _run_gerrit_upload_test(
- self,
- upload_args,
- description,
- reviewers,
- squash=False,
- expected_upstream_ref='origin/refs/heads/master'):
- """Generic gerrit upload test framework."""
- self.calls = self._gerrit_base_calls()
- self.calls += self._gerrit_upload_calls(
- description, reviewers, squash,
- expected_upstream_ref=expected_upstream_ref)
- git_cl.main(['upload'] + upload_args)
- def test_gerrit_upload_without_change_id(self):
- self._run_gerrit_upload_test(
- [],
- 'desc\n\nBUG=\n',
- [])
- def test_gerrit_no_reviewer(self):
- self._run_gerrit_upload_test(
- [],
- 'desc\n\nBUG=\nChange-Id:123456789\n',
- [])
- def test_gerrit_reviewers_cmd_line(self):
- self._run_gerrit_upload_test(
- ['-r', 'foo@example.com'],
- 'desc\n\nBUG=\nChange-Id:123456789',
- ['foo@example.com'])
- def test_gerrit_reviewer_multiple(self):
- self._run_gerrit_upload_test(
- [],
- 'desc\nTBR=reviewer@example.com\nBUG=\nR=another@example.com\n'
- 'Change-Id:123456789\n',
- ['reviewer@example.com', 'another@example.com'])
- def test_gerrit_upload_squash(self):
- self._run_gerrit_upload_test(
- ['--squash'],
- 'desc\n\nBUG=\nChange-Id:123456789\n',
- [],
- squash=True,
- expected_upstream_ref='origin/master')
- def test_upload_branch_deps(self):
- def mock_run_git(*args, **_kwargs):
- if args[0] == ['for-each-ref',
- '--format=%(refname:short) %(upstream:short)',
- 'refs/heads']:
- # Create a local branch dependency tree that looks like this:
- # test1 -> test2 -> test3 -> test4 -> test5
- # -> test3.1
- # test6 -> test0
- branch_deps = [
- 'test2 test1', # test1 -> test2
- 'test3 test2', # test2 -> test3
- 'test3.1 test2', # test2 -> test3.1
- 'test4 test3', # test3 -> test4
- 'test5 test4', # test4 -> test5
- 'test6 test0', # test0 -> test6
- 'test7', # test7
- ]
- return '\n'.join(branch_deps)
- self.mock(git_cl, 'RunGit', mock_run_git)
- git_cl.settings = git_cl.Settings()
- self.mock(git_cl.settings, 'GetIsGerrit', lambda: False)
- class RecordCalls:
- times_called = 0
- record_calls = RecordCalls()
- def mock_CMDupload(*args, **_kwargs):
- record_calls.times_called += 1
- return 0
- self.mock(git_cl, 'CMDupload', mock_CMDupload)
- self.calls = [
- (('[Press enter to continue or ctrl-C to quit]',), ''),
- ]
- class MockChangelist():
- def __init__(self):
- pass
- def GetBranch(self):
- return 'test1'
- def GetIssue(self):
- return '123'
- def GetPatchset(self):
- return '1001'
- ret = git_cl.upload_branch_deps(MockChangelist(), [])
- # CMDupload should have been called 5 times because of 5 dependent branches.
- self.assertEquals(5, record_calls.times_called)
- self.assertEquals(0, ret)
- def test_config_gerrit_download_hook(self):
- self.mock(git_cl, 'FindCodereviewSettingsFile', CodereviewSettingsFileMock)
- def ParseCodereviewSettingsContent(content):
- keyvals = {}
- keyvals['CODE_REVIEW_SERVER'] = 'gerrit.chromium.org'
- keyvals['GERRIT_HOST'] = 'True'
- return keyvals
- self.mock(git_cl.gclient_utils, 'ParseCodereviewSettingsContent',
- ParseCodereviewSettingsContent)
- self.mock(git_cl.os, 'access', self._mocked_call)
- self.mock(git_cl.os, 'chmod', self._mocked_call)
- src_dir = os.path.join(os.path.sep, 'usr', 'local', 'src')
- def AbsPath(path):
- if not path.startswith(os.path.sep):
- return os.path.join(src_dir, path)
- return path
- self.mock(git_cl.os.path, 'abspath', AbsPath)
- commit_msg_path = os.path.join(src_dir, '.git', 'hooks', 'commit-msg')
- def Exists(path):
- if path == commit_msg_path:
- return False
- # others paths, such as /usr/share/locale/....
- return True
- self.mock(git_cl.os.path, 'exists', Exists)
- self.mock(git_cl, 'urlretrieve', self._mocked_call)
- self.mock(git_cl, 'hasSheBang', self._mocked_call)
- self.calls = [
- ((['git', 'config', 'rietveld.autoupdate'],),
- ''),
- ((['git', 'config', 'rietveld.server',
- 'gerrit.chromium.org'],), ''),
- ((['git', 'config', '--unset-all', 'rietveld.cc'],), ''),
- ((['git', 'config', '--unset-all',
- 'rietveld.private'],), ''),
- ((['git', 'config', '--unset-all',
- 'rietveld.tree-status-url'],), ''),
- ((['git', 'config', '--unset-all',
- 'rietveld.viewvc-url'],), ''),
- ((['git', 'config', '--unset-all',
- 'rietveld.bug-prefix'],), ''),
- ((['git', 'config', '--unset-all',
- 'rietveld.cpplint-regex'],), ''),
- ((['git', 'config', '--unset-all',
- 'rietveld.force-https-commit-url'],), ''),
- ((['git', 'config', '--unset-all',
- 'rietveld.cpplint-ignore-regex'],), ''),
- ((['git', 'config', '--unset-all',
- 'rietveld.project'],), ''),
- ((['git', 'config', '--unset-all',
- 'rietveld.pending-ref-prefix'],), ''),
- ((['git', 'config', '--unset-all',
- 'rietveld.run-post-upload-hook'],), ''),
- ((['git', 'config', 'gerrit.host', 'True'],), ''),
- # DownloadHooks(False)
- ((['git', 'config', 'gerrit.host'],), 'True'),
- ((['git', 'rev-parse', '--show-cdup'],), ''),
- ((commit_msg_path, os.X_OK,), False),
- (('https://gerrit-review.googlesource.com/tools/hooks/commit-msg',
- commit_msg_path,), ''),
- ((commit_msg_path,), True),
- ((commit_msg_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR,), ''),
- # GetCodereviewSettingsInteractively
- ((['git', 'config', 'rietveld.server'],),
- 'gerrit.chromium.org'),
- (('Rietveld server (host[:port]) [https://gerrit.chromium.org]:',),
- ''),
- ((['git', 'config', 'rietveld.cc'],), ''),
- (('CC list:',), ''),
- ((['git', 'config', 'rietveld.private'],), ''),
- (('Private flag (rietveld only):',), ''),
- ((['git', 'config', 'rietveld.tree-status-url'],), ''),
- (('Tree status URL:',), ''),
- ((['git', 'config', 'rietveld.viewvc-url'],), ''),
- (('ViewVC URL:',), ''),
- # DownloadHooks(True)
- ((['git', 'config', 'rietveld.bug-prefix'],), ''),
- (('Bug Prefix:',), ''),
- ((['git', 'config', 'rietveld.run-post-upload-hook'],), ''),
- (('Run Post Upload Hook:',), ''),
- ((commit_msg_path, os.X_OK,), True),
- ]
- git_cl.main(['config'])
- def test_update_reviewers(self):
- data = [
- ('foo', [], 'foo'),
- ('foo\nR=xx', [], 'foo\nR=xx'),
- ('foo\nTBR=xx', [], 'foo\nTBR=xx'),
- ('foo', ['a@c'], 'foo\n\nR=a@c'),
- ('foo\nR=xx', ['a@c'], 'foo\n\nR=a@c, xx'),
- ('foo\nTBR=xx', ['a@c'], 'foo\n\nR=a@c\nTBR=xx'),
- ('foo\nTBR=xx\nR=yy', ['a@c'], 'foo\n\nR=a@c, yy\nTBR=xx'),
- ('foo\nBUG=', ['a@c'], 'foo\nBUG=\nR=a@c'),
- ('foo\nR=xx\nTBR=yy\nR=bar', ['a@c'], 'foo\n\nR=a@c, xx, bar\nTBR=yy'),
- ('foo', ['a@c', 'b@c'], 'foo\n\nR=a@c, b@c'),
- ('foo\nBar\n\nR=\nBUG=', ['c@c'], 'foo\nBar\n\nR=c@c\nBUG='),
- ('foo\nBar\n\nR=\nBUG=\nR=', ['c@c'], 'foo\nBar\n\nR=c@c\nBUG='),
- # Same as the line before, but full of whitespaces.
- (
- 'foo\nBar\n\n R = \n BUG = \n R = ', ['c@c'],
- 'foo\nBar\n\nR=c@c\n BUG =',
- ),
- # Whitespaces aren't interpreted as new lines.
- ('foo BUG=allo R=joe ', ['c@c'], 'foo BUG=allo R=joe\n\nR=c@c'),
- ]
- expected = [i[2] for i in data]
- actual = []
- for orig, reviewers, _expected in data:
- obj = git_cl.ChangeDescription(orig)
- obj.update_reviewers(reviewers)
- actual.append(obj.description)
- self.assertEqual(expected, actual)
- def test_get_target_ref(self):
- # Check remote or remote branch not present.
- self.assertEqual(None, git_cl.GetTargetRef('origin', None, 'master', None))
- self.assertEqual(None, git_cl.GetTargetRef(None,
- 'refs/remotes/origin/master',
- 'master', None))
- # Check default target refs for branches.
- self.assertEqual('refs/heads/master',
- git_cl.GetTargetRef('origin', 'refs/remotes/origin/master',
- None, None))
- self.assertEqual('refs/heads/master',
- git_cl.GetTargetRef('origin', 'refs/remotes/origin/lkgr',
- None, None))
- self.assertEqual('refs/heads/master',
- git_cl.GetTargetRef('origin', 'refs/remotes/origin/lkcr',
- None, None))
- self.assertEqual('refs/branch-heads/123',
- git_cl.GetTargetRef('origin',
- 'refs/remotes/branch-heads/123',
- None, None))
- self.assertEqual('refs/diff/test',
- git_cl.GetTargetRef('origin',
- 'refs/remotes/origin/refs/diff/test',
- None, None))
- self.assertEqual('refs/heads/chrome/m42',
- git_cl.GetTargetRef('origin',
- 'refs/remotes/origin/chrome/m42',
- None, None))
- # Check target refs for user-specified target branch.
- for branch in ('branch-heads/123', 'remotes/branch-heads/123',
- 'refs/remotes/branch-heads/123'):
- self.assertEqual('refs/branch-heads/123',
- git_cl.GetTargetRef('origin',
- 'refs/remotes/origin/master',
- branch, None))
- for branch in ('origin/master', 'remotes/origin/master',
- 'refs/remotes/origin/master'):
- self.assertEqual('refs/heads/master',
- git_cl.GetTargetRef('origin',
- 'refs/remotes/branch-heads/123',
- branch, None))
- for branch in ('master', 'heads/master', 'refs/heads/master'):
- self.assertEqual('refs/heads/master',
- git_cl.GetTargetRef('origin',
- 'refs/remotes/branch-heads/123',
- branch, None))
- # Check target refs for pending prefix.
- self.assertEqual('prefix/heads/master',
- git_cl.GetTargetRef('origin', 'refs/remotes/origin/master',
- None, 'prefix/'))
- def test_patch_when_dirty(self):
- # Patch when local tree is dirty
- self.mock(git_common, 'is_dirty_git_tree', lambda x: True)
- self.assertNotEqual(git_cl.main(['patch', '123456']), 0)
- def test_diff_when_dirty(self):
- # Do 'git cl diff' when local tree is dirty
- self.mock(git_common, 'is_dirty_git_tree', lambda x: True)
- self.assertNotEqual(git_cl.main(['diff']), 0)
- def _patch_common(self):
- self.mock(git_cl.Changelist, 'GetMostRecentPatchset', lambda x: '60001')
- self.mock(git_cl.Changelist, 'GetPatchSetDiff', lambda *args: None)
- self.mock(git_cl.Changelist, 'GetDescription', lambda *args: 'Description')
- self.mock(git_cl.Changelist, 'SetIssue', lambda *args: None)
- self.mock(git_cl.Changelist, 'SetPatchset', lambda *args: None)
- self.mock(git_cl, 'IsGitVersionAtLeast', lambda *args: True)
- self.calls = [
- ((['git', 'config', 'rietveld.autoupdate'],), ''),
- ((['git', 'config', 'rietveld.server'],), 'codereview.example.com'),
- ((['git', 'rev-parse', '--show-cdup'],), ''),
- ((['sed', '-e', 's|^--- a/|--- |; s|^+++ b/|+++ |'],), ''),
- ]
- def test_patch_successful(self):
- self._patch_common()
- self.calls += [
- ((['git', 'apply', '--index', '-p0', '--3way'],), ''),
- ((['git', 'commit', '-m',
- 'Description\n\n' +
- 'patch from issue 123456 at patchset 60001 ' +
- '(http://crrev.com/123456#ps60001)'],), ''),
- ]
- self.assertEqual(git_cl.main(['patch', '123456']), 0)
- def test_patch_conflict(self):
- self._patch_common()
- self.calls += [
- ((['git', 'apply', '--index', '-p0', '--3way'],), '',
- subprocess2.CalledProcessError(1, '', '', '', '')),
- ]
- self.assertNotEqual(git_cl.main(['patch', '123456']), 0)
- if __name__ == '__main__':
- git_cl.logging.basicConfig(
- level=git_cl.logging.DEBUG if '-v' in sys.argv else git_cl.logging.ERROR)
- unittest.main()
|