Rev 38: (James Henstridge) clean up the SMTP handling, and PQM Submission logic. in http://bazaar.launchpad.net/%7Ebzr/bzr-pqm/devel

John Arbash Meinel john at arbash-meinel.com
Tue Nov 6 02:44:08 GMT 2007


At http://bazaar.launchpad.net/%7Ebzr/bzr-pqm/devel

------------------------------------------------------------
revno: 38
revision-id:john at arbash-meinel.com-20071106024333-wbjbqrtdgcvtu4bg
parent: mbp at sourcefrog.net-20070712231153-a0mqgiz3ppf2xgdx
parent: james at jamesh.id.au-20071025091923-vtb9vag27j1zatry
committer: John Arbash Meinel <john at arbash-meinel.com>
branch nick: pqm
timestamp: Mon 2007-11-05 20:43:33 -0600
message:
  (James Henstridge) clean up the SMTP handling, and PQM Submission logic.
modified:
  __init__.py                    __init__.py-20060221052551-30932fa7d369a24b
  pqm_submit.py                  pqm_submit.py-20060221060137-b3a3cdde9f50efab
  test_pqm_submit.py             test_pqm_submit.py-20060221060137-fb48d47216aa0077
    ------------------------------------------------------------
    revno: 37.1.6
    revision-id:james at jamesh.id.au-20071025091923-vtb9vag27j1zatry
    parent: james at jamesh.id.au-20071025091756-rnh1w9i4oircmo3p
    committer: James Henstridge <james at jamesh.id.au>
    branch nick: bzr-pqm.jamesh
    timestamp: Thu 2007-10-25 17:19:23 +0800
    message:
      Remove some unnecessary imports.
    modified:
      __init__.py                    __init__.py-20060221052551-30932fa7d369a24b
    ------------------------------------------------------------
    revno: 37.1.5
    revision-id:james at jamesh.id.au-20071025091756-rnh1w9i4oircmo3p
    parent: james at jamesh.id.au-20071023105033-k9rgn1x1ip8tfm2c
    committer: James Henstridge <james at jamesh.id.au>
    branch nick: bzr-pqm.jamesh
    timestamp: Thu 2007-10-25 17:17:56 +0800
    message:
      Simplify PQMSubmission object, fixing up locations in the constructor.
    modified:
      pqm_submit.py                  pqm_submit.py-20060221060137-b3a3cdde9f50efab
      test_pqm_submit.py             test_pqm_submit.py-20060221060137-fb48d47216aa0077
    ------------------------------------------------------------
    revno: 37.1.4
    revision-id:james at jamesh.id.au-20071023105033-k9rgn1x1ip8tfm2c
    parent: james at jamesh.id.au-20071018104526-grxnfe1iensz7yrf
    committer: James Henstridge <james at jamesh.id.au>
    branch nick: bzr-pqm.jamesh
    timestamp: Tue 2007-10-23 18:50:33 +0800
    message:
      * Move the PQM submission logic into a PQMSubmission object.
      * Add tests for PQM submission logic.
    modified:
      pqm_submit.py                  pqm_submit.py-20060221060137-b3a3cdde9f50efab
      test_pqm_submit.py             test_pqm_submit.py-20060221060137-fb48d47216aa0077
    ------------------------------------------------------------
    revno: 37.1.3
    revision-id:james at jamesh.id.au-20071018104526-grxnfe1iensz7yrf
    parent: james at jamesh.id.au-20071011041243-a39r6wjtcn6u18sb
    committer: James Henstridge <james at jamesh.id.au>
    branch nick: bzr-pqm.jamesh
    timestamp: Thu 2007-10-18 23:45:26 +1300
    message:
      Try and share more configuration from merge directive code:
       * Emit a warning if public_repository or pqm_branch configuration items 
         are used.
       * Use submit_branch as a fallback for pqm_branch.
    modified:
      __init__.py                    __init__.py-20060221052551-30932fa7d369a24b
      pqm_submit.py                  pqm_submit.py-20060221060137-b3a3cdde9f50efab
    ------------------------------------------------------------
    revno: 37.1.2
    revision-id:james at jamesh.id.au-20071011041243-a39r6wjtcn6u18sb
    parent: james at jamesh.id.au-20071009040938-zc6082dyt5zfabko
    committer: James Henstridge <james at jamesh.id.au>
    branch nick: bzr-pqm.jamesh
    timestamp: Thu 2007-10-11 17:12:43 +1300
    message:
      Kill the custom smtplib code, and use bzr's email+smtp infrastructure.
    modified:
      pqm_submit.py                  pqm_submit.py-20060221060137-b3a3cdde9f50efab
      test_pqm_submit.py             test_pqm_submit.py-20060221060137-fb48d47216aa0077
    ------------------------------------------------------------
    revno: 37.1.1
    revision-id:james at jamesh.id.au-20071009040938-zc6082dyt5zfabko
    parent: mbp at sourcefrog.net-20070712231153-a0mqgiz3ppf2xgdx
    committer: James Henstridge <james at jamesh.id.au>
    branch nick: bzr-pqm.jamesh
    timestamp: Tue 2007-10-09 17:09:38 +1300
    message:
      Change how we speak to the SMTP server a bit:
      
      * Explicitly say EHLO/HELO to the server, rather than doing so 
        implicitly as part of smtp.login()
      * Only try to STARTTLS if the extension is supported, and error out if 
        if fails.
      * Say EHLO again after STARTTLS in case the server only supports AUTH 
        after switching to TLS.
      
      With these changes, we should be able to successfully talk to more 
      servers (e.g. EXIM servers that only allow AUTH after TLS).
    modified:
      pqm_submit.py                  pqm_submit.py-20060221060137-b3a3cdde9f50efab
