summaryrefslogtreecommitdiff
path: root/mailman/testing
diff options
context:
space:
mode:
Diffstat (limited to 'mailman/testing')
-rw-r--r--mailman/testing/helpers.py40
-rw-r--r--mailman/testing/layers.py208
-rw-r--r--mailman/testing/smtplistener.py2
-rw-r--r--mailman/testing/testing.cfg80
-rw-r--r--mailman/testing/testing.cfg.in17
5 files changed, 314 insertions, 33 deletions
diff --git a/mailman/testing/helpers.py b/mailman/testing/helpers.py
index 47c20ca2f..05ca9253f 100644
--- a/mailman/testing/helpers.py
+++ b/mailman/testing/helpers.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2008 by the Free Software Foundation, Inc.
+# Copyright (C) 2008-2009 by the Free Software Foundation, Inc.
#
# This file is part of GNU Mailman.
#
@@ -42,7 +42,7 @@ from Queue import Empty, Queue
from datetime import datetime, timedelta
from mailman.bin.master import Loop as Master
-from mailman.configuration import config
+from mailman.config import config
from mailman.queue import Switchboard
from mailman.testing.smtplistener import Server
@@ -51,21 +51,30 @@ log = logging.getLogger('mailman.debug')
-def make_testable_runner(runner_class):
+def make_testable_runner(runner_class, name=None):
"""Create a queue runner that runs until its queue is empty.
- :param runner_class: An IRunner
+ :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)
+ self._stop = (len(self.switchboard.files) == 0)
- return EmptyingRunner()
+ return EmptyingRunner(name)
@@ -75,15 +84,14 @@ class _Bag:
setattr(self, key, value)
-def get_queue_messages(queue):
+def get_queue_messages(queue_name):
"""Return and clear all the messages in the given queue.
- :param queue: An ISwitchboard or a string naming a queue.
+ :param queue_name: A string naming a queue.
:return: A list of 2-tuples where each item contains the message and
message metadata.
"""
- if isinstance(queue, basestring):
- queue = Switchboard(queue)
+ queue = config.switchboards[queue_name]
messages = []
for filebase in queue.files:
msg, msgdata = queue.dequeue(filebase)
@@ -159,12 +167,11 @@ class TestableMaster(Master):
class SMTPServer:
"""An smtp server for testing."""
- host = 'localhost'
- port = 10825
-
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)
@@ -173,9 +180,11 @@ class SMTPServer:
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)
- smtpd.helo('test.localhost')
+ response = smtpd.helo('test.localhost')
smtpd.quit()
+ log.info('SMTP server is running: %s', response)
def stop(self):
"""Stop the smtp server."""
@@ -227,7 +236,8 @@ def get_lmtp_client():
lmtp = LMTP()
for attempts in range(3):
try:
- response = lmtp.connect(config.LMTP_HOST, config.LMTP_PORT)
+ response = lmtp.connect(
+ config.mta.lmtp_host, int(config.mta.lmtp_port))
print response
return lmtp
except socket.error, error:
diff --git a/mailman/testing/layers.py b/mailman/testing/layers.py
new file mode 100644
index 000000000..ef4535038
--- /dev/null
+++ b/mailman/testing/layers.py
@@ -0,0 +1,208 @@
+# 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."""
+
+__metaclass__ = type
+__all__ = [
+ 'ConfigLayer',
+ 'SMTPLayer',
+ ]
+
+
+import os
+import sys
+import shutil
+import logging
+import tempfile
+
+from pkg_resources import resource_string
+from string import Template
+from textwrap import dedent
+
+from mailman.config import config
+from mailman.core import initialize
+from mailman.core.logging import get_handler
+from mailman.core.styles import style_manager
+from mailman.i18n import _
+from mailman.testing.helpers import SMTPServer
+
+
+NL = '\n'
+
+
+
+class ConfigLayer:
+ """Layer for pushing and popping test configurations."""
+
+ var_dir = 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. XXX Switch to using the log support
+ # in zope.testing.
+ 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 += Template(dedent("""
+ [logging.$name]
+ propagate: yes
+ level: debug
+ """)).substitute(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):
+ # Record the current (default) set of styles so that we can reset them
+ # easily in the tear down.
+ cls.styles = set(style_manager.styles)
+
+ @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.
+ new_styles = set(style_manager.styles) - cls.styles
+ for style in new_styles:
+ style_manager.unregister(style)
+ cls.styles = None
+
+ # 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('-e', '--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/smtplistener.py b/mailman/testing/smtplistener.py
index a7e289ea1..cb9d9b323 100644
--- a/mailman/testing/smtplistener.py
+++ b/mailman/testing/smtplistener.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2007-2008 by the Free Software Foundation, Inc.
+# Copyright (C) 2007-2009 by the Free Software Foundation, Inc.
#
# This file is part of GNU Mailman.
#
diff --git a/mailman/testing/testing.cfg b/mailman/testing/testing.cfg
new file mode 100644
index 000000000..ff9bb89da
--- /dev/null
+++ b/mailman/testing/testing.cfg
@@ -0,0 +1,80 @@
+# 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
+
+[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.mail_archive]
+base_url: http://go.mail-archive.dev/
+recipient: archive@mail-archive.dev
+
+[archiver.pipermail]
+base_url: http://www.example.com/pipermail/$listname
+
+[archiver.mhonarc]
+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://www.example.com
+contact_address: postmaster@example.com
+
+[language.ja]
+description: Japanese
+charset: euc-jp
+
+[language.fr]
+description: French
+charset: iso-8859-1
diff --git a/mailman/testing/testing.cfg.in b/mailman/testing/testing.cfg.in
deleted file mode 100644
index e544242d0..000000000
--- a/mailman/testing/testing.cfg.in
+++ /dev/null
@@ -1,17 +0,0 @@
-# -*- python -*-
-
-# Configuration file template for the unit test suite. We need this because
-# both the process running the tests and all sub-processes (e.g. qrunners)
-# must share the same configuration file.
-
-MAX_RESTARTS = 1
-MTA = None
-USE_LMTP = Yes
-
-# Make sure these goes to fake domains.
-MAIL_ARCHIVE_BASEURL = 'http://go.mail-archive.dev/'
-MAIL_ARCHIVE_RECIPIENT = 'archive@mail-archive.dev'
-
-add_domain('example.com', 'http://www.example.com')
-
-# bin/testall will add additional runtime configuration variables here.