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
32
import bzrlib.osutils as osutils
33
from bzrlib.trace import mutter
34
from bzrlib.tests.TestUtil import TestLoader, TestSuite
35
from bzrlib.tests.treeshape import build_tree_contents
36
from bzrlib.errors import BzrError
39
MODULES_TO_DOCTEST = []
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):
69
Shows output in a different format, including displaying runtime for tests.
72
# assumes 80-column window, less 'ERROR 99999ms' = 13ch
73
def _elapsedTime(self):
74
return "%5dms" % (1000 * (time.time() - self._start_time))
76
def startTest(self, test):
77
unittest.TestResult.startTest(self, test)
78
# In a short description, the important words are in
79
# the beginning, but in an id, the important words are
81
SHOW_DESCRIPTIONS = False
82
what = SHOW_DESCRIPTIONS and test.shortDescription()
85
what = what[:62] + '...'
88
if what.startswith('bzrlib.tests.'):
91
what = '...' + what[-62:]
93
self.stream.write('%-65.65s' % what)
95
self._start_time = time.time()
97
def addError(self, test, err):
98
unittest.TestResult.addError(self, test, err)
100
self.stream.writeln("ERROR %s" % self._elapsedTime())
102
self.stream.write('E')
105
def addFailure(self, test, err):
106
unittest.TestResult.addFailure(self, test, err)
108
self.stream.writeln(" FAIL %s" % self._elapsedTime())
110
self.stream.write('F')
113
def addSuccess(self, test):
115
self.stream.writeln(' OK %s' % self._elapsedTime())
117
self.stream.write('~')
119
unittest.TestResult.addSuccess(self, test)
121
def printErrorList(self, flavour, errors):
122
for test, err in errors:
123
self.stream.writeln(self.separator1)
124
self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
125
if hasattr(test, '_get_log'):
126
self.stream.writeln()
127
self.stream.writeln('log from this test:')
128
print >>self.stream, test._get_log()
129
self.stream.writeln(self.separator2)
130
self.stream.writeln("%s" % err)
133
class TextTestRunner(unittest.TextTestRunner):
134
stop_on_failure = False
136
def _makeResult(self):
137
result = _MyResult(self.stream, self.descriptions, self.verbosity)
138
if self.stop_on_failure:
139
result = EarlyStoppingTestResultAdapter(result)
143
def iter_suite_tests(suite):
144
"""Return all tests in a suite, recursing through nested suites"""
145
for item in suite._tests:
146
if isinstance(item, unittest.TestCase):
148
elif isinstance(item, unittest.TestSuite):
149
for r in iter_suite_tests(item):
152
raise Exception('unknown object %r inside test suite %r'
156
class TestSkipped(Exception):
157
"""Indicates that a test was intentionally skipped, rather than failing."""
161
class CommandFailed(Exception):
164
class TestCase(unittest.TestCase):
165
"""Base class for bzr unit tests.
167
Tests that need access to disk resources should subclass
168
TestCaseInTempDir not TestCase.
170
Error and debug log messages are redirected from their usual
171
location into a temporary file, the contents of which can be
172
retrieved by _get_log(). We use a real OS file, not an in-memory object,
173
so that it can also capture file IO. When the test completes this file
174
is read into memory and removed from disk.
176
There are also convenience functions to invoke bzr's command-line
177
routine, and to build and check bzr trees.
179
In addition to the usual method of overriding tearDown(), this class also
180
allows subclasses to register functions into the _cleanups list, which is
181
run in order as the object is torn down. It's less likely this will be
182
accidentally overlooked.
186
_log_file_name = None
190
unittest.TestCase.setUp(self)
192
self._cleanEnvironment()
193
bzrlib.trace.disable_default_logging()
196
def _ndiff_strings(self, a, b):
197
"""Return ndiff between two strings containing lines.
199
A trailing newline is added if missing to make the strings
201
if b and b[-1] != '\n':
203
if a and a[-1] != '\n':
205
difflines = difflib.ndiff(a.splitlines(True),
207
linejunk=lambda x: False,
208
charjunk=lambda x: False)
209
return ''.join(difflines)
211
def assertEqualDiff(self, a, b):
212
"""Assert two texts are equal, if not raise an exception.
214
This is intended for use with multi-line strings where it can
215
be hard to find the differences by eye.
217
# TODO: perhaps override assertEquals to call this for strings?
220
raise AssertionError("texts not equal:\n" +
221
self._ndiff_strings(a, b))
223
def assertContainsRe(self, haystack, needle_re):
224
"""Assert that a contains something matching a regular expression."""
225
if not re.search(needle_re, haystack):
226
raise AssertionError('pattern "%s" not found in "%s"'
227
% (needle_re, haystack))
229
def _startLogFile(self):
230
"""Send bzr and test log messages to a temporary file.
232
The file is removed as the test is torn down.
234
fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
235
self._log_file = os.fdopen(fileno, 'w+')
236
bzrlib.trace.enable_test_log(self._log_file)
237
self._log_file_name = name
238
self.addCleanup(self._finishLogFile)
240
def _finishLogFile(self):
241
"""Finished with the log file.
243
Read contents into memory, close, and delete.
245
bzrlib.trace.disable_test_log()
246
self._log_file.seek(0)
247
self._log_contents = self._log_file.read()
248
self._log_file.close()
249
os.remove(self._log_file_name)
250
self._log_file = self._log_file_name = None
252
def addCleanup(self, callable):
253
"""Arrange to run a callable when this case is torn down.
255
Callables are run in the reverse of the order they are registered,
256
ie last-in first-out.
258
if callable in self._cleanups:
259
raise ValueError("cleanup function %r already registered on %s"
261
self._cleanups.append(callable)
263
def _cleanEnvironment(self):
266
'APPDATA': os.getcwd(),
271
self.addCleanup(self._restoreEnvironment)
272
for name, value in new_env.iteritems():
273
self._captureVar(name, value)
276
def _captureVar(self, name, newvalue):
277
"""Set an environment variable, preparing it to be reset when finished."""
278
self.__old_env[name] = os.environ.get(name, None)
280
if name in os.environ:
283
os.environ[name] = newvalue
286
def _restoreVar(name, value):
288
if name in os.environ:
291
os.environ[name] = value
293
def _restoreEnvironment(self):
294
for name, value in self.__old_env.iteritems():
295
self._restoreVar(name, value)
299
unittest.TestCase.tearDown(self)
301
def _runCleanups(self):
302
"""Run registered cleanup functions.
304
This should only be called from TestCase.tearDown.
306
for callable in reversed(self._cleanups):
309
def log(self, *args):
313
"""Return as a string the log for this test"""
314
if self._log_file_name:
315
return open(self._log_file_name).read()
317
return self._log_contents
318
# TODO: Delete the log after it's been read in
320
def capture(self, cmd, retcode=0):
321
"""Shortcut that splits cmd into words, runs, and returns stdout"""
322
return self.run_bzr_captured(cmd.split(), retcode=retcode)[0]
324
def run_bzr_captured(self, argv, retcode=0):
325
"""Invoke bzr and return (stdout, stderr).
327
Useful for code that wants to check the contents of the
328
output, the way error messages are presented, etc.
330
This should be the main method for tests that want to exercise the
331
overall behavior of the bzr application (rather than a unit test
332
or a functional test of the library.)
334
Much of the old code runs bzr by forking a new copy of Python, but
335
that is slower, harder to debug, and generally not necessary.
337
This runs bzr through the interface that catches and reports
338
errors, and with logging set to something approximating the
339
default, so that error reporting can be checked.
341
argv -- arguments to invoke bzr
342
retcode -- expected return code, or None for don't-care.
346
self.log('run bzr: %s', ' '.join(argv))
347
# FIXME: don't call into logging here
348
handler = logging.StreamHandler(stderr)
349
handler.setFormatter(bzrlib.trace.QuietFormatter())
350
handler.setLevel(logging.INFO)
351
logger = logging.getLogger('')
352
logger.addHandler(handler)
354
result = self.apply_redirected(None, stdout, stderr,
355
bzrlib.commands.run_bzr_catch_errors,
358
logger.removeHandler(handler)
359
out = stdout.getvalue()
360
err = stderr.getvalue()
362
self.log('output:\n%s', out)
364
self.log('errors:\n%s', err)
365
if retcode is not None:
366
self.assertEquals(result, retcode)
369
def run_bzr(self, *args, **kwargs):
370
"""Invoke bzr, as if it were run from the command line.
372
This should be the main method for tests that want to exercise the
373
overall behavior of the bzr application (rather than a unit test
374
or a functional test of the library.)
376
This sends the stdout/stderr results into the test's log,
377
where it may be useful for debugging. See also run_captured.
379
retcode = kwargs.pop('retcode', 0)
380
return self.run_bzr_captured(args, retcode)
382
def check_inventory_shape(self, inv, shape):
383
"""Compare an inventory to a list of expected names.
385
Fail if they are not precisely equal.
388
shape = list(shape) # copy
389
for path, ie in inv.entries():
390
name = path.replace('\\', '/')
398
self.fail("expected paths not found in inventory: %r" % shape)
400
self.fail("unexpected paths found in inventory: %r" % extras)
402
def apply_redirected(self, stdin=None, stdout=None, stderr=None,
403
a_callable=None, *args, **kwargs):
404
"""Call callable with redirected std io pipes.
406
Returns the return code."""
407
if not callable(a_callable):
408
raise ValueError("a_callable must be callable.")
412
if hasattr(self, "_log_file"):
413
stdout = self._log_file
417
if hasattr(self, "_log_file"):
418
stderr = self._log_file
421
real_stdin = sys.stdin
422
real_stdout = sys.stdout
423
real_stderr = sys.stderr
428
return a_callable(*args, **kwargs)
430
sys.stdout = real_stdout
431
sys.stderr = real_stderr
432
sys.stdin = real_stdin
435
BzrTestBase = TestCase
438
class TestCaseInTempDir(TestCase):
439
"""Derived class that runs a test within a temporary directory.
441
This is useful for tests that need to create a branch, etc.
443
The directory is created in a slightly complex way: for each
444
Python invocation, a new temporary top-level directory is created.
445
All test cases create their own directory within that. If the
446
tests complete successfully, the directory is removed.
448
InTempDir is an old alias for FunctionalTestCase.
453
OVERRIDE_PYTHON = 'python'
455
def check_file_contents(self, filename, expect):
456
self.log("check contents of file %s" % filename)
457
contents = file(filename, 'r').read()
458
if contents != expect:
459
self.log("expected: %r" % expect)
460
self.log("actually: %r" % contents)
461
self.fail("contents of %s not as expected" % filename)
463
def _make_test_root(self):
464
if TestCaseInTempDir.TEST_ROOT is not None:
468
root = u'test%04d.tmp' % i
472
if e.errno == errno.EEXIST:
477
# successfully created
478
TestCaseInTempDir.TEST_ROOT = os.path.abspath(root)
480
# make a fake bzr directory there to prevent any tests propagating
481
# up onto the source directory's real branch
482
os.mkdir(os.path.join(TestCaseInTempDir.TEST_ROOT, '.bzr'))
485
super(TestCaseInTempDir, self).setUp()
486
self._make_test_root()
487
_currentdir = os.getcwdu()
488
short_id = self.id().replace('bzrlib.tests.', '') \
489
.replace('__main__.', '')
490
self.test_dir = os.path.join(self.TEST_ROOT, short_id)
491
os.mkdir(self.test_dir)
492
os.chdir(self.test_dir)
493
os.environ['HOME'] = self.test_dir
494
def _leaveDirectory():
495
os.chdir(_currentdir)
496
self.addCleanup(_leaveDirectory)
498
def build_tree(self, shape, line_endings='native'):
499
"""Build a test tree according to a pattern.
501
shape is a sequence of file specifications. If the final
502
character is '/', a directory is created.
504
This doesn't add anything to a branch.
505
:param line_endings: Either 'binary' or 'native'
506
in binary mode, exact contents are written
507
in native mode, the line endings match the
508
default platform endings.
510
# XXX: It's OK to just create them using forward slashes on windows?
512
self.assert_(isinstance(name, basestring))
516
if line_endings == 'binary':
518
elif line_endings == 'native':
521
raise BzrError('Invalid line ending request %r' % (line_endings,))
522
print >>f, "contents of", name
525
def build_tree_contents(self, shape):
526
bzrlib.tests.build_tree_contents(shape)
528
def failUnlessExists(self, path):
529
"""Fail unless path, which may be abs or relative, exists."""
530
self.failUnless(osutils.lexists(path))
532
def assertFileEqual(self, content, path):
533
"""Fail if path does not contain 'content'."""
534
self.failUnless(osutils.lexists(path))
535
self.assertEqualDiff(content, open(path, 'r').read())
538
class MetaTestLog(TestCase):
539
def test_logging(self):
540
"""Test logs are captured when a test fails."""
541
self.log('a test message')
542
self._log_file.flush()
543
self.assertContainsRe(self._get_log(), 'a test message\n')
546
def filter_suite_by_re(suite, pattern):
547
result = TestUtil.TestSuite()
548
filter_re = re.compile(pattern)
549
for test in iter_suite_tests(suite):
550
if filter_re.search(test.id()):
555
def run_suite(suite, name='test', verbose=False, pattern=".*",
556
stop_on_failure=False, keep_output=False):
557
TestCaseInTempDir._TEST_NAME = name
562
runner = TextTestRunner(stream=sys.stdout,
565
runner.stop_on_failure=stop_on_failure
567
suite = filter_suite_by_re(suite, pattern)
568
result = runner.run(suite)
569
# This is still a little bogus,
570
# but only a little. Folk not using our testrunner will
571
# have to delete their temp directories themselves.
572
if result.wasSuccessful() or not keep_output:
573
if TestCaseInTempDir.TEST_ROOT is not None:
574
shutil.rmtree(TestCaseInTempDir.TEST_ROOT)
576
print "Failed tests working directories are in '%s'\n" % TestCaseInTempDir.TEST_ROOT
577
return result.wasSuccessful()
580
def selftest(verbose=False, pattern=".*", stop_on_failure=True,
582
"""Run the whole test suite under the enhanced runner"""
583
return run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern,
584
stop_on_failure=stop_on_failure, keep_output=keep_output)
588
"""Build and return TestSuite for the whole program."""
589
import bzrlib.store, bzrlib.inventory, bzrlib.branch
590
import bzrlib.osutils, bzrlib.merge3, bzrlib.plugin
591
from doctest import DocTestSuite
593
global MODULES_TO_TEST, MODULES_TO_DOCTEST
595
# FIXME: If these fail to load, e.g. because of a syntax error, the
596
# exception is hidden by unittest. Sucks. Should either fix that or
597
# perhaps import them and pass them to unittest as modules.
599
['bzrlib.tests.MetaTestLog',
600
'bzrlib.tests.test_api',
601
'bzrlib.tests.test_gpg',
602
'bzrlib.tests.test_identitymap',
603
'bzrlib.tests.test_inv',
604
'bzrlib.tests.test_ancestry',
605
'bzrlib.tests.test_commit',
606
'bzrlib.tests.test_command',
607
'bzrlib.tests.test_commit_merge',
608
'bzrlib.tests.test_config',
609
'bzrlib.tests.test_merge3',
610
'bzrlib.tests.test_merge',
611
'bzrlib.tests.test_hashcache',
612
'bzrlib.tests.test_status',
613
'bzrlib.tests.test_log',
614
'bzrlib.tests.test_revisionnamespaces',
615
'bzrlib.tests.test_branch',
616
'bzrlib.tests.test_revision',
617
'bzrlib.tests.test_revision_info',
618
'bzrlib.tests.test_merge_core',
619
'bzrlib.tests.test_smart_add',
620
'bzrlib.tests.test_bad_files',
621
'bzrlib.tests.test_diff',
622
'bzrlib.tests.test_parent',
623
'bzrlib.tests.test_xml',
624
'bzrlib.tests.test_weave',
625
'bzrlib.tests.test_fetch',
626
'bzrlib.tests.test_whitebox',
627
'bzrlib.tests.test_store',
628
'bzrlib.tests.blackbox',
629
'bzrlib.tests.blackbox.versioning',
630
'bzrlib.tests.test_sampler',
631
'bzrlib.tests.test_transactions',
632
'bzrlib.tests.test_transport',
633
'bzrlib.tests.test_sftp',
634
'bzrlib.tests.test_graph',
635
'bzrlib.tests.test_workingtree',
636
'bzrlib.tests.test_upgrade',
637
'bzrlib.tests.test_uncommit',
638
'bzrlib.tests.test_conflicts',
639
'bzrlib.tests.test_testament',
640
'bzrlib.tests.test_annotate',
641
'bzrlib.tests.test_revprops',
642
'bzrlib.tests.test_options',
643
'bzrlib.tests.test_http',
644
'bzrlib.tests.test_nonascii',
645
'bzrlib.tests.test_reweave',
646
'bzrlib.tests.test_tsort',
647
'bzrlib.tests.test_trace',
648
'bzrlib.tests.test_basicio',
651
for m in (bzrlib.store, bzrlib.inventory, bzrlib.branch,
652
bzrlib.osutils, bzrlib.commands, bzrlib.merge3,
655
if m not in MODULES_TO_DOCTEST:
656
MODULES_TO_DOCTEST.append(m)
658
TestCase.BZRPATH = os.path.join(os.path.realpath(os.path.dirname(bzrlib.__path__[0])), 'bzr')
659
print '%-30s %s' % ('bzr binary', TestCase.BZRPATH)
662
suite.addTest(TestLoader().loadTestsFromNames(testmod_names))
663
for m in MODULES_TO_TEST:
664
suite.addTest(TestLoader().loadTestsFromModule(m))
665
for m in (MODULES_TO_DOCTEST):
666
suite.addTest(DocTestSuite(m))
667
for p in bzrlib.plugin.all_plugins:
668
if hasattr(p, 'test_suite'):
669
suite.addTest(p.test_suite())