Rev 2950: Fix #157752 by wrapping medusa test ftp server behind FTPServerFeature in file:///v/home/vila/src/bzr/bugs/157752/

Vincent Ladeuil v.ladeuil+lp at free.fr
Tue Oct 30 17:54:14 GMT 2007


At file:///v/home/vila/src/bzr/bugs/157752/

------------------------------------------------------------
revno: 2950
revision-id:v.ladeuil+lp at free.fr-20071030175409-yvkwn2d5t1paslvh
parent: pqm at pqm.ubuntu.com-20071029221703-zy7q7a0ehfvpybtn
parent: v.ladeuil+lp at free.fr-20071029173634-yxcis9vbrv60t3z9
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: 157752
timestamp: Tue 2007-10-30 18:54:09 +0100
message:
  Fix #157752 by wrapping medusa test ftp server behind FTPServerFeature
added:
  bzrlib/tests/FTPServer.py      ftpserver.py-20071019102346-61jbvdkrr70igauv-1
modified:
  NEWS                           NEWS-20050323055033-4e00b5db738777ff
  bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
  bzrlib/tests/test_ftp_transport.py test_aftp_transport.-20060823221619-98mwjzxtwtkt527k-1
  bzrlib/transport/ftp.py        ftp.py-20051116161804-58dc9506548c2a53
    ------------------------------------------------------------
    revno: 2917.3.2
    revision-id:v.ladeuil+lp at free.fr-20071029173634-yxcis9vbrv60t3z9
    parent: v.ladeuil+lp at free.fr-20071019131948-g5793d6onl5ik5sm
    committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
    branch nick: 127164
    timestamp: Mon 2007-10-29 18:36:34 +0100
    message:
      Prepare for fixing bug #157752 (so close to a palindrome...)
      
      * bzrlib/transport/ftp.py:
      (get_test_permutations): Add a dummy FPTServer so that the test
      suite reports the skipped tests.
    modified:
      bzrlib/transport/ftp.py        ftp.py-20051116161804-58dc9506548c2a53
    ------------------------------------------------------------
    revno: 2917.3.1
    revision-id:v.ladeuil+lp at free.fr-20071019131948-g5793d6onl5ik5sm
    parent: pqm at pqm.ubuntu.com-20071019042839-xwvsz0loa77yokxm
    committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
    branch nick: 127164
    timestamp: Fri 2007-10-19 15:19:48 +0200
    message:
      Separate transport from test server.
      
      * bzrlib/transport/ftp.py:
      Move ftp test server to its own file.
      (get_test_permutations): Reworked around FTPServerFeature.
      
      * bzrlib/tests/test_ftp_transport.py:
      (TestCaseWithFTPServer.setUp): Use FTPServerFeature instead of
      MedusaFeature.
      
      * bzrlib/tests/__init__.py:
      (_FTPServerFeature): New feature allowing a cleaner separation
      between ftp.py and FTPServer.py.
      
      * bzrlib/tests/FTPServer.py: 
      New file. Extracted from bzrlib/transport/ftp.py (use case for
      tracking moving lines).
    added:
      bzrlib/tests/FTPServer.py      ftpserver.py-20071019102346-61jbvdkrr70igauv-1
    modified:
      bzrlib/tests/__init__.py       selftest.py-20050531073622-8d0e3c8845c97a64
      bzrlib/tests/test_ftp_transport.py test_aftp_transport.-20060823221619-98mwjzxtwtkt527k-1
      bzrlib/transport/ftp.py        ftp.py-20051116161804-58dc9506548c2a53
