~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/__init__.py

[patch] better zsh completion

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
 
from testsweet import TestBase, run_suite, InTempDir
 
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
 
19
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
 
20
37
 
21
38
MODULES_TO_TEST = []
22
39
MODULES_TO_DOCTEST = []
23
40
 
24
 
 
25
 
class BzrTestBase(InTempDir):
26
 
    """bzr-specific test base class"""
 
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 assertContainsRe(self, haystack, needle_re):
 
193
        """Assert that a contains something matching a regular expression."""
 
194
        if not re.search(needle_re, haystack):
 
195
            raise AssertionError('pattern "%s" not found in "%s"'
 
196
                    % (needle_re, haystack))
 
197
        
 
198
    def _enable_file_logging(self):
 
199
        fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
 
200
 
 
201
        self._log_file = os.fdopen(fileno, 'w+')
 
202
 
 
203
        hdlr = logging.StreamHandler(self._log_file)
 
204
        hdlr.setLevel(logging.DEBUG)
 
205
        hdlr.setFormatter(logging.Formatter('%(levelname)8s  %(message)s'))
 
206
        logging.getLogger('').addHandler(hdlr)
 
207
        logging.getLogger('').setLevel(logging.DEBUG)
 
208
        self._log_hdlr = hdlr
 
209
        debug('opened log file %s', name)
 
210
        
 
211
        self._log_file_name = name
 
212
 
 
213
    def tearDown(self):
 
214
        logging.getLogger('').removeHandler(self._log_hdlr)
 
215
        bzrlib.trace.enable_default_logging()
 
216
        logging.debug('%s teardown', self.id())
 
217
        self._log_file.close()
 
218
        unittest.TestCase.tearDown(self)
 
219
 
 
220
    def log(self, *args):
 
221
        logging.debug(*args)
 
222
 
 
223
    def _get_log(self):
 
224
        """Return as a string the log for this test"""
 
225
        if self._log_file_name:
 
226
            return open(self._log_file_name).read()
 
227
        else:
 
228
            return ''
 
229
 
 
230
    def capture(self, cmd):
 
231
        """Shortcut that splits cmd into words, runs, and returns stdout"""
 
232
        return self.run_bzr_captured(cmd.split())[0]
 
233
 
 
234
    def run_bzr_captured(self, argv, retcode=0):
 
235
        """Invoke bzr and return (result, stdout, stderr).
 
236
 
 
237
        Useful for code that wants to check the contents of the
 
238
        output, the way error messages are presented, etc.
 
239
 
 
240
        This should be the main method for tests that want to exercise the
 
241
        overall behavior of the bzr application (rather than a unit test
 
242
        or a functional test of the library.)
 
243
 
 
244
        Much of the old code runs bzr by forking a new copy of Python, but
 
245
        that is slower, harder to debug, and generally not necessary.
 
246
 
 
247
        This runs bzr through the interface that catches and reports
 
248
        errors, and with logging set to something approximating the
 
249
        default, so that error reporting can be checked.
 
250
 
 
251
        argv -- arguments to invoke bzr
 
252
        retcode -- expected return code, or None for don't-care.
 
253
        """
 
254
        stdout = StringIO()
 
255
        stderr = StringIO()
 
256
        self.log('run bzr: %s', ' '.join(argv))
 
257
        handler = logging.StreamHandler(stderr)
 
258
        handler.setFormatter(bzrlib.trace.QuietFormatter())
 
259
        handler.setLevel(logging.INFO)
 
260
        logger = logging.getLogger('')
 
261
        logger.addHandler(handler)
 
262
        try:
 
263
            result = self.apply_redirected(None, stdout, stderr,
 
264
                                           bzrlib.commands.run_bzr_catch_errors,
 
265
                                           argv)
 
266
        finally:
 
267
            logger.removeHandler(handler)
 
268
        out = stdout.getvalue()
 
269
        err = stderr.getvalue()
 
270
        if out:
 
271
            self.log('output:\n%s', out)
 
272
        if err:
 
273
            self.log('errors:\n%s', err)
 
274
        if retcode is not None:
 
275
            self.assertEquals(result, retcode)
 
276
        return out, err
 
277
 
27
278
    def run_bzr(self, *args, **kwargs):
28
 
        retcode = kwargs.get('retcode', 0)
29
 
        self.assertEquals(bzrlib.commands.run_bzr(args), retcode)
30
 
        
31
 
 
32
 
def selftest(verbose=False):
33
 
    from unittest import TestLoader, TestSuite
