/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/__init__.py

merge integration.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005 by Canonical Ltd
 
2
 
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
 
 
18
from cStringIO import StringIO
 
19
import difflib
 
20
import errno
 
21
import logging
 
22
import os
 
23
import re
 
24
import shutil
 
25
import sys
 
26
import tempfile
 
27
import unittest
 
28
import time
 
29
import codecs
 
30
 
 
31
import bzrlib.branch
 
32
import bzrlib.commands
 
33
from bzrlib.errors import BzrError
 
34
import bzrlib.inventory
 
35
import bzrlib.merge3
 
36
import bzrlib.osutils
 
37
import bzrlib.osutils as osutils
 
38
import bzrlib.plugin
 
39
import bzrlib.store
 
40
import bzrlib.trace
 
41
from bzrlib.transport import urlescape
 
42
from bzrlib.trace import mutter
 
43
from bzrlib.tests.TestUtil import TestLoader, TestSuite
 
44
from bzrlib.tests.treeshape import build_tree_contents
 
45
 
 
46
MODULES_TO_TEST = []
 
47
MODULES_TO_DOCTEST = [
 
48
                      bzrlib.branch,
 
49
                      bzrlib.commands,
 
50
                      bzrlib.errors,
 
51
                      bzrlib.inventory,
 
52
                      bzrlib.merge3,
 
53
                      bzrlib.osutils,
 
54
                      bzrlib.store,
 
55
                      ]
 
56
def packages_to_test():
 
57
    import bzrlib.tests.blackbox
 
58
    return [
 
59
            bzrlib.tests.blackbox
 
60
            ]
 
61
 
 
62
 
 
63
class EarlyStoppingTestResultAdapter(object):
 
64
    """An adapter for TestResult to stop at the first first failure or error"""
 
65
 
 
66
    def __init__(self, result):
 
67
        self._result = result
 
68
 
 
69
    def addError(self, test, err):
 
70
        self._result.addError(test, err)
 
71
        self._result.stop()
 
72
 
 
73
    def addFailure(self, test, err):
 
74
        self._result.addFailure(test, err)
 
75
        self._result.stop()
 
76
 
 
77
    def __getattr__(self, name):
 
78
        return getattr(self._result, name)
 
79
 
 
80
    def __setattr__(self, name, value):
 
81
        if name == '_result':
 
82
            object.__setattr__(self, name, value)
 
83
        return setattr(self._result, name, value)
 
84
 
 
85
 
 
86
class _MyResult(unittest._TextTestResult):
 
87
    """Custom TestResult.
 
88
 
 
89
    Shows output in a different format, including displaying runtime for tests.
 
90
    """
 
91
 
 
92
    def _elapsedTime(self):
 
93
        return "%5dms" % (1000 * (time.time() - self._start_time))
 
94
 
 
95
    def startTest(self, test):
 
96
        unittest.TestResult.startTest(self, test)
 
97
        # In a short description, the important words are in
 
98
        # the beginning, but in an id, the important words are
 
99
        # at the end
 
100
        SHOW_DESCRIPTIONS = False
 
101
        if self.showAll:
 
102
            width = osutils.terminal_width()
 
103
            name_width = width - 15
 
104
            what = None
 
105
            if SHOW_DESCRIPTIONS:
 
106
                what = test.shortDescription()
 
107
                if what:
 
108
                    if len(what) > name_width:
 
109
                        what = what[:name_width-3] + '...'
 
110
            if what is None:
 
111
                what = test.id()
 
112
                if what.startswith('bzrlib.tests.'):
 
113
                    what = what[13:]
 
114
                if len(what) > name_width:
 
115
                    what = '...' + what[3-name_width:]
 
116
            what = what.ljust(name_width)
 
117
            self.stream.write(what)
 
118
        self.stream.flush()
 
119
        self._start_time = time.time()
 
120
 
 
121
    def addError(self, test, err):
 
122
        if isinstance(err[1], TestSkipped):
 
123
            return self.addSkipped(test, err)    
 
124
        unittest.TestResult.addError(self, test, err)
 
