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."""
167
unittest.TestCase.setUp(self)
168
bzrlib.trace.disable_default_logging()
169
self._enable_file_logging()
172
def _enable_file_logging(self):
173
fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
175
self._log_file = os.fdopen(fileno, 'w+')
177
hdlr = logging.StreamHandler(self._log_file)
178
hdlr.setLevel(logging.DEBUG)
179
hdlr.setFormatter(logging.Formatter('%(levelname)8s %(message)s'))
180
logging.getLogger('').addHandler(hdlr)
181
logging.getLogger('').setLevel(logging.DEBUG)
182
self._log_hdlr = hdlr
183
debug('opened log file %s', name)
185
self._log_file_name = name
188
logging.getLogger('').removeHandler(self._log_hdlr)
189
bzrlib.trace.enable_default_logging()
190
logging.debug('%s teardown', self.id())
191
self._log_file.close()
192
unittest.TestCase.tearDown(self)
194
def log(self, *args):
198
"""Return as a string the log for this test"""
199
return open(self._log_file_name).read()
202
def capture(self, cmd):
203
"""Shortcut that splits cmd into words, runs, and returns stdout"""
204
return self.run_bzr_captured(cmd.split())[0]
206
def run_bzr_captured(self, argv, retcode=0):
207
"""Invoke bzr and return (result, stdout, stderr).
209
Useful for code that wants to check the contents of the
210
output, the way error messages are presented, etc.
212
This should be the main method for tests that want to exercise the
213
overall behavior of the bzr application (rather than a unit test
214
or a functional test of the library.)
216
Much of the old code runs bzr by forking a new copy of Python, but
217
that is slower, harder to debug, and generally not necessary.
219
This runs bzr through the interface that catches and reports
220
errors, and with logging set to something approximating the
221
default, so that error reporting can be checked.
223
argv -- arguments to invoke bzr
224
retcode -- expected return code, or None for don't-care.
228
self.log('run bzr: %s', ' '.join(argv))
229
handler = logging.StreamHandler(stderr)
230
handler.setFormatter(bzrlib.trace.QuietFormatter())
231
handler.setLevel(logging.INFO)
232
logger = logging.getLogger('')
233
logger.addHandler(handler)
235
result = self.apply_redirected(None, stdout, stderr,
236
bzrlib.commands.run_bzr_catch_errors,
239
logger.removeHandler(handler)
240
out = stdout.getvalue()
241
err = stderr.getvalue()
243
self.log('output:\n%s', out)
245
self.log('errors:\n%s', err)
246
if retcode is not None:
247
self.assertEquals(result, retcode)
250
def run_bzr(self, *args, **kwargs):
251
"""Invoke bzr, as if it were run from the command line.
253
This should be the main method for tests that want to exercise the
254
overall behavior of the bzr application (rather than a unit test
255
or a functional test of the library.)
257
This sends the stdout/stderr results into the test's log,
258
where it may be useful for debugging. See also run_captured.
260
retcode = kwargs.pop('retcode', 0)
261
return self.run_bzr_captured(args, retcode)
263
def check_inventory_shape(self, inv, shape):
264
"""Compare an inventory to a list of expected names.
266
Fail if they are not precisely equal.
269
shape = list(shape) # copy
270
for path, ie in inv.entries():
271
name = path.replace('\\', '/')
279
self.fail("expected paths not found in inventory: %r" % shape)
281
self.fail("unexpected paths found in inventory: %r" % extras)
283
def apply_redirected(self, stdin=None, stdout=None, stderr=None,
284
a_callable=None, *args, **kwargs):
285
"""Call callable with redirected std io pipes.
287
Returns the return code."""
288
if not callable(a_callable):
289
raise ValueError("a_callable must be callable.")
293
if hasattr(self, "_log_file"):
294
stdout = self._log_file
298
if hasattr(self, "_log_file"):
299
stderr = self._log_file
302
real_stdin = sys.stdin
303
real_stdout = sys.stdout
304
real_stderr = sys.stderr
309
return a_callable(*args, **kwargs)
311
sys.stdout = real_stdout
312
sys.stderr = real_stderr
313
sys.stdin = real_stdin
316
BzrTestBase = TestCase
319
class TestCaseInTempDir(TestCase):
320
"""Derived class that runs a test within a temporary directory.
322
This is useful for tests that need to create a branch, etc.
324
The directory is created in a slightly complex way: for each
325
Python invocation, a new temporary top-level directory is created.
326
All test cases create their own directory within that. If the
327
tests complete successfully, the directory is removed.
329
InTempDir is an old alias for FunctionalTestCase.
334
OVERRIDE_PYTHON = 'python'
336
def check_file_contents(self, filename, expect):
337
self.log("check contents of file %s" % filename)
338
contents = file(filename, 'r').read()
339
if contents != expect:
340
self.log("expected: %r" % expect)
341
self.log("actually: %r" % contents)
342
self.fail("contents of %s not as expected" % filename)
344
def _make_test_root(self):
345
if TestCaseInTempDir.TEST_ROOT is not None:
349
root = 'test%04d.tmp' % i
353
if e.errno == errno.EEXIST:
358
# successfully created
359
TestCaseInTempDir.TEST_ROOT = os.path.abspath(root)
361
# make a fake bzr directory there to prevent any tests propagating
362
# up onto the source directory's real branch
363
os.mkdir(os.path.join(TestCaseInTempDir.TEST_ROOT, '.bzr'))
366
super(TestCaseInTempDir, self).setUp()
367
self._make_test_root()
368
self._currentdir = os.getcwdu()
369
short_id = self.id().replace('bzrlib.selftest.', '') \
370
.replace('__main__.', '')
371
self.test_dir = os.path.join(self.TEST_ROOT, short_id)
372
os.mkdir(self.test_dir)
373
os.chdir(self.test_dir)
376
os.chdir(self._currentdir)
377
super(TestCaseInTempDir, self).tearDown()
379
def build_tree(self, shape):
380
"""Build a test tree according to a pattern.
382
shape is a sequence of file specifications. If the final
383
character is '/', a directory is created.
385
This doesn't add anything to a branch.
387
# XXX: It's OK to just create them using forward slashes on windows?
389
assert isinstance(name, basestring)
394
print >>f, "contents of", name
397
def failUnlessExists(self, path):
398
"""Fail unless path, which may be abs or relative, exists."""
399
self.failUnless(osutils.lexists(path))
402
class MetaTestLog(TestCase):
403
def test_logging(self):
404
"""Test logs are captured when a test fails."""
405
logging.info('an info message')
406
warning('something looks dodgy...')
407
logging.debug('hello, test is running')
411
def filter_suite_by_re(suite, pattern):
412
result = TestUtil.TestSuite()
413
filter_re = re.compile(pattern)
414
for test in iter_suite_tests(suite):
415
if filter_re.search(test.id()):
420
def run_suite(suite, name='test', verbose=False, pattern=".*"):
421
TestCaseInTempDir._TEST_NAME = name
426
runner = TextTestRunner(stream=sys.stdout,
430
suite = filter_suite_by_re(suite, pattern)
431
result = runner.run(suite)
432
# This is still a little bogus,
433
# but only a little. Folk not using our testrunner will
434
# have to delete their temp directories themselves.
435
if result.wasSuccessful():
436
if TestCaseInTempDir.TEST_ROOT is not None:
437
shutil.rmtree(TestCaseInTempDir.TEST_ROOT)
439
print "Failed tests working directories are in '%s'\n" % TestCaseInTempDir.TEST_ROOT
440
return result.wasSuccessful()
443
def selftest(verbose=False, pattern=".*"):
444
"""Run the whole test suite under the enhanced runner"""
445
return run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern)
449
"""Build and return TestSuite for the whole program."""
450
import bzrlib.store, bzrlib.inventory, bzrlib.branch
451
import bzrlib.osutils, bzrlib.merge3, bzrlib.plugin
452
from doctest import DocTestSuite
454
global MODULES_TO_TEST, MODULES_TO_DOCTEST
457
['bzrlib.selftest.MetaTestLog',
458
'bzrlib.selftest.testidentitymap',
459
'bzrlib.selftest.testinv',
460
'bzrlib.selftest.test_ancestry',
461
'bzrlib.selftest.test_commit',
462
'bzrlib.selftest.test_commit_merge',
463
'bzrlib.selftest.testconfig',
464
'bzrlib.selftest.versioning',
465
'bzrlib.selftest.testmerge3',
466
'bzrlib.selftest.testmerge',
467
'bzrlib.selftest.testhashcache',
468
'bzrlib.selftest.teststatus',
469
'bzrlib.selftest.testlog',
470
'bzrlib.selftest.testrevisionnamespaces',
471
'bzrlib.selftest.testbranch',
472
'bzrlib.selftest.testrevision',
473
'bzrlib.selftest.test_revision_info',
474
'bzrlib.selftest.test_merge_core',
475
'bzrlib.selftest.test_smart_add',
476
'bzrlib.selftest.test_bad_files',
477
'bzrlib.selftest.testdiff',
478
'bzrlib.selftest.test_parent',
479
'bzrlib.selftest.test_xml',
480
'bzrlib.selftest.test_weave',
481
'bzrlib.selftest.testfetch',
482
'bzrlib.selftest.whitebox',
483
'bzrlib.selftest.teststore',
484
'bzrlib.selftest.blackbox',
485
'bzrlib.selftest.testsampler',
486
'bzrlib.selftest.testtransactions',
487
'bzrlib.selftest.testtransport',
488
'bzrlib.selftest.testgraph',
489
'bzrlib.selftest.testworkingtree',
490
'bzrlib.selftest.test_upgrade',
491
'bzrlib.selftest.test_conflicts',
494
for m in (bzrlib.store, bzrlib.inventory, bzrlib.branch,
495
bzrlib.osutils, bzrlib.commands, bzrlib.merge3):
496
if m not in MODULES_TO_DOCTEST:
497
MODULES_TO_DOCTEST.append(m)
499
TestCase.BZRPATH = os.path.join(os.path.realpath(os.path.dirname(bzrlib.__path__[0])), 'bzr')
500
print '%-30s %s' % ('bzr binary', TestCase.BZRPATH)
503
suite.addTest(TestLoader().loadTestsFromNames(testmod_names))
504
for m in MODULES_TO_TEST:
505
suite.addTest(TestLoader().loadTestsFromModule(m))
506
for m in (MODULES_TO_DOCTEST):
507
suite.addTest(DocTestSuite(m))
508
for p in bzrlib.plugin.all_plugins:
509
if hasattr(p, 'test_suite'):
510
suite.addTest(p.test_suite())