34
 
    import bzrlib, bzrlib.store, bzrlib.inventory, bzrlib.branch
35
 
    import bzrlib.osutils, bzrlib.commands, bzrlib.merge3, bzrlib.plugin
 
279
        """Invoke bzr, as if it were run from the command line.
 
280
 
 
281
        This should be the main method for tests that want to exercise the
 
282
        overall behavior of the bzr application (rather than a unit test
 
283
        or a functional test of the library.)
 
284
 
 
285
        This sends the stdout/stderr results into the test's log,
 
286
        where it may be useful for debugging.  See also run_captured.
 
287
        """
 
288
        retcode = kwargs.pop('retcode', 0)
 
289
        return self.run_bzr_captured(args, retcode)
 
290
 
 
291
    def check_inventory_shape(self, inv, shape):
 
292
        """Compare an inventory to a list of expected names.
 
293
 
 
294
        Fail if they are not precisely equal.
 
295
        """
 
296
        extras = []
 
297
        shape = list(shape)             # copy
 
298
        for path, ie in inv.entries():
 
299
            name = path.replace('\\', '/')
 
300
            if ie.kind == 'dir':
 
301
                name = name + '/'
 
302
            if name in shape:
 
303
                shape.remove(name)
 
304
            else:
 
305
                extras.append(name)
 
306
        if shape:
 
307
            self.fail("expected paths not found in inventory: %r" % shape)
 
308
        if extras:
 
309
            self.fail("unexpected paths found in inventory: %r" % extras)
 
310
 
 
311
    def apply_redirected(self, stdin=None, stdout=None, stderr=None,
 
312
                         a_callable=None, *args, **kwargs):
 
313
        """Call callable with redirected std io pipes.
 
314
 
 
315
        Returns the return code."""
 
316
        if not callable(a_callable):
 
317
            raise ValueError("a_callable must be callable.")
 
318
        if stdin is None:
 
319
            stdin = StringIO("")
 
320
        if stdout is None:
 
321
            if hasattr(self, "_log_file"):
 
322
                stdout = self._log_file
 
323
            else:
 
324
                stdout = StringIO()
 
325
        if stderr is None:
 
326
            if hasattr(self, "_log_file"):
 
327
                stderr = self._log_file
 
328
            else:
 
329
                stderr = StringIO()
 
330
        real_stdin = sys.stdin
 
331
        real_stdout = sys.stdout
 
332
        real_stderr = sys.stderr
 
333
        try:
 
334
            sys.stdout = stdout
 
335
            sys.stderr = stderr
 
336
            sys.stdin = stdin
 
337
            return a_callable(*args, **kwargs)
 
338
        finally:
 
339
            sys.stdout = real_stdout
 
340
            sys.stderr = real_stderr
 
341
            sys.stdin = real_stdin
 
342
 
 
343
 
 
344
BzrTestBase = TestCase
 
345
 
 
346
     
 
347
class TestCaseInTempDir(TestCase):
 
348
    """Derived class that runs a test within a temporary directory.
 
349
 
 
350
    This is useful for tests that need to create a branch, etc.
 
351
 
 
352
    The directory is created in a slightly complex way: for each
 
353
    Python invocation, a new temporary top-level directory is created.
 
354
    All test cases create their own directory within that.  If the
 
355
    tests complete successfully, the directory is removed.
 
356
 
 
357
    InTempDir is an old alias for FunctionalTestCase.
 
358
    """
 
359
 
 
360
    TEST_ROOT = None
 
361
    _TEST_NAME = 'test'
 
362
    OVERRIDE_PYTHON = 'python'
 
363
 
 
364
    def check_file_contents(self, filename, expect):
 
365
        self.log("check contents of file %s" % filename)
 
366
        contents = file(filename, 'r').read()
 
367
        if contents != expect:
 
368
            self.log("expected: %r" % expect)
 
369
            self.log("actually: %r" % contents)
 
370
            self.fail("contents of %s not as expected" % filename)
 
371
 
 
372
    def _make_test_root(self):
 
373
        if TestCaseInTempDir.TEST_ROOT is not None:
 
374
            return
 
375
        i = 0
 
376
        while True:
 
377
            root = 'test%04d.tmp' % i
 
378
            try:
 
379
                os.mkdir(root)
 
380
            except OSError, e:
 
381
                if e.errno == errno.EEXIST:
 
382
                    i += 1
 
383
                    continue
 
384
                else:
 
385
                    raise
 
386
            # successfully created
 
