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
from bzrlib.selftest import TestUtil
34
from bzrlib.selftest.TestUtil import TestLoader, TestSuite
38
MODULES_TO_DOCTEST = []
40
from logging import debug, warning, error
44
class EarlyStoppingTestResultAdapter(object):
45
"""An adapter for TestResult to stop at the first first failure or error"""
47
def __init__(self, result):
50
def addError(self, test, err):
51
self._result.addError(test, err)
54
def addFailure(self, test, err):
55
self._result.addFailure(test, err)
58
def __getattr__(self, name):
59
return getattr(self._result, name)
61
def __setattr__(self, name, value):
63
object.__setattr__(self, name, value)
64
return setattr(self._result, name, value)
67
class _MyResult(unittest._TextTestResult):
71
No special behaviour for now.
74
def _elapsedTime(self):
75
return "(Took %.3fs)" % (time.time() - self._start_time)
77
def startTest(self, test):
78
unittest.TestResult.startTest(self, test)
79
# TODO: Maybe show test.shortDescription somewhere?
80
what = test.shortDescription() or test.id()
82
self.stream.write('%-70.70s' % what)
84
self._start_time = time.time()
86
def addError(self, test, err):
87
unittest.TestResult.addError(self, test, err)
89
self.stream.writeln("ERROR %s" % self._elapsedTime())
91
self.stream.write('E')
94
def addFailure(self, test, err):
95
unittest.TestResult.addFailure(self, test, err)
97
self.stream.writeln("FAIL %s" % self._elapsedTime())
99
self.stream.write('F')
102
def addSuccess(self, test):
104
self.stream.writeln('OK %s' % self._elapsedTime())
106
self.stream.write('~')
108
unittest.TestResult.addSuccess(self, test)
110
def printErrorList(self, flavour, errors):
111
for test, err in errors:
112
self.stream.writeln(self.separator1)
113
self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
114
if hasattr(test, '_get_log'):
115
self.stream.writeln()
116
self.stream.writeln('log from this test:')
117
print >>self.stream, test._get_log()
118
self.stream.writeln(self.separator2)
119
self.stream.writeln("%s" % err)
122
class TextTestRunner(unittest.TextTestRunner):
124
def _makeResult(self):
125
result = _MyResult(self.stream, self.descriptions, self.verbosity)
126
return EarlyStoppingTestResultAdapter(result)
129
def iter_suite_tests(suite):
130
"""Return all tests in a suite, recursing through nested suites"""
131
for item in suite._tests:
132
if isinstance(item, unittest.TestCase):
134
elif isinstance(item, unittest.TestSuite):
135
for r in iter_suite_tests(item):
138
raise Exception('unknown object %r inside test suite %r'
142
class TestSkipped(Exception):
143
"""Indicates that a test was intentionally skipped, rather than failing."""
147
class CommandFailed(Exception):
150
class TestCase(unittest.TestCase):
151
"""Base class for bzr unit tests.
153
Tests that need access to disk resources should subclass
154
TestCaseInTempDir not TestCase.
156
Error and debug log messages are redirected from their usual
157
location into a temporary file, the contents of which can be
158
retrieved by _get_log().
160
There are also convenience functions to invoke bzr's command-line
161
routine, and to build and check bzr trees."""
166
unittest.TestCase.setUp(self)
167
bzrlib.trace.disable_default_logging()
168
self._enable_file_logging()
171
def _enable_file_logging(self):
172
fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
174
self._log_file = os.fdopen(fileno, 'w+')
176
hdlr = logging.StreamHandler(self._log_file)
177
hdlr.setLevel(logging.DEBUG)
178
hdlr.setFormatter(logging.Formatter('%(levelname)8s %(message)s'))
179
logging.getLogger('').addHandler(hdlr)
180
logging.getLogger('').setLevel(logging.DEBUG)
181
self._log_hdlr = hdlr
182
debug('opened log file %s', name)
184
self._log_file_name = name
187
logging.getLogger('').removeHandler(self._log_hdlr)
188
bzrlib.trace.enable_default_logging()
189
logging.debug('%s teardown', self.id())
190
self._log_file.close()
191
unittest.TestCase.tearDown(self)
193
def log(self, *args):
197
"""Return as a string the log for this test"""
198
return open(self._log_file_name).read()
201
def capture(self, cmd):
202
"""Shortcut that splits cmd into words, runs, and returns stdout"""
203
return self.run_bzr_captured(cmd.split())[0]
205
def run_bzr_captured(self, argv, retcode=0):
206
"""Invoke bzr and return (result, stdout, stderr).
208
Useful for code that wants to check the contents of the
209
output, the way error messages are presented, etc.
211
This should be the main method for tests that want to exercise the
212
overall behavior of the bzr application (rather than a unit test
213
or a functional test of the library.)
215
Much of the old code runs bzr by forking a new copy of Python, but
216
that is slower, harder to debug, and generally not necessary.
218
This runs bzr through the interface that catches and reports
219
errors, and with logging set to something approximating the
220
default, so that error reporting can be checked.
222
argv -- arguments to invoke bzr
223
retcode -- expected return code, or None for don't-care.
227
self.log('run bzr: %s', ' '.join(argv))
228
handler = logging.StreamHandler(stderr)
229
handler.setFormatter(bzrlib.trace.QuietFormatter())
230
handler.setLevel(logging.INFO)
231
logger = logging.getLogger('')
232
logger.addHandler(handler)
234
result = self.apply_redirected(None, stdout, stderr,
235
bzrlib.commands.run_bzr_catch_errors,
238
logger.removeHandler(handler)
239
out = stdout.getvalue()
240
err = stderr.getvalue()
242
self.log('output:\n%s', out)
244
self.log('errors:\n%s', err)
245
if retcode is not None:
246
self.assertEquals(result, retcode)
249
def run_bzr(self, *args, **kwargs):
250
"""Invoke bzr, as if it were run from the command line.
252
This should be the main method for tests that want to exercise the
253
overall behavior of the bzr application (rather than a unit test
254
or a functional test of the library.)
256
This sends the stdout/stderr results into the test's log,
257
where it may be useful for debugging. See also run_captured.
259
retcode = kwargs.pop('retcode', 0)
260
return self.run_bzr_captured(args, retcode)
262
def check_inventory_shape(self, inv, shape):
263
"""Compare an inventory to a list of expected names.
265
Fail if they are not precisely equal.
268
shape = list(shape) # copy
269
for path, ie in inv.entries():
270
name = path.replace('\\', '/')
278
self.fail("expected paths not found in inventory: %r" % shape)
280
self.fail("unexpected paths found in inventory: %r" % extras)
282
def apply_redirected(self, stdin=None, stdout=None, stderr=None,
283
a_callable=None, *args, **kwargs):
284
"""Call callable with redirected std io pipes.
286
Returns the return code."""
287
if not callable(a_callable):
288
raise ValueError("a_callable must be callable.")
292
if hasattr(self, "_log_file"):
293
stdout = self._log_file
297
if hasattr(self, "_log_file"):
298
stderr = self._log_file
301
real_stdin = sys.stdin
302
real_stdout = sys.stdout
303
real_stderr = sys.stderr
308
return a_callable(*args, **kwargs)
310
sys.stdout = real_stdout
311
sys.stderr = real_stderr
312
sys.stdin = real_stdin
315
BzrTestBase = TestCase
318
class TestCaseInTempDir(TestCase):
319
"""Derived class that runs a test within a temporary directory.
321
This is useful for tests that need to create a branch, etc.
323
The directory is created in a slightly complex way: for each
324
Python invocation, a new temporary top-level directory is created.
325
All test cases create their own directory within that. If the
326
tests complete successfully, the directory is removed.
328
InTempDir is an old alias for FunctionalTestCase.
333
OVERRIDE_PYTHON = 'python'
335
def check_file_contents(self, filename, expect):
336
self.log("check contents of file %s" % filename)
337
contents = file(filename, 'r').read()
338
if contents != expect:
339
self.log("expected: %r" % expect)
340
self.log("actually: %r" % contents)
341
self.fail("contents of %s not as expected" % filename)
343
def _make_test_root(self):
344
if TestCaseInTempDir.TEST_ROOT is not None:
348
root = 'test%04d.tmp' % i
352
if e.errno == errno.EEXIST:
357
# successfully created
358
TestCaseInTempDir.TEST_ROOT = os.path.abspath(root)
360
# make a fake bzr directory there to prevent any tests propagating
361
# up onto the source directory's real branch
362
os.mkdir(os.path.join(TestCaseInTempDir.TEST_ROOT, '.bzr'))
365
super(TestCaseInTempDir, self).setUp()
366
self._make_test_root()
367
self._currentdir = os.getcwdu()
368
short_id = self.id().replace('bzrlib.selftest.', '') \
369
.replace('__main__.', '')
370
self.test_dir = os.path.join(self.TEST_ROOT, short_id)
371
os.mkdir(self.test_dir)
372
os.chdir(self.test_dir)
375
os.chdir(self._currentdir)
376
super(TestCaseInTempDir, self).tearDown()
378
def build_tree(self, shape):
379
"""Build a test tree according to a pattern.
381
shape is a sequence of file specifications. If the final
382
character is '/', a directory is created.
384
This doesn't add anything to a branch.
386
# XXX: It's OK to just create them using forward slashes on windows?
388
assert isinstance(name, basestring)
393
print >>f, "contents of", name
396
def failUnlessExists(self, path):
397
"""Fail unless path, which may be abs or relative, exists."""
398
self.failUnless(os.path.exists(path))
401
class MetaTestLog(TestCase):
402
def test_logging(self):
403
"""Test logs are captured when a test fails."""
404
logging.info('an info message')
405
warning('something looks dodgy...')
406
logging.debug('hello, test is running')
410
def filter_suite_by_re(suite, pattern):
411
result = TestUtil.TestSuite()
412
filter_re = re.compile(pattern)
413
for test in iter_suite_tests(suite):
414
if filter_re.search(test.id()):
419
def run_suite(suite, name='test', verbose=False, pattern=".*"):
420
TestCaseInTempDir._TEST_NAME = name
425
runner = TextTestRunner(stream=sys.stdout,
429
suite = filter_suite_by_re(suite, pattern)
430
result = runner.run(suite)
431
# This is still a little bogus,
432
# but only a little. Folk not using our testrunner will
433
# have to delete their temp directories themselves.
434
if result.wasSuccessful():
435
if TestCaseInTempDir.TEST_ROOT is not None:
436
shutil.rmtree(TestCaseInTempDir.TEST_ROOT)
438
print "Failed tests working directories are in '%s'\n" % TestCaseInTempDir.TEST_ROOT
439
return result.wasSuccessful()
442
def selftest(verbose=False, pattern=".*"):
443
"""Run the whole test suite under the enhanced runner"""
444
return run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern)
448
"""Build and return TestSuite for the whole program."""
449
import bzrlib.store, bzrlib.inventory, bzrlib.branch
450
import bzrlib.osutils, bzrlib.merge3, bzrlib.plugin
451
from doctest import DocTestSuite
453
global MODULES_TO_TEST, MODULES_TO_DOCTEST
456
['bzrlib.selftest.MetaTestLog',
457
'bzrlib.selftest.testidentitymap',
458
'bzrlib.selftest.testinv',
459
'bzrlib.selftest.test_ancestry',
460
'bzrlib.selftest.test_commit',
461
'bzrlib.selftest.test_commit_merge',
462
'bzrlib.selftest.testconfig',
463
'bzrlib.selftest.versioning',
464
'bzrlib.selftest.testmerge3',
465
'bzrlib.selftest.testmerge',
466
'bzrlib.selftest.testhashcache',
467
'bzrlib.selftest.teststatus',
468
'bzrlib.selftest.testlog',
469
'bzrlib.selftest.testrevisionnamespaces',
470
'bzrlib.selftest.testbranch',
471
'bzrlib.selftest.testrevision',
472
'bzrlib.selftest.test_revision_info',
473
'bzrlib.selftest.test_merge_core',
474
'bzrlib.selftest.test_smart_add',
475
'bzrlib.selftest.test_bad_files',
476
'bzrlib.selftest.testdiff',
477
'bzrlib.selftest.test_parent',
478
'bzrlib.selftest.test_xml',
479
'bzrlib.selftest.test_weave',
480
'bzrlib.selftest.testfetch',
481
'bzrlib.selftest.whitebox',
482
'bzrlib.selftest.teststore',
483
'bzrlib.selftest.blackbox',
484
'bzrlib.selftest.testsampler',
485
'bzrlib.selftest.testtransactions',
486
'bzrlib.selftest.testtransport',
487
'bzrlib.selftest.testgraph',
488
'bzrlib.selftest.testworkingtree',
489
'bzrlib.selftest.test_upgrade',
490
'bzrlib.selftest.test_conflicts',
493
for m in (bzrlib.store, bzrlib.inventory, bzrlib.branch,
494
bzrlib.osutils, bzrlib.commands, bzrlib.merge3):
495
if m not in MODULES_TO_DOCTEST:
496
MODULES_TO_DOCTEST.append(m)
498
TestCase.BZRPATH = os.path.join(os.path.realpath(os.path.dirname(bzrlib.__path__[0])), 'bzr')
499
print '%-30s %s' % ('bzr binary', TestCase.BZRPATH)
502
suite.addTest(TestLoader().loadTestsFromNames(testmod_names))
503
for m in MODULES_TO_TEST:
504
suite.addTest(TestLoader().loadTestsFromModule(m))
505
for m in (MODULES_TO_DOCTEST):
506
suite.addTest(DocTestSuite(m))
507
for p in bzrlib.plugin.all_plugins:
508
if hasattr(p, 'test_suite'):
509
suite.addTest(p.test_suite())