diff options
| -rw-r--r-- | setup.py | 1 | ||||
| -rw-r--r-- | src/mailman/bin/docs/master.txt | 2 | ||||
| -rw-r--r-- | src/mailman/bin/master.py | 12 | ||||
| -rw-r--r-- | src/mailman/rest/configuration.py | 3 | ||||
| -rw-r--r-- | src/mailman/rest/docs/domains.txt | 4 | ||||
| -rw-r--r-- | src/mailman/rest/docs/lists.txt | 2 | ||||
| -rw-r--r-- | src/mailman/testing/helpers.py | 68 | ||||
| -rw-r--r-- | src/mailman/testing/layers.py | 21 | ||||
| -rw-r--r-- | src/mailman/testing/smtplistener.py | 97 |
9 files changed, 41 insertions, 169 deletions
@@ -98,6 +98,7 @@ case second `m'. Any other spelling is incorrect.""", 'lazr.config', 'lazr.delegates', 'lazr.restful', + 'lazr.smtptest', 'locknix', 'munepy', 'storm', diff --git a/src/mailman/bin/docs/master.txt b/src/mailman/bin/docs/master.txt index 2c396d8aa..f44b25214 100644 --- a/src/mailman/bin/docs/master.txt +++ b/src/mailman/bin/docs/master.txt @@ -9,7 +9,7 @@ just a wrapper around the real queue runner watcher script called master.py. >>> from mailman.testing.helpers import TestableMaster -Start the master in a subthread. +Start the master in a sub-thread. >>> master = TestableMaster() >>> master.start() diff --git a/src/mailman/bin/master.py b/src/mailman/bin/master.py index 99555d890..910bad3e6 100644 --- a/src/mailman/bin/master.py +++ b/src/mailman/bin/master.py @@ -400,6 +400,13 @@ class Loop: log.debug('[%d] %s', pid, spec) self._kids.add(pid, info) + def _pause(self): + """Sleep until a signal is received.""" + # Sleep until a signal is received. This prevents the master from + # existing immediately even if there are no qrunners (as happens in + # the test suite). + signal.pause() + def loop(self): """Main loop. @@ -407,10 +414,7 @@ class Loop: and configured to do so. """ log = logging.getLogger('mailman.qrunner') - # Sleep until a signal is received. This prevents the master from - # existing immediately even if there are no qrunners (as happens in - # the test suite). - signal.pause() + self._pause() while True: try: pid, status = os.wait() diff --git a/src/mailman/rest/configuration.py b/src/mailman/rest/configuration.py index 1c7378010..c7099d5b1 100644 --- a/src/mailman/rest/configuration.py +++ b/src/mailman/rest/configuration.py @@ -27,6 +27,7 @@ __all__ = [ from lazr.config import as_boolean from lazr.restful.interfaces import IWebServiceConfiguration +from lazr.restful.wsgi import BaseWSGIWebServiceConfiguration from zope.interface import implements from mailman import version @@ -35,7 +36,7 @@ from mailman.config import config # pylint: disable-msg=W0232,R0201 -class AdminWebServiceConfiguration: +class AdminWebServiceConfiguration(BaseWSGIWebServiceConfiguration): """A configuration object for the Mailman admin web service.""" implements(IWebServiceConfiguration) diff --git a/src/mailman/rest/docs/domains.txt b/src/mailman/rest/docs/domains.txt index 32491cb6f..76710ddb2 100644 --- a/src/mailman/rest/docs/domains.txt +++ b/src/mailman/rest/docs/domains.txt @@ -127,7 +127,7 @@ directly to the URL. ... }) URL: http://localhost:8001/3.0/domains content-length: 0 - content-type: text/plain + content-type: text/plain;charset=utf-8 date: ... location: http://localhost:8001/3.0/domains/lists.example.com server: WSGIServer/... Python/... @@ -168,7 +168,7 @@ address. ... }) URL: http://localhost:8001/3.0/domains content-length: 0 - content-type: text/plain + content-type: text/plain;charset=utf-8 date: ... location: http://localhost:8001/3.0/domains/my.example.com server: WSGIServer/... Python/... diff --git a/src/mailman/rest/docs/lists.txt b/src/mailman/rest/docs/lists.txt index 9fb2f81d2..f4e2ed9ce 100644 --- a/src/mailman/rest/docs/lists.txt +++ b/src/mailman/rest/docs/lists.txt @@ -45,7 +45,7 @@ instead of posting directly to the URL. ... }) URL: http://localhost:8001/3.0/lists content-length: 0 - content-type: text/plain + content-type: text/plain;charset=utf-8 date: ... location: http://localhost:8001/3.0/lists/test-two@example.com server: WSGIServer/... Python/... diff --git a/src/mailman/testing/helpers.py b/src/mailman/testing/helpers.py index 7c225d788..a10898d5d 100644 --- a/src/mailman/testing/helpers.py +++ b/src/mailman/testing/helpers.py @@ -40,11 +40,8 @@ import smtplib import datetime import threading -from Queue import Empty, Queue - from mailman.bin.master import Loop as Master from mailman.config import config -from mailman.testing.smtplistener import Server from mailman.utilities.mailbox import Mailbox @@ -140,6 +137,12 @@ class TestableMaster(Master): self.thread.daemon = True self._started_kids = None + def _pause(self): + """See `Master`.""" + # No-op this because the tests generally do not signal the master, + # which would mean the signal.pause() never exits. + pass + def start(self, *qrunners): """Start the master.""" self.start_qrunners(qrunners) @@ -162,7 +165,7 @@ class TestableMaster(Master): try: os.kill(pid, 0) starting_kids.remove(pid) - except OSError, error: + except OSError as error: if error.errno == errno.ESRCH: # The child has not yet started. pass @@ -185,63 +188,6 @@ class TestableMaster(Master): -class SMTPServer: - """An smtp server for testing.""" - - def __init__(self): - self._messages = [] - self._queue = Queue() - self.host = config.mta.smtp_host - self.port = int(config.mta.smtp_port) - self._server = Server((self.host, self.port), self._queue) - self._thread = threading.Thread(target=self._server.start) - self._thread.daemon = True - - def start(self): - """Start the smtp server in a thread.""" - log.info('test SMTP server starting') - self._thread.start() - smtpd = smtplib.SMTP() - log.info('connecting to %s:%s', self.host, self.port) - smtpd.connect(self.host, self.port) - response = smtpd.helo('test.localhost') - smtpd.quit() - log.info('SMTP server is running: %s', response) - - def stop(self): - """Stop the smtp server.""" - smtpd = smtplib.SMTP() - smtpd.connect(self.host, self.port) - smtpd.docmd('EXIT') - self.clear() - # Wait for the thread to exit. - self._thread.join() - log.info('test SMTP server stopped') - - @property - def messages(self): - """Return all the messages received by the smtp server.""" - # Look at the thread queue and append any messages from there to our - # internal list of messages. - while True: - try: - message = self._queue.get_nowait() - except Empty: - break - else: - self._messages.append(message) - # Now return all the messages we know about. - for message in self._messages: - yield message - - def clear(self): - """Clear all messages from the queue.""" - # Just throw these away. - list(self._messages) - self._messages = [] - - - class LMTP(smtplib.SMTP): """Like a normal SMTP client, but for LMTP.""" def lhlo(self, name=''): diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py index c4968ca40..d4db9ebf2 100644 --- a/src/mailman/testing/layers.py +++ b/src/mailman/testing/layers.py @@ -35,6 +35,7 @@ import logging import datetime import tempfile +from lazr.smtptest.controller import QueueController from pkg_resources import resource_string from textwrap import dedent from urllib2 import urlopen, URLError @@ -46,7 +47,7 @@ from mailman.core.logging import get_handler from mailman.i18n import _ from mailman.interfaces.domain import IDomainManager from mailman.interfaces.messages import IMessageStore -from mailman.testing.helpers import SMTPServer, TestableMaster +from mailman.testing.helpers import TestableMaster from mailman.utilities.datetime import factory from mailman.utilities.string import expand @@ -209,6 +210,20 @@ class ConfigLayer(MockAndMonkeyLayer): +class ExtendedQueueController(QueueController): + """QueueController with a little extra API.""" + + @property + def messages(self): + """Return all the messages received by the SMTP server.""" + for message in self: + yield message + + def clear(self): + """Clear all the messages from the queue.""" + list(self) + + class SMTPLayer(ConfigLayer): """Layer for starting, stopping, and accessing a test SMTP server.""" @@ -217,7 +232,9 @@ class SMTPLayer(ConfigLayer): @classmethod def setUp(cls): assert cls.smtpd is None, 'Layer already set up' - cls.smtpd = SMTPServer() + host = config.mta.smtp_host + port = int(config.mta.smtp_port) + cls.smtpd = ExtendedQueueController(host, port) cls.smtpd.start() @classmethod diff --git a/src/mailman/testing/smtplistener.py b/src/mailman/testing/smtplistener.py deleted file mode 100644 index 2094e20de..000000000 --- a/src/mailman/testing/smtplistener.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright (C) 2007-2009 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman 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 3 of the License, or (at your option) -# any later version. -# -# GNU Mailman 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 -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""A test SMTP listener.""" - -from __future__ import absolute_import, unicode_literals - -__metaclass__ = type -__all__ = [ - 'Server', - ] - - -import smtpd -import socket -import logging -import asyncore - -from email import message_from_string - - -COMMASPACE = ', ' -log = logging.getLogger('mailman.debug') - - - -class Channel(smtpd.SMTPChannel): - """A channel that can reset the mailbox.""" - - def __init__(self, server, conn, addr): - smtpd.SMTPChannel.__init__(self, server, conn, addr) - # Stash this here since the subclass uses private attributes. :( - self._server = server - - def smtp_EXIT(self, arg): - """Respond to a new command EXIT by exiting the server.""" - self.push('250 Ok') - self._server.stop() - - def send(self, data): - """Silence the bloody asynchat/asyncore broken pipe errors!""" - try: - return smtpd.SMTPChannel.send(self, data) - except socket.error: - # Nothing here can affect the outcome, and these messages are just - # plain annoying! So ignore them. - pass - - - -class Server(smtpd.SMTPServer): - """An SMTP server that stores messages to a mailbox.""" - - def __init__(self, localaddr, queue): - smtpd.SMTPServer.__init__(self, localaddr, None) - log.info('[SMTPServer] listening: %s', localaddr) - self._queue = queue - - def handle_accept(self): - """Handle connections by creating our own Channel object.""" - conn, addr = self.accept() - log.info('[SMTPServer] accepted: %s', addr) - Channel(self, conn, addr) - - def process_message(self, peer, mailfrom, rcpttos, data): - """Process a message by adding it to the mailbox.""" - message = message_from_string(data) - message['X-Peer'] = '{0}:{1}'.format(*peer) - message['X-MailFrom'] = mailfrom - message['X-RcptTo'] = COMMASPACE.join(rcpttos) - log.info('[SMTPServer] processed message: %s', - message.get('message-id', 'n/a')) - self._queue.put(message) - - def start(self): - """Start the asyncore loop.""" - asyncore.loop() - - def stop(self): - """Stop the asyncore loop.""" - asyncore.socket_map.clear() - asyncore.close_all() - self.close() |