-------------- next part --------------
=== added file 'bzrlib/tests/FTPServer.py'
--- a/bzrlib/tests/FTPServer.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/tests/FTPServer.py	2007-10-19 13:19:48 +0000
@@ -0,0 +1,249 @@
+# Copyright (C) 2007 Canonical Ltd
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+"""
+FTP test server.
+
+Based on medusa: http://www.amk.ca/python/code/medusa.html
+"""
+
+import asyncore
+import os
+import select
+import stat
+import threading
+
+import medusa
+import medusa.filesys
+import medusa.ftp_server
+
+from bzrlib import (
+    tests,
+    trace,
+    transport,
+    )
+
+
+class test_authorizer(object):
+    """A custom Authorizer object for running the test suite.
+
+    The reason we cannot use dummy_authorizer, is because it sets the
+    channel to readonly, which we don't always want to do.
+    """
+
+    def __init__(self, root):
+        self.root = root
+        # If secured_user is set secured_password will be checked
+        self.secured_user = None
+        self.secured_password = None
+
+    def authorize(self, channel, username, password):
+        """Return (success, reply_string, filesystem)"""
+        channel.persona = -1, -1
+        if username == 'anonymous':
+            channel.read_only = 1
+        else:
+            channel.read_only = 0
+
+        # Check secured_user if set
+        if (self.secured_user is not None
+            and username == self.secured_user
+            and password != self.secured_password):
+            return 0, 'Password invalid.', None
+        else:
+            return 1, 'OK.', medusa.filesys.os_filesystem(self.root)
+
+
+class ftp_channel(medusa.ftp_server.ftp_channel):
+    """Customized ftp channel"""
+
+    def log(self, message):
+        """Redirect logging requests."""
+        trace.mutter('ftp_channel: %s', message)
+
+    def log_info(self, message, type='info'):
+        """Redirect logging requests."""
+        trace.mutter('ftp_channel %s: %s', type, message)
+
+    def cmd_rnfr(self, line):
+        """Prepare for renaming a file."""
+        self._renaming = line[1]
+        self.respond('350 Ready for RNTO')
+        # TODO: jam 20060516 in testing, the ftp server seems to
+        #       check that the file already exists, or it sends
+        #       550 RNFR command failed
+
+    def cmd_rnto(self, line):
+        """Rename a file based on the target given.
+
+        rnto must be called after calling rnfr.
+        """
+        if not self._renaming:
+            self.respond('503 RNFR required first.')
+        pfrom = self.filesystem.translate(self._renaming)
+        self._renaming = None
+        pto = self.filesystem.translate(line[1])
+        if os.path.exists(pto):
+            self.respond('550 RNTO failed: file exists')
+            return
+        try:
+            os.rename(pfrom, pto)
+        except (IOError, OSError), e:
+            # TODO: jam 20060516 return custom responses based on
+            #       why the command failed
+            # (bialix 20070418) str(e) on Python 2.5 @ Windows
+            # sometimes don't provide expected error message;
+            # so we obtain such message via os.strerror()
+            self.respond('550 RNTO failed: %s' % os.strerror(e.errno))
+        except:
+            self.respond('550 RNTO failed')
+            # For a test server, we will go ahead and just die
+            raise
+        else:
+            self.respond('250 Rename successful.')
+
+    def cmd_size(self, line):
+        """Return the size of a file
+
+        This is overloaded to help the test suite determine if the 
+        target is a directory.
+        """
+        filename = line[1]
+        if not self.filesystem.isfile(filename):
+            if self.filesystem.isdir(filename):
+                self.respond('550 "%s" is a directory' % (filename,))
+            else:
+                self.respond('550 "%s" is not a file' % (filename,))
+        else:
+            self.respond('213 %d' 
+                % (self.filesystem.stat(filename)[stat.ST_SIZE]),)
+
+    def cmd_mkd(self, line):
+        """Create a directory.
+
+        Overloaded because default implementation does not distinguish
+        *why* it cannot make a directory.
+        """
+        if len (line) != 2:
+            self.command_not_understood(''.join(line))
+        else:
+            path = line[1]
+            try:
+                self.filesystem.mkdir (path)
+                self.respond ('257 MKD command successful.')
+            except (IOError, OSError), e:
+                # (bialix 20070418) str(e) on Python 2.5 @ Windows
+                # sometimes don't provide expected error message;
+                # so we obtain such message via os.strerror()
+                self.respond ('550 error creating directory: %s' %
+                              os.strerror(e.errno))
+            except:
+                self.respond ('550 error creating directory.')
+
+
+class ftp_server(medusa.ftp_server.ftp_server):
+    """Customize the behavior of the Medusa ftp_server.
+
+    There are a few warts on the ftp_server, based on how it expects
+    to be used.
+    """
+    _renaming = None
+    ftp_channel_class = ftp_channel
+
+    def __init__(self, *args, **kwargs):
+        trace.mutter('Initializing ftp_server: %r, %r', args, kwargs)
+        medusa.ftp_server.ftp_server.__init__(self, *args, **kwargs)
+
+    def log(self, message):
+        """Redirect logging requests."""
+        trace.mutter('ftp_server: %s', message)
+
+    def log_info(self, message, type='info'):
+        """Override the asyncore.log_info so we don't stipple the screen."""
+        trace.mutter('ftp_server %s: %s', type, message)
+
+
+class FTPServer(transport.Server):
+    """Common code for FTP server facilities."""
+
+    def __init__(self):
+        self._root = None
+        self._ftp_server = None
+        self._port = None
+        self._async_thread = None
+        # ftp server logs
+        self.logs = []
+
+    def get_url(self):
+        """Calculate an ftp url to this server."""
+        return 'ftp://foo:bar@localhost:%d/' % (self._port)
+
+#    def get_bogus_url(self):
+#        """Return a URL which cannot be connected to."""
+#        return 'ftp://127.0.0.1:1'
+
+    def log(self, message):
+        """This is used by medusa.ftp_server to log connections, etc."""
+        self.logs.append(message)
+
+    def setUp(self, vfs_server=None):
+        from bzrlib.transport.local import LocalURLServer
+        assert vfs_server is None or isinstance(vfs_server, LocalURLServer), \
+            "FTPServer currently assumes local transport, got %s" % vfs_server
+
+        self._root = os.getcwdu()
+        self._ftp_server = ftp_server(
+            authorizer=test_authorizer(root=self._root),
+            ip='localhost',
+            port=0, # bind to a random port
+            resolver=None,
+            logger_object=self # Use FTPServer.log() for messages
+            )
+        self._port = self._ftp_server.getsockname()[1]
+        # Don't let it loop forever, or handle an infinite number of requests.
+        # In this case it will run for 1000s, or 10000 requests
+        self._async_thread = threading.Thread(
+                target=FTPServer._asyncore_loop_ignore_EBADF,
+                kwargs={'timeout':0.1, 'count':10000})
+        self._async_thread.setDaemon(True)
+        self._async_thread.start()
+
+    def tearDown(self):
+        """See bzrlib.transport.Server.tearDown."""
+        self._ftp_server.close()
+        asyncore.close_all()
+        self._async_thread.join()
+
+    @staticmethod
+    def _asyncore_loop_ignore_EBADF(*args, **kwargs):
+        """Ignore EBADF during server shutdown.
+
+        We close the socket to get the server to shutdown, but this causes
+        select.select() to raise EBADF.
+        """
+        try:
+            asyncore.loop(*args, **kwargs)
+            # FIXME: If we reach that point, we should raise an exception
+            # explaining that the 'count' parameter in setUp is too low or
+            # testers may wonder why their test just sits there waiting for a
+            # server that is already dead. Note that if the tester waits too
+            # long under pdb the server will also die.
+        except select.error, e:
+            if e.args[0] != errno.EBADF:
+                raise
+
+
+
+

