~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/selftest/__init__.py

- store revision properties in revision xml

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 cStringIO import StringIO
 
19
import difflib
 
20
import errno
18
21
import logging
19
 
import unittest
20
 
import tempfile
21
22
import os
 
23
import re
 
24
import shutil
22
25
import sys
23
 
import errno
24
 
import subprocess
25
 
import shutil
 
26
import tempfile
 
27
import unittest
 
28
import time
26
29
 
27
 
import testsweet
28
30
import bzrlib.commands
29
 
 
30
31
import bzrlib.trace
31
32
import bzrlib.fetch
 
33
import bzrlib.osutils as osutils
 
34
from bzrlib.selftest import TestUtil
 
35
from bzrlib.selftest.TestUtil import TestLoader, TestSuite
32
36
 
33
37
 
34
38
MODULES_TO_TEST = []
36
40
 
37
41
from logging import debug, warning, error
38
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
 
39
148
class CommandFailed(Exception):
40
149
    pass
41
150
 
53
162
    routine, and to build and check bzr trees."""
54
163
 
55
164
    BZRPATH = 'bzr'
 
165
    _log_file_name = None
56
166
 
57
167
    def setUp(self):
58
 
        # this replaces the default testsweet.TestCase; we don't want logging changed
59
168
        unittest.TestCase.setUp(self)
60
169
        bzrlib.trace.disable_default_logging()
61
170
        self._enable_file_logging()
62
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)
63
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
        
64
192
    def _enable_file_logging(self):
65
193
        fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
66
194
 
76
204
        
77
205
        self._log_file_name = name
78
206
 
79
 
        
80
207
    def tearDown(self):
81
208
        logging.getLogger('').removeHandler(self._log_hdlr)
82
209
        bzrlib.trace.enable_default_logging()
84
211
        self._log_file.close()
85
212
        unittest.TestCase.tearDown(self)
86
213
 
87
 
 
88
214
    def log(self, *args):
89
215
        logging.debug(*args)
90
216
 
91
217
    def _get_log(self):
92
218
        """Return as a string the log for this test"""
93
 
        return open(self._log_file_name).read()
 
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
94
271
 
95
272
    def run_bzr(self, *args, **kwargs):
96
273
        """Invoke bzr, as if it were run from the command line.
99
276
        overall behavior of the bzr application (rather than a unit test
100
277
        or a functional test of the library.)
101
278
 
102
 
        Much of the old code runs bzr by forking a new copy of Python, but
103
 
        that is slower, harder to debug, and generally not necessary.
 
279
        This sends the stdout/stderr results into the test's log,
 
280
        where it may be useful for debugging.  See also run_captured.
104
281
        """
105
 
        retcode = kwargs.get('retcode', 0)
106
 
        result = self.apply_redirected(None, None, None,
107
 
                                       bzrlib.commands.run_bzr, args)
108
 
        self.assertEquals(result, retcode)
109
 
        
110
 
        
 
282
        retcode = kwargs.pop('retcode', 0)
 
283
        return self.run_bzr_captured(args, retcode)
 
284
 
111
285
    def check_inventory_shape(self, inv, shape):
112
 
        """
113
 
        Compare an inventory to a list of expected names.
 
286
        """Compare an inventory to a list of expected names.
114
287
 
115
288
        Fail if they are not precisely equal.
116
289
        """
134
307
        """Call callable with redirected std io pipes.
135
308
 
136
309
        Returns the return code."""
137
 
        from StringIO import StringIO
138
310
        if not callable(a_callable):
139
311
            raise ValueError("a_callable must be callable.")
140
312
        if stdin is None:
141
313
            stdin = StringIO("")
142
314
        if stdout is None:
143
 
            stdout = self._log_file
 
315
            if hasattr(self, "_log_file"):
 
316
                stdout = self._log_file
 
317
            else:
 
318
                stdout = StringIO()
144
319
        if stderr is None:
145
 
            stderr = self._log_file
 
320
            if hasattr(self, "_log_file"):
 
321
                stderr = self._log_file
 
322
            else:
 
323
                stderr = StringIO()
146
324
        real_stdin = sys.stdin
147
325
        real_stdout = sys.stdout
148
326
        real_stderr = sys.stderr
183
361
        if contents != expect:
184
362
            self.log("expected: %r" % expect)
185
363
            self.log("actually: %r" % contents)
186
 
            self.fail("contents of %s not as expected")
 
364
            self.fail("contents of %s not as expected" % filename)
187
365
 
188
366
    def _make_test_root(self):
189
367
        if TestCaseInTempDir.TEST_ROOT is not None:
208
386
 
209
387
    def setUp(self):
210
388
        super(TestCaseInTempDir, self).setUp()
211
 
        import os
212
389
        self._make_test_root()
213
390
        self._currentdir = os.getcwdu()
214
391
        short_id = self.id().replace('bzrlib.selftest.', '') \
218
395
        os.chdir(self.test_dir)
219
396
        
220
397
    def tearDown(self):
221
 
        import os
222
398
        os.chdir(self._currentdir)
223
399
        super(TestCaseInTempDir, self).tearDown()
224
400
 
225
 
    def _formcmd(self, cmd):
226
 
        if isinstance(cmd, basestring):
227
 
            cmd = cmd.split()
228
 
        if cmd[0] == 'bzr':
229
 
            cmd[0] = self.BZRPATH
230
 
            if self.OVERRIDE_PYTHON:
231
 
                cmd.insert(0, self.OVERRIDE_PYTHON)
232
 
        self.log('$ %r' % cmd)