-------------- next part --------------
=== modified file '__init__.py'
--- a/__init__.py	2007-07-12 23:11:53 +0000
+++ b/__init__.py	2007-10-25 09:19:23 +0000
@@ -16,11 +16,8 @@
 """Functionality for controlling a Patch Queue Manager (pqm).
 """
 
-import os
-
 from bzrlib.commands import Command, register_command
 from bzrlib.option import Option
-import bzrlib.errors as errors
 
 from bzrlib.bzrdir import BzrDir
 
@@ -29,11 +26,11 @@
     """Submit the parent tree to the pqm.
 
     This acts like:
-        $ echo "star-merge $PARENT $TARGET" 
-            | gpg --cl 
+        $ echo "star-merge $PARENT $TARGET"
+            | gpg --cl
             | mail pqm at somewhere -s "merge text"
 
-    But it pays attention to who the local committer is 
+    But it pays attention to who the local committer is
     (using their e-mail address), and uses the local
     gpg signing configuration. (As well as target pqm
     settings, etc.)
@@ -41,26 +38,24 @@
     The reason we use 'parent' instead of the local branch
     is that most likely the local branch is not a public
     branch. And the branch must be available to the pqm.
-    
+
     This can be configured at the branch level using ~/.bazaar/locations.conf.
     Here is an example:
         [/home/emurphy/repo]
         pqm_email = PQM <pqm at example.com>
         pqm_user_email = User Name <user at example.com>
-        pqm_branch = http://code.example.com/code/project/devel
-        public_repository = http://code.example.com/code/emurphy/project
+        submit_branch = http://code.example.com/code/project/devel
+        # Set public_branch appropriately for all branches in repository:
+        public_branch = http://code.example.com/code/emurphy/project
+        public_branch:policy = appendpath
         [/home/emurphy/repo/branch]
-        # public_repository is used when public_branch is not set.
-        # default behavior is to append the trailing path
-        # Default value based on public_repository would be
-        # http://code.example.com/code/emurphy/project/branch
-        # override with
+        # Override public_branch for this repository:
         public_branch = http://alternate.host.example.com/other/public/branch
-                
+
         smtp_server = host:port
         smtp_username =
         smtp_password =
-        
+
     If you don't specify the smtp server, the message will be sent via localhost.
     """
 
@@ -77,7 +72,7 @@
         ]
 
     def run(self, location=None, message=None, public_location=None, dry_run=False):
-        from pqm_submit import submit
+        from bzrlib.plugins.pqm.pqm_submit import submit
 
         if location is None:
             location = '.'
