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
31
import bzrlib.commands
32
from bzrlib.errors import BzrError
33
import bzrlib.inventory
36
import bzrlib.osutils as osutils
40
from bzrlib.trace import mutter
41
from bzrlib.tests.TestUtil import TestLoader, TestSuite
42
from bzrlib.tests.treeshape import build_tree_contents
45
MODULES_TO_DOCTEST = [
54
def packages_to_test():
55
import bzrlib.tests.blackbox
61
class EarlyStoppingTestResultAdapter(object):
62
"""An adapter for TestResult to stop at the first first failure or error"""
64
def __init__(self, result):
67
def addError(self, test, err):
68
self._result.addError(test, err)
71
def addFailure(self, test, err):
72
self._result.addFailure(test, err)
75
def __getattr__(self, name):
76
return getattr(self._result, name)
78
def __setattr__(self, name, value):
80
object.__setattr__(self, name, value)
81
return setattr(self._result, name, value)
84
class _MyResult(unittest._TextTestResult):
87
Shows output in a different format, including displaying runtime for tests.
90
# assumes 80-column window, less 'ERROR 99999ms' = 13ch
91
def _elapsedTime(self):
92
return "%5dms" % (1000 * (time.time() - self._start_time))
94
def startTest(self, test):
95
unittest.TestResult.startTest(self, test)
96
# In a short description, the important words are in
97
# the beginning, but in an id, the important words are
99
SHOW_DESCRIPTIONS = False
100
what = SHOW_DESCRIPTIONS and test.shortDescription()
103
what = what[:62] + '...'
106
if what.startswith('bzrlib.tests.'):
109
what = '...' + what[-62:]
111
self.stream.write('%-65.65s' % what)
113
self._start_time = time.time()
115
def addError(self, test, err):
116
unittest.TestResult.addError(self, test, err)
118
self.stream.writeln("ERROR %s" % self._elapsedTime())
120
self.stream.write('E')
123
def addFailure(self, test, err):
124
unittest.TestResult.addFailure(self, test, err)
126
self.stream.writeln(" FAIL %s" % self._elapsedTime())
128
self.stream.write('F')
131
def addSuccess(self, test):
133
self.stream.writeln(' OK %s' % self._elapsedTime())
135
self.stream.write('~')
137
unittest.TestResult.addSuccess(self, test)
139
def printErrorList(self, flavour, errors):
140
for test, err in errors:
141
self.stream.writeln(self.separator1)
142
self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
143
if hasattr(test, '_get_log'):
144
self.stream.writeln()
145
self.stream.writeln('log from this test:')
146
print >>self.stream, test._get_log()
147
self.stream.writeln(self.separator2)
148
self.stream.writeln("%s" % err)
151
class TextTestRunner(unittest.TextTestRunner):
152
stop_on_failure = False
154
def _makeResult(self):
155
result = _MyResult(self.stream, self.descriptions, self.verbosity)
156
if self.stop_on_failure:
157
result = EarlyStoppingTestResultAdapter(result)
161
def iter_suite_tests(suite):
162
"""Return all tests in a suite, recursing through nested suites"""
163
for item in suite._tests:
164
if isinstance(item, unittest.TestCase):
166
elif isinstance(item, unittest.TestSuite):
167
for r in iter_suite_tests(item):
170
raise Exception('unknown object %r inside test suite %r'
174
class TestSkipped(Exception):
175
"""Indicates that a test was intentionally skipped, rather than failing."""
179
class CommandFailed(Exception):
182
class TestCase(unittest.TestCase):
183
"""Base class for bzr unit tests.
185
Tests that need access to disk resources should subclass
186
TestCaseInTempDir not TestCase.
188
Error and debug log messages are redirected from their usual
189
location into a temporary file, the contents of which can be
190
retrieved by _get_log(). We use a real OS file, not an in-memory object,
191
so that it can also capture file IO. When the test completes this file
192
is read into memory and removed from disk.
194
There are also convenience functions to invoke bzr's command-line
195
routine, and to build and check bzr trees.
197
In addition to the usual method of overriding tearDown(), this class also
198
allows subclasses to register functions into the _cleanups list, which is
199
run in order as the object is torn down. It's less likely this will be
200
accidentally overlooked.
204
_log_file_name = None
208
unittest.TestCase.setUp(self)
210
self._cleanEnvironment()
211
bzrlib.trace.disable_default_logging()
214
def _ndiff_strings(self, a, b):
215
"""Return ndiff between two strings containing lines.
217
A trailing newline is added if missing to make the strings
219
if b and b[-1] != '\n':
221
if a and a[-1] != '\n':
223
difflines = difflib.ndiff(a.splitlines(True),
225
linejunk=lambda x: False,
226
charjunk=lambda x: False)
227
return ''.join(difflines)
229
def assertEqualDiff(self, a, b):
230
"""Assert two texts are equal, if not raise an exception.
232
This is intended for use with multi-line strings where it can
233
be hard to find the differences by eye.
235
# TODO: perhaps override assertEquals to call this for strings?
238
raise AssertionError("texts not equal:\n" +
239
self._ndiff_strings(a, b))
241
def assertContainsRe(self, haystack, needle_re):
242
"""Assert that a contains something matching a regular expression."""
243
if not re.search(needle_re, haystack):
244
raise AssertionError('pattern "%s" not found in "%s"'
245
% (needle_re, haystack))
247
def _startLogFile(self):
248
"""Send bzr and test log messages to a temporary file.
250
The file is removed as the test is torn down.
252
fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
253
self._log_file = os.fdopen(fileno, 'w+')
254
bzrlib.trace.enable_test_log(self._log_file)
255
self._log_file_name = name
256
self.addCleanup(self._finishLogFile)
258
def _finishLogFile(self):
259
"""Finished with the log file.
261
Read contents into memory, close, and delete.
263
bzrlib.trace.disable_test_log()
264
self._log_file.seek(0)
265
self._log_contents = self._log_file.read()
266
self._log_file.close()
267
os.remove(self._log_file_name)
268
self._log_file = self._log_file_name = None
270
def addCleanup(self, callable):
271
"""Arrange to run a callable when this case is torn down.
273
Callables are run in the reverse of the order they are registered,
274
ie last-in first-out.
276
if callable in self._cleanups:
277
raise ValueError("cleanup function %r already registered on %s"
279
self._cleanups.append(callable)
281
def _cleanEnvironment(self):
284
'APPDATA': os.getcwd(),
289
self.addCleanup(self._restoreEnvironment)
290
for name, value in new_env.iteritems():
291
self._captureVar(name, value)
294
def _captureVar(self, name, newvalue):
295
"""Set an environment variable, preparing it to be reset when finished."""
296
self.__old_env[name] = os.environ.get(name, None)
298
if name in os.environ:
301
os.environ[name] = newvalue
304
def _restoreVar(name, value):
306
if name in os.environ:
309
os.environ[name] = value
311
def _restoreEnvironment(self):
312
for name, value in self.__old_env.iteritems():
313
self._restoreVar(name, value)
317
unittest.TestCase.tearDown(self)
319
def _runCleanups(self):
320
"""Run registered cleanup functions.
322
This should only be called from TestCase.tearDown.
324
for callable in reversed(self._cleanups):
327
def log(self, *args):
331
"""Return as a string the log for this test"""
332
if self._log_file_name:
333
return open(self._log_file_name).read()
335
return self._log_contents
336
# TODO: Delete the log after it's been read in
338
def capture(self, cmd, retcode=0):
339
"""Shortcut that splits cmd into words, runs, and returns stdout"""
340
return self.run_bzr_captured(cmd.split(), retcode=retcode)[0]
342
def run_bzr_captured(self, argv, retcode=0):
343
"""Invoke bzr and return (stdout, stderr).
345
Useful for code that wants to check the contents of the
346
output, the way error messages are presented, etc.
348
This should be the main method for tests that want to exercise the
349
overall behavior of the bzr application (rather than a unit test
350
or a functional test of the library.)
352
Much of the old code runs bzr by forking a new copy of Python, but
353
that is slower, harder to debug, and generally not necessary.
355
This runs bzr through the interface that catches and reports
356
errors, and with logging set to something approximating the
357
default, so that error reporting can be checked.
359
argv -- arguments to invoke bzr
360
retcode -- expected return code, or None for don't-care.
364
self.log('run bzr: %s', ' '.join(argv))
365
# FIXME: don't call into logging here
366
handler = logging.StreamHandler(stderr)
367
handler.setFormatter(bzrlib.trace.QuietFormatter())
368
handler.setLevel(logging.INFO)
369
logger = logging.getLogger('')
370
logger.addHandler(handler)
372
result = self.apply_redirected(None, stdout, stderr,
373
bzrlib.commands.run_bzr_catch_errors,
376
logger.removeHandler(handler)
377
out = stdout.getvalue()
378
err = stderr.getvalue()
380
self.log('output:\n%s', out)
382
self.log('errors:\n%s', err)
383
if retcode is not None:
384
self.assertEquals(result, retcode)
387
def run_bzr(self, *args, **kwargs):
388
"""Invoke bzr, as if it were run from the command line.
390
This should be the main method for tests that want to exercise the
391
overall behavior of the bzr application (rather than a unit test
392
or a functional test of the library.)
394
This sends the stdout/stderr results into the test's log,
395
where it may be useful for debugging. See also run_captured.
397
retcode = kwargs.pop('retcode', 0)
398
return self.run_bzr_captured(args, retcode)
400
def check_inventory_shape(self, inv, shape):
401
"""Compare an inventory to a list of expected names.
403
Fail if they are not precisely equal.
406
shape = list(shape) # copy
407
for path, ie in inv.entries():
408
name = path.replace('\\', '/')
416
self.fail("expected paths not found in inventory: %r" % shape)
418
self.fail("unexpected paths found in inventory: %r" % extras)
420
def apply_redirected(self, stdin=None, stdout=None, stderr=None,
421
a_callable=None, *args, **kwargs):
422
"""Call callable with redirected std io pipes.
424
Returns the return code."""
425
if not callable(a_callable):
426
raise ValueError("a_callable must be callable.")
430
if hasattr(self, "_log_file"):
431
stdout = self._log_file
435
if hasattr(self, "_log_file"):
436
stderr = self._log_file
439
real_stdin = sys.stdin
440
real_stdout = sys.stdout
441
real_stderr = sys.stderr
446
return a_callable(*args, **kwargs)
448
sys.stdout = real_stdout
449
sys.stderr = real_stderr
450
sys.stdin = real_stdin
453
BzrTestBase = TestCase
456
class TestCaseInTempDir(TestCase):
457
"""Derived class that runs a test within a temporary directory.
459
This is useful for tests that need to create a branch, etc.
461
The directory is created in a slightly complex way: for each
462
Python invocation, a new temporary top-level directory is created.
463
All test cases create their own directory within that. If the
464
tests complete successfully, the directory is removed.
466
InTempDir is an old alias for FunctionalTestCase.
471
OVERRIDE_PYTHON = 'python'
473
def check_file_contents(self, filename, expect):
474
self.log("check contents of file %s" % filename)
475
contents = file(filename, 'r').read()
476
if contents != expect:
477
self.log("expected: %r" % expect)
478
self.log("actually: %r" % contents)
479
self.fail("contents of %s not as expected" % filename)
481
def _make_test_root(self):
482
if TestCaseInTempDir.TEST_ROOT is not None:
486
root = u'test%04d.tmp' % i
490
if e.errno == errno.EEXIST:
495
# successfully created
496
TestCaseInTempDir.TEST_ROOT = os.path.abspath(root)
498
# make a fake bzr directory there to prevent any tests propagating
499
# up onto the source directory's real branch
500
os.mkdir(os.path.join(TestCaseInTempDir.TEST_ROOT, '.bzr'))
503
super(TestCaseInTempDir, self).setUp()
504
self._make_test_root()
505
_currentdir = os.getcwdu()
506
short_id = self.id().replace('bzrlib.tests.', '') \
507
.replace('__main__.', '')
508
self.test_dir = os.path.join(self.TEST_ROOT, short_id)
509
os.mkdir(self.test_dir)
510
os.chdir(self.test_dir)
511
os.environ['HOME'] = self.test_dir
512
def _leaveDirectory():
513
os.chdir(_currentdir)
514
self.addCleanup(_leaveDirectory)
516
def build_tree(self, shape, line_endings='native'):
517
"""Build a test tree according to a pattern.
519
shape is a sequence of file specifications. If the final
520
character is '/', a directory is created.
522
This doesn't add anything to a branch.
523
:param line_endings: Either 'binary' or 'native'
524
in binary mode, exact contents are written
525
in native mode, the line endings match the
526
default platform endings.
528
# XXX: It's OK to just create them using forward slashes on windows?
530
self.assert_(isinstance(name, basestring))
534
if line_endings == 'binary':
536
elif line_endings == 'native':
539
raise BzrError('Invalid line ending request %r' % (line_endings,))
540
print >>f, "contents of", name
543
def build_tree_contents(self, shape):
544
build_tree_contents(shape)
546
def failUnlessExists(self, path):
547
"""Fail unless path, which may be abs or relative, exists."""
548
self.failUnless(osutils.lexists(path))
550
def assertFileEqual(self, content, path):
551
"""Fail if path does not contain 'content'."""
552
self.failUnless(osutils.lexists(path))
553
self.assertEqualDiff(content, open(path, 'r').read())
556
class MetaTestLog(TestCase):
557
def test_logging(self):
558
"""Test logs are captured when a test fails."""
559
self.log('a test message')
560
self._log_file.flush()
561
self.assertContainsRe(self._get_log(), 'a test message\n')
564
def filter_suite_by_re(suite, pattern):
565
result = TestUtil.TestSuite()
566
filter_re = re.compile(pattern)
567
for test in iter_suite_tests(suite):
568
if filter_re.search(test.id()):
573
def run_suite(suite, name='test', verbose=False, pattern=".*",
574
stop_on_failure=False, keep_output=False):
575
TestCaseInTempDir._TEST_NAME = name
580
runner = TextTestRunner(stream=sys.stdout,
583
runner.stop_on_failure=stop_on_failure
585
suite = filter_suite_by_re(suite, pattern)
586
result = runner.run(suite)
587
# This is still a little bogus,
588
# but only a little. Folk not using our testrunner will
589
# have to delete their temp directories themselves.
590
if result.wasSuccessful() or not keep_output:
591
if TestCaseInTempDir.TEST_ROOT is not None:
592
shutil.rmtree(TestCaseInTempDir.TEST_ROOT)
594
print "Failed tests working directories are in '%s'\n" % TestCaseInTempDir.TEST_ROOT
595
return result.wasSuccessful()
598
def selftest(verbose=False, pattern=".*", stop_on_failure=True,
600
"""Run the whole test suite under the enhanced runner"""
601
return run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern,
602
stop_on_failure=stop_on_failure, keep_output=keep_output)
606
"""Build and return TestSuite for the whole program."""
607
from doctest import DocTestSuite
609
global MODULES_TO_DOCTEST
611
# FIXME: If these fail to load, e.g. because of a syntax error, the
612
# exception is hidden by unittest. Sucks. Should either fix that or
613
# perhaps import them and pass them to unittest as modules.
615
['bzrlib.tests.MetaTestLog',
616
'bzrlib.tests.test_api',
617
'bzrlib.tests.test_basicio',
618
'bzrlib.tests.test_gpg',
619
'bzrlib.tests.test_identitymap',
620
'bzrlib.tests.test_inv',
621
'bzrlib.tests.test_ancestry',
622
'bzrlib.tests.test_commit',
623
'bzrlib.tests.test_command',
624
'bzrlib.tests.test_commit_merge',
625
'bzrlib.tests.test_config',
626
'bzrlib.tests.test_merge3',
627
'bzrlib.tests.test_merge',
628
'bzrlib.tests.test_hashcache',
629
'bzrlib.tests.test_status',
630
'bzrlib.tests.test_log',
631
'bzrlib.tests.test_revisionnamespaces',
632
'bzrlib.tests.test_branch',
633
'bzrlib.tests.test_revision',
634
'bzrlib.tests.test_revision_info',
635
'bzrlib.tests.test_merge_core',
636
'bzrlib.tests.test_smart_add',
637
'bzrlib.tests.test_bad_files',
638
'bzrlib.tests.test_diff',
639
'bzrlib.tests.test_parent',
640
'bzrlib.tests.test_xml',
641
'bzrlib.tests.test_weave',
642
'bzrlib.tests.test_fetch',
643
'bzrlib.tests.test_whitebox',
644
'bzrlib.tests.test_store',
645
'bzrlib.tests.test_sampler',
646
'bzrlib.tests.test_transactions',
647
'bzrlib.tests.test_transport',
648
'bzrlib.tests.test_sftp',
649
'bzrlib.tests.test_graph',
650
'bzrlib.tests.test_workingtree',
651
'bzrlib.tests.test_upgrade',
652
'bzrlib.tests.test_uncommit',
653
'bzrlib.tests.test_conflicts',
654
'bzrlib.tests.test_testament',
655
'bzrlib.tests.test_annotate',
656
'bzrlib.tests.test_revprops',
657
'bzrlib.tests.test_options',
658
'bzrlib.tests.test_http',
659
'bzrlib.tests.test_nonascii',
660
'bzrlib.tests.test_plugins',
661
'bzrlib.tests.test_reweave',
662
'bzrlib.tests.test_tsort',
663
'bzrlib.tests.test_trace',
666
TestCase.BZRPATH = os.path.join(os.path.realpath(os.path.dirname(bzrlib.__path__[0])), 'bzr')
667
print '%-30s %s' % ('bzr binary', TestCase.BZRPATH)
670
suite.addTest(TestLoader().loadTestsFromNames(testmod_names))
671
for package in packages_to_test():
672
suite.addTest(package.test_suite())
673
for m in MODULES_TO_TEST:
674
suite.addTest(TestLoader().loadTestsFromModule(m))
675
for m in (MODULES_TO_DOCTEST):
676
suite.addTest(DocTestSuite(m))
677
for name, plugin in bzrlib.plugin.all_plugins().items():
678
if hasattr(plugin, 'test_suite'):
679
suite.addTest(plugin.test_suite())