1
# Copyright (C) 2005 by Canonical Ltd
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.
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.
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
18
from cStringIO import StringIO
30
import bzrlib.commands
33
import bzrlib.osutils as osutils
34
from bzrlib.selftest import TestUtil
35
from bzrlib.selftest.TestUtil import TestLoader, TestSuite
39
MODULES_TO_DOCTEST = []
41
from logging import debug, warning, error
45
class EarlyStoppingTestResultAdapter(object):
46
"""An adapter for TestResult to stop at the first first failure or error"""
48
def __init__(self, result):
51
def addError(self, test, err):
52
self._result.addError(test, err)
55
def addFailure(self, test, err):
56
self._result.addFailure(test, err)
59
def __getattr__(self, name):
60
return getattr(self._result, name)
62
def __setattr__(self, name, value):
64
object.__setattr__(self, name, value)
65
return setattr(self._result, name, value)
68
class _MyResult(unittest._TextTestResult):
72
No special behaviour for now.
75
def _elapsedTime(self):
76
return "(Took %.3fs)" % (time.time() - self._start_time)
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()
83
self.stream.write('%-70.70s' % what)
85
self._start_time = time.time()
87
def addError(self, test, err):
88
unittest.TestResult.addError(self, test, err)
90
self.stream.writeln("ERROR %s" % self._elapsedTime())
92
self.stream.write('E')
95
def addFailure(self, test, err):
96
unittest.TestResult.addFailure(self, test, err)
98
self.stream.writeln("FAIL %s" % self._elapsedTime())
100
self.stream.write('F')
103
def addSuccess(self, test):
105
self.stream.writeln('OK %s' % self._elapsedTime())
107
self.stream.write('~')
109
unittest.TestResult.addSuccess(self, test)
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)
123
class TextTestRunner(unittest.TextTestRunner):
125
def _makeResult(self):
126
result = _MyResult(self.stream, self.descriptions, self.verbosity)
127
return EarlyStoppingTestResultAdapter(result)
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):
135
elif isinstance(item, unittest.TestSuite):
136
for r in iter_suite_tests(item):
139
raise Exception('unknown object %r inside test suite %r'
143
class TestSkipped(Exception):
144
"""Indicates that a test was intentionally skipped, rather than failing."""
148
class CommandFailed(Exception):
151
class TestCase(unittest.TestCase):
152
"""Base class for bzr unit tests.
154
Tests that need access to disk resources should subclass
155
TestCaseInTempDir not TestCase.
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().
161
There are also convenience functions to invoke bzr's command-line
162
routine, and to build and check bzr trees."""
165
_log_file_name = None
168
unittest.TestCase.setUp(self)
169
bzrlib.trace.disable_default_logging()
170
self._enable_file_logging()
172
def _ndiff_strings(self, a, b):
173
"""Return ndiff between two strings containing lines."""
174
difflines = difflib.ndiff(a.splitlines(True),
176
linejunk=lambda x: False,
177
charjunk=lambda x: False)
178
return ''.join(difflines)
180
def assertEqualDiff(self, a, b):
181
"""Assert two texts are equal, if not raise an exception.
183
This is intended for use with multi-line strings where it can
184
be hard to find the differences by eye.
186
# TODO: perhaps override assertEquals to call this for strings?
189
raise AssertionError("texts not equal:\n" +
190
self._ndiff_strings(a, b))
192
def _enable_file_logging(self):
193
fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
195
self._log_file = os.fdopen(fileno, 'w+')
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)
205
self._log_file_name = name
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)
214
def log(self, *args):
218
"""Return as a string the log for this test"""
219
if self._log_file_name:
220
return open(self._log_file_name).read()
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]
228
def run_bzr_captured(self, argv, retcode=0):
229
"""Invoke bzr and return (result, stdout, stderr).
231
Useful for code that wants to check the contents of the
232
output, the way error messages are presented, etc.
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.)
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.
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.
245
argv -- arguments to invoke bzr
246
retcode -- expected return code, or None for don't-care.
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)
257
result = self.apply_redirected(None, stdout, stderr,
258
bzrlib.commands.run_bzr_catch_errors,
261
logger.removeHandler(handler)
262
out = stdout.getvalue()
263
err = stderr.getvalue()
265
self.log('output:\n%s', out)
267
self.log('errors:\n%s', err)
268
if retcode is not None:
269
self.assertEquals(result, retcode)
272
def run_bzr(self, *args, **kwargs):
273
"""Invoke bzr, as if it were run from the command line.
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.)
279
This sends the stdout/stderr results into the test's log,
280
where it may be useful for debugging. See also run_captured.
282
retcode = kwargs.pop('retcode', 0)
283
return self.run_bzr_captured(args, retcode)
285
def check_inventory_shape(self, inv, shape):
286
"""Compare an inventory to a list of expected names.
288
Fail if they are not precisely equal.
291
shape = list(shape) # copy
292
for path, ie in inv.entries():
293
name = path.replace('\\', '/')
301
self.fail("expected paths not found in inventory: %r" % shape)
303
self.fail("unexpected paths found in inventory: %r" % extras)
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.
309
Returns the return code."""
310
if not callable(a_callable):
311
raise ValueError("a_callable must be callable.")
315
if hasattr(self, "_log_file"):
316
stdout = self._log_file
320
if hasattr(self, "_log_file"):
321
stderr = self._log_file
324
real_stdin = sys.stdin
325
real_stdout = sys.stdout
326
real_stderr = sys.stderr
331
return a_callable(*args, **kwargs)
333
sys.stdout = real_stdout
334
sys.stderr = real_stderr
335
sys.stdin = real_stdin
338
BzrTestBase = TestCase
341
class TestCaseInTempDir(TestCase):
342
"""Derived class that runs a test within a temporary directory.
344
This is useful for tests that need to create a branch, etc.
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.
351
InTempDir is an old alias for FunctionalTestCase.
356
OVERRIDE_PYTHON = 'python'
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)
366
def _make_test_root(self):
367
if TestCaseInTempDir.TEST_ROOT is not None:
371
root = 'test%04d.tmp' % i
375
if e.errno == errno.EEXIST:
380
# successfully created
381
TestCaseInTempDir.TEST_ROOT = os.path.abspath(root)
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'))
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)
398
os.chdir(self._currentdir)
399
super(TestCaseInTempDir, self).tearDown()
401
def build_tree(self, shape):
402
"""Build a test tree according to a pattern.
404
shape is a sequence of file specifications. If the final
405
character is '/', a directory is created.
407
This doesn't add anything to a branch.
409
# XXX: It's OK to just create them using forward slashes on windows?
411
assert isinstance(name, basestring)
416
print >>f, "contents of", name
419
def failUnlessExists(self, path):
420
"""Fail unless path, which may be abs or relative, exists."""
421
self.failUnless(osutils.lexists(path))
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')
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()):
442
def run_suite(suite, name='test', verbose=False, pattern=".*"):
443
TestCaseInTempDir._TEST_NAME = name
448
runner = TextTestRunner(stream=sys.stdout,
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)
461
print "Failed tests working directories are in '%s'\n" % TestCaseInTempDir.TEST_ROOT
462
return result.wasSuccessful()
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)
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
476
global MODULES_TO_TEST, MODULES_TO_DOCTEST
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',
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)
524
TestCase.BZRPATH = os.path.join(os.path.realpath(os.path.dirname(bzrlib.__path__[0])), 'bzr')
525
print '%-30s %s' % ('bzr binary', TestCase.BZRPATH)
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())