@@ -97,7 +92,7 @@
 
 
 def test_suite():
-    from bzrlib.tests import TestSuite, TestLoader
+    from bzrlib.tests import TestLoader
     import test_pqm_submit
 
     loader = TestLoader()

=== modified file 'pqm_submit.py'
--- a/pqm_submit.py	2007-04-26 02:05:22 +0000
+++ b/pqm_submit.py	2007-10-25 09:17:56 +0000
@@ -15,22 +15,17 @@
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 """Submit an email to a Patch Queue Manager"""
 
-import smtplib
-
-import bzrlib
 from bzrlib import (
+    config as _mod_config,
     errors,
+    gpg,
+    osutils,
+    urlutils,
     )
 from bzrlib.branch import Branch
-import bzrlib.config
-import bzrlib.gpg
-import bzrlib.osutils
-from bzrlib.trace import note
-import bzrlib.ui
-import bzrlib.urlutils
-
-
-_default_smtp_server = 'localhost'
+from bzrlib.email_message import EmailMessage
+from bzrlib.smtp_connection import SMTPConnection
+from bzrlib.trace import note, warning
 
 
 class BadCommitMessage(errors.BzrError):
@@ -42,129 +37,131 @@
         self.msg = message
 
 
-def _signed_submission(branch, config, target_base):
-    """Get the signed text that we would submit to the pqm"""
-
-    merge_target = config.get_user_option('pqm_branch')
-    if not merge_target:
-        raise errors.BzrCommandError('No PQM target branch specified '
-                                     'in configuration')
-
-    unsigned_text = 'star-merge %s %s' % (target_base, merge_target)
-    unsigned_text = unsigned_text.encode('ascii') #URLs should be ascii
-
-    strategy = bzrlib.gpg.GPGStrategy(config)
-
-    return strategy.sign(unsigned_text)
-
-
-_default_headers = '''From: %s
-To: %s
-Subject: %s
-
-'''
-
-
-def _setup_headers(message, user_from, user_to):
-    """Get all of the important headers filled out."""
-    if '\n' in message:
-        raise BadCommitMessage(message)
-    return _default_headers % (user_from, user_to, message)
-
-
-def public_branch(branch, config):
-    target_base = config.get_user_option('public_branch')
-    if target_base is None:
-        repo_loc = branch.repository.bzrdir.root_transport.local_abspath('.')
-        repo_config = bzrlib.config.LocationConfig(repo_loc)
-        public_repo = repo_config.get_user_option("public_repository")
-        if public_repo is not None:
-            branch_relpath = bzrlib.osutils.relpath(repo_loc,
-                branch.bzrdir.root_transport.local_abspath('.'))
-            target_base = bzrlib.urlutils.join(public_repo, branch_relpath)
-    return target_base
+class PQMSubmission(object):
+    """A request to perform a PQM merge into a branch."""
+
+    def __init__(self, source_branch, public_location=None,
+                 submit_location=None, message=None):
+        """Create a PQMSubmission object.
+
+        :param source_branch: the source branch for the merge
+        :param public_location: the public location of the source branch
+        :param submit_location: the location of the target branch
+        :param message: The message to use when committing this merge
+
+        If any of public_location, submit_location or message are
+        omitted, they will be calculated from source_branch.
+        """
+        if source_branch is None:
+            raise errors.NoMergeSource()
+        self.source_branch = source_branch
+
+        if public_location is None:
+            public_location = self.source_branch.get_public_branch()
+            # Fall back to the old public_repository hack.
+            if public_location is None:
+                src_loc = source_branch.bzrdir.root_transport.local_abspath('.')
+                repository = source_branch.repository
+                repo_loc = repository.bzrdir.root_transport.local_abspath('.')
+                repo_config = _mod_config.LocationConfig(repo_loc)
+                public_repo = repo_config.get_user_option("public_repository")
+                if public_repo is not None:
+                    warning("Please use public_branch, not public_repository, "
+                            "to set the public location of branches.")
+                    branch_relpath = osutils.relpath(repo_loc, src_loc)
+                    public_location = urlutils.join(public_repo, branch_relpath)
+
+            if public_location is None:
+                raise errors.BzrCommandError(
+                    'No public branch location given.  Please specify with '
+                    '--public-location or see "bzr help pqm-submit" to see how '
+                    'to set it in ~/.bazaar/locations.conf')
+        self.public_location = public_location
+
+        if submit_location is None:
+            config = self.source_branch.get_config()
+            # First check the deprecated pqm_branch config key:
+            submit_location = config.get_user_option('pqm_branch')
+            if submit_location is not None:
+                warning("Please use submit_branch, not pqm_branch to set "
+                        "the PQM merge target branch.")
+            else:
+                # Otherwise, use the standard config key:
+                submit_location = self.source_branch.get_submit_branch()
+
+            if submit_location is None:
+                raise errors.NoSubmitBranch(self.source_branch)
+        self.submit_location = submit_location
+
+        # Check that the message is okay to pass to PQM
+        if message is None:
+            repository = self.source_branch.repository
+            rev = repository.get_revision(self.source_branch.last_revision())
+            message = rev.message
+        self.message = message.encode('utf8')
+        if '\n' in self.message:
+            raise BadCommitMessage(self.message)
+
+    def check_public_branch(self):
+        """Check that the public branch is up to date with the local copy."""
+        note('Checking that the public branch is up to date ...')
+        local_revision = self.source_branch.last_revision()
+        public_revision = Branch.open(self.public_location).last_revision()
+        if local_revision != public_revision:
+            raise errors.PublicBranchOutOfDate(
+                self.public_location, local_revision)
+
+    def to_lines(self):
+        """Serialise as a list of lines."""
+        return ['star-merge %s %s\n' % (self.public_location, self.submit_location)]
+
+    def to_signed(self):
+        """Serialize as a signed string."""
+        unsigned_text = ''.join(self.to_lines())
+        unsigned_text = unsigned_text.encode('ascii') #URLs should be ascii
+
+        strategy = gpg.GPGStrategy(self.source_branch.get_config())
+        return strategy.sign(unsigned_text)
+
+    def to_email(self, mail_from, mail_to, sign=True):
+        """Serialize as an email message.
+
+        :param mail_from: The from address for the message
+        :param mail_to: The address to send the message to
+        :param sign: If True, gpg-sign the email
+        :return: an email message
+        """
+        if sign:
+            body = self.to_signed()
+        else:
+            body = ''.join(self.to_lines())
+        message = EmailMessage(mail_from, mail_to, self.message, body)
+        return message
 
 
 def submit(branch, message, dry_run=False, public_location=None):
     """Submit the given branch to the pqm."""
     config = branch.get_config()
 
