/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: Aaron Bentley
  • Date: 2005-12-13 18:56:57 UTC
  • mto: (1185.60.3 Aaron's mergeable stuff)
  • mto: This revision was merged to the branch mainline in revision 1530.
  • Revision ID: abentley@panoramicfeedback.com-20051213185657-3f900268287d6559
bzr add reports ignored patterns.

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