summaryrefslogtreecommitdiff
path: root/src/mailman/testing/layers.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/mailman/testing/layers.py')
-rw-r--r--src/mailman/testing/layers.py204
1 files changed, 204 insertions, 0 deletions
diff --git a/src/mailman/testing/layers.py b/src/mailman/testing/layers.py
new file mode 100644
index 000000000..19300ba1e
--- /dev/null
+++ b/src/mailman/testing/layers.py
@@ -0,0 +1,204 @@
+# 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