diff options
| author | Barry Warsaw | 2009-01-25 13:01:41 -0500 |
|---|---|---|
| committer | Barry Warsaw | 2009-01-25 13:01:41 -0500 |
| commit | eefd06f1b88b8ecbb23a9013cd223b72ca85c20d (patch) | |
| tree | 72c947fe16fce0e07e996ee74020b26585d7e846 /mailman/testing | |
| parent | 07871212f74498abd56bef3919bf3e029eb8b930 (diff) | |
| download | mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.gz mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.zst mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.zip | |
Diffstat (limited to 'mailman/testing')
| -rw-r--r-- | mailman/testing/__init__.py | 0 | ||||
| -rw-r--r-- | mailman/testing/helpers.py | 248 | ||||
| -rw-r--r-- | mailman/testing/layers.py | 204 | ||||
| -rw-r--r-- | mailman/testing/mta.py | 46 | ||||
| -rw-r--r-- | mailman/testing/smtplistener.py | 97 | ||||
| -rw-r--r-- | mailman/testing/testing.cfg | 87 |
6 files changed, 0 insertions, 682 deletions
diff --git a/mailman/testing/__init__.py b/mailman/testing/__init__.py deleted file mode 100644 index e69de29bb..000000000 --- a/mailman/testing/__init__.py +++ /dev/null diff --git a/mailman/testing/helpers.py b/mailman/testing/helpers.py deleted file mode 100644 index f92c5f012..000000000 --- a/mailman/testing/helpers.py +++ /dev/null @@ -1,248 +0,0 @@ -# Copyright (C) 2008-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/>. - -"""Various test helpers.""" - -from __future__ import absolute_import, unicode_literals - -__metaclass__ = type -__all__ = [ - 'TestableMaster', - 'digest_mbox', - 'get_lmtp_client', - 'get_queue_messages', - 'make_testable_runner', - ] - - -import os -import time -import errno -import signal -import socket -import logging -import mailbox -import smtplib -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 - - -log = logging.getLogger('mailman.debug') - - - -def make_testable_runner(runner_class, name=None): - """Create a queue runner that runs until its queue is empty. - - :param runner_class: The queue runner's class. - :type runner_class: class - :param name: Optional queue name; if not given, it is calculated from the - class name. - :type name: string or None - :return: A runner instance. - """ - - if name is None: - assert runner_class.__name__.endswith('Runner'), ( - 'Unparseable runner class name: %s' % runner_class.__name__) - name = runner_class.__name__[:-6].lower() - - class EmptyingRunner(runner_class): - """Stop processing when the queue is empty.""" - - def _do_periodic(self): - """Stop when the queue is empty.""" - self._stop = (len(self.switchboard.files) == 0) - - return EmptyingRunner(name) - - - -class _Bag: - def __init__(self, **kws): - for key, value in kws.items(): - setattr(self, key, value) - - -def get_queue_messages(queue_name): - """Return and clear all the messages in the given queue. - - :param queue_name: A string naming a queue. - :return: A list of 2-tuples where each item contains the message and - message metadata. - """ - queue = config.switchboards[queue_name] - messages = [] - for filebase in queue.files: - msg, msgdata = queue.dequeue(filebase) - messages.append(_Bag(msg=msg, msgdata=msgdata)) - queue.finish(filebase) - return messages - - - -def digest_mbox(mlist): - """The mailing list's pending digest as a mailbox. - - :param mlist: The mailing list. - :return: The mailing list's pending digest as a mailbox. - """ - path = os.path.join(mlist.data_path, 'digest.mbox') - return mailbox.mbox(path) - - - -class TestableMaster(Master): - """A testable master loop watcher.""" - - def __init__(self): - super(TestableMaster, self).__init__( - restartable=False, config_file=config.filename) - self.event = threading.Event() - self.thread = threading.Thread(target=self.loop) - self._started_kids = None - - def start(self, *qrunners): - """Start the master.""" - self.start_qrunners(qrunners) - self.thread.start() - # Wait until all the children are definitely started. - self.event.wait() - - def stop(self): - """Stop the master by killing all the children.""" - for pid in self.qrunner_pids: - os.kill(pid, signal.SIGTERM) - self.cleanup() - self.thread.join() - - def loop(self): - """Wait until all the qrunners are actually running before looping.""" - starting_kids = set(self._kids) - while starting_kids: - for pid in self._kids: - try: - os.kill(pid, 0) - starting_kids.remove(pid) - except OSError, error: - if error.errno == errno.ESRCH: - # The child has not yet started. - pass - raise - # Keeping a copy of all the started child processes for use by the - # testing environment, even after all have exited. - self._started_kids = set(self._kids) - # Let the blocking thread know everything's running. - self.event.set() - super(TestableMaster, self).loop() - - @property - def qrunner_pids(self): - """The pids of all the child qrunner processes.""" - for pid in self._started_kids: - yield pid - - - -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) - - 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=''): - self.putcmd('lhlo', name or self.local_hostname) - code, msg = self.getreply() - self.helo_resp = msg - return code, msg - - -def get_lmtp_client(): - """Return a connected LMTP client.""" - # It's possible the process has started but is not yet accepting - # connections. Wait a little while. - lmtp = LMTP() - for attempts in range(3): - try: - response = lmtp.connect( - config.mta.lmtp_host, int(config.mta.lmtp_port)) - print response - return lmtp - except socket.error, error: - if error[0] == errno.ECONNREFUSED: - time.sleep(1) - else: - raise - else: - raise RuntimeError('Connection refused') diff --git a/mailman/testing/layers.py b/mailman/testing/layers.py deleted file mode 100644 index 19300ba1e..000000000 --- a/mailman/testing/layers.py +++ /dev/null @@ -1,204 +0,0 @@ -# Copyright (C) 2008-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/>. - -"""Mailman test layers.""" - -from __future__ import absolute_import, unicode_literals - -__metaclass__ = type -__all__ = [ - 'ConfigLayer', - 'SMTPLayer', - ] - - -import os -import sys -import shutil -import logging -import tempfile - -from pkg_resources import resource_string -from textwrap import dedent - -from mailman.config import config -from mailman.core import initialize -from mailman.core.logging import get_handler -from mailman.i18n import _ -from mailman.testing.helpers import SMTPServer -from mailman.utilities.string import expand - - -NL = '\n' - - - -class ConfigLayer: - """Layer for pushing and popping test configurations.""" - - var_dir = None - styles = None - - @classmethod - def setUp(cls): - # Set up the basic configuration stuff. - initialize.initialize_1() - assert cls.var_dir is None, 'Layer already set up' - # Calculate a temporary VAR_DIR directory so that run-time artifacts - # of the tests won't tread on the installation's data. This also - # makes it easier to clean up after the tests are done, and insures - # isolation of test suite runs. - cls.var_dir = tempfile.mkdtemp() - # We need a test configuration both for the foreground process and any - # child processes that get spawned. lazr.config would allow us to do - # it all in a string that gets pushed, and we'll do that for the - # foreground, but because we may be spawning processes (such as queue - # runners) we'll need a file that we can specify to the with the -C - # option. Craft the full test configuration string here, push it, and - # also write it out to a temp file for -C. - test_config = dedent(""" - [mailman] - var_dir: %s - """ % cls.var_dir) - # Read the testing config and push it. - test_config += resource_string('mailman.testing', 'testing.cfg') - config.push('test config', test_config) - # Initialize everything else. - initialize.initialize_2() - initialize.initialize_3() - # When stderr debugging is enabled, subprocess root loggers should - # also be more verbose. - if cls.stderr: - test_config += dedent(""" - [logging.root] - propagate: yes - level: debug - """) - # Enable log message propagation and reset the log paths so that the - # doctests can check the output. - for logger_config in config.logger_configs: - sub_name = logger_config.name.split('.')[-1] - if sub_name == 'root': - continue - logger_name = 'mailman.' + sub_name - log = logging.getLogger(logger_name) - log.propagate = True - # Reopen the file to a new path that tests can get at. Instead of - # using the configuration file path though, use a path that's - # specific to the logger so that tests can find expected output - # more easily. - path = os.path.join(config.LOG_DIR, sub_name) - get_handler(sub_name).reopen(path) - log.setLevel(logging.DEBUG) - # If stderr debugging is enabled, make sure subprocesses are also - # more verbose. - if cls.stderr: - test_config += expand(dedent(""" - [logging.$name] - propagate: yes - level: debug - """), dict(name=sub_name, path=path)) - # zope.testing sets up logging before we get to our own initialization - # function. This messes with the root logger, so explicitly set it to - # go to stderr. - if cls.stderr: - console = logging.StreamHandler(sys.stderr) - formatter = logging.Formatter(config.logging.root.format, - config.logging.root.datefmt) - console.setFormatter(formatter) - logging.getLogger().addHandler(console) - # Write the configuration file for subprocesses and set up the config - # object to pass that properly on the -C option. - config_file = os.path.join(cls.var_dir, 'test.cfg') - with open(config_file, 'w') as fp: - fp.write(test_config) - print >> fp - config.filename = config_file - - @classmethod - def tearDown(cls): - assert cls.var_dir is not None, 'Layer not set up' - config.pop('test config') - shutil.rmtree(cls.var_dir) - cls.var_dir = None - - @classmethod - def testSetUp(cls): - pass - - @classmethod - def testTearDown(cls): - # Reset the database between tests. - config.db._reset() - # Remove all residual queue files. - for dirpath, dirnames, filenames in os.walk(config.QUEUE_DIR): - for filename in filenames: - os.remove(os.path.join(dirpath, filename)) - # Clear out messages in the message store. - for message in config.db.message_store.messages: - config.db.message_store.delete_message(message['message-id']) - config.db.commit() - # Reset the global style manager. - config.style_manager.populate() - - # Flag to indicate that loggers should propagate to the console. - stderr = False - - @classmethod - def handle_stderr(cls, *ignore): - cls.stderr = True - - @classmethod - def hack_options_parser(cls): - """Hack our way into the zc.testing framework. - - Add our custom command line option parsing into zc.testing's. We do - the imports here so that if zc.testing isn't invoked, this stuff never - gets in the way. This is pretty fragile, depend on changes in the - zc.testing package. There should be a better way! - """ - from zope.testing.testrunner.options import parser - parser.add_option(str('-e'), str('--stderr'), - action='callback', callback=cls.handle_stderr, - help=_('Propagate log errors to stderr.')) - - - -class SMTPLayer(ConfigLayer): - """Layer for starting, stopping, and accessing a test SMTP server.""" - - smtpd = None - - @classmethod - def setUp(cls): - assert cls.smtpd is None, 'Layer already set up' - cls.smtpd = SMTPServer() - cls.smtpd.start() - - @classmethod - def tearDown(cls): - assert cls.smtpd is not None, 'Layer not set up' - cls.smtpd.clear() - cls.smtpd.stop() - - @classmethod - def testSetUp(cls): - pass - - @classmethod - def testTearDown(cls): - pass diff --git a/mailman/testing/mta.py b/mailman/testing/mta.py deleted file mode 100644 index a10ba3c81..000000000 --- a/mailman/testing/mta.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (C) 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/>. - -"""Fake MTA for testing purposes.""" - -from __future__ import absolute_import, unicode_literals - -__metaclass__ = type -__all__ = [ - 'FakeMTA', - ] - - -from zope.interface import implements - -from mailman.interfaces.mta import IMailTransportAgent - - - -class FakeMTA: - """Fake MTA for testing purposes.""" - - implements(IMailTransportAgent) - - def create(self, mlist): - pass - - def delete(self, mlist): - pass - - def regenerate(self): - pass diff --git a/mailman/testing/smtplistener.py b/mailman/testing/smtplistener.py deleted file mode 100644 index 2094e20de..000000000 --- a/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() diff --git a/mailman/testing/testing.cfg b/mailman/testing/testing.cfg deleted file mode 100644 index 107db86ed..000000000 --- a/mailman/testing/testing.cfg +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright (C) 2008-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 testing configuration. - -[mta] -smtp_port: 9025 -incoming: mailman.testing.mta.FakeMTA - -[qrunner.archive] -max_restarts: 1 - -[qrunner.bounces] -max_restarts: 1 - -[qrunner.command] -max_restarts: 1 - -[qrunner.in] -max_restarts: 1 - -[qrunner.lmtp] -max_restarts: 1 - -[qrunner.maildir] -max_restarts: 1 - -[qrunner.news] -max_restarts: 1 - -[qrunner.out] -max_restarts: 1 - -[qrunner.pipeline] -max_restarts: 1 - -[qrunner.retry] -max_restarts: 1 - -[qrunner.shunt] -max_restarts: 1 - -[qrunner.virgin] -max_restarts: 1 - -[archiver.prototype] -enable: yes - -[archiver.mail_archive] -enable: yes -base_url: http://go.mail-archive.dev/ -recipient: archive@mail-archive.dev - -[archiver.pipermail] -enable: yes -base_url: http://www.example.com/pipermail/$listname - -[archiver.mhonarc] -enable: yes -command: /bin/echo "/usr/bin/mhonarc -add -dbfile $PRIVATE_ARCHIVE_FILE_DIR/${listname}.mbox/mhonarc.db -outdir $VAR_DIR/mhonarc/${listname} -stderr $LOG_DIR/mhonarc -stdout $LOG_DIR/mhonarc -spammode -umask 022" - -[domain.example_dot_com] -email_host: example.com -base_url: http://lists.example.com -contact_address: postmaster@example.com - -[language.ja] -description: Japanese -charset: euc-jp - -[language.fr] -description: French -charset: iso-8859-1 |
