~wilunix/mailman/lmtp

« back to all changes in this revision

Viewing changes to mailman/bin/testall.py

  • Committer: William Mead
  • Date: 2008-07-10 12:36:55 UTC
  • Revision ID: wam22@quant.staff.uscs.susx.ac.uk-20080710123655-7qqhiw03a32cpn87
rev1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2001-2008 by the Free Software Foundation, Inc.
 
2
#
 
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.
 
7
#
 
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.
 
12
#
 
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,
 
16
# USA.
 
17
 
 
18
"""Mailman unit test driver."""
 
19
 
 
20
from __future__ import with_statement
 
21
 
 
22
import os
 
23
import re
 
24
import grp
 
25
import pwd
 
26
import sys
 
27
import random
 
28
import shutil
 
29
import optparse
 
30
import tempfile
 
31
import unittest
 
32
import pkg_resources
 
33
 
 
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
 
38
 
 
39
 
 
40
basedir = None
 
41
 
 
42
 
 
43
 
 
44
def v_callback(option, opt, value, parser):
 
45
    if opt in ('-q', '--quiet'):
 
46
        delta = -1
 
47
    elif opt in ('-v', '--verbose'):
 
48
        delta = 1
 
49
    else:
 
50
        raise AssertionError('Unexpected option: %s' % opt)
 
51
    dest = getattr(parser.values, option.dest)
 
52
    setattr(parser.values, option.dest, max(0, dest + delta))
 
53
 
 
54
 
 
55
def parseargs():
 
56
    parser = optparse.OptionParser(version=MAILMAN_VERSION,
 
57
                                   usage=_("""\
 
58
%prog [options] [tests]
 
59
 
 
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',
 
81
                      help=_("""\
 
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:
 
87
        arguments = ['.']
 
88
    parser.options = options
 
89
    parser.arguments = arguments
 
90
    return parser
 
91
 
 
92
 
 
93
 
 
94
def search():
 
95
    testnames = []
 
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
 
100
    # modules.
 
101
    for dirpath, dirnames, filenames in os.walk(basedir):
 
102
        for fn in filenames:
 
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)
 
113
    return testnames
 
114
 
 
115
 
 
116
def match(pat, name):
 
117
    if not pat:
 
118
        return True
 
119
    if pat.startswith('!'):
 
120
        # Negative test
 
121
        return re.search(pat[1:], name) is None
 
122
    else:
 
123
        # Positive test
 
124
        return re.search(pat, name) is not None
 
125
 
 
126
 
 
127
def filter_tests(suite, patterns):
 
128
    if '.' in patterns:
 
129
        return suite
 
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
 
134
            name = test.id()
 
135
            for pat in patterns:
 
136
                if match(pat, name):
 
137
                    new.addTest(test)
 
138
                    break
 
139
        else:
 
140
            filtered = filter_tests(test, patterns)
 
141
            if filtered:
 
142
                new.addTest(filtered)
 
143
    return new
 
144
 
 
145
 
 
146
def suite(patterns, randomize):
 
147
    if patterns is None:
 
148
        patterns = '.'
 
149
    loader = unittest.TestLoader()
 
150
    # Search for all tests that match the given patterns
 
151
    testnames = search()
 
152
    suite = loader.loadTestsFromNames(testnames)
 
153
    tests = filter_tests(suite, patterns)
 
154
    if randomize:
 
155
        random.shuffle(tests._tests)
 
156
    else:
 
157
        tests._tests.sort()
 
158
    return tests
 
159
 
 
160
 
 
161
 
 
162
def main():
 
163
    global basedir
 
164
 
 
165
    parser = parseargs()
 
166
 
 
167
    # Set verbosity level for test_documentation.py.  XXX There should be a
 
168
    # better way to do this.
 
169
    class Bag: pass
 
170
    config.tests = Bag()
 
171
    config.tests.verbosity = parser.options.verbosity
 
172
    config.tests.randomize = parser.options.randomize
 
173
 
 
174
    # Turn on code coverage if selected.
 
175
    if parser.options.coverage:
 
176
        try:
 
177
            import coverage
 
178
        except ImportError:
 
179
            parser.options.coverage = False
 
180
        else:
 
181
            coverage.start()
 
182
 
 
183
    # Set up the testing configuration file both for this process, and for all
 
184
    # sub-processes testing will spawn (e.g. the qrunners).
 
185
    #
 
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')
 
189
    os.close(fd)
 
190
    enable_logging_cfg = False
 
191
    with open(logging_cfg, 'w') as fp:
 
192
        print >> 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
 
199
 
 
200
    cfg_in = pkg_resources.resource_string(
 
201
        'mailman.tests', 'testing.cfg.in')
 
202
    fd, cfg_out = tempfile.mkstemp(suffix='.cfg')
 
203
    os.close(fd)
 
204
    with open(cfg_out, 'w') as fp:
 
205
        fp.write(cfg_in)
 
206
        if enable_logging_cfg:
 
207
            print >> fp, 'LOG_CONFIG_FILE = "%s"' % logging_cfg
 
208
 
 
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
 
212
    # test suite runs.
 
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
 
219
 
 
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
 
224
 
 
225
    try:
 
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'"
 
233
 
 
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)
 
239
 
 
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')
 
243
        os.close(fd)
 
244
        os.chmod(config.dbfile, 0660)
 
245
        os.chown(config.dbfile, mailman_uid, mailman_gid)
 
246
 
 
247
        # Patch ups.
 
248
        test_engine_url = 'sqlite:///' + config.dbfile
 
249
        config.DEFAULT_DATABASE_URL = test_engine_url
 
250
 
 
251
        # Write this to the config file so subprocesses share the same testing
 
252
        # database file.
 
253
        with open(cfg_out, 'a') as fp:
 
254
            print >> fp, 'DEFAULT_DATABASE_URL = "%s"' % test_engine_url
 
255
 
 
256
        # With -vvv, turn on engine debugging.
 
257
        initialize_2(parser.options.verbosity > 3)
 
258
 
 
259
        # Run the tests.  XXX I'm not sure if basedir can be converted to
 
260
        # pkg_resources.
 
261
        import mailman
 
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))
 
265
    finally:
 
266
        os.remove(cfg_out)
 
267
        os.remove(logging_cfg)
 
268
        shutil.rmtree(var_dir)
 
269
 
 
270
    # Turn off coverage and print a report
 
271
    if parser.options.coverage:
 
272
        coverage.stop()
 
273
        modules = [module for name, module in sys.modules.items()
 
274
                   if module
 
275
                   and name is not None
 
276
                   and name.split('.')[0] == 'mailman']
 
277
        coverage.report(modules)
 
278
    sys.exit(bool(results.failures or results.errors))
 
279
 
 
280
 
 
281
 
 
282
if __name__ == '__main__':
 
283
    main()