125
        if self.showAll:
 
126
            self.stream.writeln("ERROR %s" % self._elapsedTime())
 
127
        elif self.dots:
 
128
            self.stream.write('E')
 
129
        self.stream.flush()
 
130
 
 
131
    def addFailure(self, test, err):
 
132
        unittest.TestResult.addFailure(self, test, err)
 
133
        if self.showAll:
 
134
            self.stream.writeln(" FAIL %s" % self._elapsedTime())
 
135
        elif self.dots:
 
136
            self.stream.write('F')
 
137
        self.stream.flush()
 
138
 
 
139
    def addSuccess(self, test):
 
140
        if self.showAll:
 
141
            self.stream.writeln('   OK %s' % self._elapsedTime())
 
142
        elif self.dots:
 
143
            self.stream.write('~')
 
144
        self.stream.flush()
 
145
        unittest.TestResult.addSuccess(self, test)
 
146
 
 
147
    def addSkipped(self, test, skip_excinfo):
 
148
        if self.showAll:
 
149
            print >>self.stream, ' SKIP %s' % self._elapsedTime()
 
150
            print >>self.stream, '     %s' % skip_excinfo[1]
 
151
        elif self.dots:
 
152
            self.stream.write('S')
 
153
        self.stream.flush()
 
154
        # seems best to treat this as success from point-of-view of unittest
 
155
        # -- it actually does nothing so it barely matters :)
 
156
        unittest.TestResult.addSuccess(self, test)
 
157
 
 
158
    def printErrorList(self, flavour, errors):
 
159
        for test, err in errors:
 
160
            self.stream.writeln(self.separator1)
 
161
            self.stream.writeln("%s: %s" % (flavour, self.getDescription(test)))
 
162
            if hasattr(test, '_get_log'):
 
163
                print >>self.stream
 
164
                print >>self.stream, \
 
165
                        ('vvvv[log from %s]' % test.id()).ljust(78,'-')
 
166
                print >>self.stream, test._get_log()
 
167
                print >>self.stream, \
 
168
                        ('^^^^[log from %s]' % test.id()).ljust(78,'-')
 
169
            self.stream.writeln(self.separator2)
 
170
            self.stream.writeln("%s" % err)
 
171
 
 
172
 
 
173
class TextTestRunner(unittest.TextTestRunner):
 
174
    stop_on_failure = False
 
175
 
 
176
    def _makeResult(self):
 
177
        result = _MyResult(self.stream, self.descriptions, self.verbosity)
 
178
        if self.stop_on_failure:
 
179
            result = EarlyStoppingTestResultAdapter(result)
 
180
        return result
 
181
 
 
182
 
 
183
def iter_suite_tests(suite):
 
184
    """Return all tests in a suite, recursing through nested suites"""
 
185
    for item in suite._tests:
 
186
        if isinstance(item, unittest.TestCase):
 
187
            yield item
 
188
        elif isinstance(item, unittest.TestSuite):
 
189
            for r in iter_suite_tests(item):
 
190
                yield r
 
191
        else:
 
192
            raise Exception('unknown object %r inside test suite %r'
 
193
                            % (item, suite))
 
194
 
 
195
 
 
196
class TestSkipped(Exception):
 
197
    """Indicates that a test was intentionally skipped, rather than failing."""
 
198
    # XXX: Not used yet
 
199
 
 
200
 
 
201
class CommandFailed(Exception):
 
202
    pass
 
203
 
 
204
class TestCase(unittest.TestCase):
 
205
    """Base class for bzr unit tests.
 
206
    
 
207
    Tests that need access to disk resources should subclass 
 
208
    TestCaseInTempDir not TestCase.
 
209
 
 
210
    Error and debug log messages are redirected from their usual
 
211
    location into a temporary file, the contents of which can be
 
212
    retrieved by _get_log().  We use a real OS file, not an in-memory object,
 
213
    so that it can also capture file IO.  When the test completes this file
 
214
    is read into memory and removed from disk.
 
215
       
 
216
    There are also convenience functions to invoke bzr's command-line
 
217
    routine, and to build and check bzr trees.
 
218
   
 
219
    In addition to the usual method of overriding tearDown(), this class also
 
220
    allows subclasses to register functions into the _cleanups list, which is
 
221
    run in order as the object is torn down.  It's less likely this will be
 
222
    accidentally overlooked.
 
223
    """
 
