~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/__init__.py

  • Committer: mbp at sourcefrog
  • Date: 2005-03-09 04:51:05 UTC
  • Revision ID: mbp@sourcefrog.net-20050309045105-d02cd410a115da2c
import all docs from arch

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