-    if message is None:
-        rev = branch.repository.get_revision(branch.last_revision())
-        message = rev.message
-    message = message.encode('utf8')
+    submission = PQMSubmission(
+        source_branch=branch, public_location=public_location, message=message)
 
-    user_from = config.get_user_option('pqm_user_email')
-    if not user_from:
-        user_from = config.username()
-    user_from = user_from.encode('utf8') # Make sure this isn't unicode
-    user_to = config.get_user_option('pqm_email')
-    if not user_to:
+    mail_from = config.get_user_option('pqm_user_email')
+    if not mail_from:
+        mail_from = config.username()
+    mail_from = mail_from.encode('utf8') # Make sure this isn't unicode
+    mail_to = config.get_user_option('pqm_email')
+    if not mail_to:
         raise errors.BzrCommandError('No PQM submission address specified '
                                      'in configuration')
-    user_to = user_to.encode('utf8') # same here
-
-    # All the entries must be utf-8 so the message creation doesn't fail
-    msg = _setup_headers(message, user_from, user_to)
-
-    server = config.get_user_option('smtp_server')
-    if not server:
-        server = _default_smtp_server
-    smtp_username = config.get_user_option('smtp_username')
-    smtp_password = config.get_user_option('smtp_password')
-
-    if public_location is None:
-        target_base = public_branch(branch, config)
-    else:
-        target_base = public_location
-
-    if target_base is None:
-        raise errors.BzrCommandError('Could not find public location, either'
-                                     'specify with --public-location, or see'
-                                     ' "bzr help pqm-submit" to see how to set'
-                                     'it in ~/.bazaar/locations.conf')
-
-    note('Checking that the public branch is up to date ...')
-    try:
-        public_revision = Branch.open(target_base).last_revision()
-    except errors.NotBranchError:
-        raise errors.BzrCommandError(
-            'Public location has no branch: %s' % target_base)
-    if public_revision != branch.last_revision():
-        raise errors.BzrCommandError(
-            'Local last revision does not match public last revision: %s' %
-            (target_base,))
-    # Get the signed text, this should be done just before
-    # emailing, so that we don't run gpg --cl for nothing
-    msg += _signed_submission(branch, config, target_base)
+    mail_to = mail_to.encode('utf8') # same here
+
+    submission.check_public_branch()
+
+    message = submission.to_email(mail_from, mail_to)
 
     if dry_run:
