~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/__init__.py

Add trace-revisions utility, which can recover lost or alternative revision
history

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