377
379
if isinstance(test, TestCase):
378
380
test.addCleanup(self._check_leaked_threads, test)
382
def stopTest(self, test):
383
super(ExtendedTestResult, self).stopTest(test)
384
# Manually break cycles, means touching various private things but hey
385
getDetails = getattr(test, "getDetails", None)
386
if getDetails is not None:
388
# Clear _type_equality_funcs to try to stop TestCase instances
389
# from wasting memory. 'clear' is not available in all Python
390
# versions (bug 809048)
391
type_equality_funcs = getattr(test, "_type_equality_funcs", None)
392
if type_equality_funcs is not None:
393
tef_clear = getattr(type_equality_funcs, "clear", None)
394
if tef_clear is None:
395
tef_instance_dict = getattr(type_equality_funcs, "__dict__", None)
396
if tef_instance_dict is not None:
397
tef_clear = tef_instance_dict.clear
398
if tef_clear is not None:
400
self._traceback_from_test = None
380
402
def startTests(self):
381
403
self.report_tests_starting()
382
404
self._active_threads = threading.enumerate()
384
def stopTest(self, test):
385
self._traceback_from_test = None
387
406
def _check_leaked_threads(self, test):
388
407
"""See if any threads have leaked since last call
449
468
self.known_failure_count += 1
450
469
self.report_known_failure(test, err)
471
def addUnexpectedSuccess(self, test, details=None):
472
"""Tell result the test unexpectedly passed, counting as a failure
474
When the minimum version of testtools required becomes 0.9.8 this
475
can be updated to use the new handling there.
477
super(ExtendedTestResult, self).addFailure(test, details=details)
478
self.failure_count += 1
479
self.report_unexpected_success(test,
480
"".join(details["reason"].iter_text()))
452
484
def addNotSupported(self, test, feature):
453
485
"""The test will not be run because of a missing feature.
670
709
% (self._testTimeString(test),
671
710
self._error_summary(err)))
712
def report_unexpected_success(self, test, reason):
713
self.stream.write(' FAIL %s\n%s: %s\n'
714
% (self._testTimeString(test),
715
"Unexpected success. Should have failed",
673
718
def report_success(self, test):
674
719
self.stream.write(' OK %s\n' % self._testTimeString(test))
675
720
for bench_called, stats in getattr(test, '_benchcalls', []):
958
999
# settled on or a the FIXME associated with _get_expand_default_value
959
1000
# is addressed -- vila 20110219
960
1001
self.overrideAttr(config, '_expand_default_value', None)
1002
self._log_files = set()
1003
# Each key in the ``_counters`` dict holds a value for a different
1004
# counter. When the test ends, addDetail() should be used to output the
1005
# counter values. This happens in install_counter_hook().
1007
if 'config_stats' in selftest_debug_flags:
1008
self._install_config_stats_hooks()
1009
# Do not use i18n for tests (unless the test reverses this)
962
1012
def debug(self):
963
1013
# debug a frame up.
965
pdb.Pdb().set_trace(sys._getframe().f_back)
1015
# The sys preserved stdin/stdout should allow blackbox tests debugging
1016
pdb.Pdb(stdin=sys.__stdin__, stdout=sys.__stdout__
1017
).set_trace(sys._getframe().f_back)
967
1019
def discardDetail(self, name):
968
1020
"""Extend the addDetail, getDetails api so we can remove a detail.
980
1032
if name in details:
981
1033
del details[name]
1035
def install_counter_hook(self, hooks, name, counter_name=None):
1036
"""Install a counting hook.
1038
Any hook can be counted as long as it doesn't need to return a value.
1040
:param hooks: Where the hook should be installed.
1042
:param name: The hook name that will be counted.
1044
:param counter_name: The counter identifier in ``_counters``, defaults
1047
_counters = self._counters # Avoid closing over self
1048
if counter_name is None:
1050
if _counters.has_key(counter_name):
1051
raise AssertionError('%s is already used as a counter name'
1053
_counters[counter_name] = 0
1054
self.addDetail(counter_name, content.Content(content.UTF8_TEXT,
1055
lambda: ['%d' % (_counters[counter_name],)]))
1056
def increment_counter(*args, **kwargs):
1057
_counters[counter_name] += 1
1058
label = 'count %s calls' % (counter_name,)
1059
hooks.install_named_hook(name, increment_counter, label)
1060
self.addCleanup(hooks.uninstall_named_hook, name, label)
1062
def _install_config_stats_hooks(self):
1063
"""Install config hooks to count hook calls.
1066
for hook_name in ('get', 'set', 'remove', 'load', 'save'):
1067
self.install_counter_hook(config.ConfigHooks, hook_name,
1068
'config.%s' % (hook_name,))
1070
# The OldConfigHooks are private and need special handling to protect
1071
# against recursive tests (tests that run other tests), so we just do
1072
# manually what registering them into _builtin_known_hooks will provide
1074
self.overrideAttr(config, 'OldConfigHooks', config._OldConfigHooks())
1075
for hook_name in ('get', 'set', 'remove', 'load', 'save'):
1076
self.install_counter_hook(config.OldConfigHooks, hook_name,
1077
'old_config.%s' % (hook_name,))
983
1079
def _clear_debug_flags(self):
984
1080
"""Prevent externally set debug flags affecting tests.
1039
1135
# break some locks on purpose and should be taken into account by
1040
1136
# considering that breaking a lock is just a dirty way of releasing it.
1041
1137
if len(acquired_locks) != (len(released_locks) + len(broken_locks)):
1042
message = ('Different number of acquired and '
1043
'released or broken locks. (%s, %s + %s)' %
1044
(acquired_locks, released_locks, broken_locks))
1139
'Different number of acquired and '
1140
'released or broken locks.\n'
1144
(acquired_locks, released_locks, broken_locks))
1045
1145
if not self._lock_check_thorough:
1046
1146
# Rather than fail, just warn
1047
1147
print "Broken test %s: %s" % (self, message)
1290
1390
length, len(obj_with_len), obj_with_len))
1292
1392
def assertLogsError(self, exception_class, func, *args, **kwargs):
1293
"""Assert that func(*args, **kwargs) quietly logs a specific exception.
1393
"""Assert that `func(*args, **kwargs)` quietly logs a specific error.
1296
1396
orig_log_exception_quietly = trace.log_exception_quietly
1299
1399
orig_log_exception_quietly()
1300
captured.append(sys.exc_info())
1400
captured.append(sys.exc_info()[1])
1301
1401
trace.log_exception_quietly = capture
1302
1402
func(*args, **kwargs)
1304
1404
trace.log_exception_quietly = orig_log_exception_quietly
1305
1405
self.assertLength(1, captured)
1306
err = captured[0][1]
1307
1407
self.assertIsInstance(err, exception_class)
1605
1706
The file is removed as the test is torn down.
1607
self._log_file = StringIO()
1708
pseudo_log_file = StringIO()
1709
def _get_log_contents_for_weird_testtools_api():
1710
return [pseudo_log_file.getvalue().decode(
1711
"utf-8", "replace").encode("utf-8")]
1712
self.addDetail("log", content.Content(content.ContentType("text",
1713
"plain", {"charset": "utf8"}),
1714
_get_log_contents_for_weird_testtools_api))
1715
self._log_file = pseudo_log_file
1608
1716
self._log_memento = trace.push_log_file(self._log_file)
1609
1717
self.addCleanup(self._finishLogFile)
1611
1719
def _finishLogFile(self):
1612
1720
"""Finished with the log file.
1614
Close the file and delete it, unless setKeepLogfile was called.
1722
Close the file and delete it.
1616
1724
if trace._trace_file:
1617
1725
# flush the log file, to get all content
1618
1726
trace._trace_file.flush()
1619
1727
trace.pop_log_file(self._log_memento)
1620
# Cache the log result and delete the file on disk
1621
self._get_log(False)
1623
1729
def thisFailsStrictLockCheck(self):
1624
1730
"""It is known that this test would fail with -Dstrict_locks.
1666
1775
self.addCleanup(osutils.set_or_unset_env, name, value)
1778
def recordCalls(self, obj, attr_name):
1779
"""Monkeypatch in a wrapper that will record calls.
1781
The monkeypatch is automatically removed when the test concludes.
1783
:param obj: The namespace holding the reference to be replaced;
1784
typically a module, class, or object.
1785
:param attr_name: A string for the name of the attribute to
1787
:returns: A list that will be extended with one item every time the
1788
function is called, with a tuple of (args, kwargs).
1792
def decorator(*args, **kwargs):
1793
calls.append((args, kwargs))
1794
return orig(*args, **kwargs)
1795
orig = self.overrideAttr(obj, attr_name, decorator)
1669
1798
def _cleanEnvironment(self):
1670
1799
for name, value in isolated_environ.iteritems():
1671
1800
self.overrideEnv(name, value)
1673
1802
def _restoreHooks(self):
1674
1803
for klass, (name, hooks) in self._preserved_hooks.items():
1675
1804
setattr(klass, name, hooks)
1676
hooks._lazy_hooks = self._preserved_lazy_hooks
1805
self._preserved_hooks.clear()
1806
bzrlib.hooks._lazy_hooks = self._preserved_lazy_hooks
1807
self._preserved_lazy_hooks.clear()
1678
1809
def knownFailure(self, reason):
1679
"""This test has failed for some known reason."""
1680
raise KnownFailure(reason)
1810
"""Declare that this test fails for a known reason
1812
Tests that are known to fail should generally be using expectedFailure
1813
with an appropriate reverse assertion if a change could cause the test
1814
to start passing. Conversely if the test has no immediate prospect of
1815
succeeding then using skip is more suitable.
1817
When this method is called while an exception is being handled, that
1818
traceback will be used, otherwise a new exception will be thrown to
1819
provide one but won't be reported.
1821
self._add_reason(reason)
1823
exc_info = sys.exc_info()
1824
if exc_info != (None, None, None):
1825
self._report_traceback(exc_info)
1828
raise self.failureException(reason)
1829
except self.failureException:
1830
exc_info = sys.exc_info()
1831
# GZ 02-08-2011: Maybe cleanup this err.exc_info attribute too?
1832
raise testtools.testcase._ExpectedFailure(exc_info)
1682
1836
def _suppress_log(self):
1683
1837
"""Remove the log info from details."""
1771
1925
def log(self, *args):
1772
1926
trace.mutter(*args)
1774
def _get_log(self, keep_log_file=False):
1775
"""Internal helper to get the log from bzrlib.trace for this test.
1777
Please use self.getDetails, or self.get_log to access this in test case
1780
:param keep_log_file: When True, if the log is still a file on disk
1781
leave it as a file on disk. When False, if the log is still a file
1782
on disk, the log file is deleted and the log preserved as
1784
:return: A string containing the log.
1786
if self._log_contents is not None:
1788
self._log_contents.decode('utf8')
1789
except UnicodeDecodeError:
1790
unicodestr = self._log_contents.decode('utf8', 'replace')
1791
self._log_contents = unicodestr.encode('utf8')
1792
return self._log_contents
1793
if self._log_file is not None:
1794
log_contents = self._log_file.getvalue()
1796
log_contents.decode('utf8')
1797
except UnicodeDecodeError:
1798
unicodestr = log_contents.decode('utf8', 'replace')
1799
log_contents = unicodestr.encode('utf8')
1800
if not keep_log_file:
1801
self._log_file = None
1802
# Permit multiple calls to get_log until we clean it up in
1804
self._log_contents = log_contents
1807
return "No log file content."
1809
1928
def get_log(self):
1810
1929
"""Get a unicode string containing the log from bzrlib.trace.
2205
def _add_subprocess_log(self, log_file_path):
2206
if len(self._log_files) == 0:
2207
# Register an addCleanup func. We do this on the first call to
2208
# _add_subprocess_log rather than in TestCase.setUp so that this
2209
# addCleanup is registered after any cleanups for tempdirs that
2210
# subclasses might create, which will probably remove the log file
2212
self.addCleanup(self._subprocess_log_cleanup)
2213
# self._log_files is a set, so if a log file is reused we won't grab it
2215
self._log_files.add(log_file_path)
2217
def _subprocess_log_cleanup(self):
2218
for count, log_file_path in enumerate(self._log_files):
2219
# We use buffer_now=True to avoid holding the file open beyond
2220
# the life of this function, which might interfere with e.g.
2221
# cleaning tempdirs on Windows.
2222
# XXX: Testtools 0.9.5 doesn't have the content_from_file helper
2223
#detail_content = content.content_from_file(
2224
# log_file_path, buffer_now=True)
2225
with open(log_file_path, 'rb') as log_file:
2226
log_file_bytes = log_file.read()
2227
detail_content = content.Content(content.ContentType("text",
2228
"plain", {"charset": "utf8"}), lambda: [log_file_bytes])
2229
self.addDetail("start_bzr_subprocess-log-%d" % (count,),
2080
2232
def _popen(self, *args, **kwargs):
2081
2233
"""Place a call to Popen.
2234
2386
class TestCaseWithMemoryTransport(TestCase):
2235
2387
"""Common test class for tests that do not need disk resources.
2237
Tests that need disk resources should derive from TestCaseWithTransport.
2389
Tests that need disk resources should derive from TestCaseInTempDir
2390
orTestCaseWithTransport.
2239
2392
TestCaseWithMemoryTransport sets the TEST_ROOT variable for all bzr tests.
2241
For TestCaseWithMemoryTransport the test_home_dir is set to the name of
2394
For TestCaseWithMemoryTransport the ``test_home_dir`` is set to the name of
2242
2395
a directory which does not exist. This serves to help ensure test isolation
2243
is preserved. test_dir is set to the TEST_ROOT, as is cwd, because they
2244
must exist. However, TestCaseWithMemoryTransport does not offer local
2245
file defaults for the transport in tests, nor does it obey the command line
2396
is preserved. ``test_dir`` is set to the TEST_ROOT, as is cwd, because they
2397
must exist. However, TestCaseWithMemoryTransport does not offer local file
2398
defaults for the transport in tests, nor does it obey the command line
2246
2399
override, so tests that accidentally write to the common directory should
2249
:cvar TEST_ROOT: Directory containing all temporary directories, plus
2250
a .bzr directory that stops us ascending higher into the filesystem.
2402
:cvar TEST_ROOT: Directory containing all temporary directories, plus a
2403
``.bzr`` directory that stops us ascending higher into the filesystem.
2253
2406
TEST_ROOT = None
2412
2566
root = TestCaseWithMemoryTransport.TEST_ROOT
2413
bzrdir.BzrDir.create_standalone_workingtree(root)
2568
# Make sure we get a readable and accessible home for .bzr.log
2569
# and/or config files, and not fallback to weird defaults (see
2570
# http://pad.lv/825027).
2571
self.assertIs(None, os.environ.get('BZR_HOME', None))
2572
os.environ['BZR_HOME'] = root
2573
wt = bzrdir.BzrDir.create_standalone_workingtree(root)
2574
del os.environ['BZR_HOME']
2575
except Exception, e:
2576
self.fail("Fail to initialize the safety net: %r\nExiting\n" % (e,))
2577
# Hack for speed: remember the raw bytes of the dirstate file so that
2578
# we don't need to re-open the wt to check it hasn't changed.
2579
TestCaseWithMemoryTransport._SAFETY_NET_PRISTINE_DIRSTATE = (
2580
wt.control_transport.get_bytes('dirstate'))
2415
2582
def _check_safety_net(self):
2416
2583
"""Check that the safety .bzr directory have not been touched.
2463
2630
def make_branch(self, relpath, format=None):
2464
2631
"""Create a branch on the transport at relpath."""
2465
2632
repo = self.make_repository(relpath, format=format)
2466
return repo.bzrdir.create_branch()
2633
return repo.bzrdir.create_branch(append_revisions_only=False)
2635
def resolve_format(self, format):
2636
"""Resolve an object to a ControlDir format object.
2638
The initial format object can either already be
2639
a ControlDirFormat, None (for the default format),
2640
or a string with the name of the control dir format.
2642
:param format: Object to resolve
2643
:return A ControlDirFormat instance
2647
if isinstance(format, basestring):
2648
format = bzrdir.format_registry.make_bzrdir(format)
2651
def resolve_format(self, format):
2652
"""Resolve an object to a ControlDir format object.
2654
The initial format object can either already be
2655
a ControlDirFormat, None (for the default format),
2656
or a string with the name of the control dir format.
2658
:param format: Object to resolve
2659
:return A ControlDirFormat instance
2663
if isinstance(format, basestring):
2664
format = bzrdir.format_registry.make_bzrdir(format)
2468
2667
def make_bzrdir(self, relpath, format=None):
2473
2672
t = _mod_transport.get_transport(maybe_a_url)
2474
2673
if len(segments) > 1 and segments[-1] not in ('', '.'):
2475
2674
t.ensure_base()
2478
if isinstance(format, basestring):
2479
format = bzrdir.format_registry.make_bzrdir(format)
2675
format = self.resolve_format(format)
2480
2676
return format.initialize_on_transport(t)
2481
2677
except errors.UninitializableFormat:
2482
2678
raise TestSkipped("Format %s is not initializable." % format)
2484
def make_repository(self, relpath, shared=False, format=None):
2680
def make_repository(self, relpath, shared=None, format=None):
2485
2681
"""Create a repository on our default transport at relpath.
2487
2683
Note that relpath must be a relative path, not a full url.
2521
2717
def setUp(self):
2522
2718
super(TestCaseWithMemoryTransport, self).setUp()
2523
2719
# Ensure that ConnectedTransport doesn't leak sockets
2524
def get_transport_with_cleanup(*args, **kwargs):
2525
t = orig_get_transport(*args, **kwargs)
2720
def get_transport_from_url_with_cleanup(*args, **kwargs):
2721
t = orig_get_transport_from_url(*args, **kwargs)
2526
2722
if isinstance(t, _mod_transport.ConnectedTransport):
2527
2723
self.addCleanup(t.disconnect)
2530
orig_get_transport = self.overrideAttr(_mod_transport, 'get_transport',
2531
get_transport_with_cleanup)
2726
orig_get_transport_from_url = self.overrideAttr(
2727
_mod_transport, 'get_transport_from_url',
2728
get_transport_from_url_with_cleanup)
2532
2729
self._make_test_root()
2533
2730
self.addCleanup(os.chdir, os.getcwdu())
2534
2731
self.makeAndChdirToTestDir()
3782
3987
'bzrlib.tests.test_email_message',
3783
3988
'bzrlib.tests.test_eol_filters',
3784
3989
'bzrlib.tests.test_errors',
3990
'bzrlib.tests.test_estimate_compressed_size',
3785
3991
'bzrlib.tests.test_export',
3992
'bzrlib.tests.test_export_pot',
3786
3993
'bzrlib.tests.test_extract',
3994
'bzrlib.tests.test_features',
3787
3995
'bzrlib.tests.test_fetch',
3788
3996
'bzrlib.tests.test_fixtures',
3789
3997
'bzrlib.tests.test_fifo_cache',
3790
3998
'bzrlib.tests.test_filters',
3999
'bzrlib.tests.test_filter_tree',
3791
4000
'bzrlib.tests.test_ftp_transport',
3792
4001
'bzrlib.tests.test_foreign',
3793
4002
'bzrlib.tests.test_generate_docs',
4228
4440
% (os.path.basename(dirname), printable_e))
4231
class Feature(object):
4232
"""An operating system Feature."""
4235
self._available = None
4237
def available(self):
4238
"""Is the feature available?
4240
:return: True if the feature is available.
4242
if self._available is None:
4243
self._available = self._probe()
4244
return self._available
4247
"""Implement this method in concrete features.
4249
:return: True if the feature is available.
4251
raise NotImplementedError
4254
if getattr(self, 'feature_name', None):
4255
return self.feature_name()
4256
return self.__class__.__name__
4259
class _SymlinkFeature(Feature):
4262
return osutils.has_symlinks()
4264
def feature_name(self):
4267
SymlinkFeature = _SymlinkFeature()
4270
class _HardlinkFeature(Feature):
4273
return osutils.has_hardlinks()
4275
def feature_name(self):
4278
HardlinkFeature = _HardlinkFeature()
4281
class _OsFifoFeature(Feature):
4284
return getattr(os, 'mkfifo', None)
4286
def feature_name(self):
4287
return 'filesystem fifos'
4289
OsFifoFeature = _OsFifoFeature()
4292
class _UnicodeFilenameFeature(Feature):
4293
"""Does the filesystem support Unicode filenames?"""
4297
# Check for character combinations unlikely to be covered by any
4298
# single non-unicode encoding. We use the characters
4299
# - greek small letter alpha (U+03B1) and
4300
# - braille pattern dots-123456 (U+283F).
4301
os.stat(u'\u03b1\u283f')
4302
except UnicodeEncodeError:
4304
except (IOError, OSError):
4305
# The filesystem allows the Unicode filename but the file doesn't
4309
# The filesystem allows the Unicode filename and the file exists,
4313
UnicodeFilenameFeature = _UnicodeFilenameFeature()
4316
class _CompatabilityThunkFeature(Feature):
4317
"""This feature is just a thunk to another feature.
4319
It issues a deprecation warning if it is accessed, to let you know that you
4320
should really use a different feature.
4323
def __init__(self, dep_version, module, name,
4324
replacement_name, replacement_module=None):
4325
super(_CompatabilityThunkFeature, self).__init__()
4326
self._module = module
4327
if replacement_module is None:
4328
replacement_module = module
4329
self._replacement_module = replacement_module
4331
self._replacement_name = replacement_name
4332
self._dep_version = dep_version
4333
self._feature = None
4336
if self._feature is None:
4337
depr_msg = self._dep_version % ('%s.%s'
4338
% (self._module, self._name))
4339
use_msg = ' Use %s.%s instead.' % (self._replacement_module,
4340
self._replacement_name)
4341
symbol_versioning.warn(depr_msg + use_msg, DeprecationWarning)
4342
# Import the new feature and use it as a replacement for the
4344
self._feature = pyutils.get_named_object(
4345
self._replacement_module, self._replacement_name)
4349
return self._feature._probe()
4352
class ModuleAvailableFeature(Feature):
4353
"""This is a feature than describes a module we want to be available.
4355
Declare the name of the module in __init__(), and then after probing, the
4356
module will be available as 'self.module'.
4358
:ivar module: The module if it is available, else None.
4361
def __init__(self, module_name):
4362
super(ModuleAvailableFeature, self).__init__()
4363
self.module_name = module_name
4367
self._module = __import__(self.module_name, {}, {}, [''])
4374
if self.available(): # Make sure the probe has been done
4378
def feature_name(self):
4379
return self.module_name
4382
4443
def probe_unicode_in_user_encoding():
4383
4444
"""Try to encode several unicode strings to use in unicode-aware tests.
4384
4445
Return first successfull match.
4415
class _HTTPSServerFeature(Feature):
4416
"""Some tests want an https Server, check if one is available.
4418
Right now, the only way this is available is under python2.6 which provides
4429
def feature_name(self):
4430
return 'HTTPSServer'
4433
HTTPSServerFeature = _HTTPSServerFeature()
4436
class _UnicodeFilename(Feature):
4437
"""Does the filesystem support Unicode filenames?"""
4442
except UnicodeEncodeError:
4444
except (IOError, OSError):
4445
# The filesystem allows the Unicode filename but the file doesn't
4449
# The filesystem allows the Unicode filename and the file exists,
4453
UnicodeFilename = _UnicodeFilename()
4456
class _ByteStringNamedFilesystem(Feature):
4457
"""Is the filesystem based on bytes?"""
4460
if os.name == "posix":
4464
ByteStringNamedFilesystem = _ByteStringNamedFilesystem()
4467
class _UTF8Filesystem(Feature):
4468
"""Is the filesystem UTF-8?"""
4471
if osutils._fs_enc.upper() in ('UTF-8', 'UTF8'):
4475
UTF8Filesystem = _UTF8Filesystem()
4478
class _BreakinFeature(Feature):
4479
"""Does this platform support the breakin feature?"""
4482
from bzrlib import breakin
4483
if breakin.determine_signal() is None:
4485
if sys.platform == 'win32':
4486
# Windows doesn't have os.kill, and we catch the SIGBREAK signal.
4487
# We trigger SIGBREAK via a Console api so we need ctypes to
4488
# access the function
4495
def feature_name(self):
4496
return "SIGQUIT or SIGBREAK w/ctypes on win32"
4499
BreakinFeature = _BreakinFeature()
4502
class _CaseInsCasePresFilenameFeature(Feature):
4503
"""Is the file-system case insensitive, but case-preserving?"""
4506
fileno, name = tempfile.mkstemp(prefix='MixedCase')
4508
# first check truly case-preserving for created files, then check
4509
# case insensitive when opening existing files.
4510
name = osutils.normpath(name)
4511
base, rel = osutils.split(name)
4512
found_rel = osutils.canonical_relpath(base, name)
4513
return (found_rel == rel
4514
and os.path.isfile(name.upper())
4515
and os.path.isfile(name.lower()))
4520
def feature_name(self):
4521
return "case-insensitive case-preserving filesystem"
4523
CaseInsCasePresFilenameFeature = _CaseInsCasePresFilenameFeature()
4526
class _CaseInsensitiveFilesystemFeature(Feature):
4527
"""Check if underlying filesystem is case-insensitive but *not* case
4530
# Note that on Windows, Cygwin, MacOS etc, the file-systems are far
4531
# more likely to be case preserving, so this case is rare.
4534
if CaseInsCasePresFilenameFeature.available():
4537
if TestCaseWithMemoryTransport.TEST_ROOT is None:
4538
root = osutils.mkdtemp(prefix='testbzr-', suffix='.tmp')
4539
TestCaseWithMemoryTransport.TEST_ROOT = root
4541
root = TestCaseWithMemoryTransport.TEST_ROOT
4542
tdir = osutils.mkdtemp(prefix='case-sensitive-probe-', suffix='',
4544
name_a = osutils.pathjoin(tdir, 'a')
4545
name_A = osutils.pathjoin(tdir, 'A')
4547
result = osutils.isdir(name_A)
4548
_rmtree_temp_dir(tdir)
4551
def feature_name(self):
4552
return 'case-insensitive filesystem'
4554
CaseInsensitiveFilesystemFeature = _CaseInsensitiveFilesystemFeature()
4557
class _CaseSensitiveFilesystemFeature(Feature):
4560
if CaseInsCasePresFilenameFeature.available():
4562
elif CaseInsensitiveFilesystemFeature.available():
4567
def feature_name(self):
4568
return 'case-sensitive filesystem'
4570
# new coding style is for feature instances to be lowercase
4571
case_sensitive_filesystem_feature = _CaseSensitiveFilesystemFeature()
4574
4476
# Only define SubUnitBzrRunner if subunit is available.
4576
4478
from subunit import TestProtocolClient
4594
4496
except ImportError:
4597
class _PosixPermissionsFeature(Feature):
4601
# create temporary file and check if specified perms are maintained.
4604
write_perms = stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
4605
f = tempfile.mkstemp(prefix='bzr_perms_chk_')
4608
os.chmod(name, write_perms)
4610
read_perms = os.stat(name).st_mode & 0777
4612
return (write_perms == read_perms)
4614
return (os.name == 'posix') and has_perms()
4616
def feature_name(self):
4617
return 'POSIX permissions support'
4619
posix_permissions_feature = _PosixPermissionsFeature()
4500
@deprecated_function(deprecated_in((2, 5, 0)))
4501
def ModuleAvailableFeature(name):
4502
from bzrlib.tests import features
4503
return features.ModuleAvailableFeature(name)