333
336
def test_scenarios(self):
334
337
# check that constructor parameters are passed through to the adapted
336
from bzrlib.tests.per_workingtree import make_scenarios
339
from .per_workingtree import make_scenarios
339
formats = [workingtree.WorkingTreeFormat2(),
340
workingtree.WorkingTreeFormat3(),]
341
scenarios = make_scenarios(server1, server2, formats)
342
formats = [workingtree_4.WorkingTreeFormat4(),
343
workingtree_3.WorkingTreeFormat3(),
344
workingtree_4.WorkingTreeFormat6()]
345
scenarios = make_scenarios(server1, server2, formats,
346
remote_server='c', remote_readonly_server='d',
347
remote_backing_server='e')
342
348
self.assertEqual([
343
('WorkingTreeFormat2',
344
{'bzrdir_format': formats[0]._matchingbzrdir,
349
('WorkingTreeFormat4',
350
{'bzrdir_format': formats[0]._matchingcontroldir,
345
351
'transport_readonly_server': 'b',
346
352
'transport_server': 'a',
347
353
'workingtree_format': formats[0]}),
348
354
('WorkingTreeFormat3',
349
{'bzrdir_format': formats[1]._matchingbzrdir,
350
'transport_readonly_server': 'b',
351
'transport_server': 'a',
352
'workingtree_format': formats[1]})],
355
{'bzrdir_format': formats[1]._matchingcontroldir,
356
'transport_readonly_server': 'b',
357
'transport_server': 'a',
358
'workingtree_format': formats[1]}),
359
('WorkingTreeFormat6',
360
{'bzrdir_format': formats[2]._matchingcontroldir,
361
'transport_readonly_server': 'b',
362
'transport_server': 'a',
363
'workingtree_format': formats[2]}),
364
('WorkingTreeFormat6,remote',
365
{'bzrdir_format': formats[2]._matchingcontroldir,
366
'repo_is_remote': True,
367
'transport_readonly_server': 'd',
368
'transport_server': 'c',
369
'vfs_transport_factory': 'e',
370
'workingtree_format': formats[2]}),
356
374
class TestTreeScenarios(tests.TestCase):
358
376
def test_scenarios(self):
359
377
# the tree implementation scenario generator is meant to setup one
360
# instance for each working tree format, and one additional instance
378
# instance for each working tree format, one additional instance
361
379
# that will use the default wt format, but create a revision tree for
362
# the tests. this means that the wt ones should have the
363
# workingtree_to_test_tree attribute set to 'return_parameter' and the
364
# revision one set to revision_tree_from_workingtree.
380
# the tests, and one more that uses the default wt format as a
381
# lightweight checkout of a remote repository. This means that the wt
382
# ones should have the workingtree_to_test_tree attribute set to
383
# 'return_parameter' and the revision one set to
384
# revision_tree_from_workingtree.
366
from bzrlib.tests.per_tree import (
386
from .per_tree import (
367
387
_dirstate_tree_from_workingtree,
369
389
preview_tree_pre,
376
formats = [workingtree.WorkingTreeFormat2(),
377
workingtree.WorkingTreeFormat3(),]
396
smart_server = test_server.SmartTCPServer_for_testing
397
smart_readonly_server = test_server.ReadonlySmartTCPServer_for_testing
398
mem_server = memory.MemoryServer
399
formats = [workingtree_4.WorkingTreeFormat4(),
400
workingtree_3.WorkingTreeFormat3(),]
378
401
scenarios = make_scenarios(server1, server2, formats)
379
self.assertEqual(7, len(scenarios))
380
default_wt_format = workingtree.WorkingTreeFormat4._default_format
381
wt4_format = workingtree.WorkingTreeFormat4()
382
wt5_format = workingtree.WorkingTreeFormat5()
402
self.assertEqual(8, len(scenarios))
403
default_wt_format = workingtree.format_registry.get_default()
404
wt4_format = workingtree_4.WorkingTreeFormat4()
405
wt5_format = workingtree_4.WorkingTreeFormat5()
406
wt6_format = workingtree_4.WorkingTreeFormat6()
383
407
expected_scenarios = [
384
('WorkingTreeFormat2',
385
{'bzrdir_format': formats[0]._matchingbzrdir,
408
('WorkingTreeFormat4',
409
{'bzrdir_format': formats[0]._matchingcontroldir,
386
410
'transport_readonly_server': 'b',
387
411
'transport_server': 'a',
388
412
'workingtree_format': formats[0],
389
413
'_workingtree_to_test_tree': return_parameter,
391
415
('WorkingTreeFormat3',
392
{'bzrdir_format': formats[1]._matchingbzrdir,
416
{'bzrdir_format': formats[1]._matchingcontroldir,
393
417
'transport_readonly_server': 'b',
394
418
'transport_server': 'a',
395
419
'workingtree_format': formats[1],
396
420
'_workingtree_to_test_tree': return_parameter,
422
('WorkingTreeFormat6,remote',
423
{'bzrdir_format': wt6_format._matchingcontroldir,
424
'repo_is_remote': True,
425
'transport_readonly_server': smart_readonly_server,
426
'transport_server': smart_server,
427
'vfs_transport_factory': mem_server,
428
'workingtree_format': wt6_format,
429
'_workingtree_to_test_tree': return_parameter,
399
432
{'_workingtree_to_test_tree': revision_tree_from_workingtree,
400
'bzrdir_format': default_wt_format._matchingbzrdir,
433
'bzrdir_format': default_wt_format._matchingcontroldir,
401
434
'transport_readonly_server': 'b',
402
435
'transport_server': 'a',
403
436
'workingtree_format': default_wt_format,
405
438
('DirStateRevisionTree,WT4',
406
439
{'_workingtree_to_test_tree': _dirstate_tree_from_workingtree,
407
'bzrdir_format': wt4_format._matchingbzrdir,
440
'bzrdir_format': wt4_format._matchingcontroldir,
408
441
'transport_readonly_server': 'b',
409
442
'transport_server': 'a',
410
443
'workingtree_format': wt4_format,
412
445
('DirStateRevisionTree,WT5',
413
446
{'_workingtree_to_test_tree': _dirstate_tree_from_workingtree,
414
'bzrdir_format': wt5_format._matchingbzrdir,
447
'bzrdir_format': wt5_format._matchingcontroldir,
415
448
'transport_readonly_server': 'b',
416
449
'transport_server': 'a',
417
450
'workingtree_format': wt5_format,
420
453
{'_workingtree_to_test_tree': preview_tree_pre,
421
'bzrdir_format': default_wt_format._matchingbzrdir,
454
'bzrdir_format': default_wt_format._matchingcontroldir,
422
455
'transport_readonly_server': 'b',
423
456
'transport_server': 'a',
424
457
'workingtree_format': default_wt_format}),
425
458
('PreviewTreePost',
426
459
{'_workingtree_to_test_tree': preview_tree_post,
427
'bzrdir_format': default_wt_format._matchingbzrdir,
460
'bzrdir_format': default_wt_format._matchingcontroldir,
428
461
'transport_readonly_server': 'b',
429
462
'transport_server': 'a',
430
463
'workingtree_format': default_wt_format}),
603
642
def test_dangling_locks_cause_failures(self):
604
643
class TestDanglingLock(tests.TestCaseWithMemoryTransport):
605
644
def test_function(self):
606
t = self.get_transport('.')
645
t = self.get_transport_from_path('.')
607
646
l = lockdir.LockDir(t, 'lock')
610
649
test = TestDanglingLock('test_function')
611
650
result = test.run()
651
total_failures = result.errors + result.failures
612
652
if self._lock_check_thorough:
613
self.assertEqual(1, len(result.errors))
653
self.assertEqual(1, len(total_failures))
615
655
# When _lock_check_thorough is disabled, then we don't trigger a
617
self.assertEqual(0, len(result.errors))
657
self.assertEqual(0, len(total_failures))
620
660
class TestTestCaseWithTransport(tests.TestCaseWithTransport):
621
661
"""Tests for the convenience functions TestCaseWithTransport introduces."""
623
663
def test_get_readonly_url_none(self):
624
from bzrlib.transport import get_transport
625
from bzrlib.transport.readonly import ReadonlyTransportDecorator
664
from ..transport.readonly import ReadonlyTransportDecorator
626
665
self.vfs_transport_factory = memory.MemoryServer
627
666
self.transport_readonly_server = None
628
667
# calling get_readonly_transport() constructs a decorator on the url
630
669
url = self.get_readonly_url()
631
670
url2 = self.get_readonly_url('foo/bar')
632
t = get_transport(url)
633
t2 = get_transport(url2)
634
self.failUnless(isinstance(t, ReadonlyTransportDecorator))
635
self.failUnless(isinstance(t2, ReadonlyTransportDecorator))
671
t = transport.get_transport_from_url(url)
672
t2 = transport.get_transport_from_url(url2)
673
self.assertIsInstance(t, ReadonlyTransportDecorator)
674
self.assertIsInstance(t2, ReadonlyTransportDecorator)
636
675
self.assertEqual(t2.base[:-1], t.abspath('foo/bar'))
638
677
def test_get_readonly_url_http(self):
639
from bzrlib.tests.http_server import HttpServer
640
from bzrlib.transport import get_transport
641
from bzrlib.transport.http import HttpTransportBase
678
from .http_server import HttpServer
679
from ..transport.http import HttpTransportBase
642
680
self.transport_server = test_server.LocalURLServer
643
681
self.transport_readonly_server = HttpServer
644
682
# calling get_readonly_transport() gives us a HTTP server instance.
645
683
url = self.get_readonly_url()
646
684
url2 = self.get_readonly_url('foo/bar')
647
685
# the transport returned may be any HttpTransportBase subclass
648
t = get_transport(url)
649
t2 = get_transport(url2)
650
self.failUnless(isinstance(t, HttpTransportBase))
651
self.failUnless(isinstance(t2, HttpTransportBase))
686
t = transport.get_transport_from_url(url)
687
t2 = transport.get_transport_from_url(url2)
688
self.assertIsInstance(t, HttpTransportBase)
689
self.assertIsInstance(t2, HttpTransportBase)
652
690
self.assertEqual(t2.base[:-1], t.abspath('foo/bar'))
654
692
def test_is_directory(self):
1643
1670
class Test(tests.TestCase):
1645
1672
def setUp(self):
1646
tests.TestCase.setUp(self)
1673
super(Test, self).setUp()
1647
1674
self.orig = self.overrideAttr(obj, 'test_attr', new='modified')
1649
1676
def test_value(self):
1650
1677
self.assertEqual('original', self.orig)
1651
1678
self.assertEqual('modified', obj.test_attr)
1653
test = Test('test_value')
1654
test.run(unittest.TestResult())
1680
self._run_successful_test(Test('test_value'))
1655
1681
self.assertEqual('original', obj.test_attr)
1683
def test_overrideAttr_with_no_existing_value_and_value(self):
1684
# Do not define the test_attribute
1685
obj = self # Make 'obj' visible to the embedded test
1686
class Test(tests.TestCase):
1689
tests.TestCase.setUp(self)
1690
self.orig = self.overrideAttr(obj, 'test_attr', new='modified')
1692
def test_value(self):
1693
self.assertEqual(tests._unitialized_attr, self.orig)
1694
self.assertEqual('modified', obj.test_attr)
1696
self._run_successful_test(Test('test_value'))
1697
self.assertRaises(AttributeError, getattr, obj, 'test_attr')
1699
def test_overrideAttr_with_no_existing_value_and_no_value(self):
1700
# Do not define the test_attribute
1701
obj = self # Make 'obj' visible to the embedded test
1702
class Test(tests.TestCase):
1705
tests.TestCase.setUp(self)
1706
self.orig = self.overrideAttr(obj, 'test_attr')
1708
def test_value(self):
1709
self.assertEqual(tests._unitialized_attr, self.orig)
1710
self.assertRaises(AttributeError, getattr, obj, 'test_attr')
1712
self._run_successful_test(Test('test_value'))
1713
self.assertRaises(AttributeError, getattr, obj, 'test_attr')
1715
def test_recordCalls(self):
1716
from breezy.tests import test_selftest
1717
calls = self.recordCalls(
1718
test_selftest, '_add_numbers')
1719
self.assertEqual(test_selftest._add_numbers(2, 10),
1721
self.assertEqual(calls, [((2, 10), {})])
1724
def _add_numbers(a, b):
1728
class _MissingFeature(features.Feature):
1731
missing_feature = _MissingFeature()
1734
def _get_test(name):
1735
"""Get an instance of a specific example test.
1737
We protect this in a function so that they don't auto-run in the test
1741
class ExampleTests(tests.TestCase):
1743
def test_fail(self):
1744
mutter('this was a failing test')
1745
self.fail('this test will fail')
1747
def test_error(self):
1748
mutter('this test errored')
1749
raise RuntimeError('gotcha')
1751
def test_missing_feature(self):
1752
mutter('missing the feature')
1753
self.requireFeature(missing_feature)
1755
def test_skip(self):
1756
mutter('this test will be skipped')
1757
raise tests.TestSkipped('reason')
1759
def test_success(self):
1760
mutter('this test succeeds')
1762
def test_xfail(self):
1763
mutter('test with expected failure')
1764
self.knownFailure('this_fails')
1766
def test_unexpected_success(self):
1767
mutter('test with unexpected success')
1768
self.expectFailure('should_fail', lambda: None)
1770
return ExampleTests(name)
1773
def _get_skip_reasons(result):
1774
# GZ 2017-06-06: Newer testtools doesn't have this, uses detail instead
1775
return result.skip_reasons
1778
class TestTestCaseLogDetails(tests.TestCase):
1780
def _run_test(self, test_name):
1781
test = _get_test(test_name)
1782
result = testtools.TestResult()
1786
def test_fail_has_log(self):
1787
result = self._run_test('test_fail')
1788
self.assertEqual(1, len(result.failures))
1789
result_content = result.failures[0][1]
1790
self.assertContainsRe(result_content,
1791
'(?m)^(?:Text attachment: )?log(?:$|: )')
1792
self.assertContainsRe(result_content, 'this was a failing test')
1794
def test_error_has_log(self):
1795
result = self._run_test('test_error')
1796
self.assertEqual(1, len(result.errors))
1797
result_content = result.errors[0][1]
1798
self.assertContainsRe(result_content,
1799
'(?m)^(?:Text attachment: )?log(?:$|: )')
1800
self.assertContainsRe(result_content, 'this test errored')
1802
def test_skip_has_no_log(self):
1803
result = self._run_test('test_skip')
1804
reasons = _get_skip_reasons(result)
1805
self.assertEqual({'reason'}, set(reasons))
1806
skips = reasons['reason']
1807
self.assertEqual(1, len(skips))
1809
self.assertFalse('log' in test.getDetails())
1811
def test_missing_feature_has_no_log(self):
1812
# testtools doesn't know about addNotSupported, so it just gets
1813
# considered as a skip
1814
result = self._run_test('test_missing_feature')
1815
reasons = _get_skip_reasons(result)
1816
self.assertEqual({missing_feature}, set(reasons))
1817
skips = reasons[missing_feature]
1818
self.assertEqual(1, len(skips))
1820
self.assertFalse('log' in test.getDetails())
1822
def test_xfail_has_no_log(self):
1823
result = self._run_test('test_xfail')
1824
self.assertEqual(1, len(result.expectedFailures))
1825
result_content = result.expectedFailures[0][1]
1826
self.assertNotContainsRe(result_content,
1827
'(?m)^(?:Text attachment: )?log(?:$|: )')
1828
self.assertNotContainsRe(result_content, 'test with expected failure')
1830
def test_unexpected_success_has_log(self):
1831
result = self._run_test('test_unexpected_success')
1832
self.assertEqual(1, len(result.unexpectedSuccesses))
1833
# Inconsistency, unexpectedSuccesses is a list of tests,
1834
# expectedFailures is a list of reasons?
1835
test = result.unexpectedSuccesses[0]
1836
details = test.getDetails()
1837
self.assertTrue('log' in details)
1840
class TestTestCloning(tests.TestCase):
1841
"""Tests that test cloning of TestCases (as used by multiply_tests)."""
1843
def test_cloned_testcase_does_not_share_details(self):
1844
"""A TestCase cloned with clone_test does not share mutable attributes
1845
such as details or cleanups.
1847
class Test(tests.TestCase):
1849
self.addDetail('foo', Content('text/plain', lambda: 'foo'))
1850
orig_test = Test('test_foo')
1851
cloned_test = tests.clone_test(orig_test, orig_test.id() + '(cloned)')
1852
orig_test.run(unittest.TestResult())
1853
self.assertEqual('foo', orig_test.getDetails()['foo'].iter_bytes())
1854
self.assertEqual(None, cloned_test.getDetails().get('foo'))
1856
def test_double_apply_scenario_preserves_first_scenario(self):
1857
"""Applying two levels of scenarios to a test preserves the attributes
1858
added by both scenarios.
1860
class Test(tests.TestCase):
1863
test = Test('test_foo')
1864
scenarios_x = [('x=1', {'x': 1}), ('x=2', {'x': 2})]
1865
scenarios_y = [('y=1', {'y': 1}), ('y=2', {'y': 2})]
1866
suite = tests.multiply_tests(test, scenarios_x, unittest.TestSuite())
1867
suite = tests.multiply_tests(suite, scenarios_y, unittest.TestSuite())
1868
all_tests = list(tests.iter_suite_tests(suite))
1869
self.assertLength(4, all_tests)
1870
all_xys = sorted((t.x, t.y) for t in all_tests)
1871
self.assertEqual([(1, 1), (1, 2), (2, 1), (2, 2)], all_xys)
1658
1874
# NB: Don't delete this; it's not actually from 0.11!
1659
1875
@deprecated_function(deprecated_in((0, 11, 0)))
1971
2184
load_list='missing file name', list_only=True)
2187
class TestSubunitLogDetails(tests.TestCase, SelfTestHelper):
2189
_test_needs_features = [features.subunit]
2191
def run_subunit_stream(self, test_name):
2192
from subunit import ProtocolTestCase
2194
return TestUtil.TestSuite([_get_test(test_name)])
2195
stream = self.run_selftest(runner_class=tests.SubUnitBzrRunner,
2196
test_suite_factory=factory)
2197
test = ProtocolTestCase(stream)
2198
result = testtools.TestResult()
2200
content = stream.getvalue()
2201
return content, result
2203
def test_fail_has_log(self):
2204
content, result = self.run_subunit_stream('test_fail')
2205
self.assertEqual(1, len(result.failures))
2206
self.assertContainsRe(content, '(?m)^log$')
2207
self.assertContainsRe(content, 'this test will fail')
2209
def test_error_has_log(self):
2210
content, result = self.run_subunit_stream('test_error')
2211
self.assertContainsRe(content, '(?m)^log$')
2212
self.assertContainsRe(content, 'this test errored')
2214
def test_skip_has_no_log(self):
2215
content, result = self.run_subunit_stream('test_skip')
2216
self.assertNotContainsRe(content, '(?m)^log$')
2217
self.assertNotContainsRe(content, 'this test will be skipped')
2218
reasons = _get_skip_reasons(result)
2219
self.assertEqual({'reason'}, set(reasons))
2220
skips = reasons['reason']
2221
self.assertEqual(1, len(skips))
2223
# RemotedTestCase doesn't preserve the "details"
2224
## self.assertFalse('log' in test.getDetails())
2226
def test_missing_feature_has_no_log(self):
2227
content, result = self.run_subunit_stream('test_missing_feature')
2228
self.assertNotContainsRe(content, '(?m)^log$')
2229
self.assertNotContainsRe(content, 'missing the feature')
2230
reasons = _get_skip_reasons(result)
2231
self.assertEqual({'_MissingFeature\n'}, set(reasons))
2232
skips = reasons['_MissingFeature\n']
2233
self.assertEqual(1, len(skips))
2235
# RemotedTestCase doesn't preserve the "details"
2236
## self.assertFalse('log' in test.getDetails())
2238
def test_xfail_has_no_log(self):
2239
content, result = self.run_subunit_stream('test_xfail')
2240
self.assertNotContainsRe(content, '(?m)^log$')
2241
self.assertNotContainsRe(content, 'test with expected failure')
2242
self.assertEqual(1, len(result.expectedFailures))
2243
result_content = result.expectedFailures[0][1]
2244
self.assertNotContainsRe(result_content,
2245
'(?m)^(?:Text attachment: )?log(?:$|: )')
2246
self.assertNotContainsRe(result_content, 'test with expected failure')
2248
def test_unexpected_success_has_log(self):
2249
content, result = self.run_subunit_stream('test_unexpected_success')
2250
self.assertContainsRe(content, '(?m)^log$')
2251
self.assertContainsRe(content, 'test with unexpected success')
2252
# GZ 2011-05-18: Old versions of subunit treat unexpected success as a
2253
# success, if a min version check is added remove this
2254
from subunit import TestProtocolClient as _Client
2255
if _Client.addUnexpectedSuccess.__func__ is _Client.addSuccess.__func__:
2256
self.expectFailure('subunit treats "unexpectedSuccess"'
2257
' as a plain success',
2258
self.assertEqual, 1, len(result.unexpectedSuccesses))
2259
self.assertEqual(1, len(result.unexpectedSuccesses))
2260
test = result.unexpectedSuccesses[0]
2261
# RemotedTestCase doesn't preserve the "details"
2262
## self.assertTrue('log' in test.getDetails())
2264
def test_success_has_no_log(self):
2265
content, result = self.run_subunit_stream('test_success')
2266
self.assertEqual(1, result.testsRun)
2267
self.assertNotContainsRe(content, '(?m)^log$')
2268
self.assertNotContainsRe(content, 'this test succeeds')
1974
2271
class TestRunBzr(tests.TestCase):
2264
2561
class TestStartBzrSubProcess(tests.TestCase):
2562
"""Stub test start_bzr_subprocess."""
2266
def check_popen_state(self):
2267
"""Replace to make assertions when popen is called."""
2564
def _subprocess_log_cleanup(self):
2565
"""Inhibits the base version as we don't produce a log file."""
2269
2567
def _popen(self, *args, **kwargs):
2270
"""Record the command that is run, so that we can ensure it is correct"""
2568
"""Override the base version to record the command that is run.
2570
From there we can ensure it is correct without spawning a real process.
2271
2572
self.check_popen_state()
2272
2573
self._popen_args = args
2273
2574
self._popen_kwargs = kwargs
2274
2575
raise _DontSpawnProcess()
2577
def check_popen_state(self):
2578
"""Replace to make assertions when popen is called."""
2276
2580
def test_run_bzr_subprocess_no_plugins(self):
2277
2581
self.assertRaises(_DontSpawnProcess, self.start_bzr_subprocess, [])
2278
2582
command = self._popen_args[0]
2279
2583
self.assertEqual(sys.executable, command[0])
2280
self.assertEqual(self.get_bzr_path(), command[1])
2584
self.assertEqual(self.get_brz_path(), command[1])
2281
2585
self.assertEqual(['--no-plugins'], command[2:])
2283
2587
def test_allow_plugins(self):
2284
2588
self.assertRaises(_DontSpawnProcess, self.start_bzr_subprocess, [],
2286
2590
command = self._popen_args[0]
2287
2591
self.assertEqual([], command[2:])
2289
2593
def test_set_env(self):
2290
self.failIf('EXISTANT_ENV_VAR' in os.environ)
2594
self.assertFalse('EXISTANT_ENV_VAR' in os.environ)
2291
2595
# set in the child
2292
2596
def check_environment():
2293
2597
self.assertEqual('set variable', os.environ['EXISTANT_ENV_VAR'])
2294
2598
self.check_popen_state = check_environment
2295
2599
self.assertRaises(_DontSpawnProcess, self.start_bzr_subprocess, [],
2296
env_changes={'EXISTANT_ENV_VAR':'set variable'})
2600
env_changes={'EXISTANT_ENV_VAR':'set variable'})
2297
2601
# not set in theparent
2298
2602
self.assertFalse('EXISTANT_ENV_VAR' in os.environ)
2300
2604
def test_run_bzr_subprocess_env_del(self):
2301
2605
"""run_bzr_subprocess can remove environment variables too."""
2302
self.failIf('EXISTANT_ENV_VAR' in os.environ)
2606
self.assertFalse('EXISTANT_ENV_VAR' in os.environ)
2303
2607
def check_environment():
2304
2608
self.assertFalse('EXISTANT_ENV_VAR' in os.environ)
2305
2609
os.environ['EXISTANT_ENV_VAR'] = 'set variable'
2306
2610
self.check_popen_state = check_environment
2307
2611
self.assertRaises(_DontSpawnProcess, self.start_bzr_subprocess, [],
2308
env_changes={'EXISTANT_ENV_VAR':None})
2612
env_changes={'EXISTANT_ENV_VAR':None})
2309
2613
# Still set in parent
2310
2614
self.assertEqual('set variable', os.environ['EXISTANT_ENV_VAR'])
2311
2615
del os.environ['EXISTANT_ENV_VAR']
2313
2617
def test_env_del_missing(self):
2314
self.failIf('NON_EXISTANT_ENV_VAR' in os.environ)
2618
self.assertFalse('NON_EXISTANT_ENV_VAR' in os.environ)
2315
2619
def check_environment():
2316
2620
self.assertFalse('NON_EXISTANT_ENV_VAR' in os.environ)
2317
2621
self.check_popen_state = check_environment
2318
2622
self.assertRaises(_DontSpawnProcess, self.start_bzr_subprocess, [],
2319
env_changes={'NON_EXISTANT_ENV_VAR':None})
2623
env_changes={'NON_EXISTANT_ENV_VAR':None})
2321
2625
def test_working_dir(self):
2322
2626
"""Test that we can specify the working dir for the child"""
2354
2657
result = self.finish_bzr_subprocess(process, send_signal=signal.SIGINT,
2356
2659
self.assertEqual('', result[0])
2357
self.assertEqual('bzr: interrupted\n', result[1])
2360
class TestFeature(tests.TestCase):
2362
def test_caching(self):
2363
"""Feature._probe is called by the feature at most once."""
2364
class InstrumentedFeature(tests.Feature):
2366
super(InstrumentedFeature, self).__init__()
2369
self.calls.append('_probe')
2371
feature = InstrumentedFeature()
2373
self.assertEqual(['_probe'], feature.calls)
2375
self.assertEqual(['_probe'], feature.calls)
2377
def test_named_str(self):
2378
"""Feature.__str__ should thunk to feature_name()."""
2379
class NamedFeature(tests.Feature):
2380
def feature_name(self):
2382
feature = NamedFeature()
2383
self.assertEqual('symlinks', str(feature))
2385
def test_default_str(self):
2386
"""Feature.__str__ should default to __class__.__name__."""
2387
class NamedFeature(tests.Feature):
2389
feature = NamedFeature()
2390
self.assertEqual('NamedFeature', str(feature))
2393
class TestUnavailableFeature(tests.TestCase):
2395
def test_access_feature(self):
2396
feature = tests.Feature()
2397
exception = tests.UnavailableFeature(feature)
2398
self.assertIs(feature, exception.args[0])
2401
simple_thunk_feature = tests._CompatabilityThunkFeature(
2402
deprecated_in((2, 1, 0)),
2403
'bzrlib.tests.test_selftest',
2404
'simple_thunk_feature','UnicodeFilename',
2405
replacement_module='bzrlib.tests'
2408
class Test_CompatibilityFeature(tests.TestCase):
2410
def test_does_thunk(self):
2411
res = self.callDeprecated(
2412
['bzrlib.tests.test_selftest.simple_thunk_feature was deprecated'
2413
' in version 2.1.0. Use bzrlib.tests.UnicodeFilename instead.'],
2414
simple_thunk_feature.available)
2415
self.assertEqual(tests.UnicodeFilename.available(), res)
2418
class TestModuleAvailableFeature(tests.TestCase):
2420
def test_available_module(self):
2421
feature = tests.ModuleAvailableFeature('bzrlib.tests')
2422
self.assertEqual('bzrlib.tests', feature.module_name)
2423
self.assertEqual('bzrlib.tests', str(feature))
2424
self.assertTrue(feature.available())
2425
self.assertIs(tests, feature.module)
2427
def test_unavailable_module(self):
2428
feature = tests.ModuleAvailableFeature('bzrlib.no_such_module_exists')
2429
self.assertEqual('bzrlib.no_such_module_exists', str(feature))
2430
self.assertFalse(feature.available())
2431
self.assertIs(None, feature.module)
2660
self.assertEqual('brz: interrupted\n', result[1])
2434
2663
class TestSelftestFiltering(tests.TestCase):
2436
2665
def setUp(self):
2437
tests.TestCase.setUp(self)
2666
super(TestSelftestFiltering, self).setUp()
2438
2667
self.suite = TestUtil.TestSuite()
2439
2668
self.loader = TestUtil.TestLoader()
2440
2669
self.suite.addTest(self.loader.loadTestsFromModule(
2441
sys.modules['bzrlib.tests.test_selftest']))
2670
sys.modules['breezy.tests.test_selftest']))
2442
2671
self.all_names = _test_ids(self.suite)
2444
2673
def test_condition_id_re(self):
2445
test_name = ('bzrlib.tests.test_selftest.TestSelftestFiltering.'
2674
test_name = ('breezy.tests.test_selftest.TestSelftestFiltering.'
2446
2675
'test_condition_id_re')
2447
2676
filtered_suite = tests.filter_suite_by_condition(
2448
2677
self.suite, tests.condition_id_re('test_condition_id_re'))
2449
2678
self.assertEqual([test_name], _test_ids(filtered_suite))
2451
2680
def test_condition_id_in_list(self):
2452
test_names = ['bzrlib.tests.test_selftest.TestSelftestFiltering.'
2681
test_names = ['breezy.tests.test_selftest.TestSelftestFiltering.'
2453
2682
'test_condition_id_in_list']
2454
2683
id_list = tests.TestIdList(test_names)
2455
2684
filtered_suite = tests.filter_suite_by_condition(
2953
3184
def test_predefined_prefixes(self):
2954
3185
tpr = tests.test_prefix_alias_registry
2955
self.assertEquals('bzrlib', tpr.resolve_alias('bzrlib'))
2956
self.assertEquals('bzrlib.doc', tpr.resolve_alias('bd'))
2957
self.assertEquals('bzrlib.utils', tpr.resolve_alias('bu'))
2958
self.assertEquals('bzrlib.tests', tpr.resolve_alias('bt'))
2959
self.assertEquals('bzrlib.tests.blackbox', tpr.resolve_alias('bb'))
2960
self.assertEquals('bzrlib.plugins', tpr.resolve_alias('bp'))
3186
self.assertEqual('breezy', tpr.resolve_alias('breezy'))
3187
self.assertEqual('breezy.doc', tpr.resolve_alias('bd'))
3188
self.assertEqual('breezy.utils', tpr.resolve_alias('bu'))
3189
self.assertEqual('breezy.tests', tpr.resolve_alias('bt'))
3190
self.assertEqual('breezy.tests.blackbox', tpr.resolve_alias('bb'))
3191
self.assertEqual('breezy.plugins', tpr.resolve_alias('bp'))
3194
class TestThreadLeakDetection(tests.TestCase):
3195
"""Ensure when tests leak threads we detect and report it"""
3197
class LeakRecordingResult(tests.ExtendedTestResult):
3199
tests.ExtendedTestResult.__init__(self, StringIO(), 0, 1)
3201
def _report_thread_leak(self, test, leaks, alive):
3202
self.leaks.append((test, leaks))
3204
def test_testcase_without_addCleanups(self):
3205
"""Check old TestCase instances don't break with leak detection"""
3206
class Test(unittest.TestCase):
3209
result = self.LeakRecordingResult()
3211
result.startTestRun()
3213
result.stopTestRun()
3214
self.assertEqual(result._tests_leaking_threads_count, 0)
3215
self.assertEqual(result.leaks, [])
3217
def test_thread_leak(self):
3218
"""Ensure a thread that outlives the running of a test is reported
3220
Uses a thread that blocks on an event, and is started by the inner
3221
test case. As the thread outlives the inner case's run, it should be
3222
detected as a leak, but the event is then set so that the thread can
3223
be safely joined in cleanup so it's not leaked for real.
3225
event = threading.Event()
3226
thread = threading.Thread(name="Leaker", target=event.wait)
3227
class Test(tests.TestCase):
3228
def test_leak(self):
3230
result = self.LeakRecordingResult()
3231
test = Test("test_leak")
3232
self.addCleanup(thread.join)
3233
self.addCleanup(event.set)
3234
result.startTestRun()
3236
result.stopTestRun()
3237
self.assertEqual(result._tests_leaking_threads_count, 1)
3238
self.assertEqual(result._first_thread_leaker_id, test.id())
3239
self.assertEqual(result.leaks, [(test, {thread})])
3240
self.assertContainsString(result.stream.getvalue(), "leaking threads")
3242
def test_multiple_leaks(self):
3243
"""Check multiple leaks are blamed on the test cases at fault
3245
Same concept as the previous test, but has one inner test method that
3246
leaks two threads, and one that doesn't leak at all.
3248
event = threading.Event()
3249
thread_a = threading.Thread(name="LeakerA", target=event.wait)
3250
thread_b = threading.Thread(name="LeakerB", target=event.wait)
3251
thread_c = threading.Thread(name="LeakerC", target=event.wait)
3252
class Test(tests.TestCase):
3253
def test_first_leak(self):
3255
def test_second_no_leak(self):
3257
def test_third_leak(self):
3260
result = self.LeakRecordingResult()
3261
first_test = Test("test_first_leak")
3262
third_test = Test("test_third_leak")
3263
self.addCleanup(thread_a.join)
3264
self.addCleanup(thread_b.join)
3265
self.addCleanup(thread_c.join)
3266
self.addCleanup(event.set)
3267
result.startTestRun()
3269
[first_test, Test("test_second_no_leak"), third_test]
3271
result.stopTestRun()
3272
self.assertEqual(result._tests_leaking_threads_count, 2)
3273
self.assertEqual(result._first_thread_leaker_id, first_test.id())
3274
self.assertEqual(result.leaks, [
3275
(first_test, {thread_b}),
3276
(third_test, {thread_a, thread_c})])
3277
self.assertContainsString(result.stream.getvalue(), "leaking threads")
3280
class TestPostMortemDebugging(tests.TestCase):
3281
"""Check post mortem debugging works when tests fail or error"""
3283
class TracebackRecordingResult(tests.ExtendedTestResult):
3285
tests.ExtendedTestResult.__init__(self, StringIO(), 0, 1)
3286
self.postcode = None
3287
def _post_mortem(self, tb=None):
3288
"""Record the code object at the end of the current traceback"""
3289
tb = tb or sys.exc_info()[2]
3292
while next is not None:
3295
self.postcode = tb.tb_frame.f_code
3296
def report_error(self, test, err):
3298
def report_failure(self, test, err):
3301
def test_location_unittest_error(self):
3302
"""Needs right post mortem traceback with erroring unittest case"""
3303
class Test(unittest.TestCase):
3306
result = self.TracebackRecordingResult()
3308
self.assertEqual(result.postcode, Test.runTest.__code__)
3310
def test_location_unittest_failure(self):
3311
"""Needs right post mortem traceback with failing unittest case"""
3312
class Test(unittest.TestCase):
3314
raise self.failureException
3315
result = self.TracebackRecordingResult()
3317
self.assertEqual(result.postcode, Test.runTest.__code__)
3319
def test_location_bt_error(self):
3320
"""Needs right post mortem traceback with erroring breezy.tests case"""
3321
class Test(tests.TestCase):
3322
def test_error(self):
3324
result = self.TracebackRecordingResult()
3325
Test("test_error").run(result)
3326
self.assertEqual(result.postcode, Test.test_error.__code__)
3328
def test_location_bt_failure(self):
3329
"""Needs right post mortem traceback with failing breezy.tests case"""
3330
class Test(tests.TestCase):
3331
def test_failure(self):
3332
raise self.failureException
3333
result = self.TracebackRecordingResult()
3334
Test("test_failure").run(result)
3335
self.assertEqual(result.postcode, Test.test_failure.__code__)
3337
def test_env_var_triggers_post_mortem(self):
3338
"""Check pdb.post_mortem is called iff BRZ_TEST_PDB is set"""
3340
result = tests.ExtendedTestResult(StringIO(), 0, 1)
3341
post_mortem_calls = []
3342
self.overrideAttr(pdb, "post_mortem", post_mortem_calls.append)
3343
self.overrideEnv('BRZ_TEST_PDB', None)
3344
result._post_mortem(1)
3345
self.overrideEnv('BRZ_TEST_PDB', 'on')
3346
result._post_mortem(2)
3347
self.assertEqual([2], post_mortem_calls)
2963
3350
class TestRunSuite(tests.TestCase):
2976
3363
self.verbosity)
2977
3364
tests.run_suite(suite, runner_class=MyRunner, stream=StringIO())
2978
3365
self.assertLength(1, calls)
3368
class _Selftest(object):
3369
"""Mixin for tests needing full selftest output"""
3371
def _inject_stream_into_subunit(self, stream):
3372
"""To be overridden by subclasses that run tests out of process"""
3374
def _run_selftest(self, **kwargs):
3376
self._inject_stream_into_subunit(sio)
3377
tests.selftest(stream=sio, stop_on_failure=False, **kwargs)
3378
return sio.getvalue()
3381
class _ForkedSelftest(_Selftest):
3382
"""Mixin for tests needing full selftest output with forked children"""
3384
_test_needs_features = [features.subunit]
3386
def _inject_stream_into_subunit(self, stream):
3387
"""Monkey-patch subunit so the extra output goes to stream not stdout
3389
Some APIs need rewriting so this kind of bogus hackery can be replaced
3390
by passing the stream param from run_tests down into ProtocolTestCase.
3392
from subunit import ProtocolTestCase
3393
_original_init = ProtocolTestCase.__init__
3394
def _init_with_passthrough(self, *args, **kwargs):
3395
_original_init(self, *args, **kwargs)
3396
self._passthrough = stream
3397
self.overrideAttr(ProtocolTestCase, "__init__", _init_with_passthrough)
3399
def _run_selftest(self, **kwargs):
3400
# GZ 2011-05-26: Add a PosixSystem feature so this check can go away
3401
if getattr(os, "fork", None) is None:
3402
raise tests.TestNotApplicable("Platform doesn't support forking")
3403
# Make sure the fork code is actually invoked by claiming two cores
3404
self.overrideAttr(osutils, "local_concurrency", lambda: 2)
3405
kwargs.setdefault("suite_decorators", []).append(tests.fork_decorator)
3406
return super(_ForkedSelftest, self)._run_selftest(**kwargs)
3409
class TestParallelFork(_ForkedSelftest, tests.TestCase):
3410
"""Check operation of --parallel=fork selftest option"""
3412
def test_error_in_child_during_fork(self):
3413
"""Error in a forked child during test setup should get reported"""
3414
class Test(tests.TestCase):
3415
def testMethod(self):
3417
# We don't care what, just break something that a child will run
3418
self.overrideAttr(tests, "workaround_zealous_crypto_random", None)
3419
out = self._run_selftest(test_suite_factory=Test)
3420
# Lines from the tracebacks of the two child processes may be mixed
3421
# together due to the way subunit parses and forwards the streams,
3422
# so permit extra lines between each part of the error output.
3423
self.assertContainsRe(out,
3426
".+ in fork_for_tests\n"
3428
"\s*workaround_zealous_crypto_random\(\)\n"
3433
class TestUncollectedWarnings(_Selftest, tests.TestCase):
3434
"""Check a test case still alive after being run emits a warning"""
3436
class Test(tests.TestCase):
3437
def test_pass(self):
3439
def test_self_ref(self):
3440
self.also_self = self.test_self_ref
3441
def test_skip(self):
3442
self.skipTest("Don't need")
3444
def _get_suite(self):
3445
return TestUtil.TestSuite([
3446
self.Test("test_pass"),
3447
self.Test("test_self_ref"),
3448
self.Test("test_skip"),
3451
def _run_selftest_with_suite(self, **kwargs):
3452
old_flags = tests.selftest_debug_flags
3453
tests.selftest_debug_flags = old_flags.union(["uncollected_cases"])
3454
gc_on = gc.isenabled()
3458
output = self._run_selftest(test_suite_factory=self._get_suite,
3463
tests.selftest_debug_flags = old_flags
3464
self.assertNotContainsRe(output, "Uncollected test case.*test_pass")
3465
self.assertContainsRe(output, "Uncollected test case.*test_self_ref")
3468
def test_testsuite(self):
3469
self._run_selftest_with_suite()
3471
def test_pattern(self):
3472
out = self._run_selftest_with_suite(pattern="test_(?:pass|self_ref)$")
3473
self.assertNotContainsRe(out, "test_skip")
3475
def test_exclude_pattern(self):
3476
out = self._run_selftest_with_suite(exclude_pattern="test_skip$")
3477
self.assertNotContainsRe(out, "test_skip")
3479
def test_random_seed(self):
3480
self._run_selftest_with_suite(random_seed="now")
3482
def test_matching_tests_first(self):
3483
self._run_selftest_with_suite(matching_tests_first=True,
3484
pattern="test_self_ref$")
3486
def test_starting_with_and_exclude(self):
3487
out = self._run_selftest_with_suite(starting_with=["bt."],
3488
exclude_pattern="test_skip$")
3489
self.assertNotContainsRe(out, "test_skip")
3491
def test_additonal_decorator(self):
3492
out = self._run_selftest_with_suite(
3493
suite_decorators=[tests.TestDecorator])
3496
class TestUncollectedWarningsSubunit(TestUncollectedWarnings):
3497
"""Check warnings from tests staying alive are emitted with subunit"""
3499
_test_needs_features = [features.subunit]
3501
def _run_selftest_with_suite(self, **kwargs):
3502
return TestUncollectedWarnings._run_selftest_with_suite(self,
3503
runner_class=tests.SubUnitBzrRunner, **kwargs)
3506
class TestUncollectedWarningsForked(_ForkedSelftest, TestUncollectedWarnings):
3507
"""Check warnings from tests staying alive are emitted when forking"""
3510
class TestEnvironHandling(tests.TestCase):
3512
def test_overrideEnv_None_called_twice_doesnt_leak(self):
3513
self.assertFalse('MYVAR' in os.environ)
3514
self.overrideEnv('MYVAR', '42')
3515
# We use an embedded test to make sure we fix the _captureVar bug
3516
class Test(tests.TestCase):
3518
# The first call save the 42 value
3519
self.overrideEnv('MYVAR', None)
3520
self.assertEqual(None, os.environ.get('MYVAR'))
3521
# Make sure we can call it twice
3522
self.overrideEnv('MYVAR', None)
3523
self.assertEqual(None, os.environ.get('MYVAR'))
3525
result = tests.TextTestResult(output, 0, 1)
3526
Test('test_me').run(result)
3527
if not result.wasStrictlySuccessful():
3528
self.fail(output.getvalue())
3529
# We get our value back
3530
self.assertEqual('42', os.environ.get('MYVAR'))
3533
class TestIsolatedEnv(tests.TestCase):
3534
"""Test isolating tests from os.environ.
3536
Since we use tests that are already isolated from os.environ a bit of care
3537
should be taken when designing the tests to avoid bootstrap side-effects.
3538
The tests start an already clean os.environ which allow doing valid
3539
assertions about which variables are present or not and design tests around
3543
class ScratchMonkey(tests.TestCase):
3548
def test_basics(self):
3549
# Make sure we know the definition of BRZ_HOME: not part of os.environ
3550
# for tests.TestCase.
3551
self.assertTrue('BRZ_HOME' in tests.isolated_environ)
3552
self.assertEqual(None, tests.isolated_environ['BRZ_HOME'])
3553
# Being part of isolated_environ, BRZ_HOME should not appear here
3554
self.assertFalse('BRZ_HOME' in os.environ)
3555
# Make sure we know the definition of LINES: part of os.environ for
3557
self.assertTrue('LINES' in tests.isolated_environ)
3558
self.assertEqual('25', tests.isolated_environ['LINES'])
3559
self.assertEqual('25', os.environ['LINES'])
3561
def test_injecting_unknown_variable(self):
3562
# BRZ_HOME is known to be absent from os.environ
3563
test = self.ScratchMonkey('test_me')
3564
tests.override_os_environ(test, {'BRZ_HOME': 'foo'})
3565
self.assertEqual('foo', os.environ['BRZ_HOME'])
3566
tests.restore_os_environ(test)
3567
self.assertFalse('BRZ_HOME' in os.environ)
3569
def test_injecting_known_variable(self):
3570
test = self.ScratchMonkey('test_me')
3571
# LINES is known to be present in os.environ
3572
tests.override_os_environ(test, {'LINES': '42'})
3573
self.assertEqual('42', os.environ['LINES'])
3574
tests.restore_os_environ(test)
3575
self.assertEqual('25', os.environ['LINES'])
3577
def test_deleting_variable(self):
3578
test = self.ScratchMonkey('test_me')
3579
# LINES is known to be present in os.environ
3580
tests.override_os_environ(test, {'LINES': None})
3581
self.assertTrue('LINES' not in os.environ)
3582
tests.restore_os_environ(test)
3583
self.assertEqual('25', os.environ['LINES'])
3586
class TestDocTestSuiteIsolation(tests.TestCase):
3587
"""Test that `tests.DocTestSuite` isolates doc tests from os.environ.
3589
Since tests.TestCase alreay provides an isolation from os.environ, we use
3590
the clean environment as a base for testing. To precisely capture the
3591
isolation provided by tests.DocTestSuite, we use doctest.DocTestSuite to
3594
We want to make sure `tests.DocTestSuite` respect `tests.isolated_environ`,
3595
not `os.environ` so each test overrides it to suit its needs.
3599
def get_doctest_suite_for_string(self, klass, string):
3600
class Finder(doctest.DocTestFinder):
3602
def find(*args, **kwargs):
3603
test = doctest.DocTestParser().get_doctest(
3604
string, {}, 'foo', 'foo.py', 0)
3607
suite = klass(test_finder=Finder())
3610
def run_doctest_suite_for_string(self, klass, string):
3611
suite = self.get_doctest_suite_for_string(klass, string)
3613
result = tests.TextTestResult(output, 0, 1)
3615
return result, output
3617
def assertDocTestStringSucceds(self, klass, string):
3618
result, output = self.run_doctest_suite_for_string(klass, string)
3619
if not result.wasStrictlySuccessful():
3620
self.fail(output.getvalue())
3622
def assertDocTestStringFails(self, klass, string):
3623
result, output = self.run_doctest_suite_for_string(klass, string)
3624
if result.wasStrictlySuccessful():
3625
self.fail(output.getvalue())
3627
def test_injected_variable(self):
3628
self.overrideAttr(tests, 'isolated_environ', {'LINES': '42'})
3631
>>> os.environ['LINES']
3634
# doctest.DocTestSuite fails as it sees '25'
3635
self.assertDocTestStringFails(doctest.DocTestSuite, test)
3636
# tests.DocTestSuite sees '42'
3637
self.assertDocTestStringSucceds(tests.IsolatedDocTestSuite, test)
3639
def test_deleted_variable(self):
3640
self.overrideAttr(tests, 'isolated_environ', {'LINES': None})
3643
>>> os.environ.get('LINES')
3645
# doctest.DocTestSuite fails as it sees '25'
3646
self.assertDocTestStringFails(doctest.DocTestSuite, test)
3647
# tests.DocTestSuite sees None
3648
self.assertDocTestStringSucceds(tests.IsolatedDocTestSuite, test)
3651
class TestSelftestExcludePatterns(tests.TestCase):
3654
super(TestSelftestExcludePatterns, self).setUp()
3655
self.overrideAttr(tests, 'test_suite', self.suite_factory)
3657
def suite_factory(self, keep_only=None, starting_with=None):
3658
"""A test suite factory with only a few tests."""
3659
class Test(tests.TestCase):
3661
# We don't need the full class path
3662
return self._testMethodName
3669
return TestUtil.TestSuite([Test("a"), Test("b"), Test("c")])
3671
def assertTestList(self, expected, *selftest_args):
3672
# We rely on setUp installing the right test suite factory so we can
3673
# test at the command level without loading the whole test suite
3674
out, err = self.run_bzr(('selftest', '--list') + selftest_args)
3675
actual = out.splitlines()
3676
self.assertEqual(expected, actual)
3678
def test_full_list(self):
3679
self.assertTestList(['a', 'b', 'c'])
3681
def test_single_exclude(self):
3682
self.assertTestList(['b', 'c'], '-x', 'a')
3684
def test_mutiple_excludes(self):
3685
self.assertTestList(['c'], '-x', 'a', '-x', 'b')
3688
class TestCounterHooks(tests.TestCase, SelfTestHelper):
3690
_test_needs_features = [features.subunit]
3693
super(TestCounterHooks, self).setUp()
3694
class Test(tests.TestCase):
3697
super(Test, self).setUp()
3698
self.hooks = hooks.Hooks()
3699
self.hooks.add_hook('myhook', 'Foo bar blah', (2,4))
3700
self.install_counter_hook(self.hooks, 'myhook')
3705
def run_hook_once(self):
3706
for hook in self.hooks['myhook']:
3709
self.test_class = Test
3711
def assertHookCalls(self, expected_calls, test_name):
3712
test = self.test_class(test_name)
3713
result = unittest.TestResult()
3715
self.assertTrue(hasattr(test, '_counters'))
3716
self.assertTrue('myhook' in test._counters)
3717
self.assertEqual(expected_calls, test._counters['myhook'])
3719
def test_no_hook(self):
3720
self.assertHookCalls(0, 'no_hook')
3722
def test_run_hook_once(self):
3723
tt = features.testtools
3724
if tt.module.__version__ < (0, 9, 8):
3725
raise tests.TestSkipped('testtools-0.9.8 required for addDetail')
3726
self.assertHookCalls(1, 'run_hook_once')