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
29
import bzrlib.commands
32
from bzrlib.selftest import TestUtil
33
from bzrlib.selftest.TestUtil import TestLoader, TestSuite
37
MODULES_TO_DOCTEST = []
39
from logging import debug, warning, error
43
class EarlyStoppingTestResultAdapter(object):
44
"""An adapter for TestResult to stop at the first first failure or error"""
46
def __init__(self, result):
49
def addError(self, test, err):
50
self._result.addError(test, err)
53
def addFailure(self, test, err):
54
self._result.addFailure(test, err)
57
def __getattr__(self, name):
58
return getattr(self._result, name)
60
def __setattr__(self, name, value):
62
object.__setattr__(self, name, value)
63
return setattr(self._result, name, value)
66
class _MyResult(unittest._TextTestResult):
70
No special behaviour for now.
73
def startTest(self, test):
74
unittest.TestResult.startTest(self, test)
75
# TODO: Maybe show test.shortDescription somewhere?
76
what = test.shortDescription() or test.id()
78
self.stream.write('%-70.70s' % what)
81
def addError(self, test, err):
82
super(_MyResult, self).addError(test, err)
85
def addFailure(self, test, err):
86
super(_MyResult, self).addFailure(test, err)
89
def addSuccess(self, test):
91
self.stream.writeln('OK')
93
self.stream.write('~')
95
unittest.TestResult.addSuccess(self, test)
97
def printErrorList(self, flavour, errors):
98
for test, err in errors:
99
self.stream.writeln(self.separator1)
100
self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
101
if hasattr(test, '_get_log'):
102
self.stream.writeln()
103
self.stream.writeln('log from this test:')
104
print >>self.stream, test._get_log()
105
self.stream.writeln(self.separator2)
106
self.stream.writeln("%s" % err)
109
class TextTestRunner(unittest.TextTestRunner):
111
def _makeResult(self):
112
result = _MyResult(self.stream, self.descriptions, self.verbosity)
113
return EarlyStoppingTestResultAdapter(result)
116
def iter_suite_tests(suite):
117
"""Return all tests in a suite, recursing through nested suites"""
118
for item in suite._tests:
119
if isinstance(item, unittest.TestCase):
121
elif isinstance(item, unittest.TestSuite):
122
for r in iter_suite_tests(item):
125
raise Exception('unknown object %r inside test suite %r'
129
class TestSkipped(Exception):
130
"""Indicates that a test was intentionally skipped, rather than failing."""
134
class CommandFailed(Exception):
137
class TestCase(unittest.TestCase):
138
"""Base class for bzr unit tests.
140
Tests that need access to disk resources should subclass
141
TestCaseInTempDir not TestCase.
143
Error and debug log messages are redirected from their usual
144
location into a temporary file, the contents of which can be
145
retrieved by _get_log().
147
There are also convenience functions to invoke bzr's command-line
148
routine, and to build and check bzr trees."""
153
unittest.TestCase.setUp(self)
154
bzrlib.trace.disable_default_logging()
155
self._enable_file_logging()
158
def _enable_file_logging(self):
159
fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
161
self._log_file = os.fdopen(fileno, 'w+')
163
hdlr = logging.StreamHandler(self._log_file)
164
hdlr.setLevel(logging.DEBUG)
165
hdlr.setFormatter(logging.Formatter('%(levelname)8s %(message)s'))
166
logging.getLogger('').addHandler(hdlr)
167
logging.getLogger('').setLevel(logging.DEBUG)
168
self._log_hdlr = hdlr
169
debug('opened log file %s', name)
171
self._log_file_name = name
174
logging.getLogger('').removeHandler(self._log_hdlr)
175
bzrlib.trace.enable_default_logging()
176
logging.debug('%s teardown', self.id())
177
self._log_file.close()
178
unittest.TestCase.tearDown(self)
180
def log(self, *args):
184
"""Return as a string the log for this test"""
185
return open(self._log_file_name).read()
188
def capture(self, cmd):
189
"""Shortcut that splits cmd into words, runs, and returns stdout"""
190
return self.run_bzr_captured(cmd.split())[0]
192
def run_bzr_captured(self, argv, retcode=0):
193
"""Invoke bzr and return (result, stdout, stderr).
195
Useful for code that wants to check the contents of the
196
output, the way error messages are presented, etc.
198
This should be the main method for tests that want to exercise the
199
overall behavior of the bzr application (rather than a unit test
200
or a functional test of the library.)
202
Much of the old code runs bzr by forking a new copy of Python, but
203
that is slower, harder to debug, and generally not necessary.
205
This runs bzr through the interface that catches and reports
206
errors, and with logging set to something approximating the
207
default, so that error reporting can be checked.
209
argv -- arguments to invoke bzr
210
retcode -- expected return code, or None for don't-care.
214
self.log('run bzr: %s', ' '.join(argv))
215
handler = logging.StreamHandler(stderr)
216
handler.setFormatter(bzrlib.trace.QuietFormatter())
217
handler.setLevel(logging.INFO)
218
logger = logging.getLogger('')
219
logger.addHandler(handler)
221
result = self.apply_redirected(None, stdout, stderr,
222
bzrlib.commands.run_bzr_catch_errors,
225
logger.removeHandler(handler)
226
out = stdout.getvalue()
227
err = stderr.getvalue()
229
self.log('output:\n%s', out)
231
self.log('errors:\n%s', err)
232
if retcode is not None:
233
self.assertEquals(result, retcode)
236
def run_bzr(self, *args, **kwargs):
237
"""Invoke bzr, as if it were run from the command line.
239
This should be the main method for tests that want to exercise the
240
overall behavior of the bzr application (rather than a unit test
241
or a functional test of the library.)
243
This sends the stdout/stderr results into the test's log,
244
where it may be useful for debugging. See also run_captured.
246
retcode = kwargs.pop('retcode', 0)
247
return self.run_bzr_captured(args, retcode)
249
def check_inventory_shape(self, inv, shape):
250
"""Compare an inventory to a list of expected names.
252
Fail if they are not precisely equal.
255
shape = list(shape) # copy
256
for path, ie in inv.entries():
257
name = path.replace('\\', '/')
265
self.fail("expected paths not found in inventory: %r" % shape)
267
self.fail("unexpected paths found in inventory: %r" % extras)
269
def apply_redirected(self, stdin=None, stdout=None, stderr=None,
270
a_callable=None, *args, **kwargs):
271
"""Call callable with redirected std io pipes.
273
Returns the return code."""
274
if not callable(a_callable):
275
raise ValueError("a_callable must be callable.")
279
if hasattr(self, "_log_file"):
280
stdout = self._log_file
284
if hasattr(self, "_log_file"):
285
stderr = self._log_file
288
real_stdin = sys.stdin
289
real_stdout = sys.stdout
290
real_stderr = sys.stderr
295
return a_callable(*args, **kwargs)
297
sys.stdout = real_stdout
298
sys.stderr = real_stderr
299
sys.stdin = real_stdin
302
BzrTestBase = TestCase
305
class TestCaseInTempDir(TestCase):
306
"""Derived class that runs a test within a temporary directory.
308
This is useful for tests that need to create a branch, etc.
310
The directory is created in a slightly complex way: for each
311
Python invocation, a new temporary top-level directory is created.
312
All test cases create their own directory within that. If the
313
tests complete successfully, the directory is removed.
315
InTempDir is an old alias for FunctionalTestCase.
320
OVERRIDE_PYTHON = 'python'
322
def check_file_contents(self, filename, expect):
323
self.log("check contents of file %s" % filename)
324
contents = file(filename, 'r').read()
325
if contents != expect:
326
self.log("expected: %r" % expect)
327
self.log("actually: %r" % contents)
328
self.fail("contents of %s not as expected" % filename)
330
def _make_test_root(self):
331
if TestCaseInTempDir.TEST_ROOT is not None:
335
root = 'test%04d.tmp' % i
339
if e.errno == errno.EEXIST:
344
# successfully created
345
TestCaseInTempDir.TEST_ROOT = os.path.abspath(root)
347
# make a fake bzr directory there to prevent any tests propagating
348
# up onto the source directory's real branch
349
os.mkdir(os.path.join(TestCaseInTempDir.TEST_ROOT, '.bzr'))
352
super(TestCaseInTempDir, self).setUp()
353
self._make_test_root()
354
self._currentdir = os.getcwdu()
355
short_id = self.id().replace('bzrlib.selftest.', '') \
356
.replace('__main__.', '')
357
self.test_dir = os.path.join(self.TEST_ROOT, short_id)
358
os.mkdir(self.test_dir)
359
os.chdir(self.test_dir)
362
os.chdir(self._currentdir)
363
super(TestCaseInTempDir, self).tearDown()
365
def build_tree(self, shape):
366
"""Build a test tree according to a pattern.
368
shape is a sequence of file specifications. If the final
369
character is '/', a directory is created.
371
This doesn't add anything to a branch.
373
# XXX: It's OK to just create them using forward slashes on windows?
375
assert isinstance(name, basestring)
380
print >>f, "contents of", name
383
def failUnlessExists(self, path):
384
"""Fail unless path, which may be abs or relative, exists."""
385
self.failUnless(os.path.exists(path))
388
class MetaTestLog(TestCase):
389
def test_logging(self):
390
"""Test logs are captured when a test fails."""
391
logging.info('an info message')
392
warning('something looks dodgy...')
393
logging.debug('hello, test is running')
397
def filter_suite_by_re(suite, pattern):
398
result = TestUtil.TestSuite()
399
filter_re = re.compile(pattern)
400
for test in iter_suite_tests(suite):
401
if filter_re.match(test.id()):
406
def filter_suite_by_names(suite, wanted_names):
407
"""Return a new suite containing only selected tests.
409
Names are considered to match if any name is a substring of the
410
fully-qualified test id (i.e. the class ."""
412
for test in iter_suite_tests(suite):
414
for p in wanted_names:
415
if this_id.find(p) != -1:
420
def run_suite(suite, name='test', verbose=False, pattern=".*", testnames=None):
421
TestCaseInTempDir._TEST_NAME = name
426
runner = TextTestRunner(stream=sys.stdout,
430
suite = filter_suite_by_names(suite, testnames)
432
suite = filter_suite_by_re(suite, pattern)
433
result = runner.run(suite)
434
# This is still a little bogus,
435
# but only a little. Folk not using our testrunner will
436
# have to delete their temp directories themselves.
437
if result.wasSuccessful():
438
if TestCaseInTempDir.TEST_ROOT is not None:
439
shutil.rmtree(TestCaseInTempDir.TEST_ROOT)
441
print "Failed tests working directories are in '%s'\n" % TestCaseInTempDir.TEST_ROOT
442
return result.wasSuccessful()
445
def selftest(verbose=False, pattern=".*", testnames=None):
446
"""Run the whole test suite under the enhanced runner"""
447
return run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern,
452
"""Build and return TestSuite for the whole program."""
453
import bzrlib.store, bzrlib.inventory, bzrlib.branch
454
import bzrlib.osutils, bzrlib.merge3, bzrlib.plugin
455
from doctest import DocTestSuite
457
global MODULES_TO_TEST, MODULES_TO_DOCTEST
460
['bzrlib.selftest.MetaTestLog',
461
'bzrlib.selftest.testinv',
462
'bzrlib.selftest.test_ancestry',
463
'bzrlib.selftest.test_commit',
464
'bzrlib.selftest.test_commit_merge',
465
'bzrlib.selftest.versioning',
466
'bzrlib.selftest.testmerge3',
467
'bzrlib.selftest.testmerge',
468
'bzrlib.selftest.testhashcache',
469
'bzrlib.selftest.teststatus',
470
'bzrlib.selftest.testlog',
471
'bzrlib.selftest.testrevisionnamespaces',
472
'bzrlib.selftest.testbranch',
473
'bzrlib.selftest.testrevision',
474
'bzrlib.selftest.test_revision_info',
475
'bzrlib.selftest.test_merge_core',
476
'bzrlib.selftest.test_smart_add',
477
'bzrlib.selftest.test_bad_files',
478
'bzrlib.selftest.testdiff',
479
'bzrlib.selftest.test_parent',
480
'bzrlib.selftest.test_xml',
481
'bzrlib.selftest.test_weave',
482
'bzrlib.selftest.testfetch',
483
'bzrlib.selftest.whitebox',
484
'bzrlib.selftest.teststore',
485
'bzrlib.selftest.blackbox',
486
'bzrlib.selftest.testtransport',
487
'bzrlib.selftest.testgraph',
488
'bzrlib.selftest.testworkingtree',
489
'bzrlib.selftest.test_upgrade',
492
for m in (bzrlib.store, bzrlib.inventory, bzrlib.branch,
493
bzrlib.osutils, bzrlib.commands, bzrlib.merge3):
494
if m not in MODULES_TO_DOCTEST:
495
MODULES_TO_DOCTEST.append(m)
497
TestCase.BZRPATH = os.path.join(os.path.realpath(os.path.dirname(bzrlib.__path__[0])), 'bzr')
498
print '%-30s %s' % ('bzr binary', TestCase.BZRPATH)
501
suite.addTest(TestLoader().loadTestsFromNames(testmod_names))
502
for m in MODULES_TO_TEST:
503
suite.addTest(TestLoader().loadTestsFromModule(m))
504
for m in (MODULES_TO_DOCTEST):
505
suite.addTest(DocTestSuite(m))
506
for p in bzrlib.plugin.all_plugins:
507
if hasattr(p, 'test_suite'):
508
suite.addTest(p.test_suite())