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
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
def _elapsedTime(self):
91
return "%5dms" % (1000 * (time.time() - self._start_time))
93
def startTest(self, test):
94
unittest.TestResult.startTest(self, test)
95
# In a short description, the important words are in
96
# the beginning, but in an id, the important words are
98
SHOW_DESCRIPTIONS = False
100
width = bzrlib.osutils.terminal_width()
101
name_width = width - 15
103
if SHOW_DESCRIPTIONS:
104
what = test.shortDescription()
106
if len(what) > name_width:
107
what = what[:name_width-3] + '...'
110
if what.startswith('bzrlib.tests.'):
112
if len(what) > name_width:
113
what = '...' + what[3-name_width:]
114
what = what.ljust(name_width)
115
self.stream.write(what)
117
self._start_time = time.time()
119
def addError(self, test, err):
120
if isinstance(err[1], TestSkipped):
121
return self.addSkipped(test, err)
122
unittest.TestResult.addError(self, test, err)
124
self.stream.writeln("ERROR %s" % self._elapsedTime())
126
self.stream.write('E')
129
def addFailure(self, test, err):
130
unittest.TestResult.addFailure(self, test, err)
132
self.stream.writeln(" FAIL %s" % self._elapsedTime())
134
self.stream.write('F')
137
def addSuccess(self, test):
139
self.stream.writeln(' OK %s' % self._elapsedTime())
141
self.stream.write('~')
143
unittest.TestResult.addSuccess(self, test)
145
def addSkipped(self, test, skip_excinfo):
147
print >>self.stream, ' SKIP %s' % self._elapsedTime()
148
print >>self.stream, ' %s' % skip_excinfo[1]
150
self.stream.write('S')
152
# seems best to treat this as success from point-of-view of unittest
153
# -- it actually does nothing so it barely matters :)
154
unittest.TestResult.addSuccess(self, test)
156
def printErrorList(self, flavour, errors):
157
for test, err in errors:
158
self.stream.writeln(self.separator1)
159
self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
160
if hasattr(test, '_get_log'):
162
print >>self.stream, \
163
('vvvv[log from %s]' % test).ljust(78,'-')
164
print >>self.stream, test._get_log()
165
print >>self.stream, \
166
('^^^^[log from %s]' % test).ljust(78,'-')
167
self.stream.writeln(self.separator2)
168
self.stream.writeln("%s" % err)
171
class TextTestRunner(unittest.TextTestRunner):
172
stop_on_failure = False
174
def _makeResult(self):
175
result = _MyResult(self.stream, self.descriptions, self.verbosity)
176
if self.stop_on_failure:
177
result = EarlyStoppingTestResultAdapter(result)
181
def iter_suite_tests(suite):
182
"""Return all tests in a suite, recursing through nested suites"""
183
for item in suite._tests:
184
if isinstance(item, unittest.TestCase):
186
elif isinstance(item, unittest.TestSuite):
187
for r in iter_suite_tests(item):
190
raise Exception('unknown object %r inside test suite %r'
194
class TestSkipped(Exception):
195
"""Indicates that a test was intentionally skipped, rather than failing."""
199
class CommandFailed(Exception):
202
class TestCase(unittest.TestCase):
203
"""Base class for bzr unit tests.
205
Tests that need access to disk resources should subclass
206
TestCaseInTempDir not TestCase.
208
Error and debug log messages are redirected from their usual
209
location into a temporary file, the contents of which can be
210
retrieved by _get_log(). We use a real OS file, not an in-memory object,
211
so that it can also capture file IO. When the test completes this file
212
is read into memory and removed from disk.
214
There are also convenience functions to invoke bzr's command-line
215
routine, and to build and check bzr trees.
217
In addition to the usual method of overriding tearDown(), this class also
218
allows subclasses to register functions into the _cleanups list, which is
219
run in order as the object is torn down. It's less likely this will be
220
accidentally overlooked.
224
_log_file_name = None
228
unittest.TestCase.setUp(self)
230
self._cleanEnvironment()
231
bzrlib.trace.disable_default_logging()
234
def _ndiff_strings(self, a, b):
235
"""Return ndiff between two strings containing lines.
237
A trailing newline is added if missing to make the strings
239
if b and b[-1] != '\n':
241
if a and a[-1] != '\n':
243
difflines = difflib.ndiff(a.splitlines(True),
245
linejunk=lambda x: False,
246
charjunk=lambda x: False)
247
return ''.join(difflines)
249
def assertEqualDiff(self, a, b):
250
"""Assert two texts are equal, if not raise an exception.
252
This is intended for use with multi-line strings where it can
253
be hard to find the differences by eye.
255
# TODO: perhaps override assertEquals to call this for strings?
258
raise AssertionError("texts not equal:\n" +
259
self._ndiff_strings(a, b))
261
def assertContainsRe(self, haystack, needle_re):
262
"""Assert that a contains something matching a regular expression."""
263
if not re.search(needle_re, haystack):
264
raise AssertionError('pattern "%s" not found in "%s"'
265
% (needle_re, haystack))
267
def _startLogFile(self):
268
"""Send bzr and test log messages to a temporary file.
270
The file is removed as the test is torn down.
272
fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
273
encoder, decoder, stream_reader, stream_writer = codecs.lookup('UTF-8')
274
self._log_file = stream_writer(os.fdopen(fileno, 'w+'))
275
bzrlib.trace.enable_test_log(self._log_file)
276
self._log_file_name = name
277
self.addCleanup(self._finishLogFile)
279
def _finishLogFile(self):
280
"""Finished with the log file.
282
Read contents into memory, close, and delete.
284
bzrlib.trace.disable_test_log()
285
self._log_file.seek(0)
286
self._log_contents = self._log_file.read()
287
self._log_file.close()
288
os.remove(self._log_file_name)
289
self._log_file = self._log_file_name = None
291
def addCleanup(self, callable):
292
"""Arrange to run a callable when this case is torn down.
294
Callables are run in the reverse of the order they are registered,
295
ie last-in first-out.
297
if callable in self._cleanups:
298
raise ValueError("cleanup function %r already registered on %s"
300
self._cleanups.append(callable)
302
def _cleanEnvironment(self):
305
'APPDATA': os.getcwd(),
310
self.addCleanup(self._restoreEnvironment)
311
for name, value in new_env.iteritems():
312
self._captureVar(name, value)
315
def _captureVar(self, name, newvalue):
316
"""Set an environment variable, preparing it to be reset when finished."""
317
self.__old_env[name] = os.environ.get(name, None)
319
if name in os.environ:
322
os.environ[name] = newvalue
325
def _restoreVar(name, value):
327
if name in os.environ:
330
os.environ[name] = value
332
def _restoreEnvironment(self):
333
for name, value in self.__old_env.iteritems():
334
self._restoreVar(name, value)
338
unittest.TestCase.tearDown(self)
340
def _runCleanups(self):
341
"""Run registered cleanup functions.
343
This should only be called from TestCase.tearDown.
345
for cleanup_fn in reversed(self._cleanups):
348
def log(self, *args):
352
"""Return as a string the log for this test"""
353
if self._log_file_name:
354
return open(self._log_file_name).read()
356
return self._log_contents
357
# TODO: Delete the log after it's been read in
359
def capture(self, cmd, retcode=0):
360
"""Shortcut that splits cmd into words, runs, and returns stdout"""
361
return self.run_bzr_captured(cmd.split(), retcode=retcode)[0]
363
def run_bzr_captured(self, argv, retcode=0):
364
"""Invoke bzr and return (stdout, stderr).
366
Useful for code that wants to check the contents of the
367
output, the way error messages are presented, etc.
369
This should be the main method for tests that want to exercise the
370
overall behavior of the bzr application (rather than a unit test
371
or a functional test of the library.)
373
Much of the old code runs bzr by forking a new copy of Python, but
374
that is slower, harder to debug, and generally not necessary.
376
This runs bzr through the interface that catches and reports
377
errors, and with logging set to something approximating the
378
default, so that error reporting can be checked.
380
argv -- arguments to invoke bzr
381
retcode -- expected return code, or None for don't-care.
385
self.log('run bzr: %s', ' '.join(argv))
386
# FIXME: don't call into logging here
387
handler = logging.StreamHandler(stderr)
388
handler.setFormatter(bzrlib.trace.QuietFormatter())
389
handler.setLevel(logging.INFO)
390
logger = logging.getLogger('')
391
logger.addHandler(handler)
393
result = self.apply_redirected(None, stdout, stderr,
394
bzrlib.commands.run_bzr_catch_errors,
397
logger.removeHandler(handler)
398
out = stdout.getvalue()
399
err = stderr.getvalue()
401
self.log('output:\n%s', out)
403
self.log('errors:\n%s', err)
404
if retcode is not None:
405
self.assertEquals(result, retcode)
408
def run_bzr(self, *args, **kwargs):
409
"""Invoke bzr, as if it were run from the command line.
411
This should be the main method for tests that want to exercise the
412
overall behavior of the bzr application (rather than a unit test
413
or a functional test of the library.)
415
This sends the stdout/stderr results into the test's log,
416
where it may be useful for debugging. See also run_captured.
418
retcode = kwargs.pop('retcode', 0)
419
return self.run_bzr_captured(args, retcode)
421
def check_inventory_shape(self, inv, shape):
422
"""Compare an inventory to a list of expected names.
424
Fail if they are not precisely equal.
427
shape = list(shape) # copy
428
for path, ie in inv.entries():
429
name = path.replace('\\', '/')
437
self.fail("expected paths not found in inventory: %r" % shape)
439
self.fail("unexpected paths found in inventory: %r" % extras)
441
def apply_redirected(self, stdin=None, stdout=None, stderr=None,
442
a_callable=None, *args, **kwargs):
443
"""Call callable with redirected std io pipes.
445
Returns the return code."""
446
if not callable(a_callable):
447
raise ValueError("a_callable must be callable.")
451
if hasattr(self, "_log_file"):
452
stdout = self._log_file
456
if hasattr(self, "_log_file"):
457
stderr = self._log_file
460
real_stdin = sys.stdin
461
real_stdout = sys.stdout
462
real_stderr = sys.stderr
467
return a_callable(*args, **kwargs)
469
sys.stdout = real_stdout
470
sys.stderr = real_stderr
471
sys.stdin = real_stdin
474
BzrTestBase = TestCase
477
class TestCaseInTempDir(TestCase):
478
"""Derived class that runs a test within a temporary directory.
480
This is useful for tests that need to create a branch, etc.
482
The directory is created in a slightly complex way: for each
483
Python invocation, a new temporary top-level directory is created.
484
All test cases create their own directory within that. If the
485
tests complete successfully, the directory is removed.
487
InTempDir is an old alias for FunctionalTestCase.
492
OVERRIDE_PYTHON = 'python'
494
def check_file_contents(self, filename, expect):
495
self.log("check contents of file %s" % filename)
496
contents = file(filename, 'r').read()
497
if contents != expect:
498
self.log("expected: %r" % expect)
499
self.log("actually: %r" % contents)
500
self.fail("contents of %s not as expected" % filename)
502
def _make_test_root(self):
503
if TestCaseInTempDir.TEST_ROOT is not None:
507
root = u'test%04d.tmp' % i
511
if e.errno == errno.EEXIST:
516
# successfully created
517
TestCaseInTempDir.TEST_ROOT = os.path.abspath(root)
519
# make a fake bzr directory there to prevent any tests propagating
520
# up onto the source directory's real branch
521
os.mkdir(os.path.join(TestCaseInTempDir.TEST_ROOT, '.bzr'))
524
super(TestCaseInTempDir, self).setUp()
525
self._make_test_root()
526
_currentdir = os.getcwdu()
527
short_id = self.id().replace('bzrlib.tests.', '') \
528
.replace('__main__.', '')
529
self.test_dir = os.path.join(self.TEST_ROOT, short_id)
530
os.mkdir(self.test_dir)
531
os.chdir(self.test_dir)
532
os.environ['HOME'] = self.test_dir
533
def _leaveDirectory():
534
os.chdir(_currentdir)
535
self.addCleanup(_leaveDirectory)
537
def build_tree(self, shape, line_endings='native'):
538
"""Build a test tree according to a pattern.
540
shape is a sequence of file specifications. If the final
541
character is '/', a directory is created.
543
This doesn't add anything to a branch.
544
:param line_endings: Either 'binary' or 'native'
545
in binary mode, exact contents are written
546
in native mode, the line endings match the
547
default platform endings.
549
# XXX: It's OK to just create them using forward slashes on windows?
551
self.assert_(isinstance(name, basestring))
555
if line_endings == 'binary':
557
elif line_endings == 'native':
560
raise BzrError('Invalid line ending request %r' % (line_endings,))
561
print >>f, "contents of", name
564
def build_tree_contents(self, shape):
565
build_tree_contents(shape)
567
def failUnlessExists(self, path):
568
"""Fail unless path, which may be abs or relative, exists."""
569
self.failUnless(bzrlib.osutils.lexists(path))
571
def assertFileEqual(self, content, path):
572
"""Fail if path does not contain 'content'."""
573
self.failUnless(bzrlib.osutils.lexists(path))
574
self.assertEqualDiff(content, open(path, 'r').read())
577
def filter_suite_by_re(suite, pattern):
579
filter_re = re.compile(pattern)
580
for test in iter_suite_tests(suite):
581
if filter_re.search(test.id()):
586
def run_suite(suite, name='test', verbose=False, pattern=".*",
587
stop_on_failure=False, keep_output=False):
588
TestCaseInTempDir._TEST_NAME = name
593
runner = TextTestRunner(stream=sys.stdout,
596
runner.stop_on_failure=stop_on_failure
598
suite = filter_suite_by_re(suite, pattern)
599
result = runner.run(suite)
600
# This is still a little bogus,
601
# but only a little. Folk not using our testrunner will
602
# have to delete their temp directories themselves.
603
if result.wasSuccessful() or not keep_output:
604
if TestCaseInTempDir.TEST_ROOT is not None:
605
shutil.rmtree(TestCaseInTempDir.TEST_ROOT)
607
print "Failed tests working directories are in '%s'\n" % TestCaseInTempDir.TEST_ROOT
608
return result.wasSuccessful()
611
def selftest(verbose=False, pattern=".*", stop_on_failure=True,
613
"""Run the whole test suite under the enhanced runner"""
614
return run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern,
615
stop_on_failure=stop_on_failure, keep_output=keep_output)
619
"""Build and return TestSuite for the whole program."""
620
from doctest import DocTestSuite
622
global MODULES_TO_DOCTEST
625
'bzrlib.tests.test_ancestry',
626
'bzrlib.tests.test_annotate',
627
'bzrlib.tests.test_api',
628
'bzrlib.tests.test_bad_files',
629
'bzrlib.tests.test_branch',
630
'bzrlib.tests.test_command',
631
'bzrlib.tests.test_commit',
632
'bzrlib.tests.test_commit_merge',
633
'bzrlib.tests.test_config',
634
'bzrlib.tests.test_conflicts',
635
'bzrlib.tests.test_diff',
636
'bzrlib.tests.test_fetch',
637
'bzrlib.tests.test_gpg',
638
'bzrlib.tests.test_graph',
639
'bzrlib.tests.test_hashcache',
640
'bzrlib.tests.test_http',
641
'bzrlib.tests.test_identitymap',
642
'bzrlib.tests.test_inv',
643
'bzrlib.tests.test_log',
644
'bzrlib.tests.test_merge',
645
'bzrlib.tests.test_merge3',
646
'bzrlib.tests.test_merge_core',
647
'bzrlib.tests.test_missing',
648
'bzrlib.tests.test_msgeditor',
649
'bzrlib.tests.test_nonascii',
650
'bzrlib.tests.test_options',
651
'bzrlib.tests.test_parent',
652
'bzrlib.tests.test_plugins',
653
'bzrlib.tests.test_remove',
654
'bzrlib.tests.test_revision',
655
'bzrlib.tests.test_revision_info',
656
'bzrlib.tests.test_revisionnamespaces',
657
'bzrlib.tests.test_revprops',
658
'bzrlib.tests.test_reweave',
659
'bzrlib.tests.test_rio',
660
'bzrlib.tests.test_sampler',
661
'bzrlib.tests.test_selftest',
662
'bzrlib.tests.test_setup',
663
'bzrlib.tests.test_sftp',
664
'bzrlib.tests.test_smart_add',
665
'bzrlib.tests.test_source',
666
'bzrlib.tests.test_status',
667
'bzrlib.tests.test_store',
668
'bzrlib.tests.test_testament',
669
'bzrlib.tests.test_trace',
670
'bzrlib.tests.test_transactions',
671
'bzrlib.tests.test_transport',
672
'bzrlib.tests.test_tsort',
673
'bzrlib.tests.test_ui',
674
'bzrlib.tests.test_uncommit',
675
'bzrlib.tests.test_upgrade',
676
'bzrlib.tests.test_weave',
677
'bzrlib.tests.test_whitebox',
678
'bzrlib.tests.test_workingtree',
679
'bzrlib.tests.test_xml',
680
'bzrlib.tests.test_file_involved',
683
print '%10s: %s' % ('bzr', os.path.realpath(sys.argv[0]))
684
print '%10s: %s' % ('bzrlib', bzrlib.__path__[0])
687
# python2.4's TestLoader.loadTestsFromNames gives very poor
688
# errors if it fails to load a named module - no indication of what's
689
# actually wrong, just "no such module". We should probably override that
690
# class, but for the moment just load them ourselves. (mbp 20051202)
691
loader = TestLoader()
692
for mod_name in testmod_names:
693
mod = _load_module_by_name(mod_name)
694
suite.addTest(loader.loadTestsFromModule(mod))
695
for package in packages_to_test():
696
suite.addTest(package.test_suite())
697
for m in MODULES_TO_TEST:
698
suite.addTest(loader.loadTestsFromModule(m))
699
for m in (MODULES_TO_DOCTEST):
700
suite.addTest(DocTestSuite(m))
701
for name, plugin in bzrlib.plugin.all_plugins().items():
702
if hasattr(plugin, 'test_suite'):
703
suite.addTest(plugin.test_suite())
707
def _load_module_by_name(mod_name):
708
parts = mod_name.split('.')
709
module = __import__(mod_name)
711
# for historical reasons python returns the top-level module even though
712
# it loads the submodule; we need to walk down to get the one we want.
714
module = getattr(module, parts.pop(0))