224
 
 
225
    BZRPATH = 'bzr'
 
226
    _log_file_name = None
 
227
    _log_contents = ''
 
228
 
 
229
    def setUp(self):
 
230
        unittest.TestCase.setUp(self)
 
231
        self._cleanups = []
 
232
        self._cleanEnvironment()
 
233
        bzrlib.trace.disable_default_logging()
 
234
        self._startLogFile()
 
235
 
 
236
    def _ndiff_strings(self, a, b):
 
237
        """Return ndiff between two strings containing lines.
 
238
        
 
239
        A trailing newline is added if missing to make the strings
 
240
        print properly."""
 
241
        if b and b[-1] != '\n':
 
242
            b += '\n'
 
243
        if a and a[-1] != '\n':
 
244
            a += '\n'
 
245
        difflines = difflib.ndiff(a.splitlines(True),
 
246
                                  b.splitlines(True),
 
247
                                  linejunk=lambda x: False,
 
248
                                  charjunk=lambda x: False)
 
249
        return ''.join(difflines)
 
250
 
 
251
    def assertEqualDiff(self, a, b):
 
252
        """Assert two texts are equal, if not raise an exception.
 
253
        
 
254
        This is intended for use with multi-line strings where it can 
 
255
        be hard to find the differences by eye.
 
256
        """
 
257
        # TODO: perhaps override assertEquals to call this for strings?
 
258
        if a == b:
 
259
            return
 
260
        raise AssertionError("texts not equal:\n" + 
 
261
                             self._ndiff_strings(a, b))      
 
262
        
 
263
    def assertStartsWith(self, s, prefix):
 
264
        if not s.startswith(prefix):
 
265
            raise AssertionError('string %r does not start with %r' % (s, prefix))
 
266
 
 
267
    def assertEndsWith(self, s, suffix):
 
268
        if not s.endswith(prefix):
 
269
            raise AssertionError('string %r does not end with %r' % (s, suffix))
 
270
 
 
271
    def assertContainsRe(self, haystack, needle_re):
 
272
        """Assert that a contains something matching a regular expression."""
 
273
        if not re.search(needle_re, haystack):
 
274
            raise AssertionError('pattern "%s" not found in "%s"'
 
275
                    % (needle_re, haystack))
 
276
 
 
277
    def AssertSubset(self, sublist, superlist):
 
278
        """Assert that every entry in sublist is present in superlist."""
 
279
        missing = []
 
280
        for entry in sublist:
 
281
            if entry not in superlist:
 
282
                missing.append(entry)
 
283
        if len(missing) > 0:
 
284
            raise AssertionError("value(s) %r not present in container %r" % 
 
285
                                 (missing, superlist))
 
286
 
 
287
    def _startLogFile(self):
 
288
        """Send bzr and test log messages to a temporary file.
 
289
 
 
290
        The file is removed as the test is torn down.
 
291
        """
 
292
        fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
 
293
        encoder, decoder, stream_reader, stream_writer = codecs.lookup('UTF-8')
 
294
        self._log_file = stream_writer(os.fdopen(fileno, 'w+'))
 
295
        bzrlib.trace.enable_test_log(self._log_file)
 
296
        self._log_file_name = name
 
297
        self.addCleanup(self._finishLogFile)
 
298
 
 
299
    def _finishLogFile(self):
 
300
        """Finished with the log file.
 
301
 
 
302
        Read contents into memory, close, and delete.
 
303
        """
 
304
        bzrlib.trace.disable_test_log()
 
305
        self._log_file.seek(0)
 
306
        self._log_contents = self._log_file.read()
 
307
        self._log_file.close()
 
308
        os.remove(self._log_file_name)
 
309
        self._log_file = self._log_file_name = None
 
310
 
 
311
    def addCleanup(self, callable):
 
