1 # Copyright (C) 2008-2009 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 """Mailman test layers."""
20 from __future__
import absolute_import
, unicode_literals
38 from pkg_resources
import resource_string
39 from textwrap
import dedent
40 from urllib2
import urlopen
, URLError
41 from zope
.component
import getUtility
43 from mailman
.config
import config
44 from mailman
.core
import initialize
45 from mailman
.core
.logging
import get_handler
46 from mailman
.i18n
import _
47 from mailman
.interfaces
.domain
import IDomainManager
48 from mailman
.interfaces
.messages
import IMessageStore
49 from mailman
.testing
.helpers
import TestableMaster
50 from mailman
.testing
.mta
import ConnectionCountingController
51 from mailman
.utilities
.datetime
import factory
52 from mailman
.utilities
.string
import expand
55 TEST_TIMEOUT
= datetime
.timedelta(seconds
=5)
60 class MockAndMonkeyLayer
:
61 """Layer for mocking and monkey patching for testing."""
65 factory
.testing_mode
= True
69 factory
.testing_mode
= False
72 def testTearDown(cls
):
77 class ConfigLayer(MockAndMonkeyLayer
):
78 """Layer for pushing and popping test configurations."""
85 # Set up the basic configuration stuff.
86 initialize
.initialize_1()
87 assert cls
.var_dir
is None, 'Layer already set up'
88 # Calculate a temporary VAR_DIR directory so that run-time artifacts
89 # of the tests won't tread on the installation's data. This also
90 # makes it easier to clean up after the tests are done, and insures
91 # isolation of test suite runs.
92 cls
.var_dir
= tempfile
.mkdtemp()
93 # We need a test configuration both for the foreground process and any
94 # child processes that get spawned. lazr.config would allow us to do
95 # it all in a string that gets pushed, and we'll do that for the
96 # foreground, but because we may be spawning processes (such as queue
97 # runners) we'll need a file that we can specify to the with the -C
98 # option. Craft the full test configuration string here, push it, and
99 # also write it out to a temp file for -C.
100 test_config
= dedent("""
104 # Read the testing config and push it.
105 test_config
+= resource_string('mailman.testing', 'testing.cfg')
106 config
.push('test config', test_config
)
107 # Initialize everything else.
108 initialize
.initialize_2()
109 initialize
.initialize_3()
110 # When stderr debugging is enabled, subprocess root loggers should
111 # also be more verbose.
113 test_config
+= dedent("""
118 # Enable log message propagation and reset the log paths so that the
119 # doctests can check the output.
120 for logger_config
in config
.logger_configs
:
121 sub_name
= logger_config
.name
.split('.')[-1]
122 if sub_name
== 'root':
124 logger_name
= 'mailman.' + sub_name
125 log
= logging
.getLogger(logger_name
)
127 # Reopen the file to a new path that tests can get at. Instead of
128 # using the configuration file path though, use a path that's
129 # specific to the logger so that tests can find expected output
131 path
= os
.path
.join(config
.LOG_DIR
, sub_name
)
132 get_handler(sub_name
).reopen(path
)
133 log
.setLevel(logging
.DEBUG
)
134 # If stderr debugging is enabled, make sure subprocesses are also
137 test_config
+= expand(dedent("""
141 """), dict(name
=sub_name
, path
=path
))
142 # zope.testing sets up logging before we get to our own initialization
143 # function. This messes with the root logger, so explicitly set it to
146 console
= logging
.StreamHandler(sys
.stderr
)
147 formatter
= logging
.Formatter(config
.logging
.root
.format
,
148 config
.logging
.root
.datefmt
)
149 console
.setFormatter(formatter
)
150 logging
.getLogger().addHandler(console
)
151 # Write the configuration file for subprocesses and set up the config
152 # object to pass that properly on the -C option.
153 config_file
= os
.path
.join(cls
.var_dir
, 'test.cfg')
154 with
open(config_file
, 'w') as fp
:
155 fp
.write(test_config
)
157 config
.filename
= config_file
161 assert cls
.var_dir
is not None, 'Layer not set up'
162 config
.pop('test config')
163 shutil
.rmtree(cls
.var_dir
)
168 # Add an example domain.
169 IDomainManager(config
).add(
170 'example.com', 'An example domain.',
171 'http://lists.example.com', 'postmaster@example.com')
175 def testTearDown(cls
):
176 # Reset the database between tests.
178 # Remove all residual queue files.
179 for dirpath
, dirnames
, filenames
in os
.walk(config
.QUEUE_DIR
):
180 for filename
in filenames
:
181 os
.remove(os
.path
.join(dirpath
, filename
))
182 # Clear out messages in the message store.
183 message_store
= getUtility(IMessageStore
)
184 for message
in message_store
.messages
:
185 message_store
.delete_message(message
['message-id'])
187 # Reset the global style manager.
188 config
.style_manager
.populate()
190 # Flag to indicate that loggers should propagate to the console.
194 def handle_stderr(cls
, *ignore
):
198 def hack_options_parser(cls
):
199 """Hack our way into the zc.testing framework.
201 Add our custom command line option parsing into zc.testing's. We do
202 the imports here so that if zc.testing isn't invoked, this stuff never
203 gets in the way. This is pretty fragile, depend on changes in the
204 zc.testing package. There should be a better way!
206 from zope
.testing
.testrunner
.options
import parser
207 parser
.add_option(str('-e'), str('--stderr'),
208 action
='callback', callback
=cls
.handle_stderr
,
209 help=_('Propagate log errors to stderr.'))
213 class SMTPLayer(ConfigLayer
):
214 """Layer for starting, stopping, and accessing a test SMTP server."""
220 assert cls
.smtpd
is None, 'Layer already set up'
221 host
= config
.mta
.smtp_host
222 port
= int(config
.mta
.smtp_port
)
223 cls
.smtpd
= ConnectionCountingController(host
, port
)
228 assert cls
.smtpd
is not None, 'Layer not set up'
237 def testTearDown(cls
):
242 class RESTLayer(SMTPLayer
):
243 """Layer for starting, stopping, and accessing the test REST layer."""
248 def _wait_for_rest_server():
249 until
= datetime
.datetime
.now() + TEST_TIMEOUT
250 while datetime
.datetime
.now() < until
:
252 fp
= urlopen('http://localhost:8001/3.0/system')
259 raise RuntimeError('REST server did not start up')
263 assert cls
.server
is None, 'Layer already set up'
264 cls
.server
= TestableMaster(cls
._wait
_for
_rest
_server
)
265 cls
.server
.start('rest')
269 assert cls
.server
is not None, 'Layer not set up'