~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/__init__.py

- stub for revision properties

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