34
41
from logging import debug, warning, error
45
class EarlyStoppingTestResultAdapter(object):
46
"""An adapter for TestResult to stop at the first first failure or error"""
48
def __init__(self, result):
51
def addError(self, test, err):
52
self._result.addError(test, err)
55
def addFailure(self, test, err):
56
self._result.addFailure(test, err)
59
def __getattr__(self, name):
60
return getattr(self._result, name)
62
def __setattr__(self, name, value):
64
object.__setattr__(self, name, value)
65
return setattr(self._result, name, value)
68
class _MyResult(unittest._TextTestResult):
72
No special behaviour for now.
75
def _elapsedTime(self):
76
return "(Took %.3fs)" % (time.time() - self._start_time)
78
def startTest(self, test):
79
unittest.TestResult.startTest(self, test)
80
# TODO: Maybe show test.shortDescription somewhere?
81
what = test.shortDescription() or test.id()
83
self.stream.write('%-70.70s' % what)
85
self._start_time = time.time()
87
def addError(self, test, err):
88
unittest.TestResult.addError(self, test, err)
90
self.stream.writeln("ERROR %s" % self._elapsedTime())
92
self.stream.write('E')
95
def addFailure(self, test, err):
96
unittest.TestResult.addFailure(self, test, err)
98
self.stream.writeln("FAIL %s" % self._elapsedTime())
100
self.stream.write('F')
103
def addSuccess(self, test):
105
self.stream.writeln('OK %s' % self._elapsedTime())
107
self.stream.write('~')
109
unittest.TestResult.addSuccess(self, test)
111
def printErrorList(self, flavour, errors):
112
for test, err in errors:
113
self.stream.writeln(self.separator1)
114
self.stream.writeln("%s: %s" % (flavour,self.getDescription(test)))
115
if hasattr(test, '_get_log'):
116
self.stream.writeln()
117
self.stream.writeln('log from this test:')
118
print >>self.stream, test._get_log()
119
self.stream.writeln(self.separator2)
120
self.stream.writeln("%s" % err)
123
class TextTestRunner(unittest.TextTestRunner):
124
stop_on_failure = False
126
def _makeResult(self):
127
result = _MyResult(self.stream, self.descriptions, self.verbosity)
128
if self.stop_on_failure:
129
result = EarlyStoppingTestResultAdapter(result)
133
def iter_suite_tests(suite):
134
"""Return all tests in a suite, recursing through nested suites"""
135
for item in suite._tests:
136
if isinstance(item, unittest.TestCase):
138
elif isinstance(item, unittest.TestSuite):
139
for r in iter_suite_tests(item):
142
raise Exception('unknown object %r inside test suite %r'
146
class TestSkipped(Exception):
147
"""Indicates that a test was intentionally skipped, rather than failing."""
151
class CommandFailed(Exception):
37
154
class TestCase(unittest.TestCase):
38
155
"""Base class for bzr unit tests.
48
165
routine, and to build and check bzr trees."""
168
_log_file_name = None
53
# this replaces the default testsweet.TestCase; we don't want logging changed
54
171
unittest.TestCase.setUp(self)
172
self.oldenv = os.environ.get('HOME', None)
173
os.environ['HOME'] = os.getcwd()
174
self.bzr_email = os.environ.get('BZREMAIL')
175
if self.bzr_email is not None:
176
del os.environ['BZREMAIL']
177
self.email = os.environ.get('EMAIL')
178
if self.email is not None:
179
del os.environ['EMAIL']
55
180
bzrlib.trace.disable_default_logging()
56
181
self._enable_file_logging()
183
def _ndiff_strings(self, a, b):
184
"""Return ndiff between two strings containing lines.
186
A trailing newline is added if missing to make the strings
188
if b and b[-1] != '\n':
190
if a and a[-1] != '\n':
192
difflines = difflib.ndiff(a.splitlines(True),
194
linejunk=lambda x: False,
195
charjunk=lambda x: False)
196
return ''.join(difflines)
198
def assertEqualDiff(self, a, b):
199
"""Assert two texts are equal, if not raise an exception.
201
This is intended for use with multi-line strings where it can
202
be hard to find the differences by eye.
204
# TODO: perhaps override assertEquals to call this for strings?
207
raise AssertionError("texts not equal:\n" +
208
self._ndiff_strings(a, b))
210
def assertContainsRe(self, haystack, needle_re):
211
"""Assert that a contains something matching a regular expression."""
212
if not re.search(needle_re, haystack):
213
raise AssertionError('pattern "%s" not found in "%s"'
214
% (needle_re, haystack))
59
216
def _enable_file_logging(self):
60
217
fileno, name = tempfile.mkstemp(suffix='.log', prefix='testbzr')
72
229
self._log_file_name = name
75
231
def tearDown(self):
232
os.environ['HOME'] = self.oldenv
233
if os.environ.get('BZREMAIL') is not None:
234
del os.environ['BZREMAIL']
235
if self.bzr_email is not None:
236
os.environ['BZREMAIL'] = self.bzr_email
237
if os.environ.get('EMAIL') is not None:
238
del os.environ['EMAIL']
239
if self.email is not None:
240
os.environ['EMAIL'] = self.email
76
241
logging.getLogger('').removeHandler(self._log_hdlr)
77
242
bzrlib.trace.enable_default_logging()
78
243
logging.debug('%s teardown', self.id())
79
244
self._log_file.close()
80
245
unittest.TestCase.tearDown(self)
83
247
def log(self, *args):
84
248
logging.debug(*args)
86
250
def _get_log(self):
87
251
"""Return as a string the log for this test"""
88
return open(self._log_file_name).read()
252
if self._log_file_name:
253
return open(self._log_file_name).read()
257
def capture(self, cmd):
258
"""Shortcut that splits cmd into words, runs, and returns stdout"""
259
return self.run_bzr_captured(cmd.split())[0]
261
def run_bzr_captured(self, argv, retcode=0):
262
"""Invoke bzr and return (result, stdout, stderr).
264
Useful for code that wants to check the contents of the
265
output, the way error messages are presented, etc.
267
This should be the main method for tests that want to exercise the
268
overall behavior of the bzr application (rather than a unit test
269
or a functional test of the library.)
271
Much of the old code runs bzr by forking a new copy of Python, but
272
that is slower, harder to debug, and generally not necessary.
274
This runs bzr through the interface that catches and reports
275
errors, and with logging set to something approximating the
276
default, so that error reporting can be checked.
278
argv -- arguments to invoke bzr
279
retcode -- expected return code, or None for don't-care.
283
self.log('run bzr: %s', ' '.join(argv))
284
handler = logging.StreamHandler(stderr)
285
handler.setFormatter(bzrlib.trace.QuietFormatter())
286
handler.setLevel(logging.INFO)
287
logger = logging.getLogger('')
288
logger.addHandler(handler)
290
result = self.apply_redirected(None, stdout, stderr,
291
bzrlib.commands.run_bzr_catch_errors,
294
logger.removeHandler(handler)
295
out = stdout.getvalue()
296
err = stderr.getvalue()
298
self.log('output:\n%s', out)
300
self.log('errors:\n%s', err)
301
if retcode is not None:
302
self.assertEquals(result, retcode)
90
305
def run_bzr(self, *args, **kwargs):
91
306
"""Invoke bzr, as if it were run from the command line.
94
309
overall behavior of the bzr application (rather than a unit test
95
310
or a functional test of the library.)
97
Much of the old code runs bzr by forking a new copy of Python, but
98
that is slower, harder to debug, and generally not necessary.
312
This sends the stdout/stderr results into the test's log,
313
where it may be useful for debugging. See also run_captured.
100
retcode = kwargs.get('retcode', 0)
101
result = self.apply_redirected(None, None, None,
102
bzrlib.commands.run_bzr, args)
103
self.assertEquals(result, retcode)
315
retcode = kwargs.pop('retcode', 0)
316
return self.run_bzr_captured(args, retcode)
105
318
def check_inventory_shape(self, inv, shape):
107
Compare an inventory to a list of expected names.
319
"""Compare an inventory to a list of expected names.
109
321
Fail if they are not precisely equal.
179
394
if contents != expect:
180
395
self.log("expected: %r" % expect)
181
396
self.log("actually: %r" % contents)
182
self.fail("contents of %s not as expected")
397
self.fail("contents of %s not as expected" % filename)
184
399
def _make_test_root(self):
189
400
if TestCaseInTempDir.TEST_ROOT is not None:
191
TestCaseInTempDir.TEST_ROOT = os.path.abspath(
192
tempfile.mkdtemp(suffix='.tmp',
193
prefix=self._TEST_NAME + '-',
404
root = 'test%04d.tmp' % i
408
if e.errno == errno.EEXIST:
413
# successfully created
414
TestCaseInTempDir.TEST_ROOT = os.path.abspath(root)
196
416
# make a fake bzr directory there to prevent any tests propagating
197
417
# up onto the source directory's real branch
198
418
os.mkdir(os.path.join(TestCaseInTempDir.TEST_ROOT, '.bzr'))
201
super(TestCaseInTempDir, self).setUp()
203
421
self._make_test_root()
204
422
self._currentdir = os.getcwdu()
205
self.test_dir = os.path.join(self.TEST_ROOT, self.id())
423
short_id = self.id().replace('bzrlib.selftest.', '') \
424
.replace('__main__.', '')
425
self.test_dir = os.path.join(self.TEST_ROOT, short_id)
206
426
os.mkdir(self.test_dir)
207
427
os.chdir(self.test_dir)
428
super(TestCaseInTempDir, self).setUp()
209
430
def tearDown(self):
211
431
os.chdir(self._currentdir)
212
432
super(TestCaseInTempDir, self).tearDown()
214
def _formcmd(self, cmd):
215
if isinstance(cmd, basestring):
218
cmd[0] = self.BZRPATH
219
if self.OVERRIDE_PYTHON:
220
cmd.insert(0, self.OVERRIDE_PYTHON)
221
self.log('$ %r' % cmd)
224
def runcmd(self, cmd, retcode=0):
225
"""Run one command and check the return code.
227
Returns a tuple of (stdout,stderr) strings.
229
If a single string is based, it is split into words.
230
For commands that are not simple space-separated words, please
231
pass a list instead."""
232
cmd = self._formcmd(cmd)
233
self.log('$ ' + ' '.join(cmd))
234
actual_retcode = subprocess.call(cmd, stdout=self._log_file,
235
stderr=self._log_file)
236
if retcode != actual_retcode:
237
raise CommandFailed("test failed: %r returned %d, expected %d"
238
% (cmd, actual_retcode, retcode))
240
def backtick(self, cmd, retcode=0):
241
"""Run a command and return its output"""
242
cmd = self._formcmd(cmd)
243
child = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=self._log_file)
244
outd, errd = child.communicate()
246
actual_retcode = child.wait()
248
outd = outd.replace('\r', '')
250
if retcode != actual_retcode:
251
raise CommandFailed("test failed: %r returned %d, expected %d"
252
% (cmd, actual_retcode, retcode))
258
434
def build_tree(self, shape):
259
435
"""Build a test tree according to a pattern.
282
463
logging.info('an info message')
283
464
warning('something looks dodgy...')
284
465
logging.debug('hello, test is running')
288
def selftest(verbose=False, pattern=".*"):
289
return run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern)
469
def filter_suite_by_re(suite, pattern):
470
result = TestUtil.TestSuite()
471
filter_re = re.compile(pattern)
472
for test in iter_suite_tests(suite):
473
if filter_re.search(test.id()):
478
def run_suite(suite, name='test', verbose=False, pattern=".*",
479
stop_on_failure=False):
480
TestCaseInTempDir._TEST_NAME = name
485
runner = TextTestRunner(stream=sys.stdout,
488
runner.stop_on_failure=stop_on_failure
490
suite = filter_suite_by_re(suite, pattern)
491
result = runner.run(suite)
492
# This is still a little bogus,
493
# but only a little. Folk not using our testrunner will
494
# have to delete their temp directories themselves.
495
if result.wasSuccessful():
496
if TestCaseInTempDir.TEST_ROOT is not None:
497
shutil.rmtree(TestCaseInTempDir.TEST_ROOT)
499
print "Failed tests working directories are in '%s'\n" % TestCaseInTempDir.TEST_ROOT
500
return result.wasSuccessful()
503
def selftest(verbose=False, pattern=".*", stop_on_failure=True):
504
"""Run the whole test suite under the enhanced runner"""
505
return run_suite(test_suite(), 'testbzr', verbose=verbose, pattern=pattern,
506
stop_on_failure=stop_on_failure)
292
509
def test_suite():
293
from bzrlib.selftest.TestUtil import TestLoader, TestSuite
294
import bzrlib, bzrlib.store, bzrlib.inventory, bzrlib.branch
295
import bzrlib.osutils, bzrlib.commands, bzrlib.merge3, bzrlib.plugin
510
"""Build and return TestSuite for the whole program."""
511
import bzrlib.store, bzrlib.inventory, bzrlib.branch
512
import bzrlib.osutils, bzrlib.merge3, bzrlib.plugin
296
513
from doctest import DocTestSuite
302
515
global MODULES_TO_TEST, MODULES_TO_DOCTEST
304
517
testmod_names = \
305
518
['bzrlib.selftest.MetaTestLog',
519
'bzrlib.selftest.testgpg',
520
'bzrlib.selftest.testidentitymap',
306
521
'bzrlib.selftest.testinv',
307
'bzrlib.selftest.testfetch',
522
'bzrlib.selftest.test_ancestry',
523
'bzrlib.selftest.test_commit',
524
'bzrlib.selftest.test_commit_merge',
525
'bzrlib.selftest.testconfig',
308
526
'bzrlib.selftest.versioning',
309
'bzrlib.selftest.whitebox',
310
527
'bzrlib.selftest.testmerge3',
528
'bzrlib.selftest.testmerge',
311
529
'bzrlib.selftest.testhashcache',
312
530
'bzrlib.selftest.teststatus',
313
531
'bzrlib.selftest.testlog',
314
'bzrlib.selftest.blackbox',
315
532
'bzrlib.selftest.testrevisionnamespaces',
316
533
'bzrlib.selftest.testbranch',
317
534
'bzrlib.selftest.testrevision',
535
'bzrlib.selftest.test_revision_info',
318
536
'bzrlib.selftest.test_merge_core',
319
537
'bzrlib.selftest.test_smart_add',
538
'bzrlib.selftest.test_bad_files',
320
539
'bzrlib.selftest.testdiff',
540
'bzrlib.selftest.test_parent',
541
'bzrlib.selftest.test_xml',
542
'bzrlib.selftest.test_weave',
543
'bzrlib.selftest.testfetch',
544
'bzrlib.selftest.whitebox',
545
'bzrlib.selftest.teststore',
546
'bzrlib.selftest.blackbox',
547
'bzrlib.selftest.testsampler',
548
'bzrlib.selftest.testtransactions',
549
'bzrlib.selftest.testtransport',
550
'bzrlib.selftest.testgraph',
551
'bzrlib.selftest.testworkingtree',
552
'bzrlib.selftest.test_upgrade',
553
'bzrlib.selftest.test_conflicts',
554
'bzrlib.selftest.testtestament',
555
'bzrlib.selftest.testannotate',
556
'bzrlib.selftest.testrevprops',
557
'bzrlib.selftest.testoptions',
558
'bzrlib.selftest.testhttp',
559
'bzrlib.selftest.testnonascii',
324
562
for m in (bzrlib.store, bzrlib.inventory, bzrlib.branch,
325
bzrlib.osutils, bzrlib.commands, bzrlib.merge3):
563
bzrlib.osutils, bzrlib.commands, bzrlib.merge3,
326
566
if m not in MODULES_TO_DOCTEST:
327
567
MODULES_TO_DOCTEST.append(m)