312
        """Arrange to run a callable when this case is torn down.
 
313
 
 
314
        Callables are run in the reverse of the order they are registered, 
 
315
        ie last-in first-out.
 
316
        """
 
317
        if callable in self._cleanups:
 
318
            raise ValueError("cleanup function %r already registered on %s" 
 
319
                    % (callable, self))
 
320
        self._cleanups.append(callable)
 
321
 
 
322
    def _cleanEnvironment(self):
 
323
        new_env = {
 
324
            'HOME': os.getcwd(),
 
325
            'APPDATA': os.getcwd(),
 
326
            'BZREMAIL': None,
 
327
            'EMAIL': None,
 
328
        }
 
329
        self.__old_env = {}
 
330
        self.addCleanup(self._restoreEnvironment)
 
331
        for name, value in new_env.iteritems():
 
332
            self._captureVar(name, value)
 
333
 
 
334
 
 
335
    def _captureVar(self, name, newvalue):
 
336
        """Set an environment variable, preparing it to be reset when finished."""
 
337
        self.__old_env[name] = os.environ.get(name, None)
 
338
        if newvalue is None:
 
339
            if name in os.environ:
 
340
                del os.environ[name]
 
341
        else:
 
342
            os.environ[name] = newvalue
 
343
 
 
344
    @staticmethod
 
345
    def _restoreVar(name, value):
 
346
        if value is None:
 
347
            if name in os.environ:
 
348
                del os.environ[name]
 
349
        else:
 
350
            os.environ[name] = value
 
351
 
 
352
    def _restoreEnvironment(self):
 
353
        for name, value in self.__old_env.iteritems():
 
354
            self._restoreVar(name, value)
 
355
 
 
356
    def tearDown(self):
 
357
        self._runCleanups()
 
358
        unittest.TestCase.tearDown(self)
 
359
 
 
360
    def _runCleanups(self):
 
361
        """Run registered cleanup functions. 
 
362
 
 
363
        This should only be called from TestCase.tearDown.
 
364
        """
 
365
        for cleanup_fn in reversed(self._cleanups):
 
366
            cleanup_fn()
 
367
 
 
368
    def log(self, *args):
 
369
        mutter(*args)
 
370
 
 
371
    def _get_log(self):
 
372
        """Return as a string the log for this test"""
 
373
        if self._log_file_name:
 
374
            return open(self._log_file_name).read()
 
375
        else:
 
376
            return self._log_contents
 
377
        # TODO: Delete the log after it's been read in
 
378
 
 
379
    def capture(self, cmd, retcode=0):
 
380
        """Shortcut that splits cmd into words, runs, and returns stdout"""
 
381
        return self.run_bzr_captured(cmd.split(), retcode=retcode)[0]
 
382
 
 
383
    def run_bzr_captured(self, argv, retcode=0):
 
384
        """Invoke bzr and return (stdout, stderr).
 
385
 
 
386
        Useful for code that wants to check the contents of the
 
387
        output, the way error messages are presented, etc.
 
388
 
 
389
        This should be the main method for tests that want to exercise the
 
390
        overall behavior of the bzr application (rather than a unit test
 
391
        or a functional test of the library.)
 
392
 
 
393
        Much of the old code runs bzr by forking a new copy of Python, but
 
394
        that is slower, harder to debug, and generally not necessary.
 
395
 
 
396
        This runs bzr through the interface that catches and reports
 
397
        errors, and with logging set to something approximating the
 
398
        default, so that error reporting can be checked.
 
399
 
 
400
        argv -- arguments to invoke bzr
 
401
        retcode -- expected return code, or None for don't-care.
 
402
        """
 
403
        stdout = StringIO()
 
404
        stderr = StringIO()
 
405
        self.log('run bzr: %s', ' '.join(argv))
 
406
        # FIXME: don't call into logging here
 
407
        handler = logging.StreamHandler(stderr)
 
408
        handler.setFormatter(bzrlib.trace.QuietFormatter())
 
409
        handler.setLevel(logging.INFO)
 
410
        logger = logging.getLogger('')
 
