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
36
from bzrlib.selftest.treeshape import build_tree_contents
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):
124
stop_on_failure = False
126
def _makeResult(self):
127
result = _MyResult(self.stream, self.descriptions, self.verbosity)
128
if self.stop_on_failure:
129
result = EarlyStoppingTestResultAdapter(result)
133
def iter_suite_tests(suite):
134
"""Return all tests in a suite, recursing through nested suites"""
135
for item in suite._tests:
136
if isinstance(item, unittest.TestCase):
138
elif isinstance(item, unittest.TestSuite):
139
for r in iter_suite_tests(item):
142
raise Exception('unknown object %r inside test suite %r'
146
class TestSkipped(Exception):
147
"""Indicates that a test was intentionally skipped, rather than failing."""
151
class CommandFailed(Exception):
154
class TestCase(unittest.TestCase):
155
"""Base class for bzr unit tests.
157
Tests that need access to disk resources should subclass
158
TestCaseInTempDir not TestCase.
160
Error and debug log messages are redirected from their usual
161
location into a temporary file, the contents of which can be
162
retrieved by _get_log().
164
There are also convenience functions to invoke bzr's command-line
165
routine, and to build and check bzr trees."""
168
_log_file_name = None
171
unittest.TestCase.setUp(self)
172
bzrlib.trace.disable_default_logging()
173
self._enable_file_logging()
175
def _ndiff_strings(self, a, b):
176
"""Return ndiff between two strings containing lines."""
177
difflines = difflib.ndiff(a.splitlines(True),
179
linejunk=lambda x: False,
180
charjunk=lambda x: False)
181
return ''.join(difflines)
183
def assertEqualDiff(self, a, b):
184
"""Assert two texts are equal, if not raise an exception.
186
This is intended for use with multi-line strings where it can
187
be hard to find the differences by eye.
189
# TODO: perhaps override assertEquals to call this for strings?
192
raise AssertionError("texts not equal:\n" +
193
self._ndiff_strings(a, b))
195
def assertContainsRe(self, haystack, needle_re):
196
"""Assert that a contains something matching a regular expression."""
197
if not re.search(needle_re, haystack):
198
raise AssertionError('pattern "%s" not found in "%s"'
199
% (needle_re, haystack))
201
def _enable_file_logging(self):
202
fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
204
self._log_file = os.fdopen(fileno, 'w+')
206
hdlr = logging.StreamHandler(self._log_file)
207
hdlr.setLevel(logging.DEBUG)
208
hdlr.setFormatter(logging.Formatter('%(levelname)8s %(message)s'))
209
logging.getLogger('').addHandler(hdlr)
210
logging.getLogger('').setLevel(logging.DEBUG)
211
self._log_hdlr = hdlr
212
debug('opened log file %s', name)
214
self._log_file_name = name
217
logging.getLogger('').removeHandler(self._log_hdlr)
218
bzrlib.trace.enable_default_logging()
219
logging.debug('%s teardown', self.id())
220
self._log_file.close()
221
unittest.TestCase.tearDown(self)
223
def log(self, *args):
227
"""Return as a string the log for this test"""
228
if self._log_file_name:
229
return open(self._log_file_name).read()
233
def capture(self, cmd):
234
"""Shortcut that splits cmd into words, runs, and returns stdout"""
235
return self.run_bzr_captured(cmd.split())[0]
237
def run_bzr_captured(self, argv, retcode=0):
238
"""Invoke bzr and return (result, stdout, stderr).
240
Useful for code that wants to check the contents of the
241
output, the way error messages are presented, etc.
243
This should be the main method for tests that want to exercise the
244
overall behavior of the bzr application (rather than a unit test
245
or a functional test of the library.)
247
Much of the old code runs bzr by forking a new copy of Python, but
248
that is slower, harder to debug, and generally not necessary.
250
This runs bzr through the interface that catches and reports
251
errors, and with logging set to something approximating the
252
default, so that error reporting can be checked.
254
argv -- arguments to invoke bzr
255
retcode -- expected return code, or None for don't-care.
259
self.log('run bzr: %s', ' '.join(argv))
260
handler = logging.StreamHandler(stderr)
261
handler.setFormatter(bzrlib.trace.QuietFormatter())
262
handler.setLevel(logging.INFO)
263
logger = logging.getLogger('')
264
logger.addHandler(handler)
266
result = self.apply_redirected(None, stdout, stderr,
267
bzrlib.commands.run_bzr_catch_errors,
270
logger.removeHandler(handler)
271
out = stdout.getvalue()
272
err = stderr.getvalue()
274
self.log('output:\n%s', out)
276
self.log('errors:\n%s', err)
277
if retcode is not None:
278
self.assertEquals(result, retcode)
281
def run_bzr(self, *args, **kwargs):
282
"""Invoke bzr, as if it were run from the command line.
284
This should be the main method for tests that want to exercise the
285
overall behavior of the bzr application (rather than a unit test
286
or a functional test of the library.)
288
This sends the stdout/stderr results into the test's log,
289
where it may be useful for debugging. See also run_captured.
291
retcode = kwargs.pop('retcode', 0)
292
return self.run_bzr_captured(args, retcode)
294
def check_inventory_shape(self, inv, shape):
295
"""Compare an inventory to a list of expected names.
297
Fail if they are not precisely equal.
300
shape = list(shape) # copy
301
for path, ie in inv.entries():
302
name = path.replace('\\', '/')
310
self.fail("expected paths not found in inventory: %r" % shape)
312
self.fail("unexpected paths found in inventory: %r" % extras)
314
def apply_redirected(self, stdin=None, stdout=None, stderr=None,
315
a_callable=None, *args, **kwargs):
316
"""Call callable with redirected std io pipes.
318
Returns the return code."""
319
if not callable(a_callable):
320
raise ValueError("a_callable must be callable.")
324
if hasattr(self, "_log_file"):
325
stdout = self._log_file
329
if hasattr(self, "_log_file"):
330
stderr = self._log_file
333
real_stdin = sys.stdin
334
real_stdout = sys.stdout
335
real_stderr = sys.stderr
340
return a_callable(*args, **kwargs)
342
sys.stdout = real_stdout
343
sys.stderr = real_stderr
344
sys.stdin = real_stdin
347
BzrTestBase = TestCase
350
class TestCaseInTempDir(TestCase):
351
"""Derived class that runs a test within a temporary directory.
353
This is useful for tests that need to create a branch, etc.
355
The directory is created in a slightly complex way: for each
356
Python invocation, a new temporary top-level directory is created.
357
All test cases create their own directory within that. If the
358
tests complete successfully, the directory is removed.
360
InTempDir is an old alias for FunctionalTestCase.
365
OVERRIDE_PYTHON = 'python'
367
def check_file_contents(self, filename, expect):
368
self.log("check contents of file %s" % filename)
369
contents = file(filename, 'r').read()
370
if contents != expect:
371
self.log("expected: %r" % expect)
372
self.log("actually: %r" % contents)
373
self.fail("contents of %s not as expected" % filename)
375
def _make_test_root(self):
376
if TestCaseInTempDir.TEST_ROOT is not None:
380
root = 'test%04d.tmp' % i
384
if e.errno == errno.EEXIST:
389
# successfully created
390
TestCaseInTempDir.TEST_ROOT = os.path.abspath(root)
392
# make a fake bzr directory there to prevent any tests propagating
393
# up onto the source directory's real branch
394
os.mkdir(os.path.join(TestCaseInTempDir.TEST_ROOT, '.bzr'))
397
super(TestCaseInTempDir, self).setUp()
398
self._make_test_root()
399
self._currentdir = os.getcwdu()
400
short_id = self.id().replace('bzrlib.selftest.', '') \
401
.replace('__main__.', '')
402
self.test_dir = os.path.join(self.TEST_ROOT, short_id)
403
os.mkdir(self.test_dir)
404
os.chdir(self.test_dir)
407
os.chdir(self._currentdir)
408
super(TestCaseInTempDir, self).tearDown()
410
def build_tree(self, shape):
411
"""Build a test tree according to a pattern.
413
shape is a sequence of file specifications. If the final
414
character is '/', a directory is created.
416
This doesn't add anything to a branch.
418
# XXX: It's OK to just create them using forward slashes on windows?
420
assert isinstance(name, basestring)
425
print >>f, "contents of", name
428
def build_tree_contents(self, shape):
429
bzrlib.selftest.build_tree_contents(shape)
431
def failUnlessExists(self, path):
432
"""Fail unless path, which may be abs or relative, exists."""
433
self.failUnless(osutils.lexists(path))
436
class MetaTestLog(TestCase):
437
def test_logging(self):
438
"""Test logs are captured when a test fails."""
439
logging.info('an info message')
440
warning('something looks dodgy...')
441
logging.debug('hello, test is running')
445
def filter_suite_by_re(suite, pattern):
446
result = TestUtil.TestSuite()
447
filter_re = re.compile(pattern)
448
for test in iter_suite_tests(suite):
449
if filter_re.search(test.id()):
454
def run_suite(suite, name='test', verbose=False, pattern=".*",
455
stop_on_failure=False):
456
TestCaseInTempDir._TEST_NAME = name
461
runner = TextTestRunner(stream=sys.stdout,
464
runner.stop_on_failure=stop_on_failure
466
suite = filter_suite_by_re(suite, pattern)
467
result = runner.run(suite)
468
# This is still a little bogus,
469
# but only a little. Folk not using our testrunner will
470
# have to delete their temp directories themselves.
471
if result.wasSuccessful():
472
if TestCaseInTempDir.TEST_ROOT is not None:
473
shutil.rmtree(TestCaseInTempDir.TEST_ROOT)
475
print "Failed tests working directories are in '%s'\n" % TestCaseInTempDir.TEST_ROOT
476
return result.wasSuccessful()
479
def selftest(verbose=False, pattern=".*", stop_on_failure=True):
480
"""Run the whole test suite under the enhanced runner"""
481
return run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern,
482
stop_on_failure=stop_on_failure)
486
"""Build and return TestSuite for the whole program."""
487
import bzrlib.store, bzrlib.inventory, bzrlib.branch
488
import bzrlib.osutils, bzrlib.merge3, bzrlib.plugin
489
from doctest import DocTestSuite
491
global MODULES_TO_TEST, MODULES_TO_DOCTEST
494
['bzrlib.selftest.MetaTestLog',
495
'bzrlib.selftest.testidentitymap',
496
'bzrlib.selftest.testinv',
497
'bzrlib.selftest.test_ancestry',
498
'bzrlib.selftest.test_commit',
499
'bzrlib.selftest.test_commit_merge',
500
'bzrlib.selftest.testconfig',
501
'bzrlib.selftest.versioning',
502
'bzrlib.selftest.testmerge3',
503
'bzrlib.selftest.testmerge',
504
'bzrlib.selftest.testhashcache',
505
'bzrlib.selftest.teststatus',
506
'bzrlib.selftest.testlog',
507
'bzrlib.selftest.testrevisionnamespaces',
508
'bzrlib.selftest.testbranch',
509
'bzrlib.selftest.testrevision',
510
'bzrlib.selftest.test_revision_info',
511
'bzrlib.selftest.test_merge_core',
512
'bzrlib.selftest.test_smart_add',
513
'bzrlib.selftest.test_bad_files',
514
'bzrlib.selftest.testdiff',
515
'bzrlib.selftest.test_parent',
516
'bzrlib.selftest.test_xml',
517
'bzrlib.selftest.test_weave',
518
'bzrlib.selftest.testfetch',
519
'bzrlib.selftest.whitebox',
520
'bzrlib.selftest.teststore',
521
'bzrlib.selftest.blackbox',
522
'bzrlib.selftest.testsampler',
523
'bzrlib.selftest.testtransactions',
524
'bzrlib.selftest.testtransport',
525
'bzrlib.selftest.testgraph',
526
'bzrlib.selftest.testworkingtree',
527
'bzrlib.selftest.test_upgrade',
528
'bzrlib.selftest.test_conflicts',
529
'bzrlib.selftest.testtestament',
530
'bzrlib.selftest.testannotate',
531
'bzrlib.selftest.testrevprops',
532
'bzrlib.selftest.testoptions',
535
for m in (bzrlib.store, bzrlib.inventory, bzrlib.branch,
536
bzrlib.osutils, bzrlib.commands, bzrlib.merge3):
537
if m not in MODULES_TO_DOCTEST:
538
MODULES_TO_DOCTEST.append(m)
540
TestCase.BZRPATH = os.path.join(os.path.realpath(os.path.dirname(bzrlib.__path__[0])), 'bzr')
541
print '%-30s %s' % ('bzr binary', TestCase.BZRPATH)
544
suite.addTest(TestLoader().loadTestsFromNames(testmod_names))
545
for m in MODULES_TO_TEST:
546
suite.addTest(TestLoader().loadTestsFromModule(m))
547
for m in (MODULES_TO_DOCTEST):
548
suite.addTest(DocTestSuite(m))
549
for p in bzrlib.plugin.all_plugins:
550
if hasattr(p, 'test_suite'):
551
suite.addTest(p.test_suite())