-        return print_info(msg, server, user_from, user_to)
-
-    smtp = smtplib.SMTP()
-    smtp.connect(server)
-
-    # The world can always use a little bit more encryption :)
-    smtp.starttls()
-
-    if smtp_username is not None:
-        if smtp_password is None:
-            smtp_password = bzrlib.ui.ui_factory.get_password(
-                'Please enter SMTP password for %(host)s', host=server)
-        try:
-            smtp.login(smtp_username, smtp_password)
-        except smtplib.SMTPHeloError, e:
-            raise errors.BzrCommandError('SMTP server refused HELO: %d %s'
-                                         % (e.smtp_code, e.smtp_error))
-        except smtplib.SMTPAuthenticationError, e:
-            raise errors.BzrCommandError('SMTP server refused authentication: %d %s'
-                                         % (e.smtp_code, e.smtp_error))
-        except smtplib.SMTPException, e:
-            raise errors.BzrCommandError(str(e))
-
-    smtp.sendmail(user_from, [user_to], msg)
-
-
-def print_info(msg, server, user_from, user_to):
-    print "Server: %s" % server
-    print "%s" % msg
+        print message.as_string()
+        return
+
+    SMTPConnection(config).send_email(message)

=== modified file 'test_pqm_submit.py'
--- a/test_pqm_submit.py	2007-04-26 02:05:22 +0000
+++ b/test_pqm_submit.py	2007-10-25 09:17:56 +0000
@@ -15,12 +15,256 @@
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 """Test cases for pqm submit."""
 
+import smtplib
+
+from bzrlib import (
+    config as _mod_config,
+    errors,
+    gpg,
+    )
 from bzrlib.plugins.pqm import pqm_submit