411
        logger.addHandler(handler)
 
412
        try:
 
413
            result = self.apply_redirected(None, stdout, stderr,
 
414
                                           bzrlib.commands.run_bzr_catch_errors,
 
415
                                           argv)
 
416
        finally:
 
417
            logger.removeHandler(handler)
 
418
        out = stdout.getvalue()
 
419
        err = stderr.getvalue()
 
420
        if out:
 
421
            self.log('output:\n%s', out)
 
422
        if err:
 
423
            self.log('errors:\n%s', err)
 
424
        if retcode is not None:
 
425
            self.assertEquals(result, retcode)
 
426
        return out, err
 
427
 
 
428
    def run_bzr(self, *args, **kwargs):
 
429
        """Invoke bzr, as if it were run from the command line.
 
430
 
 
431
        This should be the main method for tests that want to exercise the
 
432
        overall behavior of the bzr application (rather than a unit test
 
433
        or a functional test of the library.)
 
434
 
 
435
        This sends the stdout/stderr results into the test's log,
 
436
        where it may be useful for debugging.  See also run_captured.
 
437
        """
 
438
        retcode = kwargs.pop('retcode', 0)
 
439
        return self.run_bzr_captured(args, retcode)
 
440
 
 
441
    def check_inventory_shape(self, inv, shape):
 
442
        """Compare an inventory to a list of expected names.
 
443
 
 
444
        Fail if they are not precisely equal.
 
445
        """
 
446
        extras = []
 
447
        shape = list(shape)             # copy
 
448
        for path, ie in inv.entries():
 
449
            name = path.replace('\\', '/')
 
450
            if ie.kind == 'dir':
 
451
                name = name + '/'
 
452
            if name in shape:
 
453
                shape.remove(name)
 
454
            else:
 
455
                extras.append(name)
 
456
        if shape:
 
457
            self.fail("expected paths not found in inventory: %r" % shape)
 
458
        if extras:
 
459
            self.fail("unexpected paths found in inventory: %r" % extras)
 
460
 
 
461
    def apply_redirected(self, stdin=None, stdout=None, stderr=None,
 
462
                         a_callable=None, *args, **kwargs):
 
463
        """Call callable with redirected std io pipes.
 
464
 
 
465
        Returns the return code."""
 
466
        if not callable(a_callable):
 
467
            raise ValueError("a_callable must be callable.")
 
468
        if stdin is None:
 
469
            stdin = StringIO("")
 
470
        if stdout is None:
 
471
            if hasattr(self, "_log_file"):
 
472
                stdout = self._log_file
 
473
            else:
 
474
                stdout = StringIO()
 
475
        if stderr is None:
 
476
            if hasattr(self, "_log_file"):
 
477
                stderr = self._log_file
 
478
            else:
 
479
                stderr = StringIO()
 
480
        real_stdin = sys.stdin
 
481
        real_stdout = sys.stdout
 
482
        real_stderr = sys.stderr
 
483
        try:
 
484
            sys.stdout = stdout
 
485
            sys.stderr = stderr
 
486
            sys.stdin = stdin
 
487
            return a_callable(*args, **kwargs)
 
488
        finally:
 
489
            sys.stdout = real_stdout
 
490
            sys.stderr = real_stderr
 
491
            sys.stdin = real_stdin
 
492
 
 
493
 
 
494
BzrTestBase = TestCase
 
495
 
 
496
     
 
497
class TestCaseInTempDir(TestCase):
 
498
    """Derived class that runs a test within a temporary directory.
 
499
 
 
500
    This is useful for tests that need to create a branch, etc.
 
501
 
 
502
    The directory is created in a slightly complex way: for each
 
503
    Python invocation, a new temporary top-level directory is created.
 
504
    All test cases create their own directory within that.  If the
 
505
    tests complete successfully, the directory is removed.
 
506
 
 
507
    InTempDir is an old alias for FunctionalTestCase.
 
508
    """
 
509
 
 
510
    TEST_ROOT = None
 
511
    _TEST_NAME = 'test'
 
512
    OVERRIDE_PYTHON = 'python'
 
