/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

  • Committer: Robert Collins
  • Date: 2005-11-28 05:13:41 UTC
  • mfrom: (1185.33.54 merge-recovered)
  • Revision ID: robertc@robertcollins.net-20051128051341-059936f2f29a12c8
Merge from Martin. Adjust check to work with HTTP again.

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