387
            TestCaseInTempDir.TEST_ROOT = os.path.abspath(root)
 
388
            break
 
389
        # make a fake bzr directory there to prevent any tests propagating
 
390
        # up onto the source directory's real branch
 
391
        os.mkdir(os.path.join(TestCaseInTempDir.TEST_ROOT, '.bzr'))
 
392
 
 
393
    def setUp(self):
 
394
        super(TestCaseInTempDir, self).setUp()
 
395
        self._make_test_root()
 
396
        self._currentdir = os.getcwdu()
 
397
        short_id = self.id().replace('bzrlib.selftest.', '') \
 
398
                   .replace('__main__.', '')
 
399
        self.test_dir = os.path.join(self.TEST_ROOT, short_id)
 
400
        os.mkdir(self.test_dir)
 
401
        os.chdir(self.test_dir)
 
402
        
 
403
    def tearDown(self):
 
404
        os.chdir(self._currentdir)
 
405
        super(TestCaseInTempDir, self).tearDown()
 
406
 
 
407
    def build_tree(self, shape):
 
408
        """Build a test tree according to a pattern.
 
409
 
 
410
        shape is a sequence of file specifications.  If the final
 
411
        character is '/', a directory is created.
 
412
 
 
413
        This doesn't add anything to a branch.
 
414
        """
 
415
        # XXX: It's OK to just create them using forward slashes on windows?
 
416
        for name in shape:
 
417
            assert isinstance(name, basestring)
 
418
            if name[-1] == '/':
 
419
                os.mkdir(name[:-1])
 
420
            else:
 
421
                f = file(name, 'wt')
 
422
                print >>f, "contents of", name
 
423
                f.close()
 
424
 
 
425
    def failUnlessExists(self, path):
 
426
        """Fail unless path, which may be abs or relative, exists."""
 
427
        self.failUnless(osutils.lexists(path))
 
428
        
 
429
 
 
430
class MetaTestLog(TestCase):
 
431
    def test_logging(self):
 
432
        """Test logs are captured when a test fails."""
 
433
        logging.info('an info message')
 
434
        warning('something looks dodgy...')
 
435
        logging.debug('hello, test is running')
 
436
        ##assert 0
 
437
 
 
438
 
 
439
def filter_suite_by_re(suite, pattern):
 
440
    result = TestUtil.TestSuite()
 
441
    filter_re = re.compile(pattern)
 
442
    for test in iter_suite_tests(suite):
 
443
        if filter_re.search(test.id()):
 
444
            result.addTest(test)
 
445
    return result
 
446
 
 
447
 
 
448
def run_suite(suite, name='test', verbose=False, pattern=".*"):
 
449
    TestCaseInTempDir._TEST_NAME = name
 
450
    if verbose:
 
451
        verbosity = 2
 
452
    else:
 
453
        verbosity = 1
 
454
    runner = TextTestRunner(stream=sys.stdout,
 
455
                            descriptions=0,
 
456
                            verbosity=verbosity)
 
457
    if pattern != '.*':
 
458
        suite = filter_suite_by_re(suite, pattern)
 
459
    result = runner.run(suite)
 
460
    # This is still a little bogus, 
 
461
    # but only a little. Folk not using our testrunner will
 
462
    # have to delete their temp directories themselves.
 
463
    if result.wasSuccessful():
 
464
        if TestCaseInTempDir.TEST_ROOT is not None:
 
465
            shutil.rmtree(TestCaseInTempDir.TEST_ROOT) 
 
466
    else:
 
467
        print "Failed tests working directories are in '%s'\n" % TestCaseInTempDir.TEST_ROOT
 
468
    return result.wasSuccessful()
 
469
 
 
470
 
 
471
def selftest(verbose=False, pattern=".*"):
 
472
    """Run the whole test suite under the enhanced runner"""
 
473
    return run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern)
 
474
 
 
475
 
 
476
def test_suite():
 
477
    """Build and return TestSuite for the whole program."""
 
478
    import bzrlib.store, bzrlib.inventory, bzrlib.branch
 
479
    import bzrlib.osutils, bzrlib.merge3, bzrlib.plugin
36
480
    from doctest import DocTestSuite
37
 
    import os
38
 
    import shutil
39
 
    import time
40
 
    import sys
41
 
    import unittest
42
481
 
43
482
    global MODULES_TO_TEST, MODULES_TO_DOCTEST
44
483
 
45
484
    testmod_names = \
