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
32
import bzrlib.commands
33
from bzrlib.errors import BzrError
34
import bzrlib.inventory
37
import bzrlib.osutils as osutils
41
from bzrlib.transport import urlescape
42
from bzrlib.trace import mutter
43
from bzrlib.tests.TestUtil import TestLoader, TestSuite
44
from bzrlib.tests.treeshape import build_tree_contents
47
MODULES_TO_DOCTEST = [
56
def packages_to_test():
57
import bzrlib.tests.blackbox
63
class EarlyStoppingTestResultAdapter(object):
64
"""An adapter for TestResult to stop at the first first failure or error"""
66
def __init__(self, result):
69
def addError(self, test, err):
70
self._result.addError(test, err)
73
def addFailure(self, test, err):
74
self._result.addFailure(test, err)
77
def __getattr__(self, name):
78
return getattr(self._result, name)
80
def __setattr__(self, name, value):
82
object.__setattr__(self, name, value)
83
return setattr(self._result, name, value)
86
class _MyResult(unittest._TextTestResult):
89
Shows output in a different format, including displaying runtime for tests.
92
def _elapsedTime(self):
93
return "%5dms" % (1000 * (time.time() - self._start_time))
95
def startTest(self, test):
96
unittest.TestResult.startTest(self, test)
97
# In a short description, the important words are in
98
# the beginning, but in an id, the important words are
100
SHOW_DESCRIPTIONS = False
102
width = osutils.terminal_width()
103
name_width = width - 15
105
if SHOW_DESCRIPTIONS:
106
what = test.shortDescription()
108
if len(what) > name_width:
109
what = what[:name_width-3] + '...'
112
if what.startswith('bzrlib.tests.'):
114
if len(what) > name_width:
115
what = '...' + what[3-name_width:]
116
what = what.ljust(name_width)
117
self.stream.write(what)
119
self._start_time = time.time()
121
def addError(self, test, err):
122
if isinstance(err[1], TestSkipped):
123
return self.addSkipped(test, err)
124
unittest.TestResult.addError(self, test, err)
126
self.stream.writeln("ERROR %s" % self._elapsedTime())
128
self.stream.write('E')
131
def addFailure(self, test, err):
132
unittest.TestResult.addFailure(self, test, err)
134
self.stream.writeln(" FAIL %s" % self._elapsedTime())
136
self.stream.write('F')
139
def addSuccess(self, test):
141
self.stream.writeln(' OK %s' % self._elapsedTime())
143
self.stream.write('~')
145
unittest.TestResult.addSuccess(self, test)
147
def addSkipped(self, test, skip_excinfo):
149
print >>self.stream, ' SKIP %s' % self._elapsedTime()
150
print >>self.stream, ' %s' % skip_excinfo[1]
152
self.stream.write('S')
154
# seems best to treat this as success from point-of-view of unittest
155
# -- it actually does nothing so it barely matters :)
156
unittest.TestResult.addSuccess(self, test)
158
def printErrorList(self, flavour, errors):
159
for test, err in errors:
160
self.stream.writeln(self.separator1)
161
self.stream.writeln("%s: %s" % (flavour, self.getDescription(test)))
162
if hasattr(test, '_get_log'):
164
print >>self.stream, \
165
('vvvv[log from %s]' % test.id()).ljust(78,'-')
166
print >>self.stream, test._get_log()
167
print >>self.stream, \
168
('^^^^[log from %s]' % test.id()).ljust(78,'-')
169
self.stream.writeln(self.separator2)
170
self.stream.writeln("%s" % err)
173
class TextTestRunner(unittest.TextTestRunner):
174
stop_on_failure = False
176
def _makeResult(self):
177
result = _MyResult(self.stream, self.descriptions, self.verbosity)
178
if self.stop_on_failure:
179
result = EarlyStoppingTestResultAdapter(result)
183
def iter_suite_tests(suite):
184
"""Return all tests in a suite, recursing through nested suites"""
185
for item in suite._tests:
186
if isinstance(item, unittest.TestCase):
188
elif isinstance(item, unittest.TestSuite):
189
for r in iter_suite_tests(item):
192
raise Exception('unknown object %r inside test suite %r'
196
class TestSkipped(Exception):
197
"""Indicates that a test was intentionally skipped, rather than failing."""
201
class CommandFailed(Exception):
204
class TestCase(unittest.TestCase):
205
"""Base class for bzr unit tests.
207
Tests that need access to disk resources should subclass
208
TestCaseInTempDir not TestCase.
210
Error and debug log messages are redirected from their usual
211
location into a temporary file, the contents of which can be
212
retrieved by _get_log(). We use a real OS file, not an in-memory object,
213
so that it can also capture file IO. When the test completes this file
214
is read into memory and removed from disk.
216
There are also convenience functions to invoke bzr's command-line
217
routine, and to build and check bzr trees.
219
In addition to the usual method of overriding tearDown(), this class also
220
allows subclasses to register functions into the _cleanups list, which is
221
run in order as the object is torn down. It's less likely this will be
222
accidentally overlooked.
226
_log_file_name = None
230
unittest.TestCase.setUp(self)
232
self._cleanEnvironment()
233
bzrlib.trace.disable_default_logging()
236
def _ndiff_strings(self, a, b):
237
"""Return ndiff between two strings containing lines.
239
A trailing newline is added if missing to make the strings
241
if b and b[-1] != '\n':
243
if a and a[-1] != '\n':
245
difflines = difflib.ndiff(a.splitlines(True),
247
linejunk=lambda x: False,
248
charjunk=lambda x: False)
249
return ''.join(difflines)
251
def assertEqualDiff(self, a, b):
252
"""Assert two texts are equal, if not raise an exception.
254
This is intended for use with multi-line strings where it can
255
be hard to find the differences by eye.
257
# TODO: perhaps override assertEquals to call this for strings?
260
raise AssertionError("texts not equal:\n" +
261
self._ndiff_strings(a, b))
263
def assertStartsWith(self, s, prefix):
264
if not s.startswith(prefix):
265
raise AssertionError('string %r does not start with %r' % (s, prefix))
267
def assertEndsWith(self, s, suffix):
268
if not s.endswith(prefix):
269
raise AssertionError('string %r does not end with %r' % (s, suffix))
271
def assertContainsRe(self, haystack, needle_re):
272
"""Assert that a contains something matching a regular expression."""
273
if not re.search(needle_re, haystack):
274
raise AssertionError('pattern "%s" not found in "%s"'
275
% (needle_re, haystack))
277
def AssertSubset(self, sublist, superlist):
278
"""Assert that every entry in sublist is present in superlist."""
280
for entry in sublist:
281
if entry not in superlist:
282
missing.append(entry)
284
raise AssertionError("value(s) %r not present in container %r" %
285
(missing, superlist))
287
def _startLogFile(self):
288
"""Send bzr and test log messages to a temporary file.
290
The file is removed as the test is torn down.
292
fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
293
encoder, decoder, stream_reader, stream_writer = codecs.lookup('UTF-8')
294
self._log_file = stream_writer(os.fdopen(fileno, 'w+'))
295
bzrlib.trace.enable_test_log(self._log_file)
296
self._log_file_name = name
297
self.addCleanup(self._finishLogFile)
299
def _finishLogFile(self):
300
"""Finished with the log file.
302
Read contents into memory, close, and delete.
304
bzrlib.trace.disable_test_log()
305
self._log_file.seek(0)
306
self._log_contents = self._log_file.read()
307
self._log_file.close()
308
os.remove(self._log_file_name)
309
self._log_file = self._log_file_name = None
311
def addCleanup(self, callable):
312
"""Arrange to run a callable when this case is torn down.
314
Callables are run in the reverse of the order they are registered,
315
ie last-in first-out.
317
if callable in self._cleanups:
318
raise ValueError("cleanup function %r already registered on %s"
320
self._cleanups.append(callable)
322
def _cleanEnvironment(self):
325
'APPDATA': os.getcwd(),
330
self.addCleanup(self._restoreEnvironment)
331
for name, value in new_env.iteritems():
332
self._captureVar(name, value)
335
def _captureVar(self, name, newvalue):
336
"""Set an environment variable, preparing it to be reset when finished."""
337
self.__old_env[name] = os.environ.get(name, None)
339
if name in os.environ:
342
os.environ[name] = newvalue
345
def _restoreVar(name, value):
347
if name in os.environ:
350
os.environ[name] = value
352
def _restoreEnvironment(self):
353
for name, value in self.__old_env.iteritems():
354
self._restoreVar(name, value)
358
unittest.TestCase.tearDown(self)
360
def _runCleanups(self):
361
"""Run registered cleanup functions.
363
This should only be called from TestCase.tearDown.
365
for cleanup_fn in reversed(self._cleanups):
368
def log(self, *args):
372
"""Return as a string the log for this test"""
373
if self._log_file_name:
374
return open(self._log_file_name).read()
376
return self._log_contents
377
# TODO: Delete the log after it's been read in
379
def capture(self, cmd, retcode=0):
380
"""Shortcut that splits cmd into words, runs, and returns stdout"""
381
return self.run_bzr_captured(cmd.split(), retcode=retcode)[0]
383
def run_bzr_captured(self, argv, retcode=0):
384
"""Invoke bzr and return (stdout, stderr).
386
Useful for code that wants to check the contents of the
387
output, the way error messages are presented, etc.
389
This should be the main method for tests that want to exercise the
390
overall behavior of the bzr application (rather than a unit test
391
or a functional test of the library.)
393
Much of the old code runs bzr by forking a new copy of Python, but
394
that is slower, harder to debug, and generally not necessary.
396
This runs bzr through the interface that catches and reports
397
errors, and with logging set to something approximating the
398
default, so that error reporting can be checked.
400
argv -- arguments to invoke bzr
401
retcode -- expected return code, or None for don't-care.
405
self.log('run bzr: %s', ' '.join(argv))
406
# FIXME: don't call into logging here
407
handler = logging.StreamHandler(stderr)
408
handler.setFormatter(bzrlib.trace.QuietFormatter())
409
handler.setLevel(logging.INFO)
410
logger = logging.getLogger('')
411
logger.addHandler(handler)
413
result = self.apply_redirected(None, stdout, stderr,
414
bzrlib.commands.run_bzr_catch_errors,
417
logger.removeHandler(handler)
418
out = stdout.getvalue()
419
err = stderr.getvalue()
421
self.log('output:\n%s', out)
423
self.log('errors:\n%s', err)
424
if retcode is not None:
425
self.assertEquals(result, retcode)
428
def run_bzr(self, *args, **kwargs):
429
"""Invoke bzr, as if it were run from the command line.
431
This should be the main method for tests that want to exercise the
432
overall behavior of the bzr application (rather than a unit test
433
or a functional test of the library.)
435
This sends the stdout/stderr results into the test's log,
436
where it may be useful for debugging. See also run_captured.
438
retcode = kwargs.pop('retcode', 0)
439
return self.run_bzr_captured(args, retcode)
441
def check_inventory_shape(self, inv, shape):
442
"""Compare an inventory to a list of expected names.
444
Fail if they are not precisely equal.
447
shape = list(shape) # copy
448
for path, ie in inv.entries():
449
name = path.replace('\\', '/')
457
self.fail("expected paths not found in inventory: %r" % shape)
459
self.fail("unexpected paths found in inventory: %r" % extras)
461
def apply_redirected(self, stdin=None, stdout=None, stderr=None,
462
a_callable=None, *args, **kwargs):
463
"""Call callable with redirected std io pipes.
465
Returns the return code."""
466
if not callable(a_callable):
467
raise ValueError("a_callable must be callable.")
471
if hasattr(self, "_log_file"):
472
stdout = self._log_file
476
if hasattr(self, "_log_file"):
477
stderr = self._log_file
480
real_stdin = sys.stdin
481
real_stdout = sys.stdout
482
real_stderr = sys.stderr
487
return a_callable(*args, **kwargs)
489
sys.stdout = real_stdout
490
sys.stderr = real_stderr
491
sys.stdin = real_stdin
494
BzrTestBase = TestCase
497
class TestCaseInTempDir(TestCase):
498
"""Derived class that runs a test within a temporary directory.
500
This is useful for tests that need to create a branch, etc.
502
The directory is created in a slightly complex way: for each
503
Python invocation, a new temporary top-level directory is created.
504
All test cases create their own directory within that. If the
505
tests complete successfully, the directory is removed.
507
InTempDir is an old alias for FunctionalTestCase.
512
OVERRIDE_PYTHON = 'python'
514
def check_file_contents(self, filename, expect):
515
self.log("check contents of file %s" % filename)
516
contents = file(filename, 'r').read()
517
if contents != expect:
518
self.log("expected: %r" % expect)
519
self.log("actually: %r" % contents)
520
self.fail("contents of %s not as expected" % filename)
522
def _make_test_root(self):
523
if TestCaseInTempDir.TEST_ROOT is not None:
527
root = u'test%04d.tmp' % i
531
if e.errno == errno.EEXIST:
536
# successfully created
537
TestCaseInTempDir.TEST_ROOT = osutils.abspath(root)
539
# make a fake bzr directory there to prevent any tests propagating
540
# up onto the source directory's real branch
541
os.mkdir(osutils.pathjoin(TestCaseInTempDir.TEST_ROOT, '.bzr'))
544
super(TestCaseInTempDir, self).setUp()
545
self._make_test_root()
546
_currentdir = os.getcwdu()
547
short_id = self.id().replace('bzrlib.tests.', '') \
548
.replace('__main__.', '')
549
self.test_dir = osutils.pathjoin(self.TEST_ROOT, short_id)
550
os.mkdir(self.test_dir)
551
os.chdir(self.test_dir)
552
os.environ['HOME'] = self.test_dir
553
os.environ['APPDATA'] = self.test_dir
554
def _leaveDirectory():
555
os.chdir(_currentdir)
556
self.addCleanup(_leaveDirectory)
558
def build_tree(self, shape, line_endings='native', transport=None):
559
"""Build a test tree according to a pattern.
561
shape is a sequence of file specifications. If the final
562
character is '/', a directory is created.
564
This doesn't add anything to a branch.
565
:param line_endings: Either 'binary' or 'native'
566
in binary mode, exact contents are written
567
in native mode, the line endings match the
568
default platform endings.
570
:param transport: A transport to write to, for building trees on
571
VFS's. If the transport is readonly or None,
572
"." is opened automatically.
574
# XXX: It's OK to just create them using forward slashes on windows?
575
if transport is None or transport.is_readonly():
576
transport = bzrlib.transport.get_transport(".")
578
self.assert_(isinstance(name, basestring))
580
transport.mkdir(urlescape(name[:-1]))
582
if line_endings == 'binary':
584
elif line_endings == 'native':
587
raise BzrError('Invalid line ending request %r' % (line_endings,))
588
content = "contents of %s%s" % (name, end)
589
transport.put(urlescape(name), StringIO(content))
591
def build_tree_contents(self, shape):
592
build_tree_contents(shape)
594
def failUnlessExists(self, path):
595
"""Fail unless path, which may be abs or relative, exists."""
596
self.failUnless(osutils.lexists(path))
598
def failIfExists(self, path):
599
"""Fail if path, which may be abs or relative, exists."""
600
self.failIf(osutils.lexists(path))
602
def assertFileEqual(self, content, path):
603
"""Fail if path does not contain 'content'."""
604
self.failUnless(osutils.lexists(path))
605
self.assertEqualDiff(content, open(path, 'r').read())
608
def filter_suite_by_re(suite, pattern):
610
filter_re = re.compile(pattern)
611
for test in iter_suite_tests(suite):
612
if filter_re.search(test.id()):
617
def run_suite(suite, name='test', verbose=False, pattern=".*",
618
stop_on_failure=False, keep_output=False):
619
TestCaseInTempDir._TEST_NAME = name
624
runner = TextTestRunner(stream=sys.stdout,
627
runner.stop_on_failure=stop_on_failure
629
suite = filter_suite_by_re(suite, pattern)
630
result = runner.run(suite)
631
# This is still a little bogus,
632
# but only a little. Folk not using our testrunner will
633
# have to delete their temp directories themselves.
634
if result.wasSuccessful() or not keep_output:
635
if TestCaseInTempDir.TEST_ROOT is not None:
636
shutil.rmtree(TestCaseInTempDir.TEST_ROOT)
638
print "Failed tests working directories are in '%s'\n" % TestCaseInTempDir.TEST_ROOT
639
return result.wasSuccessful()
642
def selftest(verbose=False, pattern=".*", stop_on_failure=True,
644
"""Run the whole test suite under the enhanced runner"""
645
return run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern,
646
stop_on_failure=stop_on_failure, keep_output=keep_output)
650
"""Build and return TestSuite for the whole program."""
651
from doctest import DocTestSuite
653
global MODULES_TO_DOCTEST
656
'bzrlib.tests.test_ancestry',
657
'bzrlib.tests.test_annotate',
658
'bzrlib.tests.test_api',
659
'bzrlib.tests.test_bad_files',
660
'bzrlib.tests.test_basis_inventory',
661
'bzrlib.tests.test_branch',
662
'bzrlib.tests.test_command',
663
'bzrlib.tests.test_commit',
664
'bzrlib.tests.test_commit_merge',
665
'bzrlib.tests.test_config',
666
'bzrlib.tests.test_conflicts',
667
'bzrlib.tests.test_diff',
668
'bzrlib.tests.test_fetch',
669
'bzrlib.tests.test_gpg',
670
'bzrlib.tests.test_graph',
671
'bzrlib.tests.test_hashcache',
672
'bzrlib.tests.test_http',
673
'bzrlib.tests.test_identitymap',
674
'bzrlib.tests.test_inv',
675
'bzrlib.tests.test_log',
676
'bzrlib.tests.test_merge',
677
'bzrlib.tests.test_merge3',
678
'bzrlib.tests.test_merge_core',
679
'bzrlib.tests.test_missing',
680
'bzrlib.tests.test_msgeditor',
681
'bzrlib.tests.test_nonascii',
682
'bzrlib.tests.test_options',
683
'bzrlib.tests.test_osutils',
684
'bzrlib.tests.test_parent',
685
'bzrlib.tests.test_permissions',
686
'bzrlib.tests.test_plugins',
687
'bzrlib.tests.test_remove',
688
'bzrlib.tests.test_revision',
689
'bzrlib.tests.test_revision_info',
690
'bzrlib.tests.test_revisionnamespaces',
691
'bzrlib.tests.test_revprops',
692
'bzrlib.tests.test_reweave',
693
'bzrlib.tests.test_rio',
694
'bzrlib.tests.test_sampler',
695
'bzrlib.tests.test_selftest',
696
'bzrlib.tests.test_setup',
697
'bzrlib.tests.test_sftp_transport',
698
'bzrlib.tests.test_smart_add',
699
'bzrlib.tests.test_source',
700
'bzrlib.tests.test_status',
701
'bzrlib.tests.test_store',
702
'bzrlib.tests.test_testament',
703
'bzrlib.tests.test_trace',
704
'bzrlib.tests.test_transactions',
705
'bzrlib.tests.test_transport',
706
'bzrlib.tests.test_tsort',
707
'bzrlib.tests.test_ui',
708
'bzrlib.tests.test_uncommit',
709
'bzrlib.tests.test_upgrade',
710
'bzrlib.tests.test_weave',
711
'bzrlib.tests.test_whitebox',
712
'bzrlib.tests.test_workingtree',
713
'bzrlib.tests.test_xml',
715
test_transport_implementations = [
716
'bzrlib.tests.test_transport_implementations']
718
TestCase.BZRPATH = osutils.pathjoin(
719
osutils.realpath(osutils.dirname(bzrlib.__path__[0])), 'bzr')
720
print '%10s: %s' % ('bzr', osutils.realpath(sys.argv[0]))
721
print '%10s: %s' % ('bzrlib', bzrlib.__path__[0])
724
# python2.4's TestLoader.loadTestsFromNames gives very poor
725
# errors if it fails to load a named module - no indication of what's
726
# actually wrong, just "no such module". We should probably override that
727
# class, but for the moment just load them ourselves. (mbp 20051202)
728
loader = TestLoader()
729
from bzrlib.transport import TransportTestProviderAdapter
730
adapter = TransportTestProviderAdapter()
731
for mod_name in test_transport_implementations:
732
mod = _load_module_by_name(mod_name)
733
for test in iter_suite_tests(loader.loadTestsFromModule(mod)):
734
suite.addTests(adapter.adapt(test))
735
for mod_name in testmod_names:
736
mod = _load_module_by_name(mod_name)
737
suite.addTest(loader.loadTestsFromModule(mod))
738
for package in packages_to_test():
739
suite.addTest(package.test_suite())
740
for m in MODULES_TO_TEST:
741
suite.addTest(loader.loadTestsFromModule(m))
742
for m in (MODULES_TO_DOCTEST):
743
suite.addTest(DocTestSuite(m))
744
for name, plugin in bzrlib.plugin.all_plugins().items():
745
if hasattr(plugin, 'test_suite'):
746
suite.addTest(plugin.test_suite())
750
def _load_module_by_name(mod_name):
751
parts = mod_name.split('.')
752
module = __import__(mod_name)
754
# for historical reasons python returns the top-level module even though
755
# it loads the submodule; we need to walk down to get the one we want.
757
module = getattr(module, parts.pop(0))