=== modified file 'NEWS'
--- a/NEWS	2007-10-29 04:05:13 +0000
+++ b/NEWS	2007-10-30 17:54:09 +0000
@@ -192,6 +192,9 @@
    * ``WorkingTree.rename_one`` will now raise an error if normalisation of the
      new path causes bzr to be unable to access the file. (Robert Collins)
 
+   * Wrap medusa ftp test server as an FTPServer feature.
+     (Vincent Ladeuil, #157752)
+
    * Correctly detect a NoSuchFile when using a filezilla server. (Gary van der
      Merwe)
 

=== modified file 'bzrlib/tests/__init__.py'
--- a/bzrlib/tests/__init__.py	2007-10-27 16:33:27 +0000
+++ b/bzrlib/tests/__init__.py	2007-10-30 17:54:09 +0000
@@ -2723,3 +2723,23 @@
         except UnicodeDecodeError:
             return char
     return None
+
+
+class _FTPServerFeature(Feature):
+    """Some tests want an FTP Server, check if one is available.
+
+    Right now, the only way this is available is if 'medusa' is installed.
+    http://www.amk.ca/python/code/medusa.html
+    """
+
+    def _probe(self):
+        try:
+            import bzrlib.tests.FTPServer
+            return True
+        except ImportError:
+            return False
+
+    def feature_name(self):
+        return 'FTPServer'
+
+FTPServerFeature = _FTPServerFeature()

=== modified file 'bzrlib/tests/test_ftp_transport.py'
--- a/bzrlib/tests/test_ftp_transport.py	2007-10-19 17:55:07 +0000
+++ b/bzrlib/tests/test_ftp_transport.py	2007-10-30 17:54:09 +0000
@@ -23,38 +23,16 @@
     )
 
 
