summaryrefslogtreecommitdiff
path: root/mailman/testing
diff options
context:
space:
mode:
authorBarry Warsaw2009-01-25 13:01:41 -0500
committerBarry Warsaw2009-01-25 13:01:41 -0500
commiteefd06f1b88b8ecbb23a9013cd223b72ca85c20d (patch)
tree72c947fe16fce0e07e996ee74020b26585d7e846 /mailman/testing
parent07871212f74498abd56bef3919bf3e029eb8b930 (diff)
downloadmailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.gz
mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.tar.zst
mailman-eefd06f1b88b8ecbb23a9013cd223b72ca85c20d.zip
Diffstat (limited to 'mailman/testing')
-rw-r--r--mailman/testing/__init__.py0
-rw-r--r--mailman/testing/helpers.py248
-rw-r--r--mailman/testing/layers.py204
-rw-r--r--mailman/testing/mta.py46
-rw-r--r--mailman/testing/smtplistener.py97
-rw-r--r--mailman/testing/testing.cfg87
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