1
# Copyright (C) 2008-2012 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
# XXX 2012-03-23 BAW: Layers really really suck. For example, the
21
# test_owners_get_email() test requires that both the SMTPLayer and LMTPLayer
22
# be set up, but there's apparently no way to do that and make zope.testing
23
# happy. This causes no tests failures, but it does cause errors at the end
24
# of the full test run. For now, I'll ignore that, but I do want to
25
# eventually get rid of the zope.test* dependencies and use something like
26
# testresources or some such.
28
from __future__ import absolute_import, print_function, unicode_literals
48
from base64 import b64encode
49
from lazr.config import as_boolean, as_timedelta
50
from pkg_resources import resource_string
51
from textwrap import dedent
52
from urllib2 import Request, URLError, urlopen
53
from zope.component import getUtility
55
from mailman.config import config
56
from mailman.core import initialize
57
from mailman.core.initialize import INHIBIT_CONFIG_FILE
58
from mailman.core.logging import get_handler
59
from mailman.database.transaction import transaction
60
from mailman.interfaces.domain import IDomainManager
61
from mailman.testing.helpers import (
62
TestableMaster, get_lmtp_client, reset_the_world)
63
from mailman.testing.mta import ConnectionCountingController
64
from mailman.utilities.string import expand
67
TEST_TIMEOUT = datetime.timedelta(seconds=5)
72
class MockAndMonkeyLayer:
73
"""Layer for mocking and monkey patching for testing."""
75
# Set this to True to enable predictable datetimes, uids, etc.
78
# A registration of all testing factories, for resetting between tests.
82
def testTearDown(cls):
83
for reset in cls._resets:
87
def register_reset(cls, reset):
88
cls._resets.append(reset)
92
class ConfigLayer(MockAndMonkeyLayer):
93
"""Layer for pushing and popping test configurations."""
100
# Set up the basic configuration stuff. Turn off path creation until
101
# we've pushed the testing config.
102
config.create_paths = False
103
initialize.initialize_1(INHIBIT_CONFIG_FILE)
104
assert cls.var_dir is None, 'Layer already set up'
105
# Calculate a temporary VAR_DIR directory so that run-time artifacts
106
# of the tests won't tread on the installation's data. This also
107
# makes it easier to clean up after the tests are done, and insures
108
# isolation of test suite runs.
109
cls.var_dir = tempfile.mkdtemp()
110
# We need a test configuration both for the foreground process and any
111
# child processes that get spawned. lazr.config would allow us to do
112
# it all in a string that gets pushed, and we'll do that for the
113
# foreground, but because we may be spawning processes (such as
114
# runners) we'll need a file that we can specify to the with the -C
115
# option. Craft the full test configuration string here, push it, and
116
# also write it out to a temp file for -C.
117
test_config = dedent("""
125
# Read the testing config and push it.
126
test_config += resource_string('mailman.testing', 'testing.cfg')
127
config.create_paths = True
128
config.push('test config', test_config)
129
# Initialize everything else.
130
initialize.initialize_2(testing=True)
131
initialize.initialize_3()
132
# When stderr debugging is enabled, subprocess root loggers should
133
# also be more verbose.
135
test_config += dedent("""
140
# Enable log message propagation and reset the log paths so that the
141
# doctests can check the output.
142
for logger_config in config.logger_configs:
143
sub_name = logger_config.name.split('.')[-1]
144
if sub_name == 'root':
146
logger_name = 'mailman.' + sub_name
147
log = logging.getLogger(logger_name)
149
# Reopen the file to a new path that tests can get at. Instead of
150
# using the configuration file path though, use a path that's
151
# specific to the logger so that tests can find expected output
153
path = os.path.join(config.LOG_DIR, sub_name)
154
get_handler(sub_name).reopen(path)
155
log.setLevel(logging.DEBUG)
156
# If stderr debugging is enabled, make sure subprocesses are also
159
test_config += expand(dedent("""
163
"""), dict(name=sub_name, path=path))
164
# zope.testing sets up logging before we get to our own initialization
165
# function. This messes with the root logger, so explicitly set it to
168
console = logging.StreamHandler(sys.stderr)
169
formatter = logging.Formatter(config.logging.root.format,
170
config.logging.root.datefmt)
171
console.setFormatter(formatter)
172
logging.getLogger().addHandler(console)
173
# Write the configuration file for subprocesses and set up the config
174
# object to pass that properly on the -C option.
175
config_file = os.path.join(cls.var_dir, 'test.cfg')
176
with open(config_file, 'w') as fp:
177
fp.write(test_config)
179
config.filename = config_file
183
assert cls.var_dir is not None, 'Layer not set up'
184
config.pop('test config')
185
shutil.rmtree(cls.var_dir)
190
# Add an example domain.
192
getUtility(IDomainManager).add(
193
'example.com', 'An example domain.',
194
'http://lists.example.com', 'postmaster@example.com')
197
def testTearDown(cls):
200
# Flag to indicate that loggers should propagate to the console.
204
def enable_stderr(cls):
205
"""Enable stderr logging if -e/--stderr is given.
207
We used to hack our way into the zc.testing framework, but that was
208
undocumented and way too fragile. Well, this probably is too, but now
209
we just scan sys.argv for -e/--stderr and enable logging if found.
210
Then we remove the option from sys.argv. This works because this
211
method is called before zope.testrunner sees the options.
213
As a bonus, we'll check an environment variable too.
217
sys.argv.remove('-e')
218
if '--stderr' in sys.argv:
220
sys.argv.remove('--stderr')
221
if len(os.environ.get('MM_VERBOSE_TESTLOG', '').strip()) > 0:
224
# The top of our source tree, for tests that care (e.g. hooks.txt).
225
root_directory = None
228
def set_root_directory(cls, directory):
229
"""Set the directory at the root of our source tree.
231
zc.recipe.testrunner runs from parts/test/working-directory, but
232
that's actually changed over the life of the package. Some tests
233
care, e.g. because they need to find our built-out bin directory.
234
Fortunately, buildout can give us this information. See the
235
`buildout.cfg` file for where this method is called.
237
cls.root_directory = directory
241
class SMTPLayer(ConfigLayer):
242
"""Layer for starting, stopping, and accessing a test SMTP server."""
248
assert cls.smtpd is None, 'Layer already set up'
249
host = config.mta.smtp_host
250
port = int(config.mta.smtp_port)
251
cls.smtpd = ConnectionCountingController(host, port)
256
assert cls.smtpd is not None, 'Layer not set up'
262
# Make sure we don't call our superclass's testSetUp(), otherwise the
263
# example.com domain will get added twice.
267
def testTearDown(cls):
273
class LMTPLayer(ConfigLayer):
274
"""Layer for starting, stopping, and accessing a test LMTP server."""
279
def _wait_for_lmtp_server():
280
get_lmtp_client(quiet=True)
284
assert cls.lmtpd is None, 'Layer already set up'
285
cls.lmtpd = TestableMaster(cls._wait_for_lmtp_server)
286
cls.lmtpd.start('lmtp')
290
assert cls.lmtpd is not None, 'Layer not set up'
296
# Make sure we don't call our superclass's testSetUp(), otherwise the
297
# example.com domain will get added twice.
302
class RESTLayer(SMTPLayer):
303
"""Layer for starting, stopping, and accessing the test REST layer."""
308
def _wait_for_rest_server():
309
until = datetime.datetime.now() + as_timedelta(config.devmode.wait)
310
while datetime.datetime.now() < until:
312
request = Request('http://localhost:9001/3.0/system')
313
basic_auth = '{0}:{1}'.format(config.webservice.admin_user,
314
config.webservice.admin_pass)
315
request.add_header('Authorization',
316
'Basic ' + b64encode(basic_auth))
317
fp = urlopen(request)
324
raise RuntimeError('REST server did not start up')
328
assert cls.server is None, 'Layer already set up'
329
cls.server = TestableMaster(cls._wait_for_rest_server)
330
cls.server.start('rest')
334
assert cls.server is not None, 'Layer not set up'
341
"""Return a 'testing' flag for use with the predictable factories.
343
:return: True when in testing mode.
346
return (MockAndMonkeyLayer.testing_mode or
347
as_boolean(config.devmode.testing))