1
# Copyright (C) 2001-2008 by the Free Software Foundation, Inc.
3
# This program is free software; you can redistribute it and/or
4
# modify it under the terms of the GNU General Public License
5
# as published by the Free Software Foundation; either version 2
6
# of the License, or (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18
"""Mailman unit test driver."""
20
from __future__ import with_statement
34
from mailman.configuration import config
35
from mailman.i18n import _
36
from mailman.initialize import initialize_1, initialize_2
37
from mailman.version import MAILMAN_VERSION
44
def v_callback(option, opt, value, parser):
45
if opt in ('-q', '--quiet'):
47
elif opt in ('-v', '--verbose'):
50
raise AssertionError('Unexpected option: %s' % opt)
51
dest = getattr(parser.values, option.dest)
52
setattr(parser.values, option.dest, max(0, dest + delta))
56
parser = optparse.OptionParser(version=MAILMAN_VERSION,
58
%prog [options] [tests]
60
Run the Mailman unit test suite. 'tests' is one or more Python regular
61
expressions matching only the tests you want to run. Prefix the regular
62
expression with '!' to specify a negative test."""))
63
parser.set_defaults(verbosity=2)
64
parser.add_option('-v', '--verbose',
65
action='callback', callback=v_callback,
66
dest='verbosity', help=_("""\
67
Increase verbosity by 1, which defaults to %default. Use -q to reduce
68
verbosity. -v and -q options accumulate."""))
69
parser.add_option('-q', '--quiet',
70
action='callback', callback=v_callback,
71
dest='verbosity', help=_("""\
72
Reduce verbosity by 1 (but not below 0)."""))
73
parser.add_option('-e', '--stderr',
74
default=False, action='store_true',
75
help=_('Propagate log errors to stderr.'))
76
parser.add_option('-c', '--coverage',
77
default=False, action='store_true',
78
help=_('Enable code coverage.'))
79
parser.add_option('-r', '--randomize',
80
default=False, action='store_true',
82
Randomize the tests; good for finding subtle dependency errors. Note that
83
this isn't completely random though because the doctests are not mixed with
84
the Python tests. Each type of test is randomized within its group."""))
85
options, arguments = parser.parse_args()
86
if len(arguments) == 0:
88
parser.options = options
89
parser.arguments = arguments
96
# Walk the entire tree from the current base directory. Look for modules
97
# that start with 'test_'. Calculate the full module path name to this
98
# module, append 'test_suite' and add that to testnames. This way, we run
99
# all the suites defined in the test_suite() function inside all test
101
for dirpath, dirnames, filenames in os.walk(basedir):
103
if fn.startswith('test_') and fn.endswith('.py'):
104
# Start with full path
105
path = os.path.join(dirpath, fn)
106
# Convert the file path to a module path. First, we must make
107
# the file path relative to the root directory. Then strip
108
# off the trailing .py
109
path = path[len(basedir)+1:-3]
110
# Convert slashes to dots
111
modpath = path.replace(os.sep, '.') + '.test_suite'
112
testnames.append('mailman.' + modpath)
116
def match(pat, name):
119
if pat.startswith('!'):
121
return re.search(pat[1:], name) is None
124
return re.search(pat, name) is not None
127
def filter_tests(suite, patterns):
130
new = unittest.TestSuite()
131
for test in suite._tests:
132
if isinstance(test, unittest.TestCase):
133
# Get the fill test name: package.module.class.method
140
filtered = filter_tests(test, patterns)
142
new.addTest(filtered)
146
def suite(patterns, randomize):
149
loader = unittest.TestLoader()
150
# Search for all tests that match the given patterns
152
suite = loader.loadTestsFromNames(testnames)
153
tests = filter_tests(suite, patterns)
155
random.shuffle(tests._tests)
167
# Set verbosity level for test_documentation.py. XXX There should be a
168
# better way to do this.
171
config.tests.verbosity = parser.options.verbosity
172
config.tests.randomize = parser.options.randomize
174
# Turn on code coverage if selected.
175
if parser.options.coverage:
179
parser.options.coverage = False
183
# Set up the testing configuration file both for this process, and for all
184
# sub-processes testing will spawn (e.g. the qrunners).
186
# Also create a logging.cfg file with values reflecting verbosity and
187
# stderr propagation. Enable it only if necessary.
188
fd, logging_cfg = tempfile.mkstemp(suffix='.cfg')
190
enable_logging_cfg = False
191
with open(logging_cfg, 'w') as fp:
193
if parser.options.stderr:
194
print >> fp, 'propagate = True'
195
enable_logging_cfg = True
196
if parser.options.verbosity > 2:
197
print >> fp, 'level = DEBUG'
198
enable_logging_cfg = True
200
cfg_in = pkg_resources.resource_string(
201
'mailman.tests', 'testing.cfg.in')
202
fd, cfg_out = tempfile.mkstemp(suffix='.cfg')
204
with open(cfg_out, 'w') as fp:
206
if enable_logging_cfg:
207
print >> fp, 'LOG_CONFIG_FILE = "%s"' % logging_cfg
209
# Calculate a temporary VAR_DIR directory so that run-time artifacts of
210
# the tests won't tread on the installation's data. This also makes it
211
# easier to clean up after the tests are done, and insures isolation of
213
var_dir = tempfile.mkdtemp()
214
if parser.options.verbosity > 2:
215
print 'VAR_DIR :', var_dir
216
print 'config file:', cfg_out
217
if enable_logging_cfg:
218
print 'logging config file:', logging_cfg
220
user_id = os.getuid()
221
user_name = pwd.getpwuid(user_id).pw_name
222
group_id = os.getgid()
223
group_name = grp.getgrgid(group_id).gr_name
226
with open(cfg_out, 'a') as fp:
227
print >> fp, 'VAR_DIR = "%s"' % var_dir
228
print >> fp, 'MAILMAN_USER = "%s"' % user_name
229
print >> fp, 'MAILMAN_UID = %d' % user_id
230
print >> fp, 'MAILMAN_GROUP = "%s"' % group_name
231
print >> fp, 'MAILMAN_GID = %d' % group_id
232
print >> fp, "LANGUAGES = 'en'"
234
initialize_1(cfg_out, propagate_logs=parser.options.stderr)
235
mailman_uid = pwd.getpwnam(config.MAILMAN_USER).pw_uid
236
mailman_gid = grp.getgrnam(config.MAILMAN_GROUP).gr_gid
237
os.chmod(cfg_out, 0660)
238
os.chown(cfg_out, mailman_uid, mailman_gid)
240
# Create an empty SQLite database file with the proper permissions and
241
# calculate the SQLAlchemy engine url to this database file.
242
fd, config.dbfile = tempfile.mkstemp(dir=config.DATA_DIR, suffix='.db')
244
os.chmod(config.dbfile, 0660)
245
os.chown(config.dbfile, mailman_uid, mailman_gid)
248
test_engine_url = 'sqlite:///' + config.dbfile
249
config.DEFAULT_DATABASE_URL = test_engine_url
251
# Write this to the config file so subprocesses share the same testing
253
with open(cfg_out, 'a') as fp:
254
print >> fp, 'DEFAULT_DATABASE_URL = "%s"' % test_engine_url
256
# With -vvv, turn on engine debugging.
257
initialize_2(parser.options.verbosity > 3)
259
# Run the tests. XXX I'm not sure if basedir can be converted to
262
basedir = os.path.dirname(mailman.__file__)
263
runner = unittest.TextTestRunner(verbosity=parser.options.verbosity)
264
results = runner.run(suite(parser.arguments, parser.options.randomize))
267
os.remove(logging_cfg)
268
shutil.rmtree(var_dir)
270
# Turn off coverage and print a report
271
if parser.options.coverage:
273
modules = [module for name, module in sys.modules.items()
276
and name.split('.')[0] == 'mailman']
277
coverage.report(modules)
278
sys.exit(bool(results.failures or results.errors))
282
if __name__ == '__main__':