-class _MedusaFeature(tests.Feature):
-    """Some tests want an FTP Server, check if one is available.
-
-    Right now, the only way this is available is if 'medusa' is installed.
-    """
-
-    def _probe(self):
-        try:
-            import medusa
-            import medusa.filesys
-            import medusa.ftp_server
-            return True
-        except ImportError:
-            return False
-
-    def feature_name(self):
-        return 'medusa'
-
-MedusaFeature = _MedusaFeature()
-
-
 class TestCaseWithFTPServer(tests.TestCaseWithTransport):
 
-    _test_needs_features = [MedusaFeature]
+    _test_needs_features = [tests.FTPServerFeature]
 
     def setUp(self):
-        from bzrlib.transport.ftp import FtpServer
-        self.transport_server = FtpServer
+        from bzrlib.tests import FTPServer
+        self.transport_server = FTPServer.FTPServer
         super(TestCaseWithFTPServer, self).setUp()
 
 
-
 class TestCaseAFTP(tests.TestCaseWithTransport):
     """Test aftp transport."""
 

=== modified file 'bzrlib/transport/ftp.py'
--- a/bzrlib/transport/ftp.py	2007-10-22 01:23:51 +0000
+++ b/bzrlib/transport/ftp.py	2007-10-30 17:54:09 +0000
@@ -25,16 +25,12 @@
 """
 
 from cStringIO import StringIO
-import asyncore
 import errno
 import ftplib
 import os
 import os.path
-import urllib
 import urlparse
-import select
 import stat
-import threading
 import time
 import random
 from warnings import warn
@@ -557,246 +553,17 @@
         return self.lock_read(relpath)
 
 
-class FtpServer(Server):
-    """Common code for FTP server facilities."""
-
-    def __init__(self):
-        self._root = None
-        self._ftp_server = None
-        self._port = None
-        self._async_thread = None
-        # ftp server logs
-        self.logs = []
-
-    def get_url(self):
-        """Calculate an ftp url to this server."""
-        return 'ftp://foo:bar@localhost:%d/' % (self._port)
-
-#    def get_bogus_url(self):
-#        """Return a URL which cannot be connected to."""
-#        return 'ftp://127.0.0.1:1'
-
-    def log(self, message):
-        """This is used by medusa.ftp_server to log connections, etc."""
-        self.logs.append(message)
-
-    def setUp(self, vfs_server=None):
-        if not _have_medusa:
-            raise RuntimeError('Must have medusa to run the FtpServer')
-
-        assert vfs_server is None or isinstance(vfs_server, LocalURLServer), \
-            "FtpServer currently assumes local transport, got %s" % vfs_server
-
-        self._root = os.getcwdu()
-        self._ftp_server = _ftp_server(
-            authorizer=_test_authorizer(root=self._root),
-            ip='localhost',
-            port=0, # bind to a random port
-            resolver=None,
-            logger_object=self # Use FtpServer.log() for messages
-            )
-        self._port = self._ftp_server.getsockname()[1]
-        # Don't let it loop forever, or handle an infinite number of requests.
-        # In this case it will run for 1000s, or 10000 requests
-        self._async_thread = threading.Thread(
-                target=FtpServer._asyncore_loop_ignore_EBADF,
-                kwargs={'timeout':0.1, 'count':10000})
-        self._async_thread.setDaemon(True)
-        self._async_thread.start()
-
-    def tearDown(self):
-        """See bzrlib.transport.Server.tearDown."""
-        self._ftp_server.close()
-        asyncore.close_all()
-        self._async_thread.join()
-
-    @staticmethod
-    def _asyncore_loop_ignore_EBADF(*args, **kwargs):
-        """Ignore EBADF during server shutdown.
-
-        We close the socket to get the server to shutdown, but this causes
-        select.select() to raise EBADF.
-        """
-        try:
-            asyncore.loop(*args, **kwargs)
-            # FIXME: If we reach that point, we should raise an exception
-            # explaining that the 'count' parameter in setUp is too low or
-            # testers may wonder why their test just sits there waiting for a
-            # server that is already dead. Note that if the tester waits too
-            # long under pdb the server will also die.
-        except select.error, e:
-            if e.args[0] != errno.EBADF:
-                raise
-
-
-_ftp_channel = None
-_ftp_server = None
-_test_authorizer = None
-
-
-def _setup_medusa():
-    global _have_medusa, _ftp_channel, _ftp_server, _test_authorizer
-    try:
-        import medusa
-        import medusa.filesys
-        import medusa.ftp_server
-    except ImportError:
-        return False
-
-    _have_medusa = True
-
-    class test_authorizer(object):
-        """A custom Authorizer object for running the test suite.
-
-        The reason we cannot use dummy_authorizer, is because it sets the
-        channel to readonly, which we don't always want to do.
-        """
-
-        def __init__(self, root):
-            self.root = root
-            # If secured_user is set secured_password will be checked
-            self.secured_user = None
-            self.secured_password = None
-
-        def authorize(self, channel, username, password):
-            """Return (success, reply_string, filesystem)"""
-            if not _have_medusa:
-                return 0, 'No Medusa.', None
-
-            channel.persona = -1, -1
-            if username == 'anonymous':
-                channel.read_only = 1
-            else:
-                channel.read_only = 0
-
-            # Check secured_user if set
-            if (self.secured_user is not None
-                and username == self.secured_user
-                and password != self.secured_password):
-                return 0, 'Password invalid.', None
-            else:
-                return 1, 'OK.', medusa.filesys.os_filesystem(self.root)
-
-
-    class ftp_channel(medusa.ftp_server.ftp_channel):
-        """Customized ftp channel"""
-
-        def log(self, message):
-            """Redirect logging requests."""
-            mutter('_ftp_channel: %s', message)
-
-        def log_info(self, message, type='info'):
-            """Redirect logging requests."""
-            mutter('_ftp_channel %s: %s', type, message)
-
-        def cmd_rnfr(self, line):
-            """Prepare for renaming a file."""
-            self._renaming = line[1]
-            self.respond('350 Ready for RNTO')
-            # TODO: jam 20060516 in testing, the ftp server seems to
-            #       check that the file already exists, or it sends
-            #       550 RNFR command failed
-
-        def cmd_rnto(self, line):
-            """Rename a file based on the target given.
-
-            rnto must be called after calling rnfr.
-            """
-            if not self._renaming:
-                self.respond('503 RNFR required first.')
-            pfrom = self.filesystem.translate(self._renaming)
-            self._renaming = None
-            pto = self.filesystem.translate(line[1])
-            if os.path.exists(pto):
-                self.respond('550 RNTO failed: file exists')
-                return
-            try:
-                os.rename(pfrom, pto)
-            except (IOError, OSError), e:
-                # TODO: jam 20060516 return custom responses based on
-                #       why the command failed
-                # (bialix 20070418) str(e) on Python 2.5 @ Windows
-                # sometimes don't provide expected error message;
-                # so we obtain such message via os.strerror()
-                self.respond('550 RNTO failed: %s' % os.strerror(e.errno))
-            except:
-                self.respond('550 RNTO failed')
-                # For a test server, we will go ahead and just die
-                raise
-            else:
-                self.respond('250 Rename successful.')
-
-        def cmd_size(self, line):
-            """Return the size of a file
-
-            This is overloaded to help the test suite determine if the 
-            target is a directory.
-            """
-            filename = line[1]
-            if not self.filesystem.isfile(filename):
-                if self.filesystem.isdir(filename):
-                    self.respond('550 "%s" is a directory' % (filename,))
-                else:
-                    self.respond('550 "%s" is not a file' % (filename,))
-            else:
-                self.respond('213 %d' 
-                    % (self.filesystem.stat(filename)[stat.ST_SIZE]),)
-
-        def cmd_mkd(self, line):
-            """Create a directory.
-
-            Overloaded because default implementation does not distinguish
-            *why* it cannot make a directory.
-            """
-            if len (line) != 2:
-                self.command_not_understood(''.join(line))
-            else:
-                path = line[1]
-                try:
-                    self.filesystem.mkdir (path)
-                    self.respond ('257 MKD command successful.')
-                except (IOError, OSError), e:
-                    # (bialix 20070418) str(e) on Python 2.5 @ Windows
-                    # sometimes don't provide expected error message;
-                    # so we obtain such message via os.strerror()
-                    self.respond ('550 error creating directory: %s' %
-                                  os.strerror(e.errno))
-                except:
-                    self.respond ('550 error creating directory.')
-
-
-    class ftp_server(medusa.ftp_server.ftp_server):
-        """Customize the behavior of the Medusa ftp_server.
-
-        There are a few warts on the ftp_server, based on how it expects
-        to be used.
-        """
-        _renaming = None
-        ftp_channel_class = ftp_channel
-
-        def __init__(self, *args, **kwargs):
-            mutter('Initializing _ftp_server: %r, %r', args, kwargs)
-            medusa.ftp_server.ftp_server.__init__(self, *args, **kwargs)
-
-        def log(self, message):
-            """Redirect logging requests."""
-            mutter('_ftp_server: %s', message)
-
-        def log_info(self, message, type='info'):
-            """Override the asyncore.log_info so we don't stipple the screen."""
-            mutter('_ftp_server %s: %s', type, message)
-
-    _test_authorizer = test_authorizer
-    _ftp_channel = ftp_channel
-    _ftp_server = ftp_server
-
-    return True
-
-
 def get_test_permutations():
     """Return the permutations to be used in testing."""
-    if not _setup_medusa():
-        warn("You must install medusa (http://www.amk.ca/python/code/medusa.html) for FTP tests")
-        return []
+    from bzrlib import tests
+    if tests.FTPServerFeature.available():
+        from bzrlib.tests import FTPServer
+        return [(FtpTransport, FTPServer.FTPServer)]
     else:
-        return [(FtpTransport, FtpServer)]
+        # Dummy server to have the test suite report the number of tests
+        # needing that feature.
+        class FTPServer(object):
+            def setUp(self):
+                raise tests.UnavailableFeature(tests.FTPServerFeature)
+
+        return [(FtpTransport, FTPServer)]



More information about the bazaar-commits mailing list