233
 
        return cmd
234
 
 
235
 
    def runcmd(self, cmd, retcode=0):
236
 
        """Run one command and check the return code.
237
 
 
238
 
        Returns a tuple of (stdout,stderr) strings.
239
 
 
240
 
        If a single string is based, it is split into words.
241
 
        For commands that are not simple space-separated words, please
242
 
        pass a list instead."""
243
 
        cmd = self._formcmd(cmd)
244
 
        self.log('$ ' + ' '.join(cmd))
245
 
        actual_retcode = subprocess.call(cmd, stdout=self._log_file,
246
 
                                         stderr=self._log_file)
247
 
        if retcode != actual_retcode:
248
 
            raise CommandFailed("test failed: %r returned %d, expected %d"
249
 
                                % (cmd, actual_retcode, retcode))
250
 
 
251
 
    def backtick(self, cmd, retcode=0):
252
 
        """Run a command and return its output"""
253
 
        cmd = self._formcmd(cmd)
254
 
        child = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=self._log_file)
255
 
        outd, errd = child.communicate()
256
 
        self.log(outd)
257
 
        actual_retcode = child.wait()
258
 
 
259
 
        outd = outd.replace('\r', '')
260
 
 
261
 
        if retcode != actual_retcode:
262
 
            raise CommandFailed("test failed: %r returned %d, expected %d"
263
 
                                % (cmd, actual_retcode, retcode))
264
 
 
265
 
        return outd
266
 
 
267
 
 
268
 
 
269
401
    def build_tree(self, shape):
270
402
        """Build a test tree according to a pattern.
271
403
 
275
407
        This doesn't add anything to a branch.
276
408
        """
277
409
        # XXX: It's OK to just create them using forward slashes on windows?
278
 
        import os
279
410
        for name in shape:
280
411
            assert isinstance(name, basestring)
281
412
            if name[-1] == '/':
284
415
                f = file(name, 'wt')
285
416
                print >>f, "contents of", name
286
417
                f.close()
287
 
                
288
418
 
 
419
    def failUnlessExists(self, path):
 
420
        """Fail unless path, which may be abs or relative, exists."""
 
421
        self.failUnless(osutils.lexists(path))
 
422
        
289
423
 
290
424
class MetaTestLog(TestCase):
291
425
    def test_logging(self):
296
430
        ##assert 0
297
431
 
298
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
 
299
465
def selftest(verbose=False, pattern=".*"):
300
466
    """Run the whole test suite under the enhanced runner"""
301
 
    return testsweet.run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern)
 
467
    return run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern)
302
468
 
303
469
 
304
470
def test_suite():
305
471
    """Build and return TestSuite for the whole program."""
306
 
    from bzrlib.selftest.TestUtil import TestLoader, TestSuite
307
 
    import bzrlib, bzrlib.store, bzrlib.inventory, bzrlib.branch
308
 
    import bzrlib.osutils, bzrlib.commands, bzrlib.merge3, bzrlib.plugin
 
472
    import bzrlib.store, bzrlib.inventory, bzrlib.branch
 
473
    import bzrlib.osutils, bzrlib.merge3, bzrlib.plugin
309
474
    from doctest import DocTestSuite
310
 
    import os
311
 
    import shutil
312
 
    import time
313
 
    import sys
314
475
 
315
476
    global MODULES_TO_TEST, MODULES_TO_DOCTEST
316
477
 
317
478
    testmod_names = \
318
479
                  ['bzrlib.selftest.MetaTestLog',
 
480
                   'bzrlib.selftest.testidentitymap',
319
481
                   'bzrlib.selftest.testinv',
 
482
                   'bzrlib.selftest.test_ancestry',
 
483
                   'bzrlib.selftest.test_commit',
 
484
                   'bzrlib.selftest.test_commit_merge',
 
485
                   'bzrlib.selftest.testconfig',
320
486
                   'bzrlib.selftest.versioning',
321
487
                   'bzrlib.selftest.testmerge3',
 
488
                   'bzrlib.selftest.testmerge',
322
489
                   'bzrlib.selftest.testhashcache',
323
490
                   'bzrlib.selftest.teststatus',
324
491
                   'bzrlib.selftest.testlog',
325
492
                   'bzrlib.selftest.testrevisionnamespaces',
326
493
                   'bzrlib.selftest.testbranch',
327
 
#                   'bzrlib.selftest.testrevision',
328
 
#                   'bzrlib.selftest.test_merge_core',
 
494
                   'bzrlib.selftest.testrevision',
 
495
                   'bzrlib.selftest.test_revision_info',
 
496
                   'bzrlib.selftest.test_merge_core',
329
497
                   'bzrlib.selftest.test_smart_add',
 
498
                   'bzrlib.selftest.test_bad_files',
330
499
                   'bzrlib.selftest.testdiff',
331
 
#                   'bzrlib.selftest.test_parent',
 
500
                   'bzrlib.selftest.test_parent',
332
501
                   'bzrlib.selftest.test_xml',
333
 
#                   'bzrlib.selftest.testfetch',
334
 
#                   'bzrlib.selftest.whitebox',
 
502
                   'bzrlib.selftest.test_weave',
 
503
                   'bzrlib.selftest.testfetch',
 
504
                   'bzrlib.selftest.whitebox',
335
505
                   'bzrlib.selftest.teststore',
336
 
#                   'bzrlib.selftest.blackbox',
 
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',
337
517
                   ]
338
518
 
339
519
    for m in (bzrlib.store, bzrlib.inventory, bzrlib.branch,