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
28
import bzrlib.commands
31
from bzrlib.selftest import TestUtil
32
from bzrlib.selftest.TestUtil import TestLoader, TestSuite
36
MODULES_TO_DOCTEST = []
38
from logging import debug, warning, error
42
class EarlyStoppingTestResultAdapter(object):
43
"""An adapter for TestResult to stop at the first first failure or error"""
45
def __init__(self, result):
48
def addError(self, test, err):
49
self._result.addError(test, err)
52
def addFailure(self, test, err):
53
self._result.addFailure(test, err)
56
def __getattr__(self, name):
57
return getattr(self._result, name)
59
def __setattr__(self, name, value):
61
object.__setattr__(self, name, value)
62
return setattr(self._result, name, value)
65
class _MyResult(unittest._TextTestResult):
69
No special behaviour for now.
72
def startTest(self, test):
73
unittest.TestResult.startTest(self, test)
74
# TODO: Maybe show test.shortDescription somewhere?
75
what = test.shortDescription() or test.id()
77
self.stream.write('%-70.70s' % what)
80
def addError(self, test, err):
81
super(_MyResult, self).addError(test, err)
84
def addFailure(self, test, err):
85
super(_MyResult, self).addFailure(test, err)
88
def addSuccess(self, test):
90
self.stream.writeln('OK')
92
self.stream.write('~')
94
unittest.TestResult.addSuccess(self, test)
96
def printErrorList(self, flavour, errors):
97
for test, err in errors:
98
self.stream.writeln(self.separator1)
99
self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
100
if hasattr(test, '_get_log'):
101
self.stream.writeln()
102
self.stream.writeln('log from this test:')
103
print >>self.stream, test._get_log()
104
self.stream.writeln(self.separator2)
105
self.stream.writeln("%s" % err)
108
class TextTestRunner(unittest.TextTestRunner):
110
def _makeResult(self):
111
result = _MyResult(self.stream, self.descriptions, self.verbosity)
112
return EarlyStoppingTestResultAdapter(result)
115
class filteringVisitor(TestUtil.TestVisitor):
116
"""I accruse all the testCases I visit that pass a regexp filter on id
120
def __init__(self, filter):
122
TestUtil.TestVisitor.__init__(self)
124
self.filter=re.compile(filter)
127
"""answer the suite we are building"""
128
if self._suite is None:
129
self._suite=TestUtil.TestSuite()
132
def visitCase(self, aCase):
133
if self.filter.match(aCase.id()):
134
self.suite().addTest(aCase)
136
class TestSkipped(Exception):
137
"""Indicates that a test was intentionally skipped, rather than failing."""
141
class CommandFailed(Exception):
144
class TestCase(unittest.TestCase):
145
"""Base class for bzr unit tests.
147
Tests that need access to disk resources should subclass
148
TestCaseInTempDir not TestCase.
150
Error and debug log messages are redirected from their usual
151
location into a temporary file, the contents of which can be
152
retrieved by _get_log().
154
There are also convenience functions to invoke bzr's command-line
155
routine, and to build and check bzr trees."""
160
unittest.TestCase.setUp(self)
161
bzrlib.trace.disable_default_logging()
162
self._enable_file_logging()
165
def _enable_file_logging(self):
166
fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
168
self._log_file = os.fdopen(fileno, 'w+')
170
hdlr = logging.StreamHandler(self._log_file)
171
hdlr.setLevel(logging.DEBUG)
172
hdlr.setFormatter(logging.Formatter('%(levelname)8s %(message)s'))
173
logging.getLogger('').addHandler(hdlr)
174
logging.getLogger('').setLevel(logging.DEBUG)
175
self._log_hdlr = hdlr
176
debug('opened log file %s', name)
178
self._log_file_name = name
181
logging.getLogger('').removeHandler(self._log_hdlr)
182
bzrlib.trace.enable_default_logging()
183
logging.debug('%s teardown', self.id())
184
self._log_file.close()
185
unittest.TestCase.tearDown(self)
187
def log(self, *args):
191
"""Return as a string the log for this test"""
192
return open(self._log_file_name).read()
195
def capture(self, cmd):
196
"""Shortcut that splits cmd into words, runs, and returns stdout"""
197
return self.run_bzr_captured(cmd.split())[0]
199
def run_bzr_captured(self, argv, retcode=0):
200
"""Invoke bzr and return (result, stdout, stderr).
202
Useful for code that wants to check the contents of the
203
output, the way error messages are presented, etc.
205
This should be the main method for tests that want to exercise the
206
overall behavior of the bzr application (rather than a unit test
207
or a functional test of the library.)
209
Much of the old code runs bzr by forking a new copy of Python, but
210
that is slower, harder to debug, and generally not necessary.
212
This runs bzr through the interface that catches and reports
213
errors, and with logging set to something approximating the
214
default, so that error reporting can be checked.
216
argv -- arguments to invoke bzr
217
retcode -- expected return code, or None for don't-care.
221
self.log('run bzr: %s', ' '.join(argv))
222
handler = logging.StreamHandler(stderr)
223
handler.setFormatter(bzrlib.trace.QuietFormatter())
224
handler.setLevel(logging.INFO)
225
logger = logging.getLogger('')
226
logger.addHandler(handler)
228
result = self.apply_redirected(None, stdout, stderr,
229
bzrlib.commands.run_bzr_catch_errors,
232
logger.removeHandler(handler)
233
out = stdout.getvalue()
234
err = stderr.getvalue()
236
self.log('output:\n%s', out)
238
self.log('errors:\n%s', err)
239
if retcode is not None:
240
self.assertEquals(result, retcode)
243
def run_bzr(self, *args, **kwargs):
244
"""Invoke bzr, as if it were run from the command line.
246
This should be the main method for tests that want to exercise the
247
overall behavior of the bzr application (rather than a unit test
248
or a functional test of the library.)
250
This sends the stdout/stderr results into the test's log,
251
where it may be useful for debugging. See also run_captured.
253
retcode = kwargs.pop('retcode', 0)
254
return self.run_bzr_captured(args, retcode)
256
def check_inventory_shape(self, inv, shape):
257
"""Compare an inventory to a list of expected names.
259
Fail if they are not precisely equal.
262
shape = list(shape) # copy
263
for path, ie in inv.entries():
264
name = path.replace('\\', '/')
272
self.fail("expected paths not found in inventory: %r" % shape)
274
self.fail("unexpected paths found in inventory: %r" % extras)
276
def apply_redirected(self, stdin=None, stdout=None, stderr=None,
277
a_callable=None, *args, **kwargs):
278
"""Call callable with redirected std io pipes.
280
Returns the return code."""
281
if not callable(a_callable):
282
raise ValueError("a_callable must be callable.")
286
if hasattr(self, "_log_file"):
287
stdout = self._log_file
291
if hasattr(self, "_log_file"):
292
stderr = self._log_file
295
real_stdin = sys.stdin
296
real_stdout = sys.stdout
297
real_stderr = sys.stderr
302
return a_callable(*args, **kwargs)
304
sys.stdout = real_stdout
305
sys.stderr = real_stderr
306
sys.stdin = real_stdin
309
BzrTestBase = TestCase
312
class TestCaseInTempDir(TestCase):
313
"""Derived class that runs a test within a temporary directory.
315
This is useful for tests that need to create a branch, etc.
317
The directory is created in a slightly complex way: for each
318
Python invocation, a new temporary top-level directory is created.
319
All test cases create their own directory within that. If the
320
tests complete successfully, the directory is removed.
322
InTempDir is an old alias for FunctionalTestCase.
327
OVERRIDE_PYTHON = 'python'
329
def check_file_contents(self, filename, expect):
330
self.log("check contents of file %s" % filename)
331
contents = file(filename, 'r').read()
332
if contents != expect:
333
self.log("expected: %r" % expect)
334
self.log("actually: %r" % contents)
335
self.fail("contents of %s not as expected" % filename)
337
def _make_test_root(self):
338
if TestCaseInTempDir.TEST_ROOT is not None:
342
root = 'test%04d.tmp' % i
346
if e.errno == errno.EEXIST:
351
# successfully created
352
TestCaseInTempDir.TEST_ROOT = os.path.abspath(root)
354
# make a fake bzr directory there to prevent any tests propagating
355
# up onto the source directory's real branch
356
os.mkdir(os.path.join(TestCaseInTempDir.TEST_ROOT, '.bzr'))
359
super(TestCaseInTempDir, self).setUp()
360
self._make_test_root()
361
self._currentdir = os.getcwdu()
362
short_id = self.id().replace('bzrlib.selftest.', '') \
363
.replace('__main__.', '')
364
self.test_dir = os.path.join(self.TEST_ROOT, short_id)
365
os.mkdir(self.test_dir)
366
os.chdir(self.test_dir)
369
os.chdir(self._currentdir)
370
super(TestCaseInTempDir, self).tearDown()
372
def build_tree(self, shape):
373
"""Build a test tree according to a pattern.
375
shape is a sequence of file specifications. If the final
376
character is '/', a directory is created.
378
This doesn't add anything to a branch.
380
# XXX: It's OK to just create them using forward slashes on windows?
382
assert isinstance(name, basestring)
387
print >>f, "contents of", name
391
class MetaTestLog(TestCase):
392
def test_logging(self):
393
"""Test logs are captured when a test fails."""
394
logging.info('an info message')
395
warning('something looks dodgy...')
396
logging.debug('hello, test is running')
401
def run_suite(suite, name='test', verbose=False, pattern=".*"):
402
TestCaseInTempDir._TEST_NAME = name
407
runner = TextTestRunner(stream=sys.stdout,
410
visitor = filteringVisitor(pattern)
412
result = runner.run(visitor.suite())
413
# This is still a little bogus,
414
# but only a little. Folk not using our testrunner will
415
# have to delete their temp directories themselves.
416
if result.wasSuccessful():
417
if TestCaseInTempDir.TEST_ROOT is not None:
418
shutil.rmtree(TestCaseInTempDir.TEST_ROOT)
420
print "Failed tests working directories are in '%s'\n" % TestCaseInTempDir.TEST_ROOT
421
return result.wasSuccessful()
424
def selftest(verbose=False, pattern=".*"):
425
"""Run the whole test suite under the enhanced runner"""
426
return run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern)
430
"""Build and return TestSuite for the whole program."""
431
import bzrlib.store, bzrlib.inventory, bzrlib.branch
432
import bzrlib.osutils, bzrlib.merge3, bzrlib.plugin
433
from doctest import DocTestSuite
435
global MODULES_TO_TEST, MODULES_TO_DOCTEST
438
['bzrlib.selftest.MetaTestLog',
439
'bzrlib.selftest.testinv',
440
'bzrlib.selftest.test_ancestry',
441
'bzrlib.selftest.test_commit',
442
'bzrlib.selftest.test_commit_merge',
443
'bzrlib.selftest.versioning',
444
'bzrlib.selftest.testmerge3',
445
'bzrlib.selftest.testmerge',
446
'bzrlib.selftest.testhashcache',
447
'bzrlib.selftest.teststatus',
448
'bzrlib.selftest.testlog',
449
'bzrlib.selftest.testrevisionnamespaces',
450
'bzrlib.selftest.testbranch',
451
'bzrlib.selftest.testrevision',
452
'bzrlib.selftest.test_revision_info',
453
'bzrlib.selftest.test_merge_core',
454
'bzrlib.selftest.test_smart_add',
455
'bzrlib.selftest.test_bad_files',
456
'bzrlib.selftest.testdiff',
457
'bzrlib.selftest.test_parent',
458
'bzrlib.selftest.test_xml',
459
'bzrlib.selftest.test_weave',
460
'bzrlib.selftest.testfetch',
461
'bzrlib.selftest.whitebox',
462
'bzrlib.selftest.teststore',
463
'bzrlib.selftest.blackbox',
464
'bzrlib.selftest.testtransport',
465
'bzrlib.selftest.testgraph',
468
for m in (bzrlib.store, bzrlib.inventory, bzrlib.branch,
469
bzrlib.osutils, bzrlib.commands, bzrlib.merge3):
470
if m not in MODULES_TO_DOCTEST:
471
MODULES_TO_DOCTEST.append(m)
473
TestCase.BZRPATH = os.path.join(os.path.realpath(os.path.dirname(bzrlib.__path__[0])), 'bzr')
474
print '%-30s %s' % ('bzr binary', TestCase.BZRPATH)
477
suite.addTest(TestLoader().loadTestsFromNames(testmod_names))
478
for m in MODULES_TO_TEST:
479
suite.addTest(TestLoader().loadTestsFromModule(m))
480
for m in (MODULES_TO_DOCTEST):
481
suite.addTest(DocTestSuite(m))
482
for p in bzrlib.plugin.all_plugins:
483
if hasattr(p, 'test_suite'):
484
suite.addTest(p.test_suite())