-from bzrlib.tests import TestCase
-
-
-class TestMessage(TestCase):
-
-    def test_newlines_errors(self):
+from bzrlib.tests import TestCaseWithMemoryTransport, TestCaseWithTransport
+
+
+EMAIL = """\
+From: "J. Random Hacker" <jrandom at example.com>
+Subject: commit message
+To: PQM <pqm at example.com>
+User-Agent: Bazaar \(.*\)
+
+-----BEGIN PSEUDO-SIGNED CONTENT-----
+star-merge .*/public/ http://example.com/submit
+-----END PSEUDO-SIGNED CONTENT-----
+"""
+
+
+class PQMSubmissionTests(TestCaseWithMemoryTransport):
+
+    def test_no_source_branch(self):
+        self.assertRaises(
+            errors.NoMergeSource, pqm_submit.PQMSubmission,
+            source_branch=None)
+
+    def test_newlines_in_message(self):
+        source_branch = self.make_branch('source')
         self.assertRaises(pqm_submit.BadCommitMessage,
-            pqm_submit._setup_headers, "foo\nbar", "me", "you")
+                          pqm_submit.PQMSubmission,
+                          source_branch=source_branch,
+                          public_location='public-branch',
+                          submit_location='submit-branch',
+                          message='foo\nbar')
+
+    def test_check_public_branch(self):
+        tree = self.make_branch_and_memory_tree('source')
+        source_branch = tree.branch
+        public_branch = self.make_branch('public')
+
+        # Commit something to the source branch
+        tree.lock_write()
+        tree.add('')
+        tree.commit('message')
+        tree.unlock()
+
+        # Now the public branch is out of date:
+        submission = pqm_submit.PQMSubmission(
+            source_branch=source_branch,
+            public_location=public_branch.base,
+            submit_location='submit-branch',
+            message='merge message')
+        self.assertRaises(errors.PublicBranchOutOfDate,
+                          submission.check_public_branch)
+
+        # If we bring the public branch up to date, everything is fine.
+        public_branch.pull(source_branch)
+        submission.check_public_branch()
+
+    def test_check_public_branch_missing(self):
+        source_branch = self.make_branch('source')
+        submission = pqm_submit.PQMSubmission(
+            source_branch=source_branch,
+            public_location=self.get_transport().abspath('public'),
+            submit_location='submit-branch',
+            message='merge message')
+        self.assertRaises(errors.NotBranchError,
+                          submission.check_public_branch)
+
+    def test_to_lines(self):
+        source_branch = self.make_branch('source')
+        submission = pqm_submit.PQMSubmission(
+            source_branch=source_branch,
+            public_location='public-branch',
+            submit_location='submit-branch',
+            message='commit message')
+        lines = submission.to_lines()
+        self.assertEqual(['star-merge public-branch submit-branch\n'], lines)
+
+    def test_to_signed(self):
+        source_branch = self.make_branch('source')
+        submission = pqm_submit.PQMSubmission(
+            source_branch=source_branch,
+            public_location='public-branch',
+            submit_location='submit-branch',
+            message='commit message')
+        old_strategy = gpg.GPGStrategy
+        gpg.GPGStrategy = gpg.LoopbackGPGStrategy
+        try:
+            signed = submission.to_signed()
+        finally:
+            gpg.GPGStrategy = old_strategy
+        self.assertEqual('-----BEGIN PSEUDO-SIGNED CONTENT-----\n'
+                         'star-merge public-branch submit-branch\n'
+                         '-----END PSEUDO-SIGNED CONTENT-----\n', signed)
+
+    def test_to_email(self):
+        source_branch = self.make_branch('source')
+        submission = pqm_submit.PQMSubmission(
+            source_branch=source_branch,
+            public_location='public-branch',
+            submit_location='submit-branch',
+            message='commit message')
+        message = submission.to_email('from at example.com', 'to at example.com',
+                                      sign=False)
+        self.assertEqual('from at example.com', message.get('From'))
+        self.assertEqual('to at example.com', message.get('To'))
+        self.assertEqual('commit message', message.get('Subject'))
+
+
+class PQMSubmissionLocationsTests(TestCaseWithTransport):
+
+    def test_find_public_branch(self):
+        source_branch = self.make_branch('source')
+        source_branch.set_public_branch('http://example.com/public')
+        # Also set the deprecated public_repository config item to
+        # show that public_branch is used in preference to it.
+        source_branch.get_config().set_user_option(
+            'public_repository', 'bad-value')
+
+        submission = pqm_submit.PQMSubmission(
+            source_branch=source_branch,
+            submit_location='submit-branch',
+            message='commit message')
+        self.assertEqual('http://example.com/public',
+                         submission.public_location)
+
+    def test_find_public_branch_from_repo(self):
+        # Test that public_location can be inferred from the obsolete
+        # "public_repository" config option.
+        source_repo = self.make_repository('repo', shared=True)
+        source_branch = self.make_bzrdir('repo/source').create_branch()
+
+        config = _mod_config.LocationConfig(
+            source_repo.bzrdir.root_transport.base)
+        config.set_user_option(
+            'public_repository', 'http://example.com/repo',
+            store=_mod_config.STORE_LOCATION_NORECURSE)
+
+        submission = pqm_submit.PQMSubmission(
+            source_branch=source_branch,
+            submit_location='submit-branch',
+            message='commit message')
+        self.assertEqual('http://example.com/repo/source',
+                         submission.public_location)
+
+    def test_find_public_branch_missing(self):
+        source_branch = self.make_branch('source')
+        self.assertRaises(
+            errors.BzrCommandError, pqm_submit.PQMSubmission,
+            source_branch=source_branch,
+            submit_location='submit-branch',
+            message='commit message')
+
+    def test_find_submit_branch(self):
+        source_branch = self.make_branch('source')
+        source_branch.set_submit_branch('http://example.com/submit')
+
+        submission = pqm_submit.PQMSubmission(
+            source_branch=source_branch,
+            public_location='public-branch',
+            message='commit message')
+        self.assertEqual('http://example.com/submit',
+                         submission.submit_location)
+
+    def test_find_submit_branch_from_pqm_branch(self):
+        # Test that submit_branch can be picked up from the obsolete
+        # pqm_branch config option.
+        source_branch = self.make_branch('source')
+        source_branch.get_config().set_user_option(
+            'pqm_branch', 'http://example.com/submit')
+        # pqm_branch is used in preference to the submit_branch:
+        source_branch.set_submit_branch('bad-value')
+
+        submission = pqm_submit.PQMSubmission(
+            source_branch=source_branch,
+            public_location='public-branch',
+            message='commit message')
+        self.assertEqual('http://example.com/submit',
+                         submission.submit_location)
+
+    def test_find_submit_branch_missing(self):
+        source_branch = self.make_branch('source')
+        self.assertRaises(
+            errors.NoSubmitBranch, pqm_submit.PQMSubmission,
+            source_branch=source_branch,
+            public_location='public-branch',
+            message='commit message')
+
+    def run_bzr_fakemail(self, *args, **kwargs):
+        # Run with fake smtplib and gpg stubs in place:
+        sendmail_calls = []
+        def sendmail(self, from_, to, message):
+            sendmail_calls.append((self, from_, to, message))
+        connect_calls = []
+        def connect(self, host='localhost', port=0):
+            connect_calls.append((self, host, port))
+        def ehlo(self):
+            return (200, 'Ok')
+        def has_extn(self):
+            return False
+        def starttls(self):
+            pass
+        old_sendmail = smtplib.SMTP.sendmail
+        smtplib.SMTP.sendmail = sendmail
+        old_connect = smtplib.SMTP.connect
+        smtplib.SMTP.connect = connect
+        old_ehlo = smtplib.SMTP.ehlo
+        smtplib.SMTP.ehlo = ehlo
+        old_has_extn = smtplib.SMTP.has_extn
+        smtplib.SMTP.has_extn = has_extn
+        old_starttls = smtplib.SMTP.starttls
+        smtplib.SMTP.starttls = starttls
+        old_strategy = gpg.GPGStrategy
+        gpg.GPGStrategy = gpg.LoopbackGPGStrategy
+        try:
+            result = self.run_bzr(*args, **kwargs)
+        finally:
+            smtplib.SMTP.sendmail = old_sendmail
+            smtplib.SMTP.connect = old_connect
+            smtplib.SMTP.ehlo = old_ehlo
+            smtplib.SMTP.has_extn = old_has_extn
+            smtplib.SMTP.starttls = old_starttls
+            gpg.GPGStrategy = old_strategy
+
+        return result + (connect_calls, sendmail_calls)
+
+    def test_pqm_submit(self):
+        source_branch = self.make_branch('source')
+        public_branch = self.make_branch('public')
+        source_branch.set_public_branch(public_branch.base)
+        source_branch.set_submit_branch('http://example.com/submit')
+        config = source_branch.get_config()
+        config.set_user_option('pqm_email', 'PQM <pqm at example.com>')
+        config.set_user_option(
+            'email', 'J. Random Hacker <jrandom at example.com>')
+
+        out, err, connect_calls, sendmail_calls = \
+            self.run_bzr_fakemail(['pqm-submit', '-m', 'commit message',
+                                   './source'])
+        self.assertEqual('', out)
+        self.assertEqual(1, len(connect_calls))
+        call = connect_calls[0]
+        self.assertEqual(('localhost', 0), call[1:3])
+        self.assertEqual(1, len(sendmail_calls))
+        call = sendmail_calls[0]
+        self.assertEqual(('jrandom at example.com', ['pqm at example.com']),
+                         call[1:3])
+        self.assertContainsRe(call[3], EMAIL)



More information about the bazaar-commits mailing list