46
 
                  ['bzrlib.selftest.whitebox',
 
485
                  ['bzrlib.selftest.MetaTestLog',
 
486
                   'bzrlib.selftest.testidentitymap',
 
487
                   'bzrlib.selftest.testinv',
 
488
                   'bzrlib.selftest.test_ancestry',
 
489
                   'bzrlib.selftest.test_commit',
 
490
                   'bzrlib.selftest.test_commit_merge',
 
491
                   'bzrlib.selftest.testconfig',
47
492
                   'bzrlib.selftest.versioning',
48
 
                   'bzrlib.selftest.testinv',
49
493
                   'bzrlib.selftest.testmerge3',
 
494
                   'bzrlib.selftest.testmerge',
50
495
                   'bzrlib.selftest.testhashcache',
51
496
                   'bzrlib.selftest.teststatus',
52
497
                   'bzrlib.selftest.testlog',
53
 
                   'bzrlib.selftest.blackbox',
54
498
                   'bzrlib.selftest.testrevisionnamespaces',
55
499
                   'bzrlib.selftest.testbranch',
56
500
                   'bzrlib.selftest.testrevision',
57
 
                   'bzrlib.merge_core',
 
501
                   'bzrlib.selftest.test_revision_info',
 
502
                   'bzrlib.selftest.test_merge_core',
 
503
                   'bzrlib.selftest.test_smart_add',
 
504
                   'bzrlib.selftest.test_bad_files',
58
505
                   'bzrlib.selftest.testdiff',
 
506
                   'bzrlib.selftest.test_parent',
 
507
                   'bzrlib.selftest.test_xml',
 
508
                   'bzrlib.selftest.test_weave',
 
509
                   'bzrlib.selftest.testfetch',
 
510
                   'bzrlib.selftest.whitebox',
 
511
                   'bzrlib.selftest.teststore',
 
512
                   'bzrlib.selftest.blackbox',
 
513
                   'bzrlib.selftest.testsampler',
 
514
                   'bzrlib.selftest.testtransactions',
 
515
                   'bzrlib.selftest.testtransport',
 
516
                   'bzrlib.selftest.testgraph',
 
517
                   'bzrlib.selftest.testworkingtree',
 
518
                   'bzrlib.selftest.test_upgrade',
 
519
                   'bzrlib.selftest.test_conflicts',
 
520
                   'bzrlib.selftest.testtestament',
 
521
                   'bzrlib.selftest.testannotate',
 
522
                   'bzrlib.selftest.testrevprops',
 
523
                   'bzrlib.selftest.testoptions',
59
524
                   ]
60
525
 
61
 
    # XXX: should also test bzrlib.merge_core, but they seem to be out
62
 
    # of date with the code.
63
 
 
64
526
    for m in (bzrlib.store, bzrlib.inventory, bzrlib.branch,
65
527
              bzrlib.osutils, bzrlib.commands, bzrlib.merge3):
66
528
        if m not in MODULES_TO_DOCTEST:
67
529
            MODULES_TO_DOCTEST.append(m)
68
530
 
69
 
    
70
 
    TestBase.BZRPATH = os.path.join(os.path.realpath(os.path.dirname(bzrlib.__path__[0])), 'bzr')
71
 
    print '%-30s %s' % ('bzr binary', TestBase.BZRPATH)
72
 
 
 
531
    TestCase.BZRPATH = os.path.join(os.path.realpath(os.path.dirname(bzrlib.__path__[0])), 'bzr')
 
532
    print '%-30s %s' % ('bzr binary', TestCase.BZRPATH)
73
533
    print
74
 
 
75
534
    suite = TestSuite()
76
 
 
77
535
    suite.addTest(TestLoader().loadTestsFromNames(testmod_names))
78
 
 
79
536
    for m in MODULES_TO_TEST:
80
537
         suite.addTest(TestLoader().loadTestsFromModule(m))
81
 
 
82
538
    for m in (MODULES_TO_DOCTEST):
83
539
        suite.addTest(DocTestSuite(m))
84
 
 
85
540
    for p in bzrlib.plugin.all_plugins:
86
541
        if hasattr(p, 'test_suite'):
87
542
            suite.addTest(p.test_suite())
88
 
 
89
 
    import bzrlib.merge_core
90
 
    suite.addTest(unittest.makeSuite(bzrlib.merge_core.MergeTest, 'test_'))
91
 
 
92
 
    return run_suite(suite, 'testbzr', verbose=verbose)
93
 
 
94
 
 
 
543
    return suite
95
544