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
import bzrlib.inventory
35
import bzrlib.osutils as osutils
39
from bzrlib.trace import mutter
40
from bzrlib.tests.TestUtil import TestLoader, TestSuite
41
from bzrlib.errors import BzrError
44
MODULES_TO_DOCTEST = [
53
def packages_to_test():
54
import bzrlib.tests.blackbox
60
class EarlyStoppingTestResultAdapter(object):
61
"""An adapter for TestResult to stop at the first first failure or error"""
63
def __init__(self, result):
66
def addError(self, test, err):
67
self._result.addError(test, err)
70
def addFailure(self, test, err):
71
self._result.addFailure(test, err)
74
def __getattr__(self, name):
75
return getattr(self._result, name)
77
def __setattr__(self, name, value):
79
object.__setattr__(self, name, value)
80
return setattr(self._result, name, value)
83
class _MyResult(unittest._TextTestResult):
86
Shows output in a different format, including displaying runtime for tests.
89
# assumes 80-column window, less 'ERROR 99999ms' = 13ch
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
99
what = SHOW_DESCRIPTIONS and test.shortDescription()
102
what = what[:62] + '...'
105
if what.startswith('bzrlib.tests.'):
108
what = '...' + what[-62:]
110
self.stream.write('%-65.65s' % what)
112
self._start_time = time.time()
114
def addError(self, test, err):
115
unittest.TestResult.addError(self, test, err)
117
self.stream.writeln("ERROR %s" % self._elapsedTime())
119
self.stream.write('E')
122
def addFailure(self, test, err):
123
unittest.TestResult.addFailure(self, test, err)
125
self.stream.writeln(" FAIL %s" % self._elapsedTime())
127
self.stream.write('F')
130
def addSuccess(self, test):
132
self.stream.writeln(' OK %s' % self._elapsedTime())
134
self.stream.write('~')
136
unittest.TestResult.addSuccess(self, test)
138
def printErrorList(self, flavour, errors):
139
for test, err in errors:
140
self.stream.writeln(self.separator1)
141
self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
142
if hasattr(test, '_get_log'):
143
self.stream.writeln()
144
self.stream.writeln('log from this test:')
145
print >>self.stream, test._get_log()
146
self.stream.writeln(self.separator2)
147
self.stream.writeln("%s" % err)
150
class TextTestRunner(unittest.TextTestRunner):
151
stop_on_failure = False
153
def _makeResult(self):
154
result = _MyResult(self.stream, self.descriptions, self.verbosity)
155
if self.stop_on_failure:
156
result = EarlyStoppingTestResultAdapter(result)
160
def iter_suite_tests(suite):
161
"""Return all tests in a suite, recursing through nested suites"""
162
for item in suite._tests:
163
if isinstance(item, unittest.TestCase):
165
elif isinstance(item, unittest.TestSuite):
166
for r in iter_suite_tests(item):
169
raise Exception('unknown object %r inside test suite %r'
173
class TestSkipped(Exception):
174
"""Indicates that a test was intentionally skipped, rather than failing."""
178
class CommandFailed(Exception):
181
class TestCase(unittest.TestCase):
182
"""Base class for bzr unit tests.
184
Tests that need access to disk resources should subclass
185
TestCaseInTempDir not TestCase.
187
Error and debug log messages are redirected from their usual
188
location into a temporary file, the contents of which can be
189
retrieved by _get_log(). We use a real OS file, not an in-memory object,
190
so that it can also capture file IO. When the test completes this file
191
is read into memory and removed from disk.
193
There are also convenience functions to invoke bzr's command-line
194
routine, and to build and check bzr trees.
196
In addition to the usual method of overriding tearDown(), this class also
197
allows subclasses to register functions into the _cleanups list, which is
198
run in order as the object is torn down. It's less likely this will be
199
accidentally overlooked.
203
_log_file_name = None
207
unittest.TestCase.setUp(self)
209
self._cleanEnvironment()
210
bzrlib.trace.disable_default_logging()
213
def _ndiff_strings(self, a, b):
214
"""Return ndiff between two strings containing lines.
216
A trailing newline is added if missing to make the strings
218
if b and b[-1] != '\n':
220
if a and a[-1] != '\n':
222
difflines = difflib.ndiff(a.splitlines(True),
224
linejunk=lambda x: False,
225
charjunk=lambda x: False)
226
return ''.join(difflines)
228
def assertEqualDiff(self, a, b):
229
"""Assert two texts are equal, if not raise an exception.
231
This is intended for use with multi-line strings where it can
232
be hard to find the differences by eye.
234
# TODO: perhaps override assertEquals to call this for strings?
237
raise AssertionError("texts not equal:\n" +
238
self._ndiff_strings(a, b))
240
def assertContainsRe(self, haystack, needle_re):
241
"""Assert that a contains something matching a regular expression."""
242
if not re.search(needle_re, haystack):
243
raise AssertionError('pattern "%s" not found in "%s"'
244
% (needle_re, haystack))
246
def _startLogFile(self):
247
"""Send bzr and test log messages to a temporary file.
249
The file is removed as the test is torn down.
251
fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
252
self._log_file = os.fdopen(fileno, 'w+')
253
bzrlib.trace.enable_test_log(self._log_file)
254
self._log_file_name = name
255
self.addCleanup(self._finishLogFile)
257
def _finishLogFile(self):
258
"""Finished with the log file.
260
Read contents into memory, close, and delete.
262
bzrlib.trace.disable_test_log()
263
self._log_file.seek(0)
264
self._log_contents = self._log_file.read()
265
self._log_file.close()
266
os.remove(self._log_file_name)
267
self._log_file = self._log_file_name = None
269
def addCleanup(self, callable):
270
"""Arrange to run a callable when this case is torn down.
272
Callables are run in the reverse of the order they are registered,
273
ie last-in first-out.
275
if callable in self._cleanups:
276
raise ValueError("cleanup function %r already registered on %s"
278
self._cleanups.append(callable)
280
def _cleanEnvironment(self):
283
'APPDATA': os.getcwd(),
288
self.addCleanup(self._restoreEnvironment)
289
for name, value in new_env.iteritems():
290
self._captureVar(name, value)
293
def _captureVar(self, name, newvalue):
294
"""Set an environment variable, preparing it to be reset when finished."""
295
self.__old_env[name] = os.environ.get(name, None)
297
if name in os.environ:
300
os.environ[name] = newvalue
303
def _restoreVar(name, value):
305
if name in os.environ:
308
os.environ[name] = value
310
def _restoreEnvironment(self):
311
for name, value in self.__old_env.iteritems():
312
self._restoreVar(name, value)
316
unittest.TestCase.tearDown(self)
318
def _runCleanups(self):
319
"""Run registered cleanup functions.
321
This should only be called from TestCase.tearDown.
323
for callable in reversed(self._cleanups):
326
def log(self, *args):
330
"""Return as a string the log for this test"""
331
if self._log_file_name:
332
return open(self._log_file_name).read()
334
return self._log_contents
335
# TODO: Delete the log after it's been read in
337
def capture(self, cmd, retcode=0):
338
"""Shortcut that splits cmd into words, runs, and returns stdout"""
339
return self.run_bzr_captured(cmd.split(), retcode=retcode)[0]
341
def run_bzr_captured(self, argv, retcode=0):
342
"""Invoke bzr and return (stdout, stderr).
344
Useful for code that wants to check the contents of the
345
output, the way error messages are presented, etc.
347
This should be the main method for tests that want to exercise the
348
overall behavior of the bzr application (rather than a unit test
349
or a functional test of the library.)
351
Much of the old code runs bzr by forking a new copy of Python, but
352
that is slower, harder to debug, and generally not necessary.
354
This runs bzr through the interface that catches and reports
355
errors, and with logging set to something approximating the
356
default, so that error reporting can be checked.
358
argv -- arguments to invoke bzr
359
retcode -- expected return code, or None for don't-care.
363
self.log('run bzr: %s', ' '.join(argv))
364
# FIXME: don't call into logging here
365
handler = logging.StreamHandler(stderr)
366
handler.setFormatter(bzrlib.trace.QuietFormatter())
367
handler.setLevel(logging.INFO)
368
logger = logging.getLogger('')
369
logger.addHandler(handler)
371
result = self.apply_redirected(None, stdout, stderr,
372
bzrlib.commands.run_bzr_catch_errors,
375
logger.removeHandler(handler)
376
out = stdout.getvalue()
377
err = stderr.getvalue()
379
self.log('output:\n%s', out)
381
self.log('errors:\n%s', err)
382
if retcode is not None:
383
self.assertEquals(result, retcode)
386
def run_bzr(self, *args, **kwargs):
387
"""Invoke bzr, as if it were run from the command line.
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
This sends the stdout/stderr results into the test's log,
394
where it may be useful for debugging. See also run_captured.
396
retcode = kwargs.pop('retcode', 0)
397
return self.run_bzr_captured(args, retcode)
399
def check_inventory_shape(self, inv, shape):
400
"""Compare an inventory to a list of expected names.
402
Fail if they are not precisely equal.
405
shape = list(shape) # copy
406
for path, ie in inv.entries():
407
name = path.replace('\\', '/')
415
self.fail("expected paths not found in inventory: %r" % shape)
417
self.fail("unexpected paths found in inventory: %r" % extras)
419
def apply_redirected(self, stdin=None, stdout=None, stderr=None,
420
a_callable=None, *args, **kwargs):
421
"""Call callable with redirected std io pipes.
423
Returns the return code."""
424
if not callable(a_callable):
425
raise ValueError("a_callable must be callable.")
429
if hasattr(self, "_log_file"):
430
stdout = self._log_file
434
if hasattr(self, "_log_file"):
435
stderr = self._log_file
438
real_stdin = sys.stdin
439
real_stdout = sys.stdout
440
real_stderr = sys.stderr
445
return a_callable(*args, **kwargs)
447
sys.stdout = real_stdout
448
sys.stderr = real_stderr
449
sys.stdin = real_stdin
452
BzrTestBase = TestCase
455
class TestCaseInTempDir(TestCase):
456
"""Derived class that runs a test within a temporary directory.
458
This is useful for tests that need to create a branch, etc.
460
The directory is created in a slightly complex way: for each
461
Python invocation, a new temporary top-level directory is created.
462
All test cases create their own directory within that. If the
463
tests complete successfully, the directory is removed.
465
InTempDir is an old alias for FunctionalTestCase.
470
OVERRIDE_PYTHON = 'python'
472
def check_file_contents(self, filename, expect):
473
self.log("check contents of file %s" % filename)
474
contents = file(filename, 'r').read()
475
if contents != expect:
476
self.log("expected: %r" % expect)
477
self.log("actually: %r" % contents)
478
self.fail("contents of %s not as expected" % filename)
480
def _make_test_root(self):
481
if TestCaseInTempDir.TEST_ROOT is not None:
485
root = u'test%04d.tmp' % i
489
if e.errno == errno.EEXIST:
494
# successfully created
495
TestCaseInTempDir.TEST_ROOT = os.path.abspath(root)
497
# make a fake bzr directory there to prevent any tests propagating
498
# up onto the source directory's real branch
499
os.mkdir(os.path.join(TestCaseInTempDir.TEST_ROOT, '.bzr'))
502
super(TestCaseInTempDir, self).setUp()
503
self._make_test_root()
504
_currentdir = os.getcwdu()
505
short_id = self.id().replace('bzrlib.tests.', '') \
506
.replace('__main__.', '')
507
self.test_dir = os.path.join(self.TEST_ROOT, short_id)
508
os.mkdir(self.test_dir)
509
os.chdir(self.test_dir)
510
os.environ['HOME'] = self.test_dir
511
def _leaveDirectory():
512
os.chdir(_currentdir)
513
self.addCleanup(_leaveDirectory)
515
def build_tree(self, shape, line_endings='native'):
516
"""Build a test tree according to a pattern.
518
shape is a sequence of file specifications. If the final
519
character is '/', a directory is created.
521
This doesn't add anything to a branch.
522
:param line_endings: Either 'binary' or 'native'
523
in binary mode, exact contents are written
524
in native mode, the line endings match the
525
default platform endings.
527
# XXX: It's OK to just create them using forward slashes on windows?
529
self.assert_(isinstance(name, basestring))
533
if line_endings == 'binary':
535
elif line_endings == 'native':
538
raise BzrError('Invalid line ending request %r' % (line_endings,))
539
print >>f, "contents of", name
542
def build_tree_contents(self, shape):
543
bzrlib.tests.build_tree_contents(shape)
545
def failUnlessExists(self, path):
546
"""Fail unless path, which may be abs or relative, exists."""
547
self.failUnless(osutils.lexists(path))
549
def assertFileEqual(self, content, path):
550
"""Fail if path does not contain 'content'."""
551
self.failUnless(osutils.lexists(path))
552
self.assertEqualDiff(content, open(path, 'r').read())
555
class MetaTestLog(TestCase):
556
def test_logging(self):
557
"""Test logs are captured when a test fails."""
558
self.log('a test message')
559
self._log_file.flush()
560
self.assertContainsRe(self._get_log(), 'a test message\n')
563
def filter_suite_by_re(suite, pattern):
564
result = TestUtil.TestSuite()
565
filter_re = re.compile(pattern)
566
for test in iter_suite_tests(suite):
567
if filter_re.search(test.id()):
572
def run_suite(suite, name='test', verbose=False, pattern=".*",
573
stop_on_failure=False, keep_output=False):
574
TestCaseInTempDir._TEST_NAME = name
579
runner = TextTestRunner(stream=sys.stdout,
582
runner.stop_on_failure=stop_on_failure
584
suite = filter_suite_by_re(suite, pattern)
585
result = runner.run(suite)
586
# This is still a little bogus,
587
# but only a little. Folk not using our testrunner will
588
# have to delete their temp directories themselves.
589
if result.wasSuccessful() or not keep_output:
590
if TestCaseInTempDir.TEST_ROOT is not None:
591
shutil.rmtree(TestCaseInTempDir.TEST_ROOT)
593
print "Failed tests working directories are in '%s'\n" % TestCaseInTempDir.TEST_ROOT
594
return result.wasSuccessful()
597
def selftest(verbose=False, pattern=".*", stop_on_failure=True,
599
"""Run the whole test suite under the enhanced runner"""
600
return run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern,
601
stop_on_failure=stop_on_failure, keep_output=keep_output)
605
"""Build and return TestSuite for the whole program."""
606
from doctest import DocTestSuite
608
global MODULES_TO_DOCTEST
610
# FIXME: If these fail to load, e.g. because of a syntax error, the
611
# exception is hidden by unittest. Sucks. Should either fix that or
612
# perhaps import them and pass them to unittest as modules.
614
['bzrlib.tests.MetaTestLog',
615
'bzrlib.tests.test_api',
616
'bzrlib.tests.test_gpg',
617
'bzrlib.tests.test_identitymap',
618
'bzrlib.tests.test_inv',
619
'bzrlib.tests.test_ancestry',
620
'bzrlib.tests.test_commit',
621
'bzrlib.tests.test_command',
622
'bzrlib.tests.test_commit_merge',
623
'bzrlib.tests.test_config',
624
'bzrlib.tests.test_merge3',
625
'bzrlib.tests.test_merge',
626
'bzrlib.tests.test_hashcache',
627
'bzrlib.tests.test_status',
628
'bzrlib.tests.test_log',
629
'bzrlib.tests.test_revisionnamespaces',
630
'bzrlib.tests.test_branch',
631
'bzrlib.tests.test_revision',
632
'bzrlib.tests.test_revision_info',
633
'bzrlib.tests.test_merge_core',
634
'bzrlib.tests.test_smart_add',
635
'bzrlib.tests.test_bad_files',
636
'bzrlib.tests.test_diff',
637
'bzrlib.tests.test_parent',
638
'bzrlib.tests.test_xml',
639
'bzrlib.tests.test_weave',
640
'bzrlib.tests.test_fetch',
641
'bzrlib.tests.test_whitebox',
642
'bzrlib.tests.test_store',
643
'bzrlib.tests.test_sampler',
644
'bzrlib.tests.test_transactions',
645
'bzrlib.tests.test_transport',
646
'bzrlib.tests.test_sftp',
647
'bzrlib.tests.test_graph',
648
'bzrlib.tests.test_workingtree',
649
'bzrlib.tests.test_upgrade',
650
'bzrlib.tests.test_uncommit',
651
'bzrlib.tests.test_conflicts',
652
'bzrlib.tests.test_testament',
653
'bzrlib.tests.test_annotate',
654
'bzrlib.tests.test_revprops',
655
'bzrlib.tests.test_options',
656
'bzrlib.tests.test_http',
657
'bzrlib.tests.test_nonascii',
658
'bzrlib.tests.test_reweave',
659
'bzrlib.tests.test_tsort',
660
'bzrlib.tests.test_trace',
661
'bzrlib.tests.test_basicio',
664
TestCase.BZRPATH = os.path.join(os.path.realpath(os.path.dirname(bzrlib.__path__[0])), 'bzr')
665
print '%-30s %s' % ('bzr binary', TestCase.BZRPATH)
668
suite.addTest(TestLoader().loadTestsFromNames(testmod_names))
669
for package in packages_to_test():
670
suite.addTest(package.test_suite())
671
for m in MODULES_TO_TEST:
672
suite.addTest(TestLoader().loadTestsFromModule(m))
673
for m in (MODULES_TO_DOCTEST):
674
suite.addTest(DocTestSuite(m))
675
for p in bzrlib.plugin.all_plugins:
676
if hasattr(p, 'test_suite'):
677
suite.addTest(p.test_suite())