513
 
 
514
    def check_file_contents(self, filename, expect):
 
515
        self.log("check contents of file %s" % filename)
 
516
        contents = file(filename, 'r').read()
 
517
        if contents != expect:
 
518
            self.log("expected: %r" % expect)
 
519
            self.log("actually: %r" % contents)
 
520
            self.fail("contents of %s not as expected" % filename)
 
521
 
 
522
    def _make_test_root(self):
 
523
        if TestCaseInTempDir.TEST_ROOT is not None:
 
524
            return
 
525
        i = 0
 
526
        while True:
 
527
            root = u'test%04d.tmp' % i
 
528
            try:
 
529
                os.mkdir(root)
 
530
            except OSError, e:
 
531
                if e.errno == errno.EEXIST:
 
532
                    i += 1
 
533
                    continue
 
534
                else:
 
535
                    raise
 
536
            # successfully created
 
537
            TestCaseInTempDir.TEST_ROOT = osutils.abspath(root)
 
538
            break
 
539
        # make a fake bzr directory there to prevent any tests propagating
 
540
        # up onto the source directory's real branch
 
541
        os.mkdir(osutils.pathjoin(TestCaseInTempDir.TEST_ROOT, '.bzr'))
 
542
 
 
543
    def setUp(self):
 
544
        super(TestCaseInTempDir, self).setUp()
 
545
        self._make_test_root()
 
546
        _currentdir = os.getcwdu()
 
547
        short_id = self.id().replace('bzrlib.tests.', '') \
 
548
                   .replace('__main__.', '')
 
549
        self.test_dir = osutils.pathjoin(self.TEST_ROOT, short_id)
 
550
        os.mkdir(self.test_dir)
 
551
        os.chdir(self.test_dir)
 
552
        os.environ['HOME'] = self.test_dir
 
553
        os.environ['APPDATA'] = self.test_dir
 
554
        def _leaveDirectory():
 
555
            os.chdir(_currentdir)
 
556
        self.addCleanup(_leaveDirectory)
 
557
        
 
558
    def build_tree(self, shape, line_endings='native', transport=None):
 
559
        """Build a test tree according to a pattern.
 
560
 
 
561
        shape is a sequence of file specifications.  If the final
 
562
        character is '/', a directory is created.
 
563
 
 
564
        This doesn't add anything to a branch.
 
565
        :param line_endings: Either 'binary' or 'native'
 
566
                             in binary mode, exact contents are written
 
567
                             in native mode, the line endings match the
 
568
                             default platform endings.
 
569
 
 
570
        :param transport: A transport to write to, for building trees on 
 
571
                          VFS's. If the transport is readonly or None,
 
572
                          "." is opened automatically.
 
573
        """
 
574
        # XXX: It's OK to just create them using forward slashes on windows?
 
575
        if transport is None or transport.is_readonly():
 
576
            transport = bzrlib.transport.get_transport(".")
 
577
        for name in shape:
 
578
            self.assert_(isinstance(name, basestring))
 
579
            if name[-1] == '/':
 
580
                transport.mkdir(urlescape(name[:-1]))
 
581
            else:
 
582
                if line_endings == 'binary':
 
583
                    end = '\n'
 
584
                elif line_endings == 'native':
 
585
                    end = os.linesep
 
586
                else:
 
587
                    raise BzrError('Invalid line ending request %r' % (line_endings,))
 
588
                content = "contents of %s%s" % (name, end)
 
589
                transport.put(urlescape(name), StringIO(content))
 
590
 
 
591
    def build_tree_contents(self, shape):
 
592
        build_tree_contents(shape)
 
593
 
 
594
    def failUnlessExists(self, path):
 
595
        """Fail unless path, which may be abs or relative, exists."""
 
596
        self.failUnless(osutils.lexists(path))
 
597
 
 
598
    def failIfExists(self, path):
 
599
        """Fail if path, which may be abs or relative, exists."""
 
600
        self.failIf(osutils.lexists(path))
 
601
        
 
602
    def assertFileEqual(self, content, path):
 
603
        """Fail if path does not contain 'content'."""
 
604
        self.failUnless(osutils.lexists(path))
 
605
        self.assertEqualDiff(content, open(path, 'r').read())
 
606
 
 
607
 
 
608
def filter_suite_by_re(suite, pattern):
 
609
    result = TestSuite()
 
610
    filter_re = re.compile(pattern)
 
611
    for test in iter_suite_tests(suite):
 
612
        if filter_re.search(test.id()):
 
613
            result.addTest(test)
 
614
    return result
 
615
 
 
616
 
 
617
def run_suite(suite, name='test', verbose=False, pattern=".*",
 
618
              stop_on_failure=False, keep_output=False):
 
619
    TestCaseInTempDir._TEST_NAME = name
 
620
    if verbose:
 
621
        verbosity = 2
 
622
    else:
 
623
        verbosity = 1
 
624
    runner = TextTestRunner(stream=sys.stdout,
 
625
                            descriptions=0,
 
626
                            verbosity=verbosity)
 
627
    runner.stop_on_failure=stop_on_failure
 
628
    if pattern != '.*':
 
629
        suite = filter_suite_by_re(suite, pattern)
 
630
    result = runner.run(suite)
 
631
    # This is still a little bogus, 
 
632
    # but only a little. Folk not using our testrunner will
 
633
    # have to delete their temp directories themselves.
 
634
    if result.wasSuccessful() or not keep_output:
 
635
        if TestCaseInTempDir.TEST_ROOT is not None:
 
636
            shutil.rmtree(TestCaseInTempDir.TEST_ROOT) 
 
637
    else:
 
638
        print "Failed tests working directories are in '%s'\n" % TestCaseInTempDir.TEST_ROOT
 
639
    return result.wasSuccessful()
 
640
 
 
641
 
 
642
def selftest(verbose=False, pattern=".*", stop_on_failure=True,
 
643
             keep_output=False):
 
644
    """Run the whole test suite under the enhanced runner"""
 
645
    return run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern,
 
646
                     stop_on_failure=stop_on_failure, keep_output=keep_output)
 
647
 
 
648
 
 
649
def test_suite():
 
650
    """Build and return TestSuite for the whole program."""
 
651
    from doctest import DocTestSuite
 
652
 
 
653
    global MODULES_TO_DOCTEST
 
654
 
 
655
    testmod_names = [ \
 
656
                   'bzrlib.tests.test_ancestry',
 
657
                   'bzrlib.tests.test_annotate',
 
658
                   'bzrlib.tests.test_api',
 
659
                   'bzrlib.tests.test_bad_files',
 
660
                   'bzrlib.tests.test_basis_inventory',
 
661
                   'bzrlib.tests.test_branch',
 
662
                   'bzrlib.tests.test_command',
 
663
                   'bzrlib.tests.test_commit',
 
664
                   'bzrlib.tests.test_commit_merge',
 
665
                   'bzrlib.tests.test_config',
 
666
                   'bzrlib.tests.test_conflicts',
 
667
                   'bzrlib.tests.test_diff',
 
668
                   'bzrlib.tests.test_fetch',
 
669
                   'bzrlib.tests.test_gpg',
 
670
                   'bzrlib.tests.test_graph',
 
671
                   'bzrlib.tests.test_hashcache',
 
672
                   'bzrlib.tests.test_http',
 
673
                   'bzrlib.tests.test_identitymap',
 
674
                   'bzrlib.tests.test_inv',
 
675
                   'bzrlib.tests.test_log',
 
676
                   'bzrlib.tests.test_merge',
 
677
                   'bzrlib.tests.test_merge3',
 
678
                   'bzrlib.tests.test_merge_core',
 
679
                   'bzrlib.tests.test_missing',
 
680
                   'bzrlib.tests.test_msgeditor',
 
681
                   'bzrlib.tests.test_nonascii',
 
682
                   'bzrlib.tests.test_options',
 
683
                   'bzrlib.tests.test_osutils',
 
684
                   'bzrlib.tests.test_parent',
 
685
                   'bzrlib.tests.test_permissions',
 
686
                   'bzrlib.tests.test_plugins',
 
687
                   'bzrlib.tests.test_remove',
 
688
                   'bzrlib.tests.test_revision',
 
689
                   'bzrlib.tests.test_revision_info',
 
690
                   'bzrlib.tests.test_revisionnamespaces',
 
691
                   'bzrlib.tests.test_revprops',
 
692
                   'bzrlib.tests.test_reweave',
 
693
                   'bzrlib.tests.test_rio',
 
694
                   'bzrlib.tests.test_sampler',
 
695
                   'bzrlib.tests.test_selftest',
 
696
                   'bzrlib.tests.test_setup',
 
697
                   'bzrlib.tests.test_sftp_transport',
 
698
                   'bzrlib.tests.test_smart_add',
 
699
                   'bzrlib.tests.test_source',
 
700
                   'bzrlib.tests.test_status',
 
701
                   'bzrlib.tests.test_store',
 
702
                   'bzrlib.tests.test_testament',
 
703
                   'bzrlib.tests.test_trace',
 
704
                   'bzrlib.tests.test_transactions',
 
705
                   'bzrlib.tests.test_transport',
 
706
                   'bzrlib.tests.test_tsort',
 
707
                   'bzrlib.tests.test_ui',
 
708
                   'bzrlib.tests.test_uncommit',
 
709
                   'bzrlib.tests.test_upgrade',
 
710
                   'bzrlib.tests.test_weave',
 
711
                   'bzrlib.tests.test_whitebox',
 
712
                   'bzrlib.tests.test_workingtree',
 
713
                   'bzrlib.tests.test_xml',
 
714
                   ]
 
715
    test_transport_implementations = [
 
716
        'bzrlib.tests.test_transport_implementations']
 
717
 
 
718
    TestCase.BZRPATH = osutils.pathjoin(
 
719
            osutils.realpath(osutils.dirname(bzrlib.__path__[0])), 'bzr')
 
720
    print '%10s: %s' % ('bzr', osutils.realpath(sys.argv[0]))
 
721
    print '%10s: %s' % ('bzrlib', bzrlib.__path__[0])
 
722
    print
 
723
    suite = TestSuite()
 
724
    # python2.4's TestLoader.loadTestsFromNames gives very poor 
 
725
    # errors if it fails to load a named module - no indication of what's
 
726
    # actually wrong, just "no such module".  We should probably override that
 
727
    # class, but for the moment just load them ourselves. (mbp 20051202)
 
728
    loader = TestLoader()
 
729
    from bzrlib.transport import TransportTestProviderAdapter
 
730
    adapter = TransportTestProviderAdapter()
 
731
    for mod_name in test_transport_implementations:
 
732
        mod = _load_module_by_name(mod_name)
 
733
        for test in iter_suite_tests(loader.loadTestsFromModule(mod)):
 
734
            suite.addTests(adapter.adapt(test))
 
735
    for mod_name in testmod_names:
 
736
        mod = _load_module_by_name(mod_name)
 
737
        suite.addTest(loader.loadTestsFromModule(mod))
 
738
    for package in packages_to_test():
 
739
        suite.addTest(package.test_suite())
 
740
    for m in MODULES_TO_TEST:
 
741
        suite.addTest(loader.loadTestsFromModule(m))
 
742
    for m in (MODULES_TO_DOCTEST):
 
743
        suite.addTest(DocTestSuite(m))
 
744
    for name, plugin in bzrlib.plugin.all_plugins().items():
 
745
        if hasattr(plugin, 'test_suite'):
 
746
            suite.addTest(plugin.test_suite())
 
747
    return suite
 
748
 
 
749
 
 
750
def _load_module_by_name(mod_name):
 
751
    parts = mod_name.split('.')
 
752
    module = __import__(mod_name)
 
753
    del parts[0]
 
754
    # for historical reasons python returns the top-level module even though
 
755
    # it loads the submodule; we need to walk down to get the one we want.
 
756
    while parts:
 
757
        module = getattr(module, parts